Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 57a1da43 authored by ryanlwlin's avatar ryanlwlin
Browse files

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
parent 5c554e28
Loading
Loading
Loading
Loading
+91 −3
Original line number Diff line number Diff line
@@ -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<SendWindowStateChangedEventRunnable> 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;
        }
    }
}