summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java10
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java292
-rw-r--r--services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java59
-rw-r--r--services/accessibility/java/com/android/server/accessibility/ProxyManager.java670
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/ProxyAccessibilityServiceConnectionTest.java5
5 files changed, 888 insertions, 148 deletions
diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
index e159f18809c4..7463061256b7 100644
--- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
@@ -271,6 +271,14 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
*/
void onClientChangeLocked(boolean serviceInfoChanged);
+ /**
+ * Called back to notify the system the proxy client for a device has changed.
+ *
+ * Changes include if the proxy is unregistered, if its service info list has changed, or if
+ * its focus appearance has changed.
+ */
+ void onProxyChanged(int deviceId);
+
int getCurrentUserIdLocked();
Pair<float[], MagnificationSpec> getWindowTransformationMatrixAndMagnificationSpec(
@@ -315,8 +323,6 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
void attachAccessibilityOverlayToDisplay(int displayId, SurfaceControl sc);
- void setCurrentUserFocusAppearance(int strokeWidth, int color);
-
}
public AbstractAccessibilityServiceConnection(Context context, ComponentName componentName,
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 5417009f92d4..51325e72204d 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -25,6 +25,9 @@ import static android.accessibilityservice.AccessibilityTrace.FLAGS_PACKAGE_BROA
import static android.accessibilityservice.AccessibilityTrace.FLAGS_USER_BROADCAST_RECEIVER;
import static android.accessibilityservice.AccessibilityTrace.FLAGS_WINDOW_MAGNIFICATION_CONNECTION;
import static android.accessibilityservice.AccessibilityTrace.FLAGS_WINDOW_MANAGER_INTERNAL;
+import static android.companion.virtual.VirtualDeviceManager.ACTION_VIRTUAL_DEVICE_REMOVED;
+import static android.companion.virtual.VirtualDeviceManager.EXTRA_VIRTUAL_DEVICE_ID;
+import static android.content.Context.DEVICE_ID_DEFAULT;
import static android.provider.Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED;
import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_BUTTON;
import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY;
@@ -189,7 +192,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
AccessibilityUserState.ServiceInfoChangeListener,
AccessibilityWindowManager.AccessibilityEventSender,
AccessibilitySecurityPolicy.AccessibilityUserManager,
- SystemActionPerformer.SystemActionsChangedListener {
+ SystemActionPerformer.SystemActionsChangedListener, ProxyManager.SystemSupport{
private static final boolean DEBUG = false;
@@ -471,7 +474,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
new MagnificationScaleProvider(mContext));
mMagnificationProcessor = new MagnificationProcessor(mMagnificationController);
mCaptioningManagerImpl = new CaptioningManagerImpl(mContext);
- mProxyManager = new ProxyManager(mLock, mA11yWindowManager, mContext);
+ mProxyManager = new ProxyManager(mLock, mA11yWindowManager, mContext, mMainHandler,
+ mUiAutomationManager, this);
mFlashNotificationsController = new FlashNotificationsController(mContext);
init();
}
@@ -862,6 +866,19 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
};
mContext.registerReceiverAsUser(receiver, UserHandle.ALL, filter, null, mMainHandler,
Context.RECEIVER_EXPORTED);
+
+ final BroadcastReceiver virtualDeviceReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final int deviceId = intent.getIntExtra(
+ EXTRA_VIRTUAL_DEVICE_ID, DEVICE_ID_DEFAULT);
+ mProxyManager.clearConnections(deviceId);
+ }
+ };
+
+ final IntentFilter virtualDeviceFilter = new IntentFilter(ACTION_VIRTUAL_DEVICE_REMOVED);
+ mContext.registerReceiver(virtualDeviceReceiver, virtualDeviceFilter,
+ Context.RECEIVER_NOT_EXPORTED);
}
/**
@@ -940,21 +957,42 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
final int resolvedUserId = mSecurityPolicy
.resolveCallingUserIdEnforcingPermissionsLocked(userId);
+ AccessibilityUserState userState = getUserStateLocked(resolvedUserId);
+ // Support a process moving from the default device to a single virtual
+ // device.
+ final int deviceId = mProxyManager.getFirstDeviceIdForUidLocked(
+ Binder.getCallingUid());
+ Client client = new Client(callback, Binder.getCallingUid(), userState, deviceId);
// If the client is from a process that runs across users such as
// the system UI or the system we add it to the global state that
// is shared across users.
- AccessibilityUserState userState = getUserStateLocked(resolvedUserId);
- Client client = new Client(callback, Binder.getCallingUid(), userState);
if (mSecurityPolicy.isCallerInteractingAcrossUsers(userId)) {
+ if (mProxyManager.isProxyedDeviceId(deviceId)) {
+ if (DEBUG) {
+ Slog.v(LOG_TAG, "Added global client for proxy-ed pid: "
+ + Binder.getCallingPid() + " for device id " + deviceId
+ + " with package names " + Arrays.toString(client.mPackageNames));
+ }
+ return IntPair.of(mProxyManager.getStateLocked(deviceId,
+ mUiAutomationManager.isUiAutomationRunningLocked()),
+ client.mLastSentRelevantEventTypes);
+ }
mGlobalClients.register(callback, client);
if (DEBUG) {
Slog.i(LOG_TAG, "Added global client for pid:" + Binder.getCallingPid());
}
- return IntPair.of(
- combineUserStateAndProxyState(getClientStateLocked(userState),
- mProxyManager.getStateLocked()),
- client.mLastSentRelevantEventTypes);
} else {
+ // If the display belongs to a proxy connections
+ if (mProxyManager.isProxyedDeviceId(deviceId)) {
+ if (DEBUG) {
+ Slog.v(LOG_TAG, "Added user client for proxy-ed pid: "
+ + Binder.getCallingPid() + " for device id " + deviceId
+ + " with package names " + Arrays.toString(client.mPackageNames));
+ }
+ return IntPair.of(mProxyManager.getStateLocked(deviceId,
+ mUiAutomationManager.isUiAutomationRunningLocked()),
+ client.mLastSentRelevantEventTypes);
+ }
userState.mUserClients.register(callback, client);
// If this client is not for the current user we do not
// return a state since it is not for the foreground user.
@@ -963,12 +1001,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
Slog.i(LOG_TAG, "Added user client for pid:" + Binder.getCallingPid()
+ " and userId:" + mCurrentUserId);
}
- return IntPair.of(
- (resolvedUserId == mCurrentUserId) ? combineUserStateAndProxyState(
- getClientStateLocked(userState), mProxyManager.getStateLocked())
- : 0,
- client.mLastSentRelevantEventTypes);
}
+ return IntPair.of(
+ (resolvedUserId == mCurrentUserId) ? getClientStateLocked(userState) : 0,
+ client.mLastSentRelevantEventTypes);
}
}
@@ -1094,7 +1130,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
}
private void dispatchAccessibilityEventLocked(AccessibilityEvent event) {
- if (mProxyManager.isProxyed(event.getDisplayId())) {
+ if (mProxyManager.isProxyedDisplay(event.getDisplayId())) {
mProxyManager.sendAccessibilityEventLocked(event);
} else {
notifyAccessibilityServicesDelayedLocked(event, false);
@@ -1160,6 +1196,12 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
final int resolvedUserId;
final List<AccessibilityServiceInfo> serviceInfos;
synchronized (mLock) {
+ final int deviceId = mProxyManager.getFirstDeviceIdForUidLocked(
+ Binder.getCallingUid());
+ if (mProxyManager.isProxyedDeviceId(deviceId)) {
+ return mProxyManager.getInstalledAndEnabledServiceInfosLocked(
+ AccessibilityServiceInfo.FEEDBACK_ALL_MASK, deviceId);
+ }
// We treat calls from a profile as if made by its parent as profiles
// share the accessibility state of the parent. The call below
// performs the current profile parent resolution.
@@ -1195,6 +1237,12 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
}
synchronized (mLock) {
+ final int deviceId = mProxyManager.getFirstDeviceIdForUidLocked(
+ Binder.getCallingUid());
+ if (mProxyManager.isProxyedDeviceId(deviceId)) {
+ return mProxyManager.getInstalledAndEnabledServiceInfosLocked(feedbackType,
+ deviceId);
+ }
// We treat calls from a profile as if made by its parent as profiles
// share the accessibility state of the parent. The call below
// performs the current profile parent resolution.
@@ -1239,19 +1287,25 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
if (resolvedUserId != mCurrentUserId) {
return;
}
- List<AccessibilityServiceConnection> services =
- getUserStateLocked(resolvedUserId).mBoundServices;
- int numServices = services.size() + mProxyManager.getNumProxysLocked();
- interfacesToInterrupt = new ArrayList<>(numServices);
- for (int i = 0; i < services.size(); i++) {
- AccessibilityServiceConnection service = services.get(i);
- IBinder a11yServiceBinder = service.mService;
- IAccessibilityServiceClient a11yServiceInterface = service.mServiceInterface;
- if ((a11yServiceBinder != null) && (a11yServiceInterface != null)) {
- interfacesToInterrupt.add(a11yServiceInterface);
+
+ final int deviceId = mProxyManager.getFirstDeviceIdForUidLocked(
+ Binder.getCallingUid());
+ if (mProxyManager.isProxyedDeviceId(deviceId)) {
+ interfacesToInterrupt = new ArrayList<>();
+ mProxyManager.addServiceInterfacesLocked(interfacesToInterrupt, deviceId);
+ } else {
+ List<AccessibilityServiceConnection> services =
+ getUserStateLocked(resolvedUserId).mBoundServices;
+ interfacesToInterrupt = new ArrayList<>(services.size());
+ for (int i = 0; i < services.size(); i++) {
+ AccessibilityServiceConnection service = services.get(i);
+ IBinder a11yServiceBinder = service.mService;
+ IAccessibilityServiceClient a11yServiceInterface = service.mServiceInterface;
+ if ((a11yServiceBinder != null) && (a11yServiceInterface != null)) {
+ interfacesToInterrupt.add(a11yServiceInterface);
+ }
}
}
- mProxyManager.addServiceInterfacesLocked(interfacesToInterrupt);
}
for (int i = 0, count = interfacesToInterrupt.size(); i < count; i++) {
try {
@@ -1827,7 +1881,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
return result;
}
- private void notifyClearAccessibilityCacheLocked() {
+ @Override
+ public void notifyClearAccessibilityCacheLocked() {
AccessibilityUserState state = getCurrentUserStateLocked();
for (int i = state.mBoundServices.size() - 1; i >= 0; i--) {
AccessibilityServiceConnection service = state.mBoundServices.get(i);
@@ -2031,18 +2086,15 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
mMainHandler.post(() -> {
broadcastToClients(userState, ignoreRemoteException(client -> {
int relevantEventTypes;
- boolean changed = false;
synchronized (mLock) {
relevantEventTypes = computeRelevantEventTypesLocked(userState, client);
-
- if (client.mLastSentRelevantEventTypes != relevantEventTypes) {
- client.mLastSentRelevantEventTypes = relevantEventTypes;
- changed = true;
+ if (!mProxyManager.isProxyedDeviceId(client.mDeviceId)) {
+ if (client.mLastSentRelevantEventTypes != relevantEventTypes) {
+ client.mLastSentRelevantEventTypes = relevantEventTypes;
+ client.mCallback.setRelevantEventTypes(relevantEventTypes);
+ }
}
}
- if (changed) {
- client.mCallback.setRelevantEventTypes(relevantEventTypes);
- }
}));
});
}
@@ -2062,7 +2114,6 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
mUiAutomationManager.getServiceInfo(), client)
? mUiAutomationManager.getRelevantEventTypes()
: 0;
- relevantEventTypes |= mProxyManager.getRelevantEventTypesLocked();
return relevantEventTypes;
}
@@ -2116,7 +2167,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
}
}
- private static boolean isClientInPackageAllowlist(
+ static boolean isClientInPackageAllowlist(
@Nullable AccessibilityServiceInfo serviceInfo, Client client) {
if (serviceInfo == null) return false;
@@ -2309,24 +2360,20 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
updateAccessibilityEnabledSettingLocked(userState);
}
- private int combineUserStateAndProxyState(int userState, int proxyState) {
- return userState | proxyState;
+ void scheduleUpdateClientsIfNeededLocked(AccessibilityUserState userState) {
+ scheduleUpdateClientsIfNeededLocked(userState, false);
}
- void scheduleUpdateClientsIfNeededLocked(AccessibilityUserState userState) {
+ void scheduleUpdateClientsIfNeededLocked(AccessibilityUserState userState,
+ boolean forceUpdate) {
final int clientState = getClientStateLocked(userState);
- final int proxyState = mProxyManager.getStateLocked();
- if ((userState.getLastSentClientStateLocked() != clientState
- || mProxyManager.getLastSentStateLocked() != proxyState)
+ if (((userState.getLastSentClientStateLocked() != clientState || forceUpdate))
&& (mGlobalClients.getRegisteredCallbackCount() > 0
|| userState.mUserClients.getRegisteredCallbackCount() > 0)) {
userState.setLastSentClientStateLocked(clientState);
- mProxyManager.setLastStateLocked(proxyState);
- // Send both the user and proxy state to the app for now.
- // TODO(b/250929565): Send proxy state to proxy clients
mMainHandler.sendMessage(obtainMessage(
AccessibilityManagerService::sendStateToAllClients,
- this, combineUserStateAndProxyState(clientState, proxyState),
+ this, clientState,
userState.mUserId));
}
}
@@ -2346,8 +2393,13 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
mTraceManager.logTrace(LOG_TAG + ".sendStateToClients",
FLAGS_ACCESSIBILITY_MANAGER_CLIENT, "clientState=" + clientState);
}
- clients.broadcast(ignoreRemoteException(
- client -> client.setState(clientState)));
+ clients.broadcastForEachCookie(ignoreRemoteException(
+ client -> {
+ Client managerClient = ((Client) client);
+ if (!mProxyManager.isProxyedDeviceId(managerClient.mDeviceId)) {
+ managerClient.mCallback.setState(clientState);
+ }
+ }));
}
private void scheduleNotifyClientsOfServicesStateChangeLocked(
@@ -2370,8 +2422,14 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
mTraceManager.logTrace(LOG_TAG + ".notifyClientsOfServicesStateChange",
FLAGS_ACCESSIBILITY_MANAGER_CLIENT, "uiTimeout=" + uiTimeout);
}
- clients.broadcast(ignoreRemoteException(
- client -> client.notifyServicesStateChanged(uiTimeout)));
+
+ clients.broadcastForEachCookie(ignoreRemoteException(
+ client -> {
+ Client managerClient = ((Client) client);
+ if (!mProxyManager.isProxyedDeviceId(managerClient.mDeviceId)) {
+ managerClient.mCallback.notifyServicesStateChanged(uiTimeout);
+ }
+ }));
}
private void scheduleUpdateInputFilter(AccessibilityUserState userState) {
@@ -2444,7 +2502,6 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
}
inputFilter = mInputFilter;
setInputFilter = true;
- mProxyManager.setAccessibilityInputFilter(mInputFilter);
}
mInputFilter.setUserAndEnabledFeatures(userState.mUserId, flags);
mInputFilter.setCombinedGenericMotionEventSources(
@@ -2477,6 +2534,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
"inputFilter=" + inputFilter);
}
mWindowManagerService.setInputFilter(inputFilter);
+ mProxyManager.setAccessibilityInputFilter(inputFilter);
}
}
@@ -2541,6 +2599,20 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
* @param userState the new user state
*/
private void onUserStateChangedLocked(AccessibilityUserState userState) {
+ onUserStateChangedLocked(userState, false);
+ }
+
+ /**
+ * Called when any property of the user state has changed.
+ *
+ * @param userState the new user state
+ * @param forceUpdate whether to force an update of the app Clients.
+ */
+ private void onUserStateChangedLocked(AccessibilityUserState userState, boolean forceUpdate) {
+ if (DEBUG) {
+ Slog.v(LOG_TAG, "onUserStateChangedLocked for user " + userState.mUserId + " with "
+ + "forceUpdate: " + forceUpdate);
+ }
// TODO: Remove this hack
mInitialized = true;
updateLegacyCapabilitiesLocked(userState);
@@ -2553,7 +2625,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
scheduleUpdateFingerprintGestureHandling(userState);
scheduleUpdateInputFilter(userState);
updateRelevantEventsLocked(userState);
- scheduleUpdateClientsIfNeededLocked(userState);
+ scheduleUpdateClientsIfNeededLocked(userState, forceUpdate);
updateAccessibilityShortcutKeyTargetsLocked(userState);
updateAccessibilityButtonTargetsLocked(userState);
// Update the capabilities before the mode because we will check the current mode is
@@ -2600,7 +2672,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
if (display != null) {
if (observingWindows) {
mA11yWindowManager.startTrackingWindows(display.getDisplayId(),
- mProxyManager.isProxyed(display.getDisplayId()));
+ mProxyManager.isProxyedDisplay(display.getDisplayId()));
} else {
mA11yWindowManager.stopTrackingWindows(display.getDisplayId());
}
@@ -2867,6 +2939,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
mContext.getContentResolver(),
Settings.Secure.ACCESSIBILITY_INTERACTIVE_UI_TIMEOUT_MS, 0,
userState.mUserId);
+
+ mProxyManager.updateTimeoutsIfNeeded(nonInteractiveUiTimeout, interactiveUiTimeout);
if (nonInteractiveUiTimeout != userState.getUserNonInteractiveUiTimeoutLocked()
|| interactiveUiTimeout != userState.getUserInteractiveUiTimeoutLocked()) {
userState.setUserNonInteractiveUiTimeoutLocked(nonInteractiveUiTimeout);
@@ -3632,8 +3706,14 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
}
synchronized(mLock) {
- final AccessibilityUserState userState = getCurrentUserStateLocked();
- return getRecommendedTimeoutMillisLocked(userState);
+ final int deviceId = mProxyManager.getFirstDeviceIdForUidLocked(
+ Binder.getCallingUid());
+ if (mProxyManager.isProxyedDeviceId(deviceId)) {
+ return mProxyManager.getRecommendedTimeoutMillisLocked(deviceId);
+ } else {
+ final AccessibilityUserState userState = getCurrentUserStateLocked();
+ return getRecommendedTimeoutMillisLocked(userState);
+ }
}
}
@@ -3712,6 +3792,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
mTraceManager.logTrace(LOG_TAG + ".getFocusStrokeWidth", FLAGS_ACCESSIBILITY_MANAGER);
}
synchronized (mLock) {
+ final int deviceId = mProxyManager.getFirstDeviceIdForUidLocked(
+ Binder.getCallingUid());
+ if (mProxyManager.isProxyedDeviceId(deviceId)) {
+ return mProxyManager.getFocusStrokeWidthLocked(deviceId);
+ }
final AccessibilityUserState userState = getCurrentUserStateLocked();
return userState.getFocusStrokeWidthLocked();
@@ -3728,6 +3813,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
mTraceManager.logTrace(LOG_TAG + ".getFocusColor", FLAGS_ACCESSIBILITY_MANAGER);
}
synchronized (mLock) {
+ final int deviceId = mProxyManager.getFirstDeviceIdForUidLocked(
+ Binder.getCallingUid());
+ if (mProxyManager.isProxyedDeviceId(deviceId)) {
+ return mProxyManager.getFocusColorLocked(deviceId);
+ }
final AccessibilityUserState userState = getCurrentUserStateLocked();
return userState.getFocusColorLocked();
@@ -3814,9 +3904,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
throw new IllegalArgumentException("The display " + displayId + " does not exist or is"
+ " not tracked by accessibility.");
}
- if (mProxyManager.isProxyed(displayId)) {
+ if (mProxyManager.isProxyedDisplay(displayId)) {
throw new IllegalArgumentException("The display " + displayId + " is already being"
- + "proxy-ed");
+ + " proxy-ed");
}
final long identity = Binder.clearCallingIdentity();
@@ -3847,7 +3937,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
}
boolean isDisplayProxyed(int displayId) {
- return mProxyManager.isProxyed(displayId);
+ return mProxyManager.isProxyedDisplay(displayId);
}
@Override
@@ -3995,13 +4085,71 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
@Override
public void onClientChangeLocked(boolean serviceInfoChanged) {
+ onClientChangeLocked(serviceInfoChanged, false);
+ }
+
+ /**
+ * Called when the state of a service or proxy has changed
+ * @param serviceInfoChanged if the service info has changed
+ * @param forceUpdate whether to force an update of state for app clients
+ */
+ public void onClientChangeLocked(boolean serviceInfoChanged, boolean forceUpdate) {
AccessibilityUserState userState = getUserStateLocked(mCurrentUserId);
- onUserStateChangedLocked(userState);
+ onUserStateChangedLocked(userState, forceUpdate);
if (serviceInfoChanged) {
scheduleNotifyClientsOfServicesStateChangeLocked(userState);
}
}
+
+ @Override
+ public void onProxyChanged(int deviceId) {
+ mProxyManager.onProxyChanged(deviceId);
+ }
+
+ /**
+ * Removes the device from tracking. This will reset any AccessibilityManagerClients to be
+ * associated with the default user id.
+ */
+ @Override
+ public void removeDeviceIdLocked(int deviceId) {
+ resetClientsLocked(deviceId, getCurrentUserStateLocked().mUserClients);
+ resetClientsLocked(deviceId, mGlobalClients);
+ // Force an update of A11yManagers if the state was previously a proxy state and needs to be
+ // returned to the default device state.
+ onClientChangeLocked(true, true);
+ }
+
+ private void resetClientsLocked(int deviceId,
+ RemoteCallbackList<IAccessibilityManagerClient> clients) {
+ if (clients == null || clients.getRegisteredCallbackCount() == 0) {
+ return;
+ }
+ synchronized (mLock) {
+ for (int i = 0; i < clients.getRegisteredCallbackCount(); i++) {
+ final Client appClient = ((Client) clients.getRegisteredCallbackCookie(i));
+ if (appClient.mDeviceId == deviceId) {
+ appClient.mDeviceId = DEVICE_ID_DEFAULT;
+ }
+ }
+ }
+ }
+
+ @Override
+ public void updateWindowsForAccessibilityCallbackLocked() {
+ updateWindowsForAccessibilityCallbackLocked(getUserStateLocked(mCurrentUserId));
+ }
+
+ @Override
+ public RemoteCallbackList<IAccessibilityManagerClient> getGlobalClientsLocked() {
+ return mGlobalClients;
+ }
+
+ @Override
+ public RemoteCallbackList<IAccessibilityManagerClient> getCurrentUserClientsLocked() {
+ return getCurrentUserState().mUserClients;
+ }
+
@Override
public void onShellCommand(FileDescriptor in, FileDescriptor out,
FileDescriptor err, String[] args, ShellCallback callback,
@@ -4313,13 +4461,22 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
final IAccessibilityManagerClient mCallback;
final String[] mPackageNames;
int mLastSentRelevantEventTypes;
+ int mUid;
+ int mDeviceId = DEVICE_ID_DEFAULT;
private Client(IAccessibilityManagerClient callback, int clientUid,
- AccessibilityUserState userState) {
+ AccessibilityUserState userState, int deviceId) {
mCallback = callback;
mPackageNames = mPackageManager.getPackagesForUid(clientUid);
+ mUid = clientUid;
+ mDeviceId = deviceId;
synchronized (mLock) {
- mLastSentRelevantEventTypes = computeRelevantEventTypesLocked(userState, this);
+ if (mProxyManager.isProxyedDeviceId(deviceId)) {
+ mLastSentRelevantEventTypes =
+ mProxyManager.computeRelevantEventTypesLocked(this);
+ } else {
+ mLastSentRelevantEventTypes = computeRelevantEventTypesLocked(userState, this);
+ }
}
}
}
@@ -4805,8 +4962,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
}
mMainHandler.post(() -> {
broadcastToClients(userState, ignoreRemoteException(client -> {
- client.mCallback.setFocusAppearance(userState.getFocusStrokeWidthLocked(),
- userState.getFocusColorLocked());
+ if (!mProxyManager.isProxyedDeviceId(client.mDeviceId)) {
+ client.mCallback.setFocusAppearance(userState.getFocusStrokeWidthLocked(),
+ userState.getFocusColorLocked());
+ }
}));
});
@@ -5041,11 +5200,4 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
transaction.apply();
transaction.close();
}
-
- @Override
- public void setCurrentUserFocusAppearance(int strokeWidth, int color) {
- synchronized (mLock) {
- getCurrentUserStateLocked().setFocusAppearanceLocked(strokeWidth, color);
- }
- }
}
diff --git a/services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java
index b19a502547ab..ab01fc324ed2 100644
--- a/services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java
@@ -67,6 +67,7 @@ import java.util.Set;
public class ProxyAccessibilityServiceConnection extends AccessibilityServiceConnection {
private static final String LOG_TAG = "ProxyAccessibilityServiceConnection";
+ private int mDeviceId;
private int mDisplayId;
private List<AccessibilityServiceInfo> mInstalledAndEnabledServices;
@@ -75,6 +76,9 @@ public class ProxyAccessibilityServiceConnection extends AccessibilityServiceCon
/** The color of the focus rectangle */
private int mFocusColor;
+ private int mInteractiveTimeout;
+ private int mNonInteractiveTimeout;
+
ProxyAccessibilityServiceConnection(
Context context,
ComponentName componentName,
@@ -83,7 +87,7 @@ public class ProxyAccessibilityServiceConnection extends AccessibilityServiceCon
AccessibilitySecurityPolicy securityPolicy,
SystemSupport systemSupport, AccessibilityTrace trace,
WindowManagerInternal windowManagerInternal,
- AccessibilityWindowManager awm, int displayId) {
+ AccessibilityWindowManager awm, int displayId, int deviceId) {
super(/* userState= */null, context, componentName, accessibilityServiceInfo, id,
mainHandler, lock, securityPolicy, systemSupport, trace, windowManagerInternal,
/* systemActionPerformer= */ null, awm, /* activityTaskManagerService= */ null);
@@ -93,6 +97,14 @@ public class ProxyAccessibilityServiceConnection extends AccessibilityServiceCon
R.dimen.accessibility_focus_highlight_stroke_width);
mFocusColor = mContext.getResources().getColor(
R.color.accessibility_focus_highlight_color);
+ mDeviceId = deviceId;
+ }
+
+ int getDisplayId() {
+ return mDisplayId;
+ }
+ int getDeviceId() {
+ return mDeviceId;
}
/**
@@ -155,6 +167,8 @@ public class ProxyAccessibilityServiceConnection extends AccessibilityServiceCon
proxyInfo.setAccessibilityTool(isAccessibilityTool);
proxyInfo.setInteractiveUiTimeoutMillis(interactiveUiTimeout);
proxyInfo.setNonInteractiveUiTimeoutMillis(nonInteractiveUiTimeout);
+ mInteractiveTimeout = interactiveUiTimeout;
+ mNonInteractiveTimeout = nonInteractiveUiTimeout;
// If any one service info doesn't set package names, i.e. if it's interested in all
// apps, the proxy shouldn't filter by package name even if some infos specify this.
@@ -167,7 +181,7 @@ public class ProxyAccessibilityServiceConnection extends AccessibilityServiceCon
// Update connection with mAccessibilityServiceInfo values.
setDynamicallyConfigurableProperties(proxyInfo);
// Notify manager service.
- mSystemSupport.onClientChangeLocked(true);
+ mSystemSupport.onProxyChanged(mDeviceId);
}
} finally {
Binder.restoreCallingIdentity(identity);
@@ -235,12 +249,7 @@ public class ProxyAccessibilityServiceConnection extends AccessibilityServiceCon
mFocusStrokeWidth = strokeWidth;
mFocusColor = color;
- // Sets the appearance data in the A11yUserState for now, since the A11yManagers are not
- // separated.
- // TODO(254545943): Separate proxy and non-proxy states so the focus appearance on the
- // phone is not affected by the appearance of a proxy-ed app.
- mSystemSupport.setCurrentUserFocusAppearance(mFocusStrokeWidth, mFocusColor);
- mSystemSupport.onClientChangeLocked(false);
+ mSystemSupport.onProxyChanged(mDeviceId);
}
}
@@ -572,17 +581,51 @@ public class ProxyAccessibilityServiceConnection extends AccessibilityServiceCon
throw new UnsupportedOperationException("setAnimationScale is not supported");
}
+ public int getInteractiveTimeout() {
+ return mInteractiveTimeout;
+ }
+
+ public int getNonInteractiveTimeout() {
+ return mNonInteractiveTimeout;
+ }
+
+ /**
+ * Returns true if a timeout was updated.
+ */
+ public boolean updateTimeouts(int nonInteractiveUiTimeout, int interactiveUiTimeout) {
+ final int newInteractiveUiTimeout = interactiveUiTimeout != 0
+ ? interactiveUiTimeout
+ : mAccessibilityServiceInfo.getInteractiveUiTimeoutMillis();
+ final int newNonInteractiveUiTimeout = nonInteractiveUiTimeout != 0
+ ? nonInteractiveUiTimeout
+ : mAccessibilityServiceInfo.getNonInteractiveUiTimeoutMillis();
+ boolean updated = false;
+
+ if (mInteractiveTimeout != newInteractiveUiTimeout) {
+ mInteractiveTimeout = newInteractiveUiTimeout;
+ updated = true;
+ }
+ if (mNonInteractiveTimeout != newNonInteractiveUiTimeout) {
+ mNonInteractiveTimeout = newNonInteractiveUiTimeout;
+ updated = true;
+ }
+ return updated;
+ }
+
@Override
public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
if (!DumpUtils.checkDumpPermission(mContext, LOG_TAG, pw)) return;
synchronized (mLock) {
pw.append("Proxy[displayId=" + mDisplayId);
+ pw.append(", deviceId=" + mDeviceId);
pw.append(", feedbackType"
+ AccessibilityServiceInfo.feedbackTypeToString(mFeedbackType));
pw.append(", capabilities=" + mAccessibilityServiceInfo.getCapabilities());
pw.append(", eventTypes="
+ AccessibilityEvent.eventTypeToString(mEventTypes));
pw.append(", notificationTimeout=" + mNotificationTimeout);
+ pw.append(", nonInteractiveUiTimeout=").append(String.valueOf(mNonInteractiveTimeout));
+ pw.append(", interactiveUiTimeout=").append(String.valueOf(mInteractiveTimeout));
pw.append(", focusStrokeWidth=").append(String.valueOf(mFocusStrokeWidth));
pw.append(", focusColor=").append(String.valueOf(mFocusColor));
pw.append(", installedAndEnabledServiceCount=").append(String.valueOf(
diff --git a/services/accessibility/java/com/android/server/accessibility/ProxyManager.java b/services/accessibility/java/com/android/server/accessibility/ProxyManager.java
index e258de16caf5..d417197e1419 100644
--- a/services/accessibility/java/com/android/server/accessibility/ProxyManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/ProxyManager.java
@@ -14,26 +14,45 @@
* 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.VirtualDeviceManager;
import android.content.ComponentName;
import android.content.Context;
import android.hardware.display.DisplayManager;
import android.os.Handler;
import android.os.IBinder;
+import android.os.RemoteCallbackList;
import android.os.RemoteException;
+import android.util.IntArray;
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.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.
@@ -53,26 +72,72 @@ public class ProxyManager {
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;
- // Used to determine if we should notify AccessibilityManager clients of updates.
- // TODO(254545943): Separate this so each display id has its own state. Currently there is no
- // way to identify from AccessibilityManager which proxy state should be returned.
- private int mLastState = -1;
+ // Device Id -> state. Used to determine if we should notify AccessibilityManager clients of
+ // updates.
+ private final SparseIntArray mLastStates = new SparseIntArray();
- private SparseArray<ProxyAccessibilityServiceConnection> mProxyA11yServiceConnections =
+ // Each display id entry in a SparseArray represents a proxy a11y user.
+ private final SparseArray<ProxyAccessibilityServiceConnection> mProxyA11yServiceConnections =
new SparseArray<>();
- private AccessibilityWindowManager mA11yWindowManager;
+ private final AccessibilityWindowManager mA11yWindowManager;
private AccessibilityInputFilter mA11yInputFilter;
- ProxyManager(Object lock, AccessibilityWindowManager awm, Context context) {
+ private VirtualDeviceManagerInternal mLocalVdm;
+
+ private final SystemSupport mSystemSupport;
+
+ /**
+ * 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();
+ }
+
+ 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);
}
/**
@@ -89,6 +154,12 @@ public class ProxyManager {
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();
@@ -101,7 +172,7 @@ public class ProxyManager {
new ProxyAccessibilityServiceConnection(context, info.getComponentName(), info,
id, mainHandler, mLock, securityPolicy, systemSupport, trace,
windowManagerInternal,
- mA11yWindowManager, displayId);
+ mA11yWindowManager, displayId, deviceId);
synchronized (mLock) {
mProxyA11yServiceConnections.put(displayId, connection);
@@ -113,20 +184,16 @@ public class ProxyManager {
@Override
public void binderDied() {
client.asBinder().unlinkToDeath(this, 0);
- clearConnection(displayId);
+ clearConnectionAndUpdateState(displayId);
}
};
client.asBinder().linkToDeath(deathRecipient, 0);
- // Notify apps that the service state has changed.
- // A11yManager#A11yServicesStateChangeListener
- synchronized (mLock) {
- connection.mSystemSupport.onClientChangeLocked(true);
- }
-
- if (mA11yInputFilter != null) {
- mA11yInputFilter.disableFeaturesForDisplayIfInstalled(displayId);
- }
+ mMainHandler.post(() -> {
+ if (mA11yInputFilter != null) {
+ mA11yInputFilter.disableFeaturesForDisplayIfInstalled(displayId);
+ }
+ });
connection.initializeServiceInterface(client);
}
@@ -134,38 +201,101 @@ public class ProxyManager {
* Unregister the proxy based on display id.
*/
public boolean unregisterProxy(int displayId) {
- return clearConnection(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));
+ }
}
- private boolean clearConnection(int displayId) {
- boolean removed = false;
+ /**
+ * 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);
- removed = true;
- if (DEBUG) {
- Slog.v(LOG_TAG, "Unregister proxy for display id " + displayId);
- }
+ removedFromConnections = true;
}
}
- if (removed) {
- mA11yWindowManager.stopTrackingDisplayProxy(displayId);
- if (mA11yInputFilter != null) {
- final DisplayManager displayManager = (DisplayManager)
- mContext.getSystemService(Context.DISPLAY_SERVICE);
- final Display proxyDisplay = displayManager.getDisplay(displayId);
- if (proxyDisplay != null) {
- mA11yInputFilter.enableFeaturesForDisplayIfInstalled(proxyDisplay);
- }
+
+ 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.
+ */
+ public 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 clients. Resetting
+ // will usually happen, since in most cases there will only be one proxy for a
+ // device.
+ if (!isProxyedDeviceId(deviceId)) {
+ synchronized (mLock) {
+ mSystemSupport.removeDeviceIdLocked(deviceId);
+ mLastStates.delete(deviceId);
}
+ } else {
+ // Update with the states of the remaining proxies.
+ onProxyChanged(deviceId);
}
- return removed;
}
/**
- * Checks if a display id is being proxy-ed.
+ * Returns {@code true} if {@code displayId} is being proxy-ed.
*/
- public boolean isProxyed(int displayId) {
+ public boolean isProxyedDisplay(int displayId) {
synchronized (mLock) {
final boolean tracked = mProxyA11yServiceConnections.contains(displayId);
if (DEBUG) {
@@ -176,6 +306,23 @@ public class ProxyManager {
}
/**
+ * 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;
+ }
+
+ /**
* Sends AccessibilityEvents to a proxy given the event's displayId.
*/
public void sendAccessibilityEventLocked(AccessibilityEvent event) {
@@ -213,15 +360,37 @@ public class ProxyManager {
/**
* If there is at least one proxy, accessibility is enabled.
*/
- public int getStateLocked() {
+ public int getStateLocked(int deviceId, boolean automationRunning) {
int clientState = 0;
- final boolean a11yEnabled = mProxyA11yServiceConnections.size() > 0;
- if (a11yEnabled) {
+ if (automationRunning) {
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.
+ */
+ public 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;
}
@@ -235,61 +404,396 @@ public class ProxyManager {
!= 0));
}
return clientState;
- // TODO(b/254545943): When A11yManager is separated, include support for other properties.
}
/**
- * Gets the last state.
+ * Gets the last state for a device.
+ */
+ public int getLastSentStateLocked(int deviceId) {
+ return mLastStates.get(deviceId, 0);
+ }
+
+ /**
+ * Sets the last state for a device.
*/
- public int getLastSentStateLocked() {
- return mLastState;
+ public void setLastStateLocked(int deviceId, int proxyState) {
+ mLastStates.put(deviceId, proxyState);
}
/**
- * Sets the last state.
+ * 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.
*/
- public void setLastStateLocked(int proxyState) {
- mLastState = proxyState;
+ public 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 of every proxy.
- * TODO(254545943): When A11yManager is separated, return based on the A11yManager display.
+ * Returns the relevant event types for a Client.
*/
- public int getRelevantEventTypesLocked() {
+ int computeRelevantEventTypesLocked(AccessibilityManagerService.Client client) {
int relevantEventTypes = 0;
for (int i = 0; i < mProxyA11yServiceConnections.size(); i++) {
- ProxyAccessibilityServiceConnection proxy =
+ final ProxyAccessibilityServiceConnection proxy =
mProxyA11yServiceConnections.valueAt(i);
- relevantEventTypes |= proxy.getRelevantEventTypes();
+ 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 all proxies: "
- + AccessibilityEvent.eventTypeToString(relevantEventTypes));
+ Slog.v(LOG_TAG, "Relevant event types for device id " + client.mDeviceId
+ + ": " + AccessibilityEvent.eventTypeToString(relevantEventTypes));
}
return relevantEventTypes;
}
/**
- * Gets the number of current proxy connections.
- * @return
+ * 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 int getNumProxysLocked() {
- return mProxyA11yServiceConnections.size();
+ 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);
+ }
+ }
+ }
}
/**
- * Adds the service interfaces to a list.
- * @param interfaces
+ * 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.
*/
- public void addServiceInterfacesLocked(List<IAccessibilityServiceClient> interfaces) {
+ public void onProxyChanged(int deviceId) {
+ 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);
+ //Calls A11yManager#notifyServicesStateChanged(timeout)
+ scheduleNotifyProxyClientsOfServicesStateChangeLocked(deviceId);
+ // Calls A11yManager#setFocusAppearance
+ updateFocusAppearanceLocked(deviceId);
+ mSystemSupport.notifyClearAccessibilityCacheLocked();
+ }
+ }
+
+ /**
+ * Updates the states of the app AccessibilityManagers.
+ */
+ public void scheduleUpdateProxyClientsIfNeededLocked(int deviceId) {
+ final int proxyState = getStateLocked(deviceId,
+ mUiAutomationManager.isUiAutomationRunningLocked());
+ 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));
+ }
+ if ((getLastSentStateLocked(deviceId)) != proxyState) {
+ 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
+ */
+ public 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.
+ */
+ public 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);
- final IBinder proxyBinder = proxy.mService;
- final IAccessibilityServiceClient proxyInterface = proxy.mServiceInterface;
- if ((proxyBinder != null) && (proxyInterface != null)) {
- interfaces.add(proxyInterface);
+ 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.
+ */
+ public 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.
+ */
+ public 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 (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;
}
}
}
@@ -306,9 +810,18 @@ public class ProxyManager {
}
void setAccessibilityInputFilter(AccessibilityInputFilter filter) {
+ if (DEBUG) {
+ Slog.v(LOG_TAG, "Set proxy input filter to " + filter);
+ }
mA11yInputFilter = filter;
}
+ VirtualDeviceManagerInternal getLocalVdm() {
+ if (mLocalVdm == null) {
+ mLocalVdm = LocalServices.getService(VirtualDeviceManagerInternal.class);
+ }
+ return mLocalVdm;
+ }
/**
* Prints information belonging to each display that is controlled by an
@@ -320,13 +833,38 @@ public class ProxyManager {
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");
+ }
}
}
}
-} \ No newline at end of file
+}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/ProxyAccessibilityServiceConnectionTest.java b/services/tests/servicestests/src/com/android/server/accessibility/ProxyAccessibilityServiceConnectionTest.java
index b5e0e0730a58..dd44a7968639 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/ProxyAccessibilityServiceConnectionTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/ProxyAccessibilityServiceConnectionTest.java
@@ -50,6 +50,7 @@ import java.util.List;
public class ProxyAccessibilityServiceConnectionTest {
private static final int DISPLAY_ID = 1000;
+ private static final int DEVICE_ID = 2000;
private static final int CONNECTION_ID = 1000;
private static final ComponentName COMPONENT_NAME = new ComponentName(
"com.android.server.accessibility", ".ProxyAccessibilityServiceConnectionTest");
@@ -90,7 +91,7 @@ public class ProxyAccessibilityServiceConnectionTest {
mAccessibilityServiceInfo, CONNECTION_ID , new Handler(
getInstrumentation().getContext().getMainLooper()),
mMockLock, mMockSecurityPolicy, mMockSystemSupport, mMockA11yTrace,
- mMockWindowManagerInternal, mMockA11yWindowManager, DISPLAY_ID);
+ mMockWindowManagerInternal, mMockA11yWindowManager, DISPLAY_ID, DEVICE_ID);
}
@Test
@@ -101,7 +102,7 @@ public class ProxyAccessibilityServiceConnectionTest {
mProxyConnection.setInstalledAndEnabledServices(infos);
- verify(mMockSystemSupport).onClientChangeLocked(true);
+ verify(mMockSystemSupport).onProxyChanged(DEVICE_ID);
}
@Test