blob: 6b1c7b413532baab8550162bb2c3cd5e7e917825 [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 com.android.car;
import android.car.Car;
import android.car.CarAppFocusManager;
import android.car.IAppFocus;
import android.car.IAppFocusListener;
import android.car.IAppFocusOwnershipCallback;
import android.content.Context;
import android.content.PermissionChecker;
import android.content.pm.PackageManager;
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.ArraySet;
import android.util.IndentingPrintWriter;
import android.util.Slog;
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
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<IAppFocusOwnershipCallback> {
private static final boolean DBG = false;
private static final boolean DBG_EVENT = false;
private final SystemActivityMonitoringService mSystemActivityMonitoringService;
private final Object mLock = new Object();
@VisibleForTesting
@GuardedBy("mLock")
final ClientHolder mAllChangeClients;
@VisibleForTesting
@GuardedBy("mLock")
final OwnershipClientHolder mAllOwnershipClients;
/** K: appType, V: client owning it */
@GuardedBy("mLock")
private final SparseArray<OwnershipClientInfo> mFocusOwners = new SparseArray<>();
@GuardedBy("mLock")
private final Set<Integer> mActiveAppTypes = new ArraySet<>();
@GuardedBy("mLock")
private final List<FocusOwnershipCallback> mFocusOwnershipCallbacks = new ArrayList<>();
private final BinderInterfaceContainer.BinderEventHandler<IAppFocusListener>
mAllBinderEventHandler = bInterface -> { /* nothing to do.*/ };
private final HandlerThread mHandlerThread = CarServiceUtils.getHandlerThread(
getClass().getSimpleName());
private final DispatchHandler mDispatchHandler = new DispatchHandler(mHandlerThread.getLooper(),
this);
private final Context mContext;
public AppFocusService(Context context,
SystemActivityMonitoringService systemActivityMonitoringService) {
mContext = context;
mSystemActivityMonitoringService = systemActivityMonitoringService;
mAllChangeClients = new ClientHolder(mAllBinderEventHandler);
mAllOwnershipClients = new OwnershipClientHolder(this);
}
@Override
public void registerFocusListener(IAppFocusListener listener, int appType) {
synchronized (mLock) {
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 (mLock) {
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 (mLock) {
return mActiveAppTypes.stream().mapToInt(Integer::intValue).toArray();
}
}
@Override
public List<String> getAppTypeOwner(@CarAppFocusManager.AppFocusType int appType) {
OwnershipClientInfo owner;
synchronized (mLock) {
owner = mFocusOwners.get(appType);
}
if (owner == null) {
return null;
}
String[] packageNames = mContext.getPackageManager().getPackagesForUid(owner.getUid());
if (packageNames == null) {
return null;
}
return Arrays.asList(packageNames);
}
@Override
public boolean isOwningFocus(IAppFocusOwnershipCallback callback, int appType) {
OwnershipClientInfo info;
synchronized (mLock) {
info = (OwnershipClientInfo) mAllOwnershipClients.getBinderInterface(callback);
}
if (info == null) {
return false;
}
return info.getOwnedAppTypes().contains(appType);
}
@Override
public int requestAppFocus(IAppFocusOwnershipCallback callback, int appType) {
synchronized (mLock) {
OwnershipClientInfo info =
(OwnershipClientInfo) mAllOwnershipClients.getBinderInterface(callback);
if (info == null) {
info = new OwnershipClientInfo(mAllOwnershipClients, callback,
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) {
// Allow receiving focus if the requester has a foreground activity OR if the
// requester is privileged service.
if (isInForeground(ownerInfo) && !isInForeground(info)
&& !hasPrivilegedPermission()) {
Slog.w(CarLog.TAG_APP_FOCUS, "Focus request failed for non-foreground app("
+ "pid=" + info.getPid() + ", uid=" + info.getUid() + ")."
+ "Foreground app (pid=" + ownerInfo.getPid() + ", uid="
+ ownerInfo.getUid() + ") owns it.");
return CarAppFocusManager.APP_FOCUS_REQUEST_FAILED;
}
ownerInfo.removeOwnedAppType(appType);
mDispatchHandler.requestAppFocusOwnershipLossDispatch(
ownerInfo.binderInterface, appType);
if (DBG) {
Slog.i(CarLog.TAG_APP_FOCUS, "losing app type "
+ appType + "," + ownerInfo);
}
}
mFocusOwners.put(appType, info);
dispatchAcquireFocusOwnerLocked(appType, info, mFocusOwnershipCallbacks);
}
info.addOwnedAppType(appType);
mDispatchHandler.requestAppFocusOwnershipGrantDispatch(
info.binderInterface, appType);
mActiveAppTypes.add(appType);
if (DBG) {
Slog.i(CarLog.TAG_APP_FOCUS, "updating active app type " + appType + ","
+ info);
}
// Always dispatch.
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_SUCCEEDED;
}
private boolean isInForeground(OwnershipClientInfo info) {
return mSystemActivityMonitoringService.isInForeground(info.getPid(), info.getUid());
}
private boolean hasPrivilegedPermission() {
return mContext.checkCallingOrSelfPermission(Car.PERMISSION_CAR_DISPLAY_IN_CLUSTER)
== PermissionChecker.PERMISSION_GRANTED;
}
@Override
public void abandonAppFocus(IAppFocusOwnershipCallback callback, int appType) {
synchronized (mLock) {
OwnershipClientInfo info =
(OwnershipClientInfo) mAllOwnershipClients.getBinderInterface(callback);
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;
}
// Because this code will run as part of unit tests on older platform, we can't use
// APIs such as {@link SparseArray#contains} that are added with API 30.
if (mFocusOwners.indexOfKey(appType) >= 0) {
mFocusOwners.remove(appType);
mActiveAppTypes.remove(appType);
info.removeOwnedAppType(appType);
if (DBG) {
Slog.i(CarLog.TAG_APP_FOCUS, "abandoning focus " + appType + "," + info);
}
dispatchAbandonFocusOwnerLocked(appType, info, mFocusOwnershipCallbacks);
for (BinderInterfaceContainer.BinderInterface<IAppFocusListener> client :
mAllChangeClients.getInterfaces()) {
ClientInfo clientInfo = (ClientInfo) client;
if (clientInfo.getAppTypes().contains(appType)) {
mDispatchHandler.requestAppFocusChangeDispatch(clientInfo.binderInterface,
appType, false);
}
}
}
}
}
@Override
public void init() {
// nothing to do
}
@VisibleForTesting
public Looper getLooper() {
return mHandlerThread.getLooper();
}
@Override
public void release() {
synchronized (mLock) {
mAllChangeClients.clear();
mAllOwnershipClients.clear();
mFocusOwners.clear();
mActiveAppTypes.clear();
}
}
@Override
public void onBinderDeath(
BinderInterfaceContainer.BinderInterface<IAppFocusOwnershipCallback> bInterface) {
OwnershipClientInfo info = (OwnershipClientInfo) bInterface;
synchronized (mLock) {
for (Integer appType : info.getOwnedAppTypes()) {
abandonAppFocus(bInterface.binderInterface, appType);
}
}
}
@Override
public void dump(IndentingPrintWriter writer) {
writer.println("**AppFocusService**");
synchronized (mLock) {
writer.println("mActiveAppTypes:" + mActiveAppTypes);
for (BinderInterfaceContainer.BinderInterface<IAppFocusOwnershipCallback> client :
mAllOwnershipClients.getInterfaces()) {
OwnershipClientInfo clientInfo = (OwnershipClientInfo) client;
writer.println(clientInfo);
}
}
}
/**
* Returns true if process with given uid and pid owns provided focus.
*/
public boolean isFocusOwner(int uid, int pid, int appType) {
synchronized (mLock) {
// Because this code will run as part of unit tests on older platform, we can't use
// APIs such as {@link SparseArray#contains} that are added with API 30.
if (mFocusOwners.indexOfKey(appType) >= 0) {
OwnershipClientInfo clientInfo = mFocusOwners.get(appType);
return clientInfo.getUid() == uid && clientInfo.getPid() == pid;
}
}
return false;
}
/**
* Defines callback functions that will be called when ownership has been changed.
*/
public interface FocusOwnershipCallback {
void onFocusAcquired(int appType, int uid, int pid);
void onFocusAbandoned(int appType, int uid, int pid);
}
/**
* Registers callback.
*
* If any focus already acquired it will trigger
* {@link FocusOwnershipCallback#onFocusAcquired} call immediately in the same thread.
*/
public void registerContextOwnerChangedCallback(FocusOwnershipCallback callback) {
SparseArray<OwnershipClientInfo> owners;
synchronized (mLock) {
mFocusOwnershipCallbacks.add(callback);
owners = mFocusOwners.clone();
}
for (int idx = 0; idx < owners.size(); idx++) {
int key = owners.keyAt(idx);
OwnershipClientInfo clientInfo = owners.valueAt(idx);
callback.onFocusAcquired(key, clientInfo.getUid(), clientInfo.getPid());
}
}
/**
* Unregisters provided callback.
*/
public void unregisterContextOwnerChangedCallback(FocusOwnershipCallback callback) {
synchronized (mLock) {
mFocusOwnershipCallbacks.remove(callback);
}
}
private void dispatchAcquireFocusOwnerLocked(int appType, OwnershipClientInfo owner,
List<FocusOwnershipCallback> focusOwnershipCallbacks) {
// Dispatches each callback separately, not to make the copy of mFocusOwnershipCallbacks.
for (int i = focusOwnershipCallbacks.size() - 1; i >= 0; --i) {
FocusOwnershipCallback callback = focusOwnershipCallbacks.get(i);
mDispatchHandler.post(
() -> callback.onFocusAcquired(appType, owner.getUid(), owner.getPid()));
}
}
private void dispatchAbandonFocusOwnerLocked(int appType, OwnershipClientInfo owner,
List<FocusOwnershipCallback> focusOwnershipCallbacks) {
// Dispatches each callback separately, not to make the copy of mFocusOwnershipCallbacks.
for (int i = focusOwnershipCallbacks.size() - 1; i >= 0; --i) {
FocusOwnershipCallback callback = focusOwnershipCallbacks.get(i);
mDispatchHandler.post(
() -> callback.onFocusAbandoned(appType, owner.getUid(), owner.getPid()));
}
}
private void dispatchAppFocusOwnershipLoss(IAppFocusOwnershipCallback callback, int appType) {
try {
callback.onAppFocusOwnershipLost(appType);
} catch (RemoteException e) {
}
}
private void dispatchAppFocusOwnershipGrant(IAppFocusOwnershipCallback callback, int appType) {
try {
callback.onAppFocusOwnershipGranted(appType);
} catch (RemoteException e) {
}
}
private void dispatchAppFocusChange(IAppFocusListener listener, int appType, boolean active) {
try {
listener.onAppFocusChanged(appType, active);
} catch (RemoteException e) {
}
}
@VisibleForTesting
static class ClientHolder extends BinderInterfaceContainer<IAppFocusListener> {
private ClientHolder(BinderEventHandler<IAppFocusListener> holder) {
super(holder);
}
}
@VisibleForTesting
static class OwnershipClientHolder extends
BinderInterfaceContainer<IAppFocusOwnershipCallback> {
private OwnershipClientHolder(AppFocusService service) {
super(service);
}
}
private class ClientInfo extends
BinderInterfaceContainer.BinderInterface<IAppFocusListener> {
private final int mUid;
private final int mPid;
@GuardedBy("AppFocusService.mLock")
private final Set<Integer> mAppTypes = new ArraySet<>();
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 Set<Integer> getAppTypes() {
synchronized (mLock) {
return Collections.unmodifiableSet(mAppTypes);
}
}
private boolean addAppType(Integer appType) {
synchronized (mLock) {
return mAppTypes.add(appType);
}
}
private boolean removeAppType(Integer appType) {
synchronized (mLock) {
return mAppTypes.remove(appType);
}
}
@Override
public String toString() {
synchronized (mLock) {
return "ClientInfo{mUid=" + mUid + ",mPid=" + mPid
+ ",appTypes=" + mAppTypes + "}";
}
}
}
private class OwnershipClientInfo extends
BinderInterfaceContainer.BinderInterface<IAppFocusOwnershipCallback> {
private final int mUid;
private final int mPid;
@GuardedBy("AppFocusService.mLock")
private final Set<Integer> mOwnedAppTypes = new ArraySet<>();
private OwnershipClientInfo(OwnershipClientHolder holder, IAppFocusOwnershipCallback binder,
int uid, int pid) {
super(holder, binder);
this.mUid = uid;
this.mPid = pid;
}
private Set<Integer> getOwnedAppTypes() {
if (DBG_EVENT) {
Slog.i(CarLog.TAG_APP_FOCUS, "getOwnedAppTypes " + mOwnedAppTypes);
}
synchronized (mLock) {
return Collections.unmodifiableSet(mOwnedAppTypes);
}
}
private boolean addOwnedAppType(Integer appType) {
if (DBG_EVENT) {
Slog.i(CarLog.TAG_APP_FOCUS, "addOwnedAppType " + appType);
}
synchronized (mLock) {
return mOwnedAppTypes.add(appType);
}
}
private boolean removeOwnedAppType(Integer appType) {
if (DBG_EVENT) {
Slog.i(CarLog.TAG_APP_FOCUS, "removeOwnedAppType " + appType);
}
synchronized (mLock) {
return mOwnedAppTypes.remove(appType);
}
}
int getUid() {
return mUid;
}
int getPid() {
return mPid;
}
@Override
public String toString() {
synchronized (mLock) {
return "ClientInfo{mUid=" + mUid + ",mPid=" + mPid
+ ",owned=" + mOwnedAppTypes + "}";
}
}
}
private static final class DispatchHandler extends Handler {
private static final String TAG = CarLog.tagFor(AppFocusService.class);
private static final int MSG_DISPATCH_OWNERSHIP_LOSS = 0;
private static final int MSG_DISPATCH_OWNERSHIP_GRANT = 1;
private static final int MSG_DISPATCH_FOCUS_CHANGE = 2;
private final WeakReference<AppFocusService> mService;
private DispatchHandler(Looper looper, AppFocusService service) {
super(looper);
mService = new WeakReference<AppFocusService>(service);
}
private void requestAppFocusOwnershipLossDispatch(IAppFocusOwnershipCallback callback,
int appType) {
Message msg = obtainMessage(MSG_DISPATCH_OWNERSHIP_LOSS, appType, 0, callback);
sendMessage(msg);
}
private void requestAppFocusOwnershipGrantDispatch(IAppFocusOwnershipCallback callback,
int appType) {
Message msg = obtainMessage(MSG_DISPATCH_OWNERSHIP_GRANT, appType, 0, callback);
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) {
AppFocusService service = mService.get();
if (service == null) {
Slog.i(TAG, "handleMessage null service");
return;
}
switch (msg.what) {
case MSG_DISPATCH_OWNERSHIP_LOSS:
service.dispatchAppFocusOwnershipLoss((IAppFocusOwnershipCallback) msg.obj,
msg.arg1);
break;
case MSG_DISPATCH_OWNERSHIP_GRANT:
service.dispatchAppFocusOwnershipGrant((IAppFocusOwnershipCallback) msg.obj,
msg.arg1);
break;
case MSG_DISPATCH_FOCUS_CHANGE:
service.dispatchAppFocusChange((IAppFocusListener) msg.obj, msg.arg1,
msg.arg2 == 1);
break;
default:
Slog.e(CarLog.TAG_APP_FOCUS, "Can't dispatch message: " + msg);
}
}
}
}