diff options
| author | 2023-04-03 16:39:57 +0000 | |
|---|---|---|
| committer | 2023-04-03 16:39:57 +0000 | |
| commit | 1e7c22abdb4896af6156f34f242f62e20bdfcddb (patch) | |
| tree | c6d21919158cff152fac62a57d0382172d05e491 | |
| parent | 4c62f6be087d6d043142739425ea914870ac7c5b (diff) | |
| parent | fd24f601f82ec2bef7be00758228f19e3ec20283 (diff) | |
Merge "Support separating A11yManagers by device id" into udc-dev
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 |