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