From feb364538771a47639369f8add844288669d9cc0 Mon Sep 17 00:00:00 2001 From: Hiroki Sato Date: Thu, 24 Nov 2022 16:50:03 +0900 Subject: [PATCH] Ensure that a11y window state change event is sent on focus change Currently when the focus changes, an accessibility event of TYPE_WINDOW_STATE_CHANGED is dispatched from ViewRootImpl. This logic is implemented in performTraversals(), but the logic depends on the value mAttachInfo.mHasWindowFocus, which is updated from an input event handleWindowFocusChanged(). There's no guarantee that always performTraversals() is called after handleWindowFocusChanged(). As a result, sometimes accessibility event is not dispatched after focus change. This change fixes the issue by explicitly dispatching the event in handleWindowFocusChanged() if needed. Bug: 260310672 Bug: 214318644 Test: Accessibility CTS Change-Id: I95cf3a7a9c7951f8b3862713ed7339688a8427d5 --- core/java/android/view/ViewRootImpl.java | 38 ++++++++---------------- 1 file changed, 13 insertions(+), 25 deletions(-) diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 0f970bf4f91cd..f6cce087c2202 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -470,16 +470,6 @@ public final class ViewRootImpl implements ViewParent, private boolean mAppVisibilityChanged; int mOrigWindowType = -1; - /** Whether the window had focus during the most recent traversal. */ - boolean mHadWindowFocus; - - /** - * Whether the window lost focus during a previous traversal and has not - * yet gained it back. Used to determine whether a WINDOW_STATE_CHANGE - * accessibility events should be sent during traversal. - */ - boolean mLostWindowFocus; - // Set to true if the owner of this window is in the stopped state, // so the window should no longer be active. @UnsupportedAppUsage @@ -3573,20 +3563,8 @@ public final class ViewRootImpl implements ViewParent, } final boolean changedVisibility = (viewVisibilityChanged || mFirst) && isViewVisible; - final boolean hasWindowFocus = mAttachInfo.mHasWindowFocus && isViewVisible; - final boolean regainedFocus = hasWindowFocus && mLostWindowFocus; - if (regainedFocus) { - mLostWindowFocus = false; - } else if (!hasWindowFocus && mHadWindowFocus) { - mLostWindowFocus = true; - } - - if (changedVisibility || regainedFocus) { - // Toasts are presented as notifications - don't present them as windows as well - boolean isToast = mWindowAttributes.type == TYPE_TOAST; - if (!isToast) { - host.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); - } + if (changedVisibility) { + maybeFireAccessibilityWindowStateChangedEvent(); } mFirst = false; @@ -3594,8 +3572,8 @@ public final class ViewRootImpl implements ViewParent, mNewSurfaceNeeded = false; mActivityRelaunched = false; mViewVisibility = viewVisibility; - mHadWindowFocus = hasWindowFocus; + final boolean hasWindowFocus = mAttachInfo.mHasWindowFocus && isViewVisible; mImeFocusController.onTraversal(hasWindowFocus, mWindowAttributes); if ((relayoutResult & WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0) { @@ -3838,6 +3816,8 @@ public final class ViewRootImpl implements ViewParent, ~WindowManager.LayoutParams .SOFT_INPUT_IS_FORWARD_NAVIGATION; + maybeFireAccessibilityWindowStateChangedEvent(); + // Refocusing a window that has a focused view should fire a // focus event for the view since the global focused view changed. fireAccessibilityFocusEventIfHasFocusedNode(); @@ -3865,6 +3845,14 @@ public final class ViewRootImpl implements ViewParent, ensureTouchModeLocally(inTouchMode); } + private void maybeFireAccessibilityWindowStateChangedEvent() { + // Toasts are presented as notifications - don't present them as windows as well. + boolean isToast = mWindowAttributes != null && (mWindowAttributes.type == TYPE_TOAST); + if (!isToast && mView != null) { + mView.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); + } + } + private void fireAccessibilityFocusEventIfHasFocusedNode() { if (!AccessibilityManager.getInstance(mContext).isEnabled()) { return;