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