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