From 1132b1b602980c023e684c926b59939ac4e514a0 Mon Sep 17 00:00:00 2001 From: Sally Date: Mon, 20 Mar 2023 22:37:12 +0000 Subject: [PATCH] Force input focus to the last focused non-proxy display. This only happens under a couple of conditions: 1) An A11yService is about to perform a system action. 2) An A11yService has touch exploration enabled, and TouchState has received a TYPE_TOUCH_INTERACTION_END AccessibilityEvent, meaning the user has just completed a touch interaction. In both cases, the top focus will only move if the current top focused display is being proxyed, i.e. the display is being streamed to a ChromeBook with a11y enabled. Keep the places where focus changes minimal, since ideally a VirtualDeviceManager client like Exo should not be stealing focus and this should be fixed on the input level. In the fallback case where there is no register system action so we directly send key events, target the last focused non-proxy display. Bug: 274511647 Test: atest AccessibilityDisplayProxyTest and manual (see videos in comments) Change-Id: I79f5b4806eeed4fd222b811a919ddb296e83a895 --- .../AccessibilityManagerService.java | 17 +++++++++++++-- .../AccessibilityWindowManager.java | 21 +++++++++++++++++++ .../accessibility/SystemActionPerformer.java | 18 ++++++++++++---- .../accessibility/gestures/TouchState.java | 3 +++ .../server/wm/WindowManagerInternal.java | 5 +++++ .../server/wm/WindowManagerService.java | 5 +++++ .../SystemActionPerformerTest.java | 5 +++-- 7 files changed, 66 insertions(+), 8 deletions(-) diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index 51325e72204d1..02bd89ef17586 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -192,7 +192,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub AccessibilityUserState.ServiceInfoChangeListener, AccessibilityWindowManager.AccessibilityEventSender, AccessibilitySecurityPolicy.AccessibilityUserManager, - SystemActionPerformer.SystemActionsChangedListener, ProxyManager.SystemSupport{ + SystemActionPerformer.SystemActionsChangedListener, + SystemActionPerformer.DisplayUpdateCallBack, ProxyManager.SystemSupport { private static final boolean DEBUG = false; @@ -1181,7 +1182,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub private SystemActionPerformer getSystemActionPerformer() { if (mSystemActionPerformer == null) { mSystemActionPerformer = - new SystemActionPerformer(mContext, mWindowManagerService, null, this); + new SystemActionPerformer(mContext, mWindowManagerService, null, this, this); } return mSystemActionPerformer; } @@ -1531,6 +1532,18 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } } + @Override + // TODO(b/276459590): Remove when this is resolved at the virtual device/input level. + public void moveNonProxyTopFocusedDisplayToTopIfNeeded() { + mA11yWindowManager.moveNonProxyTopFocusedDisplayToTopIfNeeded(); + } + + @Override + // TODO(b/276459590): Remove when this is resolved at the virtual device/input level. + public int getLastNonProxyTopFocusedDisplayId() { + return mA11yWindowManager.getLastNonProxyTopFocusedDisplayId(); + } + @VisibleForTesting void notifySystemActionsChangedLocked(AccessibilityUserState userState) { for (int i = userState.mBoundServices.size() - 1; i >= 0; i--) { diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java index 0e25a06b4727b..c9ebf4ce318d0 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java @@ -100,6 +100,9 @@ public class AccessibilityWindowManager { // The top focused display and window token updated with the callback of window lists change. private int mTopFocusedDisplayId; private IBinder mTopFocusedWindowToken; + + // The non-proxy display that most recently had top focus. + private int mLastNonProxyTopFocusedDisplayId; // The display has the accessibility focused window currently. private int mAccessibilityFocusedDisplayId = Display.INVALID_DISPLAY; @@ -434,6 +437,9 @@ public class AccessibilityWindowManager { } if (shouldUpdateWindowsLocked(forceSend, windows)) { mTopFocusedDisplayId = topFocusedDisplayId; + if (!isProxyed(topFocusedDisplayId)) { + mLastNonProxyTopFocusedDisplayId = topFocusedDisplayId; + } mTopFocusedWindowToken = topFocusedWindowToken; cacheWindows(windows); // Lets the policy update the focused and active windows. @@ -1108,6 +1114,21 @@ public class AccessibilityWindowManager { return false; } + private boolean isProxyed(int displayId) { + final DisplayWindowsObserver observer = mDisplayWindowsObservers.get(displayId); + return (observer != null && observer.mIsProxy); + } + + void moveNonProxyTopFocusedDisplayToTopIfNeeded() { + if (mHasProxy + && (mLastNonProxyTopFocusedDisplayId != mTopFocusedDisplayId)) { + mWindowManagerInternal.moveDisplayToTopIfAllowed(mLastNonProxyTopFocusedDisplayId); + } + } + int getLastNonProxyTopFocusedDisplayId() { + return mLastNonProxyTopFocusedDisplayId; + } + /** * Checks if we are tracking windows on specified display. * diff --git a/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java b/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java index c89b9b8517423..a13df475d25d2 100644 --- a/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java +++ b/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java @@ -72,6 +72,13 @@ public class SystemActionPerformer { } private final SystemActionsChangedListener mListener; + interface DisplayUpdateCallBack { + void moveNonProxyTopFocusedDisplayToTopIfNeeded(); + + int getLastNonProxyTopFocusedDisplayId(); + } + private final DisplayUpdateCallBack mDisplayUpdateCallBack; + private final Object mSystemActionLock = new Object(); // Resource id based ActionId -> RemoteAction @GuardedBy("mSystemActionLock") @@ -94,7 +101,7 @@ public class SystemActionPerformer { public SystemActionPerformer( Context context, WindowManagerInternal windowManagerInternal) { - this(context, windowManagerInternal, null, null); + this(context, windowManagerInternal, null, null, null); } // Used to mock ScreenshotHelper @@ -103,17 +110,19 @@ public class SystemActionPerformer { Context context, WindowManagerInternal windowManagerInternal, Supplier screenshotHelperSupplier) { - this(context, windowManagerInternal, screenshotHelperSupplier, null); + this(context, windowManagerInternal, screenshotHelperSupplier, null, null); } public SystemActionPerformer( Context context, WindowManagerInternal windowManagerInternal, Supplier screenshotHelperSupplier, - SystemActionsChangedListener listener) { + SystemActionsChangedListener listener, + DisplayUpdateCallBack callback) { mContext = context; mWindowManagerService = windowManagerInternal; mListener = listener; + mDisplayUpdateCallBack = callback; mScreenshotHelperSupplier = screenshotHelperSupplier; mLegacyHomeAction = new AccessibilityAction( @@ -245,6 +254,7 @@ public class SystemActionPerformer { final long identity = Binder.clearCallingIdentity(); try { synchronized (mSystemActionLock) { + mDisplayUpdateCallBack.moveNonProxyTopFocusedDisplayToTopIfNeeded(); // If a system action is registered with the given actionId, call the corresponding // RemoteAction. RemoteAction registeredAction = mRegisteredSystemActions.get(actionId); @@ -341,7 +351,7 @@ public class SystemActionPerformer { int source) { KeyEvent event = KeyEvent.obtain(downTime, time, action, keyCode, 0, 0, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, KeyEvent.FLAG_FROM_SYSTEM, - source, null); + source, mDisplayUpdateCallBack.getLastNonProxyTopFocusedDisplayId(), null); mContext.getSystemService(InputManager.class) .injectInputEvent(event, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC); event.recycle(); diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/TouchState.java b/services/accessibility/java/com/android/server/accessibility/gestures/TouchState.java index eb718853a6ce5..d9e25ef7dcdce 100644 --- a/services/accessibility/java/com/android/server/accessibility/gestures/TouchState.java +++ b/services/accessibility/java/com/android/server/accessibility/gestures/TouchState.java @@ -199,6 +199,9 @@ public class TouchState { case AccessibilityEvent.TYPE_VIEW_HOVER_EXIT: mLastTouchedWindowId = event.getWindowId(); break; + case AccessibilityEvent.TYPE_TOUCH_INTERACTION_END: + mAms.moveNonProxyTopFocusedDisplayToTopIfNeeded(); + break; } } diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java index 969afe544b185..2ffbd5a638b84 100644 --- a/services/core/java/com/android/server/wm/WindowManagerInternal.java +++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java @@ -443,6 +443,11 @@ public abstract class WindowManagerInternal { */ public abstract IBinder getFocusedWindowTokenFromWindowStates(); + /** + * Moves the given display to the top. + */ + public abstract void moveDisplayToTopIfAllowed(int displayId); + /** * @return Whether the keyguard is engaged. */ diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index dde87b1c35e8c..a0624100909f1 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -7649,6 +7649,11 @@ public class WindowManagerService extends IWindowManager.Stub } } + @Override + public void moveDisplayToTopIfAllowed(int displayId) { + WindowManagerService.this.moveDisplayToTopIfAllowed(displayId); + } + @Override public boolean isKeyguardLocked() { return WindowManagerService.this.isKeyguardLocked(); diff --git a/services/tests/servicestests/src/com/android/server/accessibility/SystemActionPerformerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/SystemActionPerformerTest.java index d9461aada4d3c..b62dbcd526cbc 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/SystemActionPerformerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/SystemActionPerformerTest.java @@ -108,6 +108,7 @@ public class SystemActionPerformerTest { @Mock private StatusBarManager mMockStatusBarManager; @Mock private ScreenshotHelper mMockScreenshotHelper; @Mock private SystemActionPerformer.SystemActionsChangedListener mMockListener; + @Mock private SystemActionPerformer.DisplayUpdateCallBack mMockCallback; @Before public void setup() { @@ -125,7 +126,7 @@ public class SystemActionPerformerTest { mMockContext, mMockWindowManagerInternal, () -> mMockScreenshotHelper, - mMockListener); + mMockListener, mMockCallback); } private void setupWithRealContext() { @@ -133,7 +134,7 @@ public class SystemActionPerformerTest { InstrumentationRegistry.getContext(), mMockWindowManagerInternal, () -> mMockScreenshotHelper, - mMockListener); + mMockListener, mMockCallback); } // We need below two help functions because AccessbilityAction.equals function only compares