blob: b4deeb0a6872e01a192a9443ec5b400b7daa5b2a [file] [log] [blame]
/*
* Copyright (C) 2022 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.server.accessibility;
import static android.content.Context.DEVICE_ID_DEFAULT;
import static android.content.Context.DEVICE_ID_INVALID;
import static com.android.internal.util.FunctionalUtils.ignoreRemoteException;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.accessibilityservice.AccessibilityTrace;
import android.accessibilityservice.IAccessibilityServiceClient;
import android.annotation.NonNull;
import android.companion.virtual.VirtualDevice;
import android.companion.virtual.VirtualDeviceManager;
import android.content.ComponentName;
import android.content.Context;
import android.hardware.display.DisplayManager;
import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.util.ArraySet;
import android.util.IntArray;
import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseIntArray;
import android.view.Display;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.IAccessibilityManagerClient;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.IntPair;
import com.android.server.LocalServices;
import com.android.server.companion.virtual.VirtualDeviceManagerInternal;
import com.android.server.wm.WindowManagerInternal;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.function.Consumer;
/**
* Manages proxy connections.
*
* Currently this acts similarly to UiAutomationManager as a global manager, though ideally each
* proxy connection will belong to a separate user state.
*
* TODO(241117292): Remove or cut down during simultaneous user refactoring.
*/
public class ProxyManager {
private static final String LOG_TAG = "ProxyManager";
private static final boolean DEBUG = Log.isLoggable(LOG_TAG, Log.DEBUG) && Build.IS_DEBUGGABLE;
// Names used to populate ComponentName and ResolveInfo in connection.mA11yServiceInfo and in
// the infos of connection.setInstalledAndEnabledServices
static final String PROXY_COMPONENT_PACKAGE_NAME = "ProxyPackage";
static final String PROXY_COMPONENT_CLASS_NAME = "ProxyClass";
// AMS#mLock
private final Object mLock;
private final Context mContext;
private final Handler mMainHandler;
private final UiAutomationManager mUiAutomationManager;
// Device Id -> state. Used to determine if we should notify AccessibilityManager clients of
// updates.
private final SparseIntArray mLastStates = new SparseIntArray();
// Each display id entry in a SparseArray represents a proxy a11y user.
private final SparseArray<ProxyAccessibilityServiceConnection> mProxyA11yServiceConnections =
new SparseArray<>();
private final AccessibilityWindowManager mA11yWindowManager;
private AccessibilityInputFilter mA11yInputFilter;
private VirtualDeviceManagerInternal mLocalVdm;
private final SystemSupport mSystemSupport;
private VirtualDeviceManagerInternal.AppsOnVirtualDeviceListener
mAppsOnVirtualDeviceListener;
private VirtualDeviceManager.VirtualDeviceListener mVirtualDeviceListener;
/**
* Callbacks into AccessibilityManagerService.
*/
public interface SystemSupport {
/**
* Removes the device id from tracking.
*/
void removeDeviceIdLocked(int deviceId);
/**
* Updates the windows tracking for the current user.
*/
void updateWindowsForAccessibilityCallbackLocked();
/**
* Clears all caches.
*/
void notifyClearAccessibilityCacheLocked();
/**
* Gets the clients for all users.
*/
@NonNull
RemoteCallbackList<IAccessibilityManagerClient> getGlobalClientsLocked();
/**
* Gets the clients for the current user.
*/
@NonNull
RemoteCallbackList<IAccessibilityManagerClient> getCurrentUserClientsLocked();
}
public ProxyManager(Object lock, AccessibilityWindowManager awm,
Context context, Handler mainHandler, UiAutomationManager uiAutomationManager,
SystemSupport systemSupport) {
mLock = lock;
mA11yWindowManager = awm;
mContext = context;
mMainHandler = mainHandler;
mUiAutomationManager = uiAutomationManager;
mSystemSupport = systemSupport;
mLocalVdm = LocalServices.getService(VirtualDeviceManagerInternal.class);
}
/**
* Creates the service connection.
*/
public void registerProxy(IAccessibilityServiceClient client, int displayId,
int id, AccessibilitySecurityPolicy securityPolicy,
AbstractAccessibilityServiceConnection.SystemSupport systemSupport,
AccessibilityTrace trace,
WindowManagerInternal windowManagerInternal) throws RemoteException {
if (DEBUG) {
Slog.v(LOG_TAG, "Register proxy for display id: " + displayId);
}
VirtualDeviceManager vdm = mContext.getSystemService(VirtualDeviceManager.class);
if (vdm == null) {
return;
}
final int deviceId = vdm.getDeviceIdForDisplayId(displayId);
// Set a default AccessibilityServiceInfo that is used before the proxy's info is
// populated. A proxy has the touch exploration and window capabilities.
AccessibilityServiceInfo info = new AccessibilityServiceInfo();
info.setCapabilities(AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION
| AccessibilityServiceInfo.CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT);
final String componentClassDisplayName = PROXY_COMPONENT_CLASS_NAME + displayId;
info.setComponentName(new ComponentName(PROXY_COMPONENT_PACKAGE_NAME,
componentClassDisplayName));
ProxyAccessibilityServiceConnection connection =
new ProxyAccessibilityServiceConnection(mContext, info.getComponentName(), info,
id, mMainHandler, mLock, securityPolicy, systemSupport, trace,
windowManagerInternal,
mA11yWindowManager, displayId, deviceId);
synchronized (mLock) {
mProxyA11yServiceConnections.put(displayId, connection);
if (Flags.proxyUseAppsOnVirtualDeviceListener()) {
if (mAppsOnVirtualDeviceListener == null) {
mAppsOnVirtualDeviceListener = allRunningUids ->
notifyProxyOfRunningAppsChange(allRunningUids);
final VirtualDeviceManagerInternal localVdm = getLocalVdm();
if (localVdm != null) {
localVdm.registerAppsOnVirtualDeviceListener(mAppsOnVirtualDeviceListener);
}
}
}
if (mProxyA11yServiceConnections.size() == 1) {
registerVirtualDeviceListener();
}
}
// If the client dies, make sure to remove the connection.
IBinder.DeathRecipient deathRecipient =
new IBinder.DeathRecipient() {
@Override
public void binderDied() {
client.asBinder().unlinkToDeath(this, 0);
clearConnectionAndUpdateState(displayId);
}
};
client.asBinder().linkToDeath(deathRecipient, 0);
mMainHandler.post(() -> {
if (mA11yInputFilter != null) {
mA11yInputFilter.disableFeaturesForDisplayIfInstalled(displayId);
}
});
connection.initializeServiceInterface(client);
}
private void registerVirtualDeviceListener() {
VirtualDeviceManager vdm = mContext.getSystemService(VirtualDeviceManager.class);
if (vdm == null || !android.companion.virtual.flags.Flags.vdmPublicApis()) {
return;
}
if (mVirtualDeviceListener == null) {
mVirtualDeviceListener = new VirtualDeviceManager.VirtualDeviceListener() {
@Override
public void onVirtualDeviceClosed(int deviceId) {
clearConnections(deviceId);
}
};
}
vdm.registerVirtualDeviceListener(mContext.getMainExecutor(), mVirtualDeviceListener);
}
private void unregisterVirtualDeviceListener() {
VirtualDeviceManager vdm = mContext.getSystemService(VirtualDeviceManager.class);
if (vdm == null || !android.companion.virtual.flags.Flags.vdmPublicApis()) {
return;
}
vdm.unregisterVirtualDeviceListener(mVirtualDeviceListener);
}
/**
* Unregister the proxy based on display id.
*/
public boolean unregisterProxy(int displayId) {
return clearConnectionAndUpdateState(displayId);
}
/**
* Clears all proxy connections belonging to {@code deviceId}.
*/
public void clearConnections(int deviceId) {
final IntArray displaysToClear = new IntArray();
synchronized (mLock) {
for (int i = 0; i < mProxyA11yServiceConnections.size(); i++) {
final ProxyAccessibilityServiceConnection proxy =
mProxyA11yServiceConnections.valueAt(i);
if (proxy != null && proxy.getDeviceId() == deviceId) {
displaysToClear.add(proxy.getDisplayId());
}
}
}
for (int i = 0; i < displaysToClear.size(); i++) {
clearConnectionAndUpdateState(displaysToClear.get(i));
}
}
/**
* Removes the system connection of an AccessibilityDisplayProxy.
*
* This will:
* <ul>
* <li> Reset Clients to belong to the default device if appropriate.
* <li> Stop identifying the display's a11y windows as belonging to a proxy.
* <li> Re-enable any input filters for the display.
* <li> Notify AMS that a proxy has been removed.
* </ul>
*
* @param displayId the display id of the connection to be cleared.
* @return whether the proxy was removed.
*/
private boolean clearConnectionAndUpdateState(int displayId) {
boolean removedFromConnections = false;
int deviceId = DEVICE_ID_INVALID;
synchronized (mLock) {
if (mProxyA11yServiceConnections.contains(displayId)) {
deviceId = mProxyA11yServiceConnections.get(displayId).getDeviceId();
mProxyA11yServiceConnections.remove(displayId);
removedFromConnections = true;
if (mProxyA11yServiceConnections.size() == 0) {
unregisterVirtualDeviceListener();
}
}
}
if (removedFromConnections) {
updateStateForRemovedDisplay(displayId, deviceId);
}
if (DEBUG) {
Slog.v(LOG_TAG, "Unregistered proxy for display id " + displayId + ": "
+ removedFromConnections);
}
return removedFromConnections;
}
/**
* When the connection is removed from tracking in ProxyManager, propagate changes to other a11y
* system components like the input filter and IAccessibilityManagerClients.
*/
private void updateStateForRemovedDisplay(int displayId, int deviceId) {
mA11yWindowManager.stopTrackingDisplayProxy(displayId);
// A11yInputFilter isn't thread-safe, so post on the system thread.
mMainHandler.post(
() -> {
if (mA11yInputFilter != null) {
final DisplayManager displayManager = (DisplayManager)
mContext.getSystemService(Context.DISPLAY_SERVICE);
final Display proxyDisplay = displayManager.getDisplay(displayId);
if (proxyDisplay != null) {
// A11yInputFilter isn't thread-safe, so post on the system thread.
mA11yInputFilter.enableFeaturesForDisplayIfInstalled(proxyDisplay);
}
}
});
// If there isn't an existing proxy for the device id, reset app clients. Resetting
// will usually happen, since in most cases there will only be one proxy for a
// device.
if (!isProxyedDeviceId(deviceId)) {
synchronized (mLock) {
if (Flags.proxyUseAppsOnVirtualDeviceListener()) {
if (mProxyA11yServiceConnections.size() == 0) {
final VirtualDeviceManagerInternal localVdm = getLocalVdm();
if (localVdm != null && mAppsOnVirtualDeviceListener != null) {
localVdm.unregisterAppsOnVirtualDeviceListener(
mAppsOnVirtualDeviceListener);
mAppsOnVirtualDeviceListener = null;
}
}
}
mSystemSupport.removeDeviceIdLocked(deviceId);
mLastStates.delete(deviceId);
}
} else {
// Update with the states of the remaining proxies.
onProxyChanged(deviceId);
}
}
/**
* Returns {@code true} if {@code displayId} is being proxy-ed.
*/
public boolean isProxyedDisplay(int displayId) {
synchronized (mLock) {
final boolean tracked = mProxyA11yServiceConnections.contains(displayId);
if (DEBUG) {
Slog.v(LOG_TAG, "Tracking proxy display " + displayId + " : " + tracked);
}
return tracked;
}
}
/**
* Returns {@code true} if {@code deviceId} is being proxy-ed.
*/
public boolean isProxyedDeviceId(int deviceId) {
if (deviceId == DEVICE_ID_DEFAULT || deviceId == DEVICE_ID_INVALID) {
return false;
}
boolean isTrackingDeviceId;
synchronized (mLock) {
isTrackingDeviceId = getFirstProxyForDeviceIdLocked(deviceId) != null;
}
if (DEBUG) {
Slog.v(LOG_TAG, "Tracking device " + deviceId + " : " + isTrackingDeviceId);
}
return isTrackingDeviceId;
}
/** Returns true if the display belongs to one of the caller's virtual devices. */
public boolean displayBelongsToCaller(int callingUid, int proxyDisplayId) {
final VirtualDeviceManager vdm = mContext.getSystemService(VirtualDeviceManager.class);
final VirtualDeviceManagerInternal localVdm = getLocalVdm();
if (vdm == null || localVdm == null) {
return false;
}
final List<VirtualDevice> virtualDevices = vdm.getVirtualDevices();
for (VirtualDevice device : virtualDevices) {
if (localVdm.getDisplayIdsForDevice(device.getDeviceId()).contains(proxyDisplayId)) {
final int ownerUid = localVdm.getDeviceOwnerUid(device.getDeviceId());
if (callingUid == ownerUid) {
return true;
}
}
}
return false;
}
/**
* Sends AccessibilityEvents to a proxy given the event's displayId.
*/
public void sendAccessibilityEventLocked(AccessibilityEvent event) {
final ProxyAccessibilityServiceConnection proxy =
mProxyA11yServiceConnections.get(event.getDisplayId());
if (proxy != null) {
if (DEBUG) {
Slog.v(LOG_TAG, "Send proxy event " + event + " for display id "
+ event.getDisplayId());
}
proxy.notifyAccessibilityEvent(event);
}
}
/**
* Returns {@code true} if any proxy can retrieve windows.
* TODO(b/250929565): Retrieve per connection/user state.
*/
public boolean canRetrieveInteractiveWindowsLocked() {
boolean observingWindows = false;
for (int i = 0; i < mProxyA11yServiceConnections.size(); i++) {
final ProxyAccessibilityServiceConnection proxy =
mProxyA11yServiceConnections.valueAt(i);
if (proxy.mRetrieveInteractiveWindows) {
observingWindows = true;
break;
}
}
if (DEBUG) {
Slog.v(LOG_TAG, "At least one proxy can retrieve windows: " + observingWindows);
}
return observingWindows;
}
/**
* If there is at least one proxy, accessibility is enabled.
*/
public int getStateLocked(int deviceId) {
int clientState = 0;
final boolean uiAutomationCanIntrospect = mUiAutomationManager.canIntrospect();
if (uiAutomationCanIntrospect) {
clientState |= AccessibilityManager.STATE_FLAG_ACCESSIBILITY_ENABLED;
}
for (int i = 0; i < mProxyA11yServiceConnections.size(); i++) {
final ProxyAccessibilityServiceConnection proxy =
mProxyA11yServiceConnections.valueAt(i);
if (proxy != null && proxy.getDeviceId() == deviceId) {
// Combine proxy states.
clientState |= getStateForDisplayIdLocked(proxy);
}
}
if (DEBUG) {
Slog.v(LOG_TAG, "For device id " + deviceId + " a11y is enabled: "
+ ((clientState & AccessibilityManager.STATE_FLAG_ACCESSIBILITY_ENABLED) != 0));
Slog.v(LOG_TAG, "For device id " + deviceId + " touch exploration is enabled: "
+ ((clientState & AccessibilityManager.STATE_FLAG_TOUCH_EXPLORATION_ENABLED)
!= 0));
}
return clientState;
}
/**
* If there is at least one proxy, accessibility is enabled.
*/
private int getStateForDisplayIdLocked(ProxyAccessibilityServiceConnection proxy) {
int clientState = 0;
if (proxy != null) {
clientState |= AccessibilityManager.STATE_FLAG_ACCESSIBILITY_ENABLED;
if (proxy.mRequestTouchExplorationMode) {
clientState |= AccessibilityManager.STATE_FLAG_TOUCH_EXPLORATION_ENABLED;
}
}
if (DEBUG) {
Slog.v(LOG_TAG, "Accessibility is enabled for all proxies: "
+ ((clientState & AccessibilityManager.STATE_FLAG_ACCESSIBILITY_ENABLED) != 0));
Slog.v(LOG_TAG, "Touch exploration is enabled for all proxies: "
+ ((clientState & AccessibilityManager.STATE_FLAG_TOUCH_EXPLORATION_ENABLED)
!= 0));
}
return clientState;
}
/**
* Gets the last state for a device.
*/
private int getLastSentStateLocked(int deviceId) {
return mLastStates.get(deviceId, 0);
}
/**
* Sets the last state for a device.
*/
private void setLastStateLocked(int deviceId, int proxyState) {
mLastStates.put(deviceId, proxyState);
}
/**
* Updates the relevant event types of the app clients that are shown on a display owned by the
* specified device.
*
* A client belongs to a device id, so event types (and other state) is determined by the device
* id. In most cases, a device owns a single display. But if multiple displays may belong to one
* Virtual Device, the app clients will get the aggregated event types for all proxy-ed displays
* belonging to a VirtualDevice.
*/
private void updateRelevantEventTypesLocked(int deviceId) {
if (!isProxyedDeviceId(deviceId)) {
return;
}
mMainHandler.post(() -> {
synchronized (mLock) {
broadcastToClientsLocked(ignoreRemoteException(client -> {
int relevantEventTypes;
if (client.mDeviceId == deviceId) {
relevantEventTypes = computeRelevantEventTypesLocked(client);
if (client.mLastSentRelevantEventTypes != relevantEventTypes) {
client.mLastSentRelevantEventTypes = relevantEventTypes;
client.mCallback.setRelevantEventTypes(relevantEventTypes);
}
}
}));
}
});
}
/**
* Returns the relevant event types for a Client.
*/
public int computeRelevantEventTypesLocked(AccessibilityManagerService.Client client) {
int relevantEventTypes = 0;
for (int i = 0; i < mProxyA11yServiceConnections.size(); i++) {
final ProxyAccessibilityServiceConnection proxy =
mProxyA11yServiceConnections.valueAt(i);
if (proxy != null && proxy.getDeviceId() == client.mDeviceId) {
relevantEventTypes |= proxy.getRelevantEventTypes();
relevantEventTypes |= AccessibilityManagerService.isClientInPackageAllowlist(
mUiAutomationManager.getServiceInfo(), client)
? mUiAutomationManager.getRelevantEventTypes()
: 0;
}
}
if (DEBUG) {
Slog.v(LOG_TAG, "Relevant event types for device id " + client.mDeviceId
+ ": " + AccessibilityEvent.eventTypeToString(relevantEventTypes));
}
return relevantEventTypes;
}
/**
* Adds the service interfaces to a list.
* @param interfaces the list to add to.
* @param deviceId the device id of the interested app client.
*/
public void addServiceInterfacesLocked(@NonNull List<IAccessibilityServiceClient> interfaces,
int deviceId) {
for (int i = 0; i < mProxyA11yServiceConnections.size(); i++) {
final ProxyAccessibilityServiceConnection proxy =
mProxyA11yServiceConnections.valueAt(i);
if (proxy != null && proxy.getDeviceId() == deviceId) {
final IBinder proxyBinder = proxy.mService;
final IAccessibilityServiceClient proxyInterface = proxy.mServiceInterface;
if ((proxyBinder != null) && (proxyInterface != null)) {
interfaces.add(proxyInterface);
}
}
}
}
/**
* Gets the list of installed and enabled services for a device id.
*
* Note: Multiple display proxies may belong to the same device.
*/
public List<AccessibilityServiceInfo> getInstalledAndEnabledServiceInfosLocked(int feedbackType,
int deviceId) {
List<AccessibilityServiceInfo> serviceInfos = new ArrayList<>();
for (int i = 0; i < mProxyA11yServiceConnections.size(); i++) {
final ProxyAccessibilityServiceConnection proxy =
mProxyA11yServiceConnections.valueAt(i);
if (proxy != null && proxy.getDeviceId() == deviceId) {
// Return all proxy infos for ALL mask.
if (feedbackType == AccessibilityServiceInfo.FEEDBACK_ALL_MASK) {
serviceInfos.addAll(proxy.getInstalledAndEnabledServices());
} else if ((proxy.mFeedbackType & feedbackType) != 0) {
List<AccessibilityServiceInfo> proxyInfos =
proxy.getInstalledAndEnabledServices();
// Iterate through each info in the proxy.
for (AccessibilityServiceInfo info : proxyInfos) {
if ((info.feedbackType & feedbackType) != 0) {
serviceInfos.add(info);
}
}
}
}
}
return serviceInfos;
}
/**
* Handles proxy changes.
*
* <p>
* Changes include if the proxy is unregistered, its service info list has
* changed, or its focus appearance has changed.
* <p>
* Some responses may include updating app clients. A client belongs to a device id, so state is
* determined by the device id. In most cases, a device owns a single display. But if multiple
* displays belong to one Virtual Device, the app clients will get a difference in
* behavior depending on what is being updated.
*
* The following state methods are updated for AccessibilityManager clients belonging to a
* proxied device:
* <ul>
* <li> A11yManager#setRelevantEventTypes - The combined event types of all proxies belonging to
* a device id.
* <li> A11yManager#setState - The combined states of all proxies belonging to a device id.
* <li> A11yManager#notifyServicesStateChanged(timeout) - The highest of all proxies belonging
* to a device id.
* <li> A11yManager#setFocusAppearance - The appearance of the most recently updated display id
* belonging to the device.
* </ul>
* This is similar to onUserStateChangeLocked and onClientChangeLocked, but does not require an
* A11yUserState and only checks proxy-relevant settings.
*/
private void onProxyChanged(int deviceId, boolean forceUpdate) {
if (DEBUG) {
Slog.v(LOG_TAG, "onProxyChanged called for deviceId: " + deviceId);
}
//The following state updates are excluded:
// - Input-related state
// - Primary-device / hardware-specific state
synchronized (mLock) {
// A proxy may be registered after the client has been initialized in #addClient.
// For example, a user does not turn on accessibility until after the app has launched.
// Or the process was started with a default id context and should shift to a device.
// Update device ids of the clients if necessary.
updateDeviceIdsIfNeededLocked(deviceId);
// Start tracking of all displays if necessary.
mSystemSupport.updateWindowsForAccessibilityCallbackLocked();
// Calls A11yManager#setRelevantEventTypes (test these)
updateRelevantEventTypesLocked(deviceId);
// Calls A11yManager#setState
scheduleUpdateProxyClientsIfNeededLocked(deviceId, forceUpdate);
//Calls A11yManager#notifyServicesStateChanged(timeout)
scheduleNotifyProxyClientsOfServicesStateChangeLocked(deviceId);
// Calls A11yManager#setFocusAppearance
updateFocusAppearanceLocked(deviceId);
mSystemSupport.notifyClearAccessibilityCacheLocked();
}
}
/**
* Handles proxy changes, but does not force an update of app clients.
*/
public void onProxyChanged(int deviceId) {
onProxyChanged(deviceId, false);
}
/**
* Updates the states of the app AccessibilityManagers.
*/
private void scheduleUpdateProxyClientsIfNeededLocked(int deviceId, boolean forceUpdate) {
final int proxyState = getStateLocked(deviceId);
if (DEBUG) {
Slog.v(LOG_TAG, "State for device id " + deviceId + " is " + proxyState);
Slog.v(LOG_TAG, "Last state for device id " + deviceId + " is "
+ getLastSentStateLocked(deviceId));
Slog.v(LOG_TAG, "force update: " + forceUpdate);
}
if ((getLastSentStateLocked(deviceId)) != proxyState
|| (Flags.proxyUseAppsOnVirtualDeviceListener() && forceUpdate)) {
setLastStateLocked(deviceId, proxyState);
mMainHandler.post(() -> {
synchronized (mLock) {
broadcastToClientsLocked(ignoreRemoteException(client -> {
if (client.mDeviceId == deviceId) {
client.mCallback.setState(proxyState);
}
}));
}
});
}
}
/**
* Notifies AccessibilityManager of services state changes, which includes changes to the
* list of service infos and timeouts.
*
* @see AccessibilityManager.AccessibilityServicesStateChangeListener
*/
private void scheduleNotifyProxyClientsOfServicesStateChangeLocked(int deviceId) {
if (DEBUG) {
Slog.v(LOG_TAG, "Notify services state change at device id " + deviceId);
}
mMainHandler.post(()-> {
broadcastToClientsLocked(ignoreRemoteException(client -> {
if (client.mDeviceId == deviceId) {
synchronized (mLock) {
client.mCallback.notifyServicesStateChanged(
getRecommendedTimeoutMillisLocked(deviceId));
}
}
}));
});
}
/**
* Updates the focus appearance of AccessibilityManagerClients.
*/
private void updateFocusAppearanceLocked(int deviceId) {
if (DEBUG) {
Slog.v(LOG_TAG, "Update proxy focus appearance at device id " + deviceId);
}
// Reasonably assume that all proxies belonging to a virtual device should have the
// same focus appearance, and if they should be different these should belong to different
// virtual devices.
final ProxyAccessibilityServiceConnection proxy = getFirstProxyForDeviceIdLocked(deviceId);
if (proxy != null) {
mMainHandler.post(()-> {
broadcastToClientsLocked(ignoreRemoteException(client -> {
if (client.mDeviceId == proxy.getDeviceId()) {
client.mCallback.setFocusAppearance(
proxy.getFocusStrokeWidthLocked(),
proxy.getFocusColorLocked());
}
}));
});
}
}
private ProxyAccessibilityServiceConnection getFirstProxyForDeviceIdLocked(int deviceId) {
for (int i = 0; i < mProxyA11yServiceConnections.size(); i++) {
final ProxyAccessibilityServiceConnection proxy =
mProxyA11yServiceConnections.valueAt(i);
if (proxy != null && proxy.getDeviceId() == deviceId) {
return proxy;
}
}
return null;
}
private void broadcastToClientsLocked(
@NonNull Consumer<AccessibilityManagerService.Client> clientAction) {
final RemoteCallbackList<IAccessibilityManagerClient> userClients =
mSystemSupport.getCurrentUserClientsLocked();
final RemoteCallbackList<IAccessibilityManagerClient> globalClients =
mSystemSupport.getGlobalClientsLocked();
userClients.broadcastForEachCookie(clientAction);
globalClients.broadcastForEachCookie(clientAction);
}
/**
* Updates the timeout and notifies app clients.
*
* For real users, timeouts are tracked in A11yUserState. For proxies, timeouts are in the
* service connection. The value in user state is preferred, but if this value is 0 the service
* info value is used.
*
* This follows the pattern in readUserRecommendedUiTimeoutSettingsLocked.
*
* TODO(b/250929565): ProxyUserState or similar should hold the timeouts
*/
public void updateTimeoutsIfNeeded(int nonInteractiveUiTimeout, int interactiveUiTimeout) {
synchronized (mLock) {
for (int i = 0; i < mProxyA11yServiceConnections.size(); i++) {
final ProxyAccessibilityServiceConnection proxy =
mProxyA11yServiceConnections.valueAt(i);
if (proxy != null) {
if (proxy.updateTimeouts(nonInteractiveUiTimeout, interactiveUiTimeout)) {
scheduleNotifyProxyClientsOfServicesStateChangeLocked(proxy.getDeviceId());
}
}
}
}
}
/**
* Gets the recommended timeout belonging to a Virtual Device.
*
* This is the highest of all display proxies belonging to the virtual device.
*/
public long getRecommendedTimeoutMillisLocked(int deviceId) {
int combinedInteractiveTimeout = 0;
int combinedNonInteractiveTimeout = 0;
for (int i = 0; i < mProxyA11yServiceConnections.size(); i++) {
final ProxyAccessibilityServiceConnection proxy =
mProxyA11yServiceConnections.valueAt(i);
if (proxy != null && proxy.getDeviceId() == deviceId) {
final int proxyInteractiveUiTimeout =
(proxy != null) ? proxy.getInteractiveTimeout() : 0;
final int nonInteractiveUiTimeout =
(proxy != null) ? proxy.getNonInteractiveTimeout() : 0;
combinedInteractiveTimeout = Math.max(proxyInteractiveUiTimeout,
combinedInteractiveTimeout);
combinedNonInteractiveTimeout = Math.max(nonInteractiveUiTimeout,
combinedNonInteractiveTimeout);
}
}
return IntPair.of(combinedInteractiveTimeout, combinedNonInteractiveTimeout);
}
/**
* Gets the first focus stroke width belonging to the device.
*/
public int getFocusStrokeWidthLocked(int deviceId) {
final ProxyAccessibilityServiceConnection proxy = getFirstProxyForDeviceIdLocked(deviceId);
if (proxy != null) {
return proxy.getFocusStrokeWidthLocked();
}
return 0;
}
/**
* Gets the first focus color belonging to the device.
*/
public int getFocusColorLocked(int deviceId) {
final ProxyAccessibilityServiceConnection proxy = getFirstProxyForDeviceIdLocked(deviceId);
if (proxy != null) {
return proxy.getFocusColorLocked();
}
return 0;
}
/**
* Returns the first device id given a UID.
* @param callingUid the UID to check.
* @return the first matching device id, or DEVICE_ID_INVALID.
*/
public int getFirstDeviceIdForUidLocked(int callingUid) {
int firstDeviceId = DEVICE_ID_INVALID;
final VirtualDeviceManagerInternal localVdm = getLocalVdm();
if (localVdm == null) {
return firstDeviceId;
}
final Set<Integer> deviceIds = localVdm.getDeviceIdsForUid(callingUid);
for (Integer uidDeviceId : deviceIds) {
if (uidDeviceId != DEVICE_ID_DEFAULT && uidDeviceId != DEVICE_ID_INVALID) {
firstDeviceId = uidDeviceId;
break;
}
}
return firstDeviceId;
}
/**
* Sets a Client device id if the app uid belongs to the virtual device.
*/
private void updateDeviceIdsIfNeededLocked(int deviceId) {
final RemoteCallbackList<IAccessibilityManagerClient> userClients =
mSystemSupport.getCurrentUserClientsLocked();
final RemoteCallbackList<IAccessibilityManagerClient> globalClients =
mSystemSupport.getGlobalClientsLocked();
updateDeviceIdsIfNeededLocked(deviceId, userClients);
updateDeviceIdsIfNeededLocked(deviceId, globalClients);
}
/**
* Updates the device ids of IAccessibilityManagerClients if needed after a proxy change.
*/
private void updateDeviceIdsIfNeededLocked(int deviceId,
@NonNull RemoteCallbackList<IAccessibilityManagerClient> clients) {
final VirtualDeviceManagerInternal localVdm = getLocalVdm();
if (localVdm == null) {
return;
}
for (int i = 0; i < clients.getRegisteredCallbackCount(); i++) {
final AccessibilityManagerService.Client client =
((AccessibilityManagerService.Client) clients.getRegisteredCallbackCookie(i));
if (Flags.proxyUseAppsOnVirtualDeviceListener()) {
if (deviceId == DEVICE_ID_DEFAULT || deviceId == DEVICE_ID_INVALID) {
continue;
}
boolean uidBelongsToDevice =
localVdm.getDeviceIdsForUid(client.mUid).contains(deviceId);
if (client.mDeviceId != deviceId && uidBelongsToDevice) {
if (DEBUG) {
Slog.v(LOG_TAG, "Packages moved to device id " + deviceId + " are "
+ Arrays.toString(client.mPackageNames));
}
client.mDeviceId = deviceId;
} else if (client.mDeviceId == deviceId && !uidBelongsToDevice) {
client.mDeviceId = DEVICE_ID_DEFAULT;
if (DEBUG) {
Slog.v(LOG_TAG, "Packages moved to the default device from device id "
+ deviceId + " are " + Arrays.toString(client.mPackageNames));
}
}
} else {
if (deviceId != DEVICE_ID_DEFAULT && deviceId != DEVICE_ID_INVALID
&& localVdm.getDeviceIdsForUid(client.mUid).contains(deviceId)) {
if (DEBUG) {
Slog.v(LOG_TAG, "Packages moved to device id " + deviceId + " are "
+ Arrays.toString(client.mPackageNames));
}
client.mDeviceId = deviceId;
}
}
}
}
@VisibleForTesting
void notifyProxyOfRunningAppsChange(Set<Integer> allRunningUids) {
if (DEBUG) {
Slog.v(LOG_TAG, "notifyProxyOfRunningAppsChange: " + allRunningUids);
}
synchronized (mLock) {
if (mProxyA11yServiceConnections.size() == 0) {
return;
}
final VirtualDeviceManagerInternal localVdm = getLocalVdm();
if (localVdm == null) {
return;
}
final ArraySet<Integer> deviceIdsToUpdate = new ArraySet<>();
for (int i = 0; i < mProxyA11yServiceConnections.size(); i++) {
final ProxyAccessibilityServiceConnection proxy =
mProxyA11yServiceConnections.valueAt(i);
if (proxy != null) {
final int proxyDeviceId = proxy.getDeviceId();
for (Integer uid : allRunningUids) {
if (localVdm.getDeviceIdsForUid(uid).contains(proxyDeviceId)) {
deviceIdsToUpdate.add(proxyDeviceId);
}
}
}
}
for (Integer proxyDeviceId : deviceIdsToUpdate) {
onProxyChanged(proxyDeviceId, true);
}
}
}
/**
* Clears all proxy caches.
*/
public void clearCacheLocked() {
for (int i = 0; i < mProxyA11yServiceConnections.size(); i++) {
final ProxyAccessibilityServiceConnection proxy =
mProxyA11yServiceConnections.valueAt(i);
proxy.notifyClearAccessibilityNodeInfoCache();
}
}
/**
* Sets the input filter for enabling and disabling features for proxy displays.
*/
public void setAccessibilityInputFilter(AccessibilityInputFilter filter) {
if (DEBUG) {
Slog.v(LOG_TAG, "Set proxy input filter to " + filter);
}
mA11yInputFilter = filter;
}
private VirtualDeviceManagerInternal getLocalVdm() {
if (mLocalVdm == null) {
mLocalVdm = LocalServices.getService(VirtualDeviceManagerInternal.class);
}
return mLocalVdm;
}
@VisibleForTesting
void setLocalVirtualDeviceManager(VirtualDeviceManagerInternal localVdm) {
mLocalVdm = localVdm;
}
/**
* Prints information belonging to each display that is controlled by an
* AccessibilityDisplayProxy.
*/
void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
synchronized (mLock) {
pw.println();
pw.println("Proxy manager state:");
pw.println(" Number of proxy connections: " + mProxyA11yServiceConnections.size());
pw.println(" Registered proxy connections:");
final RemoteCallbackList<IAccessibilityManagerClient> userClients =
mSystemSupport.getCurrentUserClientsLocked();
final RemoteCallbackList<IAccessibilityManagerClient> globalClients =
mSystemSupport.getGlobalClientsLocked();
for (int i = 0; i < mProxyA11yServiceConnections.size(); i++) {
final ProxyAccessibilityServiceConnection proxy =
mProxyA11yServiceConnections.valueAt(i);
if (proxy != null) {
proxy.dump(fd, pw, args);
}
pw.println();
pw.println(" User clients for proxy's virtual device id");
printClientsForDeviceId(pw, userClients, proxy.getDeviceId());
pw.println();
pw.println(" Global clients for proxy's virtual device id");
printClientsForDeviceId(pw, globalClients, proxy.getDeviceId());
}
}
}
private void printClientsForDeviceId(PrintWriter pw,
RemoteCallbackList<IAccessibilityManagerClient> clients, int deviceId) {
if (clients != null) {
for (int j = 0; j < clients.getRegisteredCallbackCount(); j++) {
final AccessibilityManagerService.Client client =
(AccessibilityManagerService.Client)
clients.getRegisteredCallbackCookie(j);
if (client.mDeviceId == deviceId) {
pw.println(" " + Arrays.toString(client.mPackageNames) + "\n");
}
}
}
}
}