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