diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java index 0926f8ab5147c..a3592166ba037 100644 --- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java +++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java @@ -142,7 +142,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ protected final SystemSupport mSystemSupport; protected final WindowManagerInternal mWindowManagerService; private final SystemActionPerformer mSystemActionPerformer; - private final AccessibilityWindowManager mA11yWindowManager; + final AccessibilityWindowManager mA11yWindowManager; private final DisplayManager mDisplayManager; private final PowerManager mPowerManager; private final IPlatformCompat mIPlatformCompat; @@ -2006,15 +2006,14 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ return accessibilityWindowId; } - private int resolveAccessibilityWindowIdForFindFocusLocked(int windowId, int focusType) { + int resolveAccessibilityWindowIdForFindFocusLocked(int windowId, int focusType) { if (windowId == AccessibilityWindowInfo.ANY_WINDOW_ID) { final int focusedWindowId = mA11yWindowManager.getFocusedWindowId(focusType); // If the caller is a proxy and the found window doesn't belong to a proxy display // (or vice versa), then return null. This doesn't work if there are multiple active - // proxys, but in the future this code shouldn't be needed if input and a11y focus are + // proxys, but in the future this code shouldn't be needed if input focus are // properly split. (so we will deal with the issues if we see them). - //TODO(254545943): Remove this when there is user and proxy separation of input and a11y - // focus + //TODO(254545943): Remove this when there is user and proxy separation of input focus if (!mA11yWindowManager.windowIdBelongsToDisplayType(focusedWindowId, mDisplayTypes)) { return AccessibilityWindowInfo.UNDEFINED_WINDOW_ID; } diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index 328b9719709b3..1bf5a5a315a87 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -3823,7 +3823,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub try { mProxyManager.registerProxy(client, displayId, mContext, sIdCounter++, mMainHandler, mSecurityPolicy, this, getTraceManager(), - mWindowManagerService, mA11yWindowManager); + mWindowManagerService); synchronized (mLock) { notifyClearAccessibilityCacheLocked(); diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java index f0c6c4f6cf2c0..094053ed5396e 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java @@ -19,6 +19,7 @@ package com.android.server.accessibility; import static android.accessibilityservice.AccessibilityTrace.FLAGS_ACCESSIBILITY_INTERACTION_CONNECTION; import static android.accessibilityservice.AccessibilityTrace.FLAGS_WINDOW_MANAGER_INTERNAL; import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY; +import static android.view.accessibility.AccessibilityEvent.WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED; import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage; import static com.android.server.accessibility.AbstractAccessibilityServiceConnection.DISPLAY_TYPE_DEFAULT; @@ -104,6 +105,8 @@ public class AccessibilityWindowManager { private boolean mTouchInteractionInProgress; + private boolean mHasProxy; + /** List of Display Windows Observer, mapping from displayId -> DisplayWindowsObserver. */ private final SparseArray mDisplayWindowsObservers = new SparseArray<>(); @@ -159,6 +162,9 @@ public class AccessibilityWindowManager { * Returns {@code true} if the window belongs to a display of {@code displayTypes}. */ public boolean windowIdBelongsToDisplayType(int focusedWindowId, int displayTypes) { + if (!mHasProxy) { + return true; + } // UIAutomation wants focus from any display type. final int displayTypeMask = DISPLAY_TYPE_PROXY | DISPLAY_TYPE_DEFAULT; if ((displayTypes & displayTypeMask) == displayTypeMask) { @@ -195,6 +201,7 @@ public class AccessibilityWindowManager { private List mWindows; private boolean mTrackingWindows = false; private boolean mHasWatchOutsideTouchWindow; + private int mProxyDisplayAccessibilityFocusedWindow; private boolean mIsProxy; /** @@ -608,8 +615,11 @@ public class AccessibilityWindowManager { final int windowCount = windows.size(); final boolean isTopFocusedDisplay = mDisplayId == mTopFocusedDisplayId; + // A proxy with an a11y-focused window is a11y-focused should use the proxy focus id. final boolean isAccessibilityFocusedDisplay = - mDisplayId == mAccessibilityFocusedDisplayId; + mDisplayId == mAccessibilityFocusedDisplayId + || (mIsProxy && mProxyDisplayAccessibilityFocusedWindow + != AccessibilityWindowInfo.UNDEFINED_WINDOW_ID); // Modifies the value of top focused window, active window and a11y focused window // only if this display is top focused display which has the top focused window. if (isTopFocusedDisplay) { @@ -635,9 +645,12 @@ public class AccessibilityWindowManager { // We'll clear accessibility focus if the window with focus is no longer visible to // accessibility services. + int a11yFocusedWindowId = mIsProxy + ? mProxyDisplayAccessibilityFocusedWindow + : mAccessibilityFocusedWindowId; if (isAccessibilityFocusedDisplay) { - shouldClearAccessibilityFocus = mAccessibilityFocusedWindowId - != AccessibilityWindowInfo.UNDEFINED_WINDOW_ID; + shouldClearAccessibilityFocus = a11yFocusedWindowId + != AccessibilityWindowInfo.UNDEFINED_WINDOW_ID; } boolean hasWindowIgnore = false; @@ -701,7 +714,7 @@ public class AccessibilityWindowManager { if (isAccessibilityFocusedDisplay) { for (int i = 0; i < accessibilityWindowCount; i++) { final AccessibilityWindowInfo window = mWindows.get(i); - if (window.getId() == mAccessibilityFocusedWindowId) { + if (window.getId() == a11yFocusedWindowId) { window.setAccessibilityFocused(true); shouldClearAccessibilityFocus = false; break; @@ -718,7 +731,7 @@ public class AccessibilityWindowManager { } if (shouldClearAccessibilityFocus) { - clearAccessibilityFocusLocked(mAccessibilityFocusedWindowId); + clearAccessibilityFocusLocked(a11yFocusedWindowId); } } @@ -1022,6 +1035,7 @@ public class AccessibilityWindowManager { } if (proxyed && !observer.mIsProxy) { observer.mIsProxy = true; + mHasProxy = true; } if (observer.isTrackingWindowsLocked()) { return; @@ -1044,6 +1058,7 @@ public class AccessibilityWindowManager { observer.stopTrackingWindowsLocked(); mDisplayWindowsObservers.remove(displayId); } + resetHasProxyIfNeededLocked(); } } @@ -1053,11 +1068,26 @@ public class AccessibilityWindowManager { */ public void stopTrackingDisplayProxy(int displayId) { synchronized (mLock) { - DisplayWindowsObserver observer = mDisplayWindowsObservers.get(displayId); + final DisplayWindowsObserver proxyObserver = mDisplayWindowsObservers.get(displayId); + if (proxyObserver != null) { + proxyObserver.mIsProxy = false; + } + resetHasProxyIfNeededLocked(); + } + } + + private void resetHasProxyIfNeededLocked() { + boolean hasProxy = false; + final int count = mDisplayWindowsObservers.size(); + for (int i = 0; i < count; i++) { + final DisplayWindowsObserver observer = mDisplayWindowsObservers.valueAt(i); if (observer != null) { - observer.mIsProxy = false; + if (observer.mIsProxy) { + hasProxy = true; + } } } + mHasProxy = hasProxy; } /** @@ -1490,6 +1520,11 @@ public class AccessibilityWindowManager { case AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED: { synchronized (mLock) { + // If window id belongs to a proxy display, then find the display, update the + // observer focus and send WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED events. + if (mHasProxy && setProxyFocusLocked(windowId)) { + return; + } if (mAccessibilityFocusedWindowId != windowId) { clearAccessibilityFocusLocked(mAccessibilityFocusedWindowId); setAccessibilityFocusedWindowLocked(windowId); @@ -1500,6 +1535,10 @@ public class AccessibilityWindowManager { case AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED: { synchronized (mLock) { + // If cleared happened on the proxy display, then clear the tracked focus. + if (mHasProxy && clearProxyFocusLocked(windowId, eventAction)) { + return; + } if (mAccessibilityFocusNodeId == nodeId) { mAccessibilityFocusNodeId = AccessibilityNodeInfo.UNDEFINED_ITEM_ID; } @@ -1599,9 +1638,10 @@ public class AccessibilityWindowManager { if (mAccessibilityFocusedDisplayId != Display.INVALID_DISPLAY && mAccessibilityFocusedWindowId != AccessibilityWindowInfo.UNDEFINED_WINDOW_ID) { + // Previously focused window -> send a focused event for losing focus events.add(AccessibilityEvent.obtainWindowsChangedEvent( mAccessibilityFocusedDisplayId, mAccessibilityFocusedWindowId, - AccessibilityEvent.WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED)); + WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED)); } mAccessibilityFocusedWindowId = windowId; @@ -1611,8 +1651,9 @@ public class AccessibilityWindowManager { final DisplayWindowsObserver observer = mDisplayWindowsObservers.valueAt(i); if (observer != null && observer.setAccessibilityFocusedWindowLocked(windowId)) { mAccessibilityFocusedDisplayId = observer.mDisplayId; + // Newly focused window -> send a focused event for gaining focus events.add(AccessibilityEvent.obtainWindowsChangedEvent(observer.mDisplayId, - windowId, AccessibilityEvent.WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED)); + windowId, WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED)); } } @@ -1662,6 +1703,33 @@ public class AccessibilityWindowManager { * @return The focused windowId */ public int getFocusedWindowId(int focusType) { + return getFocusedWindowId(focusType, Display.INVALID_DISPLAY); + } + + /** + * Returns focused windowId or accessibility focused windowId according to given focusType and + * display id. + * @param focusType {@link AccessibilityNodeInfo#FOCUS_INPUT} or + * {@link AccessibilityNodeInfo#FOCUS_ACCESSIBILITY} + * @param displayId the display id to check. If this display is proxy-ed, the proxy's a11y focus + * will be returned. + * @return The focused windowId + */ + public int getFocusedWindowId(int focusType, int displayId) { + if (displayId == Display.INVALID_DISPLAY || displayId == Display.DEFAULT_DISPLAY + || !mHasProxy) { + return getDefaultFocus(focusType); + } + + final DisplayWindowsObserver observer = mDisplayWindowsObservers.get(displayId); + if (observer != null && observer.mIsProxy) { + return getProxyFocus(focusType, observer); + } else { + return getDefaultFocus(focusType); + } + } + + private int getDefaultFocus(int focusType) { if (focusType == AccessibilityNodeInfo.FOCUS_INPUT) { return mTopFocusedWindowId; } else if (focusType == AccessibilityNodeInfo.FOCUS_ACCESSIBILITY) { @@ -1670,6 +1738,16 @@ public class AccessibilityWindowManager { return AccessibilityWindowInfo.UNDEFINED_WINDOW_ID; } + private int getProxyFocus(int focusType, DisplayWindowsObserver observer) { + if (focusType == AccessibilityNodeInfo.FOCUS_INPUT) { + return mTopFocusedWindowId; + } else if (focusType == AccessibilityNodeInfo.FOCUS_ACCESSIBILITY) { + return observer.mProxyDisplayAccessibilityFocusedWindow; + } else { + return AccessibilityWindowInfo.UNDEFINED_WINDOW_ID; + } + } + /** * Returns {@link AccessibilityWindowInfo} of PIP window. * @@ -1987,6 +2065,78 @@ public class AccessibilityWindowManager { return mHostEmbeddedMap.get(token); } + /** + * Checks if the window belongs to a proxy display and if so clears the focused window id. + * @param focusClearedWindowId the cleared window id. + * @return true if an observer is proxy-ed and has cleared its focused window id. + */ + private boolean clearProxyFocusLocked(int focusClearedWindowId, int eventAction) { + // If we are just moving focus from one view to the other in the same window, do nothing. + if (eventAction == AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS) { + return false; + } + for (int i = 0; i < mDisplayWindowsObservers.size(); i++) { + final DisplayWindowsObserver observer = mDisplayWindowsObservers.get(i); + if (observer != null && observer.mWindows != null && observer.mIsProxy) { + final int windowCount = observer.mWindows.size(); + for (int j = 0; j < windowCount; j++) { + AccessibilityWindowInfo window = observer.mWindows.get(j); + if (window.getId() == focusClearedWindowId) { + observer.mProxyDisplayAccessibilityFocusedWindow = + AccessibilityWindowInfo.UNDEFINED_WINDOW_ID; + // TODO(268754409): Look into sending a WINDOW_FOCUS_CHANGED event since + // window no longer has focus (default window logic doesn't), and + // whether the node id needs to be cached (default window logic does). + return true; + } + } + } + } + return false; + } + + /** + * Checks if the window belongs to a proxy display and if so sends + * WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED for that window and the previously focused window. + * @param focusedWindowId the focused window id. + * @return true if an observer is proxy-ed and contains the focused window. + */ + private boolean setProxyFocusLocked(int focusedWindowId) { + for (int i = 0; i < mDisplayWindowsObservers.size(); i++) { + final DisplayWindowsObserver observer = mDisplayWindowsObservers.valueAt(i); + if (observer != null && observer.mIsProxy + && observer.setAccessibilityFocusedWindowLocked(focusedWindowId)) { + final int previouslyFocusedWindowId = + observer.mProxyDisplayAccessibilityFocusedWindow; + + if (previouslyFocusedWindowId == focusedWindowId) { + // Don't send a focus event if the window is already focused. + return true; + } + + // Previously focused window -> Clear focus on UI thread and send a focused event + // for losing focus + if (previouslyFocusedWindowId != AccessibilityWindowInfo.UNDEFINED_WINDOW_ID) { + clearAccessibilityFocusLocked(previouslyFocusedWindowId); + mAccessibilityEventSender.sendAccessibilityEventForCurrentUserLocked( + AccessibilityEvent.obtainWindowsChangedEvent( + observer.mDisplayId, previouslyFocusedWindowId, + WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED)); + } + observer.mProxyDisplayAccessibilityFocusedWindow = focusedWindowId; + // Newly focused window -> send a focused event for it gaining focus + mAccessibilityEventSender.sendAccessibilityEventForCurrentUserLocked( + AccessibilityEvent.obtainWindowsChangedEvent( + observer.mDisplayId, + observer.mProxyDisplayAccessibilityFocusedWindow, + WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED)); + + return true; + } + } + return false; + } + /** * Dumps all {@link AccessibilityWindowInfo}s here. */ diff --git a/services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java index df913aaf4893a..945d43b94dac5 100644 --- a/services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java +++ b/services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java @@ -254,6 +254,24 @@ public class ProxyAccessibilityServiceConnection extends AccessibilityServiceCon return mFocusColor; } + @Override + int resolveAccessibilityWindowIdForFindFocusLocked(int windowId, int focusType) { + if (windowId == AccessibilityWindowInfo.ANY_WINDOW_ID) { + final int focusedWindowId = mA11yWindowManager.getFocusedWindowId(focusType, + mDisplayId); + // If the caller is a proxy and the found window doesn't belong to a proxy display + // (or vice versa), then return null. This doesn't work if there are multiple active + // proxys, but in the future this code shouldn't be needed if input focus + // properly split. (so we will deal with the issues if we see them). + //TODO(254545943): Remove this when there is user and proxy separation of input + if (!mA11yWindowManager.windowIdBelongsToDisplayType(focusedWindowId, mDisplayTypes)) { + return AccessibilityWindowInfo.UNDEFINED_WINDOW_ID; + } + return focusedWindowId; + } + return windowId; + } + @Override public void binderDied() { } diff --git a/services/accessibility/java/com/android/server/accessibility/ProxyManager.java b/services/accessibility/java/com/android/server/accessibility/ProxyManager.java index 2530338a03ab0..9d91d10a3aa1f 100644 --- a/services/accessibility/java/com/android/server/accessibility/ProxyManager.java +++ b/services/accessibility/java/com/android/server/accessibility/ProxyManager.java @@ -78,8 +78,7 @@ public class ProxyManager { AccessibilitySecurityPolicy securityPolicy, AbstractAccessibilityServiceConnection.SystemSupport systemSupport, AccessibilityTrace trace, - WindowManagerInternal windowManagerInternal, - AccessibilityWindowManager awm) throws RemoteException { + WindowManagerInternal windowManagerInternal) throws RemoteException { // Set a default AccessibilityServiceInfo that is used before the proxy's info is // populated. A proxy has the touch exploration and window capabilities. @@ -93,7 +92,7 @@ public class ProxyManager { new ProxyAccessibilityServiceConnection(context, info.getComponentName(), info, id, mainHandler, mLock, securityPolicy, systemSupport, trace, windowManagerInternal, - awm, displayId); + mA11yWindowManager, displayId); synchronized (mLock) { mProxyA11yServiceConnections.put(displayId, connection); diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java index 424940567da53..503e579062d44 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java @@ -284,7 +284,7 @@ public class AccessibilityManagerServiceTest { verify(mProxyManager).registerProxy(eq(mMockServiceClient), eq(TEST_DISPLAY), eq(mTestableContext), anyInt(), any(), eq(mMockSecurityPolicy), eq(mA11yms), eq(mA11yms.getTraceManager()), - eq(mMockWindowManagerService), eq(mMockA11yWindowManager)); + eq(mMockWindowManagerService)); } @SmallTest @@ -296,7 +296,7 @@ public class AccessibilityManagerServiceTest { assertThrows(SecurityException.class, () -> mA11yms.registerProxyForDisplay(mMockServiceClient, TEST_DISPLAY)); verify(mProxyManager, never()).registerProxy(any(), anyInt(), any(), anyInt(), any(), any(), - any(), any(), any(), any()); + any(), any(), any()); } @SmallTest @@ -305,7 +305,7 @@ public class AccessibilityManagerServiceTest { assertThrows(IllegalArgumentException.class, () -> mA11yms.registerProxyForDisplay(mMockServiceClient, Display.DEFAULT_DISPLAY)); verify(mProxyManager, never()).registerProxy(any(), anyInt(), any(), anyInt(), any(), any(), - any(), any(), any(), any()); + any(), any(), any()); } @SmallTest @@ -314,7 +314,7 @@ public class AccessibilityManagerServiceTest { assertThrows(IllegalArgumentException.class, () -> mA11yms.registerProxyForDisplay(mMockServiceClient, Display.INVALID_DISPLAY)); verify(mProxyManager, never()).registerProxy(any(), anyInt(), any(), anyInt(), any(), any(), - any(), any(), any(), any()); + any(), any(), any()); } @SmallTest