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.os.Handler; 20 import android.os.IBinder; 21 import android.os.Looper; 22 import android.os.RemoteException; 23 24 import java.lang.ref.WeakReference; 25 import java.util.HashMap; 26 import java.util.Map; 27 28 /** 29 * CarAppContextManager allows applications to set and listen for the current application context 30 * like active navigation or active voice command. Usually only one instance of such application 31 * should run in the system, and other app setting the flag for the matching app should 32 * lead into other app to stop. 33 * @hide 34 */ 35 public class CarAppContextManager implements CarManagerBase { 36 /** 37 * Listener to get notification for app getting information on app context change. 38 */ 39 public interface AppContextChangeListener { 40 /** 41 * Application context has changed. Note that {@link CarAppContextManager} instance 42 * causing the change will not get this notification. 43 * @param activeContexts 44 */ onAppContextChange(int activeContexts)45 void onAppContextChange(int activeContexts); 46 } 47 48 /** 49 * Listener to get notification for app getting information on app context ownership loss. 50 */ 51 public interface AppContextOwnershipChangeListener { 52 /** 53 * Lost ownership for the context, which happens when other app has set the context. 54 * The app losing context should stop the action associated with the context. 55 * For example, navigaiton app currently running active navigation should stop navigation 56 * upon getting this for {@link CarAppContextManager#APP_CONTEXT_NAVIGATION}. 57 * @param context 58 */ onAppContextOwnershipLoss(int context)59 void onAppContextOwnershipLoss(int context); 60 } 61 62 /** @hide */ 63 public static final int APP_CONTEXT_START_FLAG = 0x1; 64 /** 65 * Flag for active navigation. 66 */ 67 public static final int APP_CONTEXT_NAVIGATION = 0x1; 68 /** 69 * Flag for active voice command. 70 */ 71 public static final int APP_CONTEXT_VOICE_COMMAND = 0x2; 72 /** 73 * Update this after adding a new flag. 74 * @hide 75 */ 76 public static final int APP_CONTEXT_END_FLAG = 0x2; 77 78 private final IAppContext mService; 79 private final Handler mHandler; 80 private final IAppContextListenerImpl mBinderListener; 81 private final Map<Integer, AppContextOwnershipChangeListener> mOwnershipListeners; 82 83 private AppContextChangeListener mListener; 84 private int mContextFilter; 85 86 /** 87 * @hide 88 */ CarAppContextManager(IBinder service, Looper looper)89 CarAppContextManager(IBinder service, Looper looper) { 90 mService = IAppContext.Stub.asInterface(service); 91 mHandler = new Handler(looper); 92 mBinderListener = new IAppContextListenerImpl(this); 93 mOwnershipListeners = new HashMap<Integer, AppContextOwnershipChangeListener>(); 94 } 95 96 /** 97 * Register listener to monitor app context change. Only one listener can be registered and 98 * registering multiple times will lead into only the last listener to be active. 99 * @param listener 100 * @param contextFilter Flags of cotexts to get notification. 101 * @throws CarNotConnectedException 102 */ registerContextListener(AppContextChangeListener listener, int contextFilter)103 public void registerContextListener(AppContextChangeListener listener, int contextFilter) 104 throws CarNotConnectedException { 105 if (listener == null) { 106 throw new IllegalArgumentException("null listener"); 107 } 108 synchronized(this) { 109 if (mListener == null || mContextFilter != contextFilter) { 110 try { 111 mService.registerContextListener(mBinderListener, contextFilter); 112 } catch (RemoteException e) { 113 throw new CarNotConnectedException(e); 114 } 115 } 116 mListener = listener; 117 mContextFilter = contextFilter; 118 } 119 } 120 121 /** 122 * Unregister listener and stop listening context change events. If app has owned a context 123 * by {@link #setActiveContext(int)}, it will be reset to inactive state. 124 * @throws CarNotConnectedException 125 */ unregisterContextListener()126 public void unregisterContextListener() throws CarNotConnectedException { 127 synchronized(this) { 128 try { 129 mService.unregisterContextListener(mBinderListener); 130 } catch (RemoteException e) { 131 throw new CarNotConnectedException(e); 132 } 133 mListener = null; 134 mContextFilter = 0; 135 } 136 } 137 getActiveAppContexts()138 public int getActiveAppContexts() throws CarNotConnectedException { 139 try { 140 return mService.getActiveAppContexts(); 141 } catch (RemoteException e) { 142 throw new CarNotConnectedException(e); 143 } 144 } 145 isOwningContext(int context)146 public boolean isOwningContext(int context) throws CarNotConnectedException { 147 try { 148 return mService.isOwningContext(mBinderListener, context); 149 } catch (RemoteException e) { 150 throw new CarNotConnectedException(e); 151 } 152 } 153 154 /** 155 * Set the given contexts as active. By setting this, the application is becoming owner 156 * of the context, and will get 157 * {@link AppContextOwnershipChangeListener#onAppContextOwnershipLoss(int)} 158 * if ownership is given to other app by calling this. Fore-ground app will have higher priority 159 * and other app cannot set the same context while owner is in fore-ground. 160 * Only one listener per context can be registered and 161 * registering multiple times will lead into only the last listener to be active. 162 * @param ownershipListener 163 * @param contexts 164 * @throws CarNotConnectedException 165 * @throws SecurityException If owner cannot be changed. 166 */ setActiveContexts(AppContextOwnershipChangeListener ownershipListener, int contexts)167 public void setActiveContexts(AppContextOwnershipChangeListener ownershipListener, int contexts) 168 throws SecurityException, CarNotConnectedException { 169 if (ownershipListener == null) { 170 throw new IllegalArgumentException("null listener"); 171 } 172 synchronized (this) { 173 try { 174 mService.setActiveContexts(mBinderListener, contexts); 175 } catch (RemoteException e) { 176 throw new CarNotConnectedException(e); 177 } 178 for (int flag = APP_CONTEXT_START_FLAG; flag <= APP_CONTEXT_END_FLAG; flag <<= 1) { 179 if ((flag & contexts) != 0) { 180 mOwnershipListeners.put(flag, ownershipListener); 181 } 182 } 183 } 184 } 185 186 /** 187 * Reset the given contexts, i.e. mark them as inactive. This also involves releasing ownership 188 * for the context. 189 * @param contexts 190 * @throws CarNotConnectedException 191 */ resetActiveContexts(int contexts)192 public void resetActiveContexts(int contexts) throws CarNotConnectedException { 193 try { 194 mService.resetActiveContexts(mBinderListener, contexts); 195 } catch (RemoteException e) { 196 throw new CarNotConnectedException(e); 197 } 198 synchronized (this) { 199 for (int flag = APP_CONTEXT_START_FLAG; flag <= APP_CONTEXT_END_FLAG; flag <<= 1) { 200 if ((flag & contexts) != 0) { 201 mOwnershipListeners.remove(flag); 202 } 203 } 204 } 205 } 206 207 @Override onCarDisconnected()208 public void onCarDisconnected() { 209 // nothing to do 210 } 211 handleAppContextChange(int activeContexts)212 private void handleAppContextChange(int activeContexts) { 213 AppContextChangeListener listener; 214 int newContext; 215 synchronized (this) { 216 if (mListener == null) { 217 return; 218 } 219 listener = mListener; 220 newContext = activeContexts & mContextFilter; 221 } 222 listener.onAppContextChange(newContext); 223 } 224 handleAppContextOwnershipLoss(int context)225 private void handleAppContextOwnershipLoss(int context) { 226 AppContextOwnershipChangeListener listener; 227 synchronized (this) { 228 listener = mOwnershipListeners.get(context); 229 if (listener == null) { 230 return; 231 } 232 } 233 listener.onAppContextOwnershipLoss(context); 234 } 235 236 private static class IAppContextListenerImpl extends IAppContextListener.Stub { 237 238 private final WeakReference<CarAppContextManager> mManager; 239 IAppContextListenerImpl(CarAppContextManager manager)240 private IAppContextListenerImpl(CarAppContextManager manager) { 241 mManager = new WeakReference<>(manager); 242 } 243 244 @Override onAppContextChange(final int activeContexts)245 public void onAppContextChange(final int activeContexts) { 246 final CarAppContextManager manager = mManager.get(); 247 if (manager == null) { 248 return; 249 } 250 manager.mHandler.post(new Runnable() { 251 @Override 252 public void run() { 253 manager.handleAppContextChange(activeContexts); 254 } 255 }); 256 } 257 258 @Override onAppContextOwnershipLoss(final int context)259 public void onAppContextOwnershipLoss(final int context) { 260 final CarAppContextManager manager = mManager.get(); 261 if (manager == null) { 262 return; 263 } 264 manager.mHandler.post(new Runnable() { 265 @Override 266 public void run() { 267 manager.handleAppContextOwnershipLoss(context); 268 } 269 }); 270 } 271 } 272 } 273