aboutsummaryrefslogtreecommitdiff
path: root/service/src/com/android/car/AppFocusService.java
diff options
context:
space:
mode:
authorVitalii Tomkiv <vitalit@google.com>2016-05-23 16:55:22 -0700
committerVitalii Tomkiv <vitalit@google.com>2016-06-01 14:34:48 -0700
commit46371473c416415fb6bcb8db85686669c3d65af6 (patch)
tree624c5dad2d5a07bb8b9ae80ec19b9c105cd7d9ad /service/src/com/android/car/AppFocusService.java
parent0ad4c14fef5cc9f9f9eedea9cfe612a5de2519f3 (diff)
downloadCar-46371473c416415fb6bcb8db85686669c3d65af6.tar.gz
Car api review: CarAppContextManager renaming to CarAppFocusManager.
Changes include: - rename to CarAppFocusManager - allow multiple client listener for listening and setting. - change name for set / reset to requestAppFocus, abandonAppFocus - rename all APIs to use focus instead of context Bug: 28886557 Change-Id: I5e5285c104435dbd2eea43c4b0e43ae5ead81218
Diffstat (limited to 'service/src/com/android/car/AppFocusService.java')
-rw-r--r--service/src/com/android/car/AppFocusService.java410
1 files changed, 410 insertions, 0 deletions
diff --git a/service/src/com/android/car/AppFocusService.java b/service/src/com/android/car/AppFocusService.java
new file mode 100644
index 0000000000..e22a76b013
--- /dev/null
+++ b/service/src/com/android/car/AppFocusService.java
@@ -0,0 +1,410 @@
+/*
+ * 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 com.android.car;
+
+import android.car.CarAppFocusManager;
+import android.car.IAppFocus;
+import android.car.IAppFocusListener;
+import android.car.IAppFocusOwnershipListener;
+import android.content.Context;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.io.PrintWriter;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * App focus service ensures only one instance of application type is active at a time.
+ */
+public class AppFocusService extends IAppFocus.Stub implements CarServiceBase,
+ BinderInterfaceContainer.BinderEventHandler<IAppFocusOwnershipListener> {
+ private static final boolean DBG = true;
+ private static final boolean DBG_EVENT = false;
+
+ private final ClientHolder mAllChangeClients;
+ private final OwnershipClientHolder mAllOwnershipClients;
+ /** K: appType, V: client owning it */
+ private final HashMap<Integer, OwnershipClientInfo> mFocusOwners = new HashMap<>();
+ private final Set<Integer> mActiveAppTypes = new HashSet<>();
+ private final HandlerThread mHandlerThread;
+ private final DispatchHandler mDispatchHandler;
+ private final BinderInterfaceContainer.BinderEventHandler<IAppFocusListener>
+ mAllBinderEventHandler =
+ new BinderInterfaceContainer.BinderEventHandler<IAppFocusListener>() {
+ @Override
+ public void onBinderDeath(
+ BinderInterfaceContainer.BinderInterface<IAppFocusListener> bInterface) {
+ // nothing to do.
+ }
+ };
+
+ public AppFocusService(Context context) {
+ mAllChangeClients = new ClientHolder(mAllBinderEventHandler);
+ mAllOwnershipClients = new OwnershipClientHolder(this);
+ mHandlerThread = new HandlerThread(AppFocusService.class.getSimpleName());
+ mHandlerThread.start();
+ mDispatchHandler = new DispatchHandler(mHandlerThread.getLooper());
+ }
+
+ @Override
+ public void registerFocusListener(IAppFocusListener listener, int appType) {
+ synchronized (this) {
+ ClientInfo info = (ClientInfo) mAllChangeClients.getBinderInterface(listener);
+ if (info == null) {
+ info = new ClientInfo(mAllChangeClients, listener, Binder.getCallingUid(),
+ Binder.getCallingPid(), appType);
+ mAllChangeClients.addBinderInterface(info);
+ } else {
+ info.addAppType(appType);
+ }
+ }
+ }
+
+ @Override
+ public void unregisterFocusListener(IAppFocusListener listener, int appType) {
+ synchronized (this) {
+ ClientInfo info = (ClientInfo) mAllChangeClients.getBinderInterface(listener);
+ if (info == null) {
+ return;
+ }
+ info.removeAppType(appType);
+ if (info.getAppTypes().isEmpty()) {
+ mAllChangeClients.removeBinder(listener);
+ }
+ }
+ }
+
+ @Override
+ public int[] getActiveAppTypes() {
+ synchronized (this) {
+ return toIntArray(mActiveAppTypes);
+ }
+ }
+
+ @Override
+ public boolean isOwningFocus(IAppFocusOwnershipListener listener, int appType) {
+ synchronized (this) {
+ OwnershipClientInfo info =
+ (OwnershipClientInfo) mAllOwnershipClients.getBinderInterface(listener);
+ if (info == null) {
+ return false;
+ }
+ return info.getOwnedAppTypes().contains(appType);
+ }
+ }
+
+ @Override
+ public int requestAppFocus(IAppFocusOwnershipListener listener, int appType) {
+ synchronized (this) {
+ OwnershipClientInfo info =
+ (OwnershipClientInfo) mAllOwnershipClients.getBinderInterface(listener);
+ if (info == null) {
+ info = new OwnershipClientInfo(mAllOwnershipClients, listener,
+ Binder.getCallingUid(), Binder.getCallingPid());
+ mAllOwnershipClients.addBinderInterface(info);
+ }
+ Set<Integer> alreadyOwnedAppTypes = info.getOwnedAppTypes();
+ if (!alreadyOwnedAppTypes.contains(appType)) {
+ OwnershipClientInfo ownerInfo = mFocusOwners.get(appType);
+ if (ownerInfo != null && ownerInfo != info) {
+ //TODO check if current owner is having fore-ground activity. If yes,
+ //reject request. Always grant if requestor is fore-ground activity.
+ ownerInfo.removeOwnedAppType(appType);
+ mDispatchHandler.requestAppFocusOwnershipLossDispatch(
+ ownerInfo.binderInterface, appType);
+ if (DBG) {
+ Log.i(CarLog.TAG_APP_FOCUS, "losing app type "
+ + appType + "," + ownerInfo.toString());
+ }
+ }
+ mFocusOwners.put(appType, info);
+ }
+ info.addOwnedAppType(appType);
+ if (mActiveAppTypes.add(appType)) {
+ if (DBG) {
+ Log.i(CarLog.TAG_APP_FOCUS, "adding active app type " + appType + ","
+ + info.toString());
+ }
+ for (BinderInterfaceContainer.BinderInterface<IAppFocusListener> client :
+ mAllChangeClients.getInterfaces()) {
+ ClientInfo clientInfo = (ClientInfo) client;
+ // dispatch events only when there is change after filter and the listener
+ // is not coming from the current caller.
+ if (clientInfo.getAppTypes().contains(appType)) {
+ mDispatchHandler.requestAppFocusChangeDispatch(clientInfo.binderInterface,
+ appType, true);
+ }
+ }
+ }
+ }
+ return CarAppFocusManager.APP_FOCUS_REQUEST_GRANTED;
+ }
+
+ @Override
+ public void abandonAppFocus(IAppFocusOwnershipListener listener, int appType) {
+ synchronized (this) {
+ OwnershipClientInfo info =
+ (OwnershipClientInfo) mAllOwnershipClients.getBinderInterface(listener);
+ if (info == null) {
+ // ignore as this client cannot have owned anything.
+ return;
+ }
+ if (!mActiveAppTypes.contains(appType)) {
+ // ignore as none of them are active;
+ return;
+ }
+ Set<Integer> currentlyOwnedAppTypes = info.getOwnedAppTypes();
+ if (!currentlyOwnedAppTypes.contains(appType)) {
+ // ignore as listener doesn't own focus.
+ return;
+ }
+ if (mFocusOwners.remove(appType) != null) {
+ mActiveAppTypes.remove(appType);
+ info.removeOwnedAppType(appType);
+ if (DBG) {
+ Log.i(CarLog.TAG_APP_FOCUS, "abandoning focus " + appType
+ + "," + info.toString());
+ }
+ for (BinderInterfaceContainer.BinderInterface<IAppFocusListener> client :
+ mAllChangeClients.getInterfaces()) {
+ ClientInfo clientInfo = (ClientInfo) client;
+ if (clientInfo.getAppTypes().contains(appType)) {
+ mDispatchHandler.requestAppFocusChangeDispatch(clientInfo.binderInterface,
+ appType, false);
+ }
+ }
+ }
+ }
+ return;
+ }
+
+ @Override
+ public void init() {
+ // nothing to do
+ }
+
+ @Override
+ public void release() {
+ synchronized (this) {
+ mAllChangeClients.clear();
+ mAllOwnershipClients.clear();
+ mFocusOwners.clear();
+ mActiveAppTypes.clear();
+ }
+ }
+
+ @Override
+ public void onBinderDeath(
+ BinderInterfaceContainer.BinderInterface<IAppFocusOwnershipListener> bInterface) {
+ OwnershipClientInfo info = (OwnershipClientInfo) bInterface;
+ for (Integer appType : info.getOwnedAppTypes()) {
+ abandonAppFocus(bInterface.binderInterface, appType);
+ }
+ }
+
+ @Override
+ public void dump(PrintWriter writer) {
+ writer.println("**AppFocusService**");
+ synchronized (this) {
+ writer.println("mActiveAppTypes:" + mActiveAppTypes);
+ for (BinderInterfaceContainer.BinderInterface<IAppFocusOwnershipListener> client :
+ mAllOwnershipClients.getInterfaces()) {
+ OwnershipClientInfo clientInfo = (OwnershipClientInfo) client;
+ writer.println(clientInfo.toString());
+ }
+ }
+ }
+
+ /**
+ * Returns true if process with given uid and pid owns provided focus.
+ */
+ public boolean isFocusOwner(int uid, int pid, int appType) {
+ synchronized (this) {
+ if (mFocusOwners.containsKey(appType)) {
+ OwnershipClientInfo clientInfo = mFocusOwners.get(appType);
+ return clientInfo.getUid() == uid && clientInfo.getPid() == pid;
+ }
+ }
+ return false;
+ }
+
+ private void dispatchAppFocusOwnershipLoss(IAppFocusOwnershipListener listener, int appType) {
+ try {
+ listener.onAppFocusOwnershipLoss(appType);
+ } catch (RemoteException e) {
+ }
+ }
+
+ private void dispatchAppFocusChange(IAppFocusListener listener, int appType, boolean active) {
+ try {
+ listener.onAppFocusChange(appType, active);
+ } catch (RemoteException e) {
+ }
+ }
+
+ private static class ClientHolder extends BinderInterfaceContainer<IAppFocusListener> {
+ private ClientHolder(BinderEventHandler<IAppFocusListener> holder) {
+ super(holder);
+ }
+ }
+
+ private static class OwnershipClientHolder extends
+ BinderInterfaceContainer<IAppFocusOwnershipListener> {
+ private OwnershipClientHolder(AppFocusService service) {
+ super(service);
+ }
+ }
+
+ private static class ClientInfo extends
+ BinderInterfaceContainer.BinderInterface<IAppFocusListener> {
+ private final int mUid;
+ private final int mPid;
+ private final Set<Integer> mAppTypes = new HashSet<>();
+
+ private ClientInfo(ClientHolder holder, IAppFocusListener binder, int uid, int pid,
+ int appType) {
+ super(holder, binder);
+ this.mUid = uid;
+ this.mPid = pid;
+ this.mAppTypes.add(appType);
+ }
+
+ private synchronized Set<Integer> getAppTypes() {
+ return mAppTypes;
+ }
+
+ private synchronized boolean addAppType(Integer appType) {
+ return mAppTypes.add(appType);
+ }
+
+ private synchronized boolean removeAppType(Integer appType) {
+ return mAppTypes.remove(appType);
+ }
+
+ private synchronized void clearAppTypes() {
+ mAppTypes.clear();
+ }
+
+ @Override
+ public String toString() {
+ synchronized (this) {
+ return "ClientInfo{mUid=" + mUid + ",mPid=" + mPid
+ + ",appTypes=" + mAppTypes + "}";
+ }
+ }
+ }
+
+ private static class OwnershipClientInfo extends
+ BinderInterfaceContainer.BinderInterface<IAppFocusOwnershipListener> {
+ private final int mUid;
+ private final int mPid;
+ private final Set<Integer> mOwnedAppTypes = new HashSet<>();
+
+ private OwnershipClientInfo(OwnershipClientHolder holder, IAppFocusOwnershipListener binder,
+ int uid, int pid) {
+ super(holder, binder);
+ this.mUid = uid;
+ this.mPid = pid;
+ }
+
+ private synchronized Set<Integer> getOwnedAppTypes() {
+ if (DBG_EVENT) {
+ Log.i(CarLog.TAG_APP_FOCUS, "getOwnedAppTypes " + mOwnedAppTypes);
+ }
+ return mOwnedAppTypes;
+ }
+
+ private synchronized boolean addOwnedAppType(Integer appType) {
+ if (DBG_EVENT) {
+ Log.i(CarLog.TAG_APP_FOCUS, "addOwnedAppType " + appType);
+ }
+ return mOwnedAppTypes.add(appType);
+ }
+
+ private synchronized boolean removeOwnedAppType(Integer appType) {
+ if (DBG_EVENT) {
+ Log.i(CarLog.TAG_APP_FOCUS, "removeOwnedAppType " + appType);
+ }
+ return mOwnedAppTypes.remove(appType);
+ }
+
+ public int getUid() {
+ return mUid;
+ }
+
+ public int getPid() {
+ return mPid;
+ }
+
+ @Override
+ public String toString() {
+ synchronized (this) {
+ return "ClientInfo{mUid=" + mUid + ",mPid=" + mPid
+ + ",owned=" + mOwnedAppTypes + "}";
+ }
+ }
+ }
+
+ private class DispatchHandler extends Handler {
+ private static final int MSG_DISPATCH_OWNERSHIP_LOSS = 0;
+ private static final int MSG_DISPATCH_FOCUS_CHANGE = 1;
+
+ private DispatchHandler(Looper looper) {
+ super(looper);
+ }
+
+ private void requestAppFocusOwnershipLossDispatch(IAppFocusOwnershipListener listener,
+ int appType) {
+ Message msg = obtainMessage(MSG_DISPATCH_OWNERSHIP_LOSS, appType, 0, listener);
+ sendMessage(msg);
+ }
+
+ private void requestAppFocusChangeDispatch(IAppFocusListener listener, int appType,
+ boolean active) {
+ Message msg = obtainMessage(MSG_DISPATCH_FOCUS_CHANGE, appType, active ? 1 : 0,
+ listener);
+ sendMessage(msg);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_DISPATCH_OWNERSHIP_LOSS:
+ dispatchAppFocusOwnershipLoss((IAppFocusOwnershipListener) msg.obj, msg.arg1);
+ break;
+ case MSG_DISPATCH_FOCUS_CHANGE:
+ dispatchAppFocusChange((IAppFocusListener) msg.obj, msg.arg1, msg.arg2 == 1);
+ break;
+ }
+ }
+ }
+
+ private static int[] toIntArray(Set<Integer> intSet) {
+ int[] intArr = new int[intSet.size()];
+ int index = 0;
+ for (Integer value : intSet) {
+ intArr[index++] = value;
+ }
+ return intArr;
+ }
+}