1 /* 2 * Copyright (C) 2015 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.car; 18 19 import android.annotation.IntDef; 20 import android.os.IBinder; 21 import android.os.RemoteException; 22 23 import java.lang.annotation.Retention; 24 import java.lang.annotation.RetentionPolicy; 25 import java.lang.ref.WeakReference; 26 import java.util.HashMap; 27 import java.util.HashSet; 28 import java.util.Map; 29 import java.util.Set; 30 31 /** 32 * CarAppFocusManager allows applications to set and listen for the current application focus 33 * like active navigation or active voice command. Usually only one instance of such application 34 * should run in the system, and other app setting the flag for the matching app should 35 * lead into other app to stop. 36 */ 37 public final class CarAppFocusManager extends CarManagerBase { 38 /** 39 * Listener to get notification for app getting information on application type status changes. 40 */ 41 public interface OnAppFocusChangedListener { 42 /** 43 * Application focus has changed. Note that {@link CarAppFocusManager} instance 44 * causing the change will not get this notification. 45 * @param appType 46 * @param active 47 */ onAppFocusChanged(@ppFocusType int appType, boolean active)48 void onAppFocusChanged(@AppFocusType int appType, boolean active); 49 } 50 51 /** 52 * Listener to get notification for app getting information on app type ownership loss. 53 */ 54 public interface OnAppFocusOwnershipCallback { 55 /** 56 * Lost ownership for the focus, which happens when other app has set the focus. 57 * The app losing focus should stop the action associated with the focus. 58 * For example, navigation app currently running active navigation should stop navigation 59 * upon getting this for {@link CarAppFocusManager#APP_FOCUS_TYPE_NAVIGATION}. 60 * @param appType 61 */ onAppFocusOwnershipLost(@ppFocusType int appType)62 void onAppFocusOwnershipLost(@AppFocusType int appType); 63 64 /** 65 * Granted ownership for the focus, which happens when app has requested the focus. 66 * The app getting focus can start the action associated with the focus. 67 * For example, navigation app can start navigation 68 * upon getting this for {@link CarAppFocusManager#APP_FOCUS_TYPE_NAVIGATION}. 69 * @param appType 70 */ onAppFocusOwnershipGranted(@ppFocusType int appType)71 void onAppFocusOwnershipGranted(@AppFocusType int appType); 72 } 73 74 /** 75 * Represents navigation focus. 76 */ 77 public static final int APP_FOCUS_TYPE_NAVIGATION = 1; 78 /** 79 * Represents voice command focus. 80 * 81 * @deprecated use {@link android.service.voice.VoiceInteractionService} instead. 82 */ 83 @Deprecated 84 public static final int APP_FOCUS_TYPE_VOICE_COMMAND = 2; 85 /** 86 * Update this after adding a new app type. 87 * @hide 88 */ 89 public static final int APP_FOCUS_MAX = 2; 90 91 /** @hide */ 92 @IntDef({ 93 APP_FOCUS_TYPE_NAVIGATION, 94 }) 95 @Retention(RetentionPolicy.SOURCE) 96 public @interface AppFocusType {} 97 98 /** 99 * A failed focus change request. 100 */ 101 public static final int APP_FOCUS_REQUEST_FAILED = 0; 102 /** 103 * A successful focus change request. 104 */ 105 public static final int APP_FOCUS_REQUEST_SUCCEEDED = 1; 106 107 /** @hide */ 108 @IntDef({ 109 APP_FOCUS_REQUEST_FAILED, 110 APP_FOCUS_REQUEST_SUCCEEDED 111 }) 112 @Retention(RetentionPolicy.SOURCE) 113 public @interface AppFocusRequestResult {} 114 115 private final IAppFocus mService; 116 private final Map<OnAppFocusChangedListener, IAppFocusListenerImpl> mChangeBinders = 117 new HashMap<>(); 118 private final Map<OnAppFocusOwnershipCallback, IAppFocusOwnershipCallbackImpl> 119 mOwnershipBinders = new HashMap<>(); 120 121 /** 122 * @hide 123 */ CarAppFocusManager(Car car, IBinder service)124 CarAppFocusManager(Car car, IBinder service) { 125 super(car); 126 mService = IAppFocus.Stub.asInterface(service); 127 } 128 129 /** 130 * Register listener to monitor app focus change. 131 * @param listener 132 * @param appType Application type to get notification for. 133 */ addFocusListener(OnAppFocusChangedListener listener, @AppFocusType int appType)134 public void addFocusListener(OnAppFocusChangedListener listener, @AppFocusType int appType) { 135 if (listener == null) { 136 throw new IllegalArgumentException("null listener"); 137 } 138 IAppFocusListenerImpl binder; 139 synchronized (this) { 140 binder = mChangeBinders.get(listener); 141 if (binder == null) { 142 binder = new IAppFocusListenerImpl(this, listener); 143 mChangeBinders.put(listener, binder); 144 } 145 binder.addAppType(appType); 146 } 147 try { 148 mService.registerFocusListener(binder, appType); 149 } catch (RemoteException e) { 150 handleRemoteExceptionFromCarService(e); 151 } 152 } 153 154 /** 155 * Unregister listener for application type and stop listening focus change events. 156 * @param listener 157 * @param appType 158 */ removeFocusListener(OnAppFocusChangedListener listener, @AppFocusType int appType)159 public void removeFocusListener(OnAppFocusChangedListener listener, @AppFocusType int appType) { 160 IAppFocusListenerImpl binder; 161 synchronized (this) { 162 binder = mChangeBinders.get(listener); 163 if (binder == null) { 164 return; 165 } 166 } 167 try { 168 mService.unregisterFocusListener(binder, appType); 169 } catch (RemoteException e) { 170 handleRemoteExceptionFromCarService(e); 171 // continue for local clean-up 172 } 173 synchronized (this) { 174 binder.removeAppType(appType); 175 if (!binder.hasAppTypes()) { 176 mChangeBinders.remove(listener); 177 } 178 179 } 180 } 181 182 /** 183 * Unregister listener and stop listening focus change events. 184 * @param listener 185 */ removeFocusListener(OnAppFocusChangedListener listener)186 public void removeFocusListener(OnAppFocusChangedListener listener) { 187 IAppFocusListenerImpl binder; 188 synchronized (this) { 189 binder = mChangeBinders.remove(listener); 190 if (binder == null) { 191 return; 192 } 193 } 194 try { 195 for (Integer appType : binder.getAppTypes()) { 196 mService.unregisterFocusListener(binder, appType); 197 } 198 } catch (RemoteException e) { 199 handleRemoteExceptionFromCarService(e); 200 } 201 } 202 203 /** 204 * Returns application types currently active in the system. 205 * @hide 206 */ getActiveAppTypes()207 public int[] getActiveAppTypes() { 208 try { 209 return mService.getActiveAppTypes(); 210 } catch (RemoteException e) { 211 return handleRemoteExceptionFromCarService(e, new int[0]); 212 } 213 } 214 215 /** 216 * Checks if listener is associated with active a focus 217 * @param callback 218 * @param appType 219 */ isOwningFocus(OnAppFocusOwnershipCallback callback, @AppFocusType int appType)220 public boolean isOwningFocus(OnAppFocusOwnershipCallback callback, @AppFocusType int appType) { 221 IAppFocusOwnershipCallbackImpl binder; 222 synchronized (this) { 223 binder = mOwnershipBinders.get(callback); 224 if (binder == null) { 225 return false; 226 } 227 } 228 try { 229 return mService.isOwningFocus(binder, appType); 230 } catch (RemoteException e) { 231 return handleRemoteExceptionFromCarService(e, false); 232 } 233 } 234 235 /** 236 * Requests application focus. 237 * By requesting this, the application is becoming owner of the focus, and will get 238 * {@link OnAppFocusOwnershipCallback#onAppFocusOwnershipLost(int)} 239 * if ownership is given to other app by calling this. Fore-ground app will have higher priority 240 * and other app cannot set the same focus while owner is in fore-ground. 241 * @param appType 242 * @param ownershipCallback 243 * @return {@link #APP_FOCUS_REQUEST_FAILED} or {@link #APP_FOCUS_REQUEST_SUCCEEDED} 244 * @throws SecurityException If owner cannot be changed. 245 */ requestAppFocus( int appType, OnAppFocusOwnershipCallback ownershipCallback)246 public @AppFocusRequestResult int requestAppFocus( 247 int appType, OnAppFocusOwnershipCallback ownershipCallback) { 248 if (ownershipCallback == null) { 249 throw new IllegalArgumentException("null listener"); 250 } 251 IAppFocusOwnershipCallbackImpl binder; 252 synchronized (this) { 253 binder = mOwnershipBinders.get(ownershipCallback); 254 if (binder == null) { 255 binder = new IAppFocusOwnershipCallbackImpl(this, ownershipCallback); 256 mOwnershipBinders.put(ownershipCallback, binder); 257 } 258 binder.addAppType(appType); 259 } 260 try { 261 return mService.requestAppFocus(binder, appType); 262 } catch (RemoteException e) { 263 return handleRemoteExceptionFromCarService(e, APP_FOCUS_REQUEST_FAILED); 264 } 265 } 266 267 /** 268 * Abandon the given focus, i.e. mark it as inactive. This also involves releasing ownership 269 * for the focus. 270 * @param ownershipCallback 271 * @param appType 272 */ abandonAppFocus(OnAppFocusOwnershipCallback ownershipCallback, @AppFocusType int appType)273 public void abandonAppFocus(OnAppFocusOwnershipCallback ownershipCallback, 274 @AppFocusType int appType) { 275 if (ownershipCallback == null) { 276 throw new IllegalArgumentException("null callback"); 277 } 278 IAppFocusOwnershipCallbackImpl binder; 279 synchronized (this) { 280 binder = mOwnershipBinders.get(ownershipCallback); 281 if (binder == null) { 282 return; 283 } 284 } 285 try { 286 mService.abandonAppFocus(binder, appType); 287 } catch (RemoteException e) { 288 handleRemoteExceptionFromCarService(e); 289 // continue for local clean-up 290 } 291 synchronized (this) { 292 binder.removeAppType(appType); 293 if (!binder.hasAppTypes()) { 294 mOwnershipBinders.remove(ownershipCallback); 295 } 296 } 297 } 298 299 /** 300 * Abandon all focuses, i.e. mark them as inactive. This also involves releasing ownership 301 * for the focus. 302 * @param ownershipCallback 303 */ abandonAppFocus(OnAppFocusOwnershipCallback ownershipCallback)304 public void abandonAppFocus(OnAppFocusOwnershipCallback ownershipCallback) { 305 IAppFocusOwnershipCallbackImpl binder; 306 synchronized (this) { 307 binder = mOwnershipBinders.remove(ownershipCallback); 308 if (binder == null) { 309 return; 310 } 311 } 312 try { 313 for (Integer appType : binder.getAppTypes()) { 314 mService.abandonAppFocus(binder, appType); 315 } 316 } catch (RemoteException e) { 317 handleRemoteExceptionFromCarService(e); 318 } 319 } 320 321 /** @hide */ 322 @Override onCarDisconnected()323 public void onCarDisconnected() { 324 // nothing to do 325 } 326 327 private static class IAppFocusListenerImpl extends IAppFocusListener.Stub { 328 329 private final WeakReference<CarAppFocusManager> mManager; 330 private final WeakReference<OnAppFocusChangedListener> mListener; 331 private final Set<Integer> mAppTypes = new HashSet<>(); 332 IAppFocusListenerImpl(CarAppFocusManager manager, OnAppFocusChangedListener listener)333 private IAppFocusListenerImpl(CarAppFocusManager manager, 334 OnAppFocusChangedListener listener) { 335 mManager = new WeakReference<>(manager); 336 mListener = new WeakReference<>(listener); 337 } 338 addAppType(@ppFocusType int appType)339 public void addAppType(@AppFocusType int appType) { 340 mAppTypes.add(appType); 341 } 342 removeAppType(@ppFocusType int appType)343 public void removeAppType(@AppFocusType int appType) { 344 mAppTypes.remove(appType); 345 } 346 getAppTypes()347 public Set<Integer> getAppTypes() { 348 return mAppTypes; 349 } 350 hasAppTypes()351 public boolean hasAppTypes() { 352 return !mAppTypes.isEmpty(); 353 } 354 355 @Override onAppFocusChanged(final @AppFocusType int appType, final boolean active)356 public void onAppFocusChanged(final @AppFocusType int appType, final boolean active) { 357 final CarAppFocusManager manager = mManager.get(); 358 final OnAppFocusChangedListener listener = mListener.get(); 359 if (manager == null || listener == null) { 360 return; 361 } 362 manager.getEventHandler().post(() -> { 363 listener.onAppFocusChanged(appType, active); 364 }); 365 } 366 } 367 368 private static class IAppFocusOwnershipCallbackImpl extends IAppFocusOwnershipCallback.Stub { 369 370 private final WeakReference<CarAppFocusManager> mManager; 371 private final WeakReference<OnAppFocusOwnershipCallback> mCallback; 372 private final Set<Integer> mAppTypes = new HashSet<>(); 373 IAppFocusOwnershipCallbackImpl(CarAppFocusManager manager, OnAppFocusOwnershipCallback callback)374 private IAppFocusOwnershipCallbackImpl(CarAppFocusManager manager, 375 OnAppFocusOwnershipCallback callback) { 376 mManager = new WeakReference<>(manager); 377 mCallback = new WeakReference<>(callback); 378 } 379 addAppType(@ppFocusType int appType)380 public void addAppType(@AppFocusType int appType) { 381 mAppTypes.add(appType); 382 } 383 removeAppType(@ppFocusType int appType)384 public void removeAppType(@AppFocusType int appType) { 385 mAppTypes.remove(appType); 386 } 387 getAppTypes()388 public Set<Integer> getAppTypes() { 389 return mAppTypes; 390 } 391 hasAppTypes()392 public boolean hasAppTypes() { 393 return !mAppTypes.isEmpty(); 394 } 395 396 @Override onAppFocusOwnershipLost(final @AppFocusType int appType)397 public void onAppFocusOwnershipLost(final @AppFocusType int appType) { 398 final CarAppFocusManager manager = mManager.get(); 399 final OnAppFocusOwnershipCallback callback = mCallback.get(); 400 if (manager == null || callback == null) { 401 return; 402 } 403 manager.getEventHandler().post(() -> { 404 callback.onAppFocusOwnershipLost(appType); 405 }); 406 } 407 408 @Override onAppFocusOwnershipGranted(final @AppFocusType int appType)409 public void onAppFocusOwnershipGranted(final @AppFocusType int appType) { 410 final CarAppFocusManager manager = mManager.get(); 411 final OnAppFocusOwnershipCallback callback = mCallback.get(); 412 if (manager == null || callback == null) { 413 return; 414 } 415 manager.getEventHandler().post(() -> { 416 callback.onAppFocusOwnershipGranted(appType); 417 }); 418 } 419 } 420 } 421