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