blob: 5eb4c569e58d25397fa432e50787715a2e9c14d2 [file] [log] [blame]
/*
* 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<Integer, AppContextOwnershipChangeListener> 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<Integer, AppContextOwnershipChangeListener>();
}
/**
* 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<CarAppContextManager> 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);
}
});
}
}
}