From 57a1da433ac379e47b4cfa31c9629bf850f12b8d Mon Sep 17 00:00:00 2001 From: ryanlwlin Date: Wed, 13 Apr 2022 18:10:46 +0800 Subject: Postpone window_state_changed events until the window is added ViewRootImpl sends window_state_changed event to notify AccessibilityService that itself is visible on the screen. However, the corresponding window is not available from accessibility framework perspective because the window information is from SurfaceFlinger process now, so the window is added only when it is really visible on the screen. To ensure accessibilityService could get the node or the window when receiving this event, we postpone this event until the corresponding window is added. We also set a timeout to send those pending events. Bug: 228442331 Bug: 226371995 Test: atest CtsAccessibilityServiceTestCases manual test: write a sample AccessibilityService to test the apis when receving window_state_changed events Change-Id: I92cf2ddb9de90c050cf145f746a53f31d3a5df83 --- .../accessibility/AccessibilityManagerService.java | 94 +++++++++++++++++++++- 1 file changed, 91 insertions(+), 3 deletions(-) diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index cbeb01a68778..4806514a0e13 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -187,6 +187,13 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub // their capabilities are ready. private static final int WAIT_MOTION_INJECTOR_TIMEOUT_MILLIS = 1000; + + // This postpones state changes events when a window doesn't exist with the expectation that + // a race condition will resolve. It is determined by observing elapsed time of the + // corresponding window added. + //TODO(b/230810909) : Fix it with a better idea. + private static final int POSTPONE_WINDOW_STATE_CHANGED_EVENT_TIMEOUT_MILLIS = 500; + private static final String FUNCTION_REGISTER_UI_TEST_AUTOMATION_SERVICE = "registerUiTestAutomationService"; @@ -272,6 +279,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub private final AccessibilityTraceManager mTraceManager; private final CaptioningManagerImpl mCaptioningManagerImpl; + private final List mSendWindowStateChangedEventRunnables = + new ArrayList<>(); + private int mCurrentUserId = UserHandle.USER_SYSTEM; //TODO: Remove this hack @@ -930,11 +940,15 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub final WindowManagerInternal wm = LocalServices.getService( WindowManagerInternal.class); wm.computeWindowsForAccessibility(displayId); + // The App side sends a event to notify that the window visible or focused, + // but the window information in framework is not updated yet, so we postpone it. + if (postponeWindowStateEvent(event)) { + return; + } } + synchronized (mLock) { - notifyAccessibilityServicesDelayedLocked(event, false); - notifyAccessibilityServicesDelayedLocked(event, true); - mUiAutomationManager.sendAccessibilityEventLocked(event); + dispatchAccessibilityEventLocked(event); } } @@ -943,6 +957,12 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } } + private void dispatchAccessibilityEventLocked(AccessibilityEvent event) { + notifyAccessibilityServicesDelayedLocked(event, false); + notifyAccessibilityServicesDelayedLocked(event, true); + mUiAutomationManager.sendAccessibilityEventLocked(event); + } + private void sendAccessibilityEventToInputFilter(AccessibilityEvent event) { synchronized (mLock) { if (mHasInputFilter && mInputFilter != null) { @@ -3339,6 +3359,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub @Override public void sendAccessibilityEventForCurrentUserLocked(AccessibilityEvent event) { + if (event.getWindowChanges() == AccessibilityEvent.WINDOWS_CHANGE_ADDED) { + // We need to ensure the window is available before sending pending + // window_state_changed events. + sendPendingWindowStateChangedEventsForAvailableWindowLocked(event.getWindowId()); + } sendAccessibilityEventLocked(event, mCurrentUserId); } @@ -4505,4 +4530,67 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } } } + + private final class SendWindowStateChangedEventRunnable implements Runnable { + + private final AccessibilityEvent mPendingEvent; + private final int mWindowId; + + SendWindowStateChangedEventRunnable(@NonNull AccessibilityEvent event) { + mPendingEvent = event; + mWindowId = event.getWindowId(); + } + + @Override + public void run() { + synchronized (mLock) { + Slog.w(LOG_TAG, " wait for adding window timeout: " + mWindowId); + sendPendingEventLocked(); + } + } + + private void sendPendingEventLocked() { + mSendWindowStateChangedEventRunnables.remove(this); + dispatchAccessibilityEventLocked(mPendingEvent); + } + + private int getWindowId() { + return mWindowId; + } + } + + void sendPendingWindowStateChangedEventsForAvailableWindowLocked(int windowId) { + final int eventSize = mSendWindowStateChangedEventRunnables.size(); + for (int i = eventSize - 1; i >= 0; i--) { + final SendWindowStateChangedEventRunnable runnable = + mSendWindowStateChangedEventRunnables.get(i); + if (runnable.getWindowId() == windowId) { + mMainHandler.removeCallbacks(runnable); + runnable.sendPendingEventLocked(); + } + } + } + + /** + * Postpones the {@link AccessibilityEvent} with + * {@link AccessibilityEvent#TYPE_WINDOW_STATE_CHANGED} + * which doesn't have the corresponding window until the window is added or timeout. + * + * @return {@code true} if the event is postponed. + */ + private boolean postponeWindowStateEvent(AccessibilityEvent event) { + synchronized (mLock) { + final int resolvedWindowId = mA11yWindowManager.resolveParentWindowIdLocked( + event.getWindowId()); + if (mA11yWindowManager.findWindowInfoByIdLocked(resolvedWindowId) != null) { + return false; + } + final SendWindowStateChangedEventRunnable pendingRunnable = + new SendWindowStateChangedEventRunnable(new AccessibilityEvent(event)); + mMainHandler.postDelayed(pendingRunnable, + POSTPONE_WINDOW_STATE_CHANGED_EVENT_TIMEOUT_MILLIS); + mSendWindowStateChangedEventRunnables.add(pendingRunnable); + return true; + } + } } -- cgit v1.2.3-59-g8ed1b