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