/* * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.car; import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.RemoteException; import java.lang.ref.WeakReference; import java.util.HashMap; import java.util.Map; /** * CarAppContextManager allows applications to set and listen for the current application context * like active navigation or active voice command. Usually only one instance of such application * should run in the system, and other app setting the flag for the matching app should * lead into other app to stop. * @hide */ public class CarAppContextManager implements CarManagerBase { /** * Listener to get notification for app getting information on app context change. */ public interface AppContextChangeListener { /** * Application context has changed. Note that {@link CarAppContextManager} instance * causing the change will not get this notification. * @param activeContexts */ void onAppContextChange(int activeContexts); } /** * Listener to get notification for app getting information on app context ownership loss. */ public interface AppContextOwnershipChangeListener { /** * Lost ownership for the context, which happens when other app has set the context. * The app losing context should stop the action associated with the context. * For example, navigaiton app currently running active navigation should stop navigation * upon getting this for {@link CarAppContextManager#APP_CONTEXT_NAVIGATION}. * @param context */ void onAppContextOwnershipLoss(int context); } /** @hide */ public static final int APP_CONTEXT_START_FLAG = 0x1; /** * Flag for active navigation. */ public static final int APP_CONTEXT_NAVIGATION = 0x1; /** * Flag for active voice command. */ public static final int APP_CONTEXT_VOICE_COMMAND = 0x2; /** * Update this after adding a new flag. * @hide */ public static final int APP_CONTEXT_END_FLAG = 0x2; private final IAppContext mService; private final Handler mHandler; private final IAppContextListenerImpl mBinderListener; private final Map mOwnershipListeners; private AppContextChangeListener mListener; private int mContextFilter; /** * @hide */ CarAppContextManager(IBinder service, Looper looper) { mService = IAppContext.Stub.asInterface(service); mHandler = new Handler(looper); mBinderListener = new IAppContextListenerImpl(this); mOwnershipListeners = new HashMap(); } /** * Register listener to monitor app context change. Only one listener can be registered and * registering multiple times will lead into only the last listener to be active. * @param listener * @param contextFilter Flags of cotexts to get notification. * @throws CarNotConnectedException */ public void registerContextListener(AppContextChangeListener listener, int contextFilter) throws CarNotConnectedException { if (listener == null) { throw new IllegalArgumentException("null listener"); } synchronized(this) { if (mListener == null || mContextFilter != contextFilter) { try { mService.registerContextListener(mBinderListener, contextFilter); } catch (RemoteException e) { throw new CarNotConnectedException(e); } } mListener = listener; mContextFilter = contextFilter; } } /** * Unregister listener and stop listening context change events. If app has owned a context * by {@link #setActiveContext(int)}, it will be reset to inactive state. * @throws CarNotConnectedException */ public void unregisterContextListener() throws CarNotConnectedException { synchronized(this) { try { mService.unregisterContextListener(mBinderListener); } catch (RemoteException e) { throw new CarNotConnectedException(e); } mListener = null; mContextFilter = 0; } } public int getActiveAppContexts() throws CarNotConnectedException { try { return mService.getActiveAppContexts(); } catch (RemoteException e) { throw new CarNotConnectedException(e); } } public boolean isOwningContext(int context) throws CarNotConnectedException { try { return mService.isOwningContext(mBinderListener, context); } catch (RemoteException e) { throw new CarNotConnectedException(e); } } /** * Set the given contexts as active. By setting this, the application is becoming owner * of the context, and will get * {@link AppContextOwnershipChangeListener#onAppContextOwnershipLoss(int)} * if ownership is given to other app by calling this. Fore-ground app will have higher priority * and other app cannot set the same context while owner is in fore-ground. * Only one listener per context can be registered and * registering multiple times will lead into only the last listener to be active. * @param ownershipListener * @param contexts * @throws CarNotConnectedException * @throws SecurityException If owner cannot be changed. */ public void setActiveContexts(AppContextOwnershipChangeListener ownershipListener, int contexts) throws SecurityException, CarNotConnectedException { if (ownershipListener == null) { throw new IllegalArgumentException("null listener"); } synchronized (this) { try { mService.setActiveContexts(mBinderListener, contexts); } catch (RemoteException e) { throw new CarNotConnectedException(e); } for (int flag = APP_CONTEXT_START_FLAG; flag <= APP_CONTEXT_END_FLAG; flag <<= 1) { if ((flag & contexts) != 0) { mOwnershipListeners.put(flag, ownershipListener); } } } } /** * Reset the given contexts, i.e. mark them as inactive. This also involves releasing ownership * for the context. * @param contexts * @throws CarNotConnectedException */ public void resetActiveContexts(int contexts) throws CarNotConnectedException { try { mService.resetActiveContexts(mBinderListener, contexts); } catch (RemoteException e) { throw new CarNotConnectedException(e); } synchronized (this) { for (int flag = APP_CONTEXT_START_FLAG; flag <= APP_CONTEXT_END_FLAG; flag <<= 1) { if ((flag & contexts) != 0) { mOwnershipListeners.remove(flag); } } } } @Override public void onCarDisconnected() { // nothing to do } private void handleAppContextChange(int activeContexts) { AppContextChangeListener listener; int newContext; synchronized (this) { if (mListener == null) { return; } listener = mListener; newContext = activeContexts & mContextFilter; } listener.onAppContextChange(newContext); } private void handleAppContextOwnershipLoss(int context) { AppContextOwnershipChangeListener listener; synchronized (this) { listener = mOwnershipListeners.get(context); if (listener == null) { return; } } listener.onAppContextOwnershipLoss(context); } private static class IAppContextListenerImpl extends IAppContextListener.Stub { private final WeakReference mManager; private IAppContextListenerImpl(CarAppContextManager manager) { mManager = new WeakReference<>(manager); } @Override public void onAppContextChange(final int activeContexts) { final CarAppContextManager manager = mManager.get(); if (manager == null) { return; } manager.mHandler.post(new Runnable() { @Override public void run() { manager.handleAppContextChange(activeContexts); } }); } @Override public void onAppContextOwnershipLoss(final int context) { final CarAppContextManager manager = mManager.get(); if (manager == null) { return; } manager.mHandler.post(new Runnable() { @Override public void run() { manager.handleAppContextOwnershipLoss(context); } }); } } }