blob: f48355036ff137bdd3f5bb0586548b87d5893a49 [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.HashSet;
import java.util.Map;
import java.util.Set;
/**
* CarAppFocusManager allows applications to set and listen for the current application focus
* 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.
*/
public final class CarAppFocusManager implements CarManagerBase {
/**
* Listener to get notification for app getting information on application type status changes.
*/
public interface AppFocusChangeListener {
/**
* Application focus has changed. Note that {@link CarAppFocusManager} instance
* causing the change will not get this notification.
* @param appType
* @param active
*/
void onAppFocusChange(int appType, boolean active);
}
/**
* Listener to get notification for app getting information on app type ownership loss.
*/
public interface AppFocusOwnershipChangeListener {
/**
* Lost ownership for the focus, which happens when other app has set the focus.
* The app losing focus should stop the action associated with the focus.
* For example, navigation app currently running active navigation should stop navigation
* upon getting this for {@link CarAppFocusManager#APP_FOCUS_TYPE_NAVIGATION}.
* @param appType
*/
void onAppFocusOwnershipLoss(int appType);
}
/**
* Represents navigation focus.
*/
public static final int APP_FOCUS_TYPE_NAVIGATION = 1;
/**
* Represents voice command focus.
*/
public static final int APP_FOCUS_TYPE_VOICE_COMMAND = 2;
/**
* Update this after adding a new app type.
* @hide
*/
public static final int APP_FOCUS_MAX = 2;
/**
* A failed focus change request.
*/
public static final int APP_FOCUS_REQUEST_FAILED = 0;
/**
* A successful focus change request.
*/
public static final int APP_FOCUS_REQUEST_GRANTED = 1;
private final IAppFocus mService;
private final Handler mHandler;
private final Map<AppFocusChangeListener, IAppFocusListenerImpl> mChangeBinders =
new HashMap<>();
private final Map<AppFocusOwnershipChangeListener, IAppFocusOwnershipListenerImpl>
mOwnershipBinders = new HashMap<>();
/**
* @hide
*/
CarAppFocusManager(IBinder service, Looper looper) {
mService = IAppFocus.Stub.asInterface(service);
mHandler = new Handler(looper);
}
/**
* Register listener to monitor app focus change.
* @param listener
* @param appType Applitcaion type to get notification for.
* @throws CarNotConnectedException
*/
public void registerFocusListener(AppFocusChangeListener listener, int appType)
throws CarNotConnectedException {
if (listener == null) {
throw new IllegalArgumentException("null listener");
}
IAppFocusListenerImpl binder;
synchronized (this) {
binder = mChangeBinders.get(listener);
if (binder == null) {
binder = new IAppFocusListenerImpl(this, listener);
mChangeBinders.put(listener, binder);
}
binder.addAppType(appType);
}
try {
mService.registerFocusListener(binder, appType);
} catch (RemoteException e) {
throw new CarNotConnectedException(e);
}
}
/**
* Unregister listener for application type and stop listening focus change events.
* @param listener
* @param appType
* @throws CarNotConnectedException
*/
public void unregisterFocusListener(AppFocusChangeListener listener, int appType)
throws CarNotConnectedException {
IAppFocusListenerImpl binder;
synchronized (this) {
binder = mChangeBinders.get(listener);
if (binder == null) {
return;
}
}
try {
mService.unregisterFocusListener(binder, appType);
} catch (RemoteException e) {
throw new CarNotConnectedException(e);
}
synchronized (this) {
binder.removeAppType(appType);
if (!binder.hasAppTypes()) {
mChangeBinders.remove(listener);
}
}
}
/**
* Unregister listener and stop listening focus change events.
* @param listener
* @throws CarNotConnectedException
*/
public void unregisterFocusListener(AppFocusChangeListener listener)
throws CarNotConnectedException {
IAppFocusListenerImpl binder;
synchronized (this) {
binder = mChangeBinders.remove(listener);
if (binder == null) {
return;
}
}
try {
for (Integer appType : binder.getAppTypes()) {
mService.unregisterFocusListener(binder, appType);
}
} catch (RemoteException e) {
throw new CarNotConnectedException(e);
}
}
/**
* Returns application types currently active in the system.
* @throws CarNotConnectedException
* @hide
*/
public int[] getActiveAppTypes() throws CarNotConnectedException {
try {
return mService.getActiveAppTypes();
} catch (RemoteException e) {
throw new CarNotConnectedException(e);
}
}
/**
* Checks if listener is associated with active a focus
* @param listener
* @param appType
* @throws CarNotConnectedException
*/
public boolean isOwningFocus(AppFocusOwnershipChangeListener listener, int appType)
throws CarNotConnectedException {
IAppFocusOwnershipListenerImpl binder;
synchronized (this) {
binder = mOwnershipBinders.get(listener);
if (binder == null) {
return false;
}
}
try {
return mService.isOwningFocus(binder, appType);
} catch (RemoteException e) {
throw new CarNotConnectedException(e);
}
}
/**
* Requests application focus.
* By requesting this, the application is becoming owner of the focus, and will get
* {@link AppFocusOwnershipChangeListener#onAppFocusOwnershipLoss(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 focus while owner is in fore-ground.
* @param ownershipListener
* @param appType
* @return {@link #APP_FOCUS_REQUEST_FAILED} or {@link #APP_FOCUS_REQUEST_GRANTED}
* @throws CarNotConnectedException
* @throws SecurityException If owner cannot be changed.
*/
public int requestAppFocus(AppFocusOwnershipChangeListener ownershipListener, int appType)
throws SecurityException, CarNotConnectedException {
if (ownershipListener == null) {
throw new IllegalArgumentException("null listener");
}
IAppFocusOwnershipListenerImpl binder;
synchronized (this) {
binder = mOwnershipBinders.get(ownershipListener);
if (binder == null) {
binder = new IAppFocusOwnershipListenerImpl(this, ownershipListener);
mOwnershipBinders.put(ownershipListener, binder);
}
binder.addAppType(appType);
}
try {
return mService.requestAppFocus(binder, appType);
} catch (RemoteException e) {
throw new CarNotConnectedException(e);
}
}
/**
* Abandon the given focus, i.e. mark it as inactive. This also involves releasing ownership
* for the focus.
* @param ownershipListener
* @param appType
* @throws CarNotConnectedException
*/
public void abandonAppFocus(AppFocusOwnershipChangeListener ownershipListener, int appType)
throws CarNotConnectedException {
if (ownershipListener == null) {
throw new IllegalArgumentException("null listener");
}
IAppFocusOwnershipListenerImpl binder;
synchronized (this) {
binder = mOwnershipBinders.get(ownershipListener);
if (binder == null) {
return;
}
}
try {
mService.abandonAppFocus(binder, appType);
} catch (RemoteException e) {
throw new CarNotConnectedException(e);
}
synchronized (this) {
binder.removeAppType(appType);
if (!binder.hasAppTypes()) {
mOwnershipBinders.remove(ownershipListener);
}
}
}
/**
* Abandon all focuses, i.e. mark them as inactive. This also involves releasing ownership
* for the focus.
* @param ownershipListener
* @throws CarNotConnectedException
*/
public void abandonAppFocus(AppFocusOwnershipChangeListener ownershipListener)
throws CarNotConnectedException {
IAppFocusOwnershipListenerImpl binder;
synchronized (this) {
binder = mOwnershipBinders.remove(ownershipListener);
if (binder == null) {
return;
}
}
try {
for (Integer appType : binder.getAppTypes()) {
mService.abandonAppFocus(binder, appType);
}
} catch (RemoteException e) {
throw new CarNotConnectedException(e);
}
}
/** @hide */
@Override
public void onCarDisconnected() {
// nothing to do
}
private static class IAppFocusListenerImpl extends IAppFocusListener.Stub {
private final WeakReference<CarAppFocusManager> mManager;
private final WeakReference<AppFocusChangeListener> mListener;
private final Set<Integer> mAppTypes = new HashSet<>();
private IAppFocusListenerImpl(CarAppFocusManager manager, AppFocusChangeListener listener) {
mManager = new WeakReference<>(manager);
mListener = new WeakReference<>(listener);
}
public void addAppType(int appType) {
mAppTypes.add(appType);
}
public void removeAppType(int appType) {
mAppTypes.remove(appType);
}
public Set<Integer> getAppTypes() {
return mAppTypes;
}
public boolean hasAppTypes() {
return !mAppTypes.isEmpty();
}
@Override
public void onAppFocusChange(final int appType, final boolean active) {
final CarAppFocusManager manager = mManager.get();
final AppFocusChangeListener listener = mListener.get();
if (manager == null || listener == null) {
return;
}
manager.mHandler.post(new Runnable() {
@Override
public void run() {
listener.onAppFocusChange(appType, active);
}
});
}
}
private static class IAppFocusOwnershipListenerImpl extends IAppFocusOwnershipListener.Stub {
private final WeakReference<CarAppFocusManager> mManager;
private final WeakReference<AppFocusOwnershipChangeListener> mListener;
private final Set<Integer> mAppTypes = new HashSet<>();
private IAppFocusOwnershipListenerImpl(CarAppFocusManager manager,
AppFocusOwnershipChangeListener listener) {
mManager = new WeakReference<>(manager);
mListener = new WeakReference<>(listener);
}
public void addAppType(int appType) {
mAppTypes.add(appType);
}
public void removeAppType(int appType) {
mAppTypes.remove(appType);
}
public Set<Integer> getAppTypes() {
return mAppTypes;
}
public boolean hasAppTypes() {
return !mAppTypes.isEmpty();
}
@Override
public void onAppFocusOwnershipLoss(final int appType) {
final CarAppFocusManager manager = mManager.get();
final AppFocusOwnershipChangeListener listener = mListener.get();
if (manager == null || listener == null) {
return;
}
manager.mHandler.post(new Runnable() {
@Override
public void run() {
listener.onAppFocusOwnershipLoss(appType);
}
});
}
}
}