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

Commit 58b04e99 authored by Hiroki Sato's avatar Hiroki Sato
Browse files

Populate display id in A11yWindowManager

When window is removed, there can be a race between a11y and wm,
and sometimes A11yManagerService#sendA11yEvent cannot get a correct
display id.

There's also a case where a11y events for windowless windows don't have
a display id.

This change fixes the issue by populating display id in
A11yWindowManager#DisplayWindowsObserver for windows change events.

Bug: 240888087
Test: AccessibilityWindowManagerTest
Test: CtsAccessibilityServiceTestCases

Change-Id: I441d98e4431913ae24ab354423828d2a031b27b4
parent 82bd3a20
Loading
Loading
Loading
Loading
+3 −1
Original line number Diff line number Diff line
@@ -1332,6 +1332,7 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par
     * Convenience method to obtain a {@link #TYPE_WINDOWS_CHANGED} event for a specific window and
     * change set.
     *
     * @param displayId The ID of the display from which the event comes from
     * @param windowId The ID of the window that changed
     * @param windowChangeTypes The changes to populate
     * @return An instance of a TYPE_WINDOWS_CHANGED, populated with the requested fields and with
@@ -1340,8 +1341,9 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par
     * @hide
     */
    public static AccessibilityEvent obtainWindowsChangedEvent(
            int windowId, int windowChangeTypes) {
            int displayId, int windowId, int windowChangeTypes) {
        final AccessibilityEvent event = new AccessibilityEvent(TYPE_WINDOWS_CHANGED);
        event.setDisplayId(displayId);
        event.setWindowId(windowId);
        event.setWindowChanges(windowChangeTypes);
        event.setImportantForAccessibility(true);
+3 −2
Original line number Diff line number Diff line
@@ -952,10 +952,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
            // current state of the windows as the window manager may be delaying
            // the computation for performance reasons.
            boolean shouldComputeWindows = false;
            int displayId = Display.INVALID_DISPLAY;
            int displayId = event.getDisplayId();
            synchronized (mLock) {
                final int windowId = event.getWindowId();
                if (windowId != AccessibilityWindowInfo.UNDEFINED_WINDOW_ID) {
                if (windowId != AccessibilityWindowInfo.UNDEFINED_WINDOW_ID
                        && displayId == Display.INVALID_DISPLAY) {
                    displayId = mA11yWindowManager.getDisplayIdByUserIdAndWindowIdLocked(
                            resolvedUserId, windowId);
                    event.setDisplayId(displayId);
+44 −24
Original line number Diff line number Diff line
@@ -19,7 +19,6 @@ 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;

@@ -275,22 +274,23 @@ public class AccessibilityWindowManager {
         * Sets the active flag of the window according to given windowId, others set to inactive.
         *
         * @param windowId The windowId
         * @return {@code true} if the window is in this display, {@code false} otherwise.
         */
        void setActiveWindowLocked(int windowId) {
        boolean setActiveWindowLocked(int windowId) {
            boolean foundWindow = false;
            if (mWindows != null) {
                final int windowCount = mWindows.size();
                for (int i = 0; i < windowCount; i++) {
                    AccessibilityWindowInfo window = mWindows.get(i);
                    if (window.getId() == windowId) {
                        window.setActive(true);
                        mAccessibilityEventSender.sendAccessibilityEventForCurrentUserLocked(
                                AccessibilityEvent.obtainWindowsChangedEvent(windowId,
                                        AccessibilityEvent.WINDOWS_CHANGE_ACTIVE));
                        foundWindow = true;
                    } else {
                        window.setActive(false);
                    }
                }
            }
            return foundWindow;
        }

        /**
@@ -298,24 +298,23 @@ public class AccessibilityWindowManager {
         * unfocused.
         *
         * @param windowId The windowId
         * @return {@code true} if the window is in this display, {@code false} otherwise.
         */
        void setAccessibilityFocusedWindowLocked(int windowId) {
        boolean setAccessibilityFocusedWindowLocked(int windowId) {
            boolean foundWindow = false;
            if (mWindows != null) {
                final int windowCount = mWindows.size();
                for (int i = 0; i < windowCount; i++) {
                    AccessibilityWindowInfo window = mWindows.get(i);
                    if (window.getId() == windowId) {
                        mAccessibilityFocusedDisplayId = mDisplayId;
                        window.setAccessibilityFocused(true);
                        mAccessibilityEventSender.sendAccessibilityEventForCurrentUserLocked(
                                AccessibilityEvent.obtainWindowsChangedEvent(
                                        windowId, WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED));

                        foundWindow = true;
                    } else {
                        window.setAccessibilityFocused(false);
                    }
                }
            }
            return foundWindow;
        }

        /**
@@ -704,7 +703,7 @@ public class AccessibilityWindowManager {
                final AccessibilityWindowInfo window = oldWindows.get(i);
                if (mA11yWindowInfoById.get(window.getId()) == null) {
                    events.add(AccessibilityEvent.obtainWindowsChangedEvent(
                            window.getId(), AccessibilityEvent.WINDOWS_CHANGE_REMOVED));
                            mDisplayId, window.getId(), AccessibilityEvent.WINDOWS_CHANGE_REMOVED));
                }
            }

@@ -714,13 +713,13 @@ public class AccessibilityWindowManager {
                final AccessibilityWindowInfo newWindow = mWindows.get(i);
                final AccessibilityWindowInfo oldWindow = oldWindowsById.get(newWindow.getId());
                if (oldWindow == null) {
                    events.add(AccessibilityEvent.obtainWindowsChangedEvent(
                    events.add(AccessibilityEvent.obtainWindowsChangedEvent(mDisplayId,
                            newWindow.getId(), AccessibilityEvent.WINDOWS_CHANGE_ADDED));
                } else {
                    int changes = newWindow.differenceFrom(oldWindow);
                    if (changes !=  0) {
                        events.add(AccessibilityEvent.obtainWindowsChangedEvent(
                                newWindow.getId(), changes));
                                mDisplayId, newWindow.getId(), changes));
                    }
                }
            }
@@ -1522,37 +1521,58 @@ public class AccessibilityWindowManager {

    private void setActiveWindowLocked(int windowId) {
        if (mActiveWindowId != windowId) {
            mAccessibilityEventSender.sendAccessibilityEventForCurrentUserLocked(
                    AccessibilityEvent.obtainWindowsChangedEvent(
            List<AccessibilityEvent> events = new ArrayList<>(2);
            if (mActiveWindowId != AccessibilityWindowInfo.UNDEFINED_WINDOW_ID) {
                final DisplayWindowsObserver observer =
                        getDisplayWindowObserverByWindowIdLocked(mActiveWindowId);
                if (observer != null) {
                    events.add(AccessibilityEvent.obtainWindowsChangedEvent(observer.mDisplayId,
                            mActiveWindowId, AccessibilityEvent.WINDOWS_CHANGE_ACTIVE));
                }
            }

            mActiveWindowId = windowId;
            // Goes through all windows for each display.
            final int count = mDisplayWindowsObservers.size();
            for (int i = 0; i < count; i++) {
                final DisplayWindowsObserver observer = mDisplayWindowsObservers.valueAt(i);
                if (observer != null) {
                    observer.setActiveWindowLocked(windowId);
                if (observer != null && observer.setActiveWindowLocked(windowId)) {
                    events.add(AccessibilityEvent.obtainWindowsChangedEvent(observer.mDisplayId,
                            windowId, AccessibilityEvent.WINDOWS_CHANGE_ACTIVE));
                }
            }

            for (final AccessibilityEvent event : events) {
                mAccessibilityEventSender.sendAccessibilityEventForCurrentUserLocked(event);
            }
        }
    }

    private void setAccessibilityFocusedWindowLocked(int windowId) {
        if (mAccessibilityFocusedWindowId != windowId) {
            mAccessibilityEventSender.sendAccessibilityEventForCurrentUserLocked(
                    AccessibilityEvent.obtainWindowsChangedEvent(
                            mAccessibilityFocusedWindowId,
                            WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED));
            List<AccessibilityEvent> events = new ArrayList<>(2);
            if (mAccessibilityFocusedDisplayId != Display.INVALID_DISPLAY
                    && mAccessibilityFocusedWindowId
                    != AccessibilityWindowInfo.UNDEFINED_WINDOW_ID) {
                events.add(AccessibilityEvent.obtainWindowsChangedEvent(
                        mAccessibilityFocusedDisplayId, mAccessibilityFocusedWindowId,
                        AccessibilityEvent.WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED));
            }

            mAccessibilityFocusedWindowId = windowId;
            // Goes through all windows for each display.
            final int count = mDisplayWindowsObservers.size();
            for (int i = 0; i < count; i++) {
                final DisplayWindowsObserver observer = mDisplayWindowsObservers.valueAt(i);
                if (observer != null) {
                    observer.setAccessibilityFocusedWindowLocked(windowId);
                if (observer != null && observer.setAccessibilityFocusedWindowLocked(windowId)) {
                    mAccessibilityFocusedDisplayId = observer.mDisplayId;
                    events.add(AccessibilityEvent.obtainWindowsChangedEvent(observer.mDisplayId,
                            windowId, AccessibilityEvent.WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED));
                }
            }

            for (final AccessibilityEvent event : events) {
                mAccessibilityEventSender.sendAccessibilityEventForCurrentUserLocked(event);
            }
        }
    }
+157 −7
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package com.android.server.accessibility;

import static com.android.server.accessibility.AccessibilityWindowManagerTest.DisplayIdMatcher.displayId;
import static com.android.server.accessibility.AccessibilityWindowManagerTest.WindowChangesMatcher.a11yWindowChanges;
import static com.android.server.accessibility.AccessibilityWindowManagerTest.WindowIdMatcher.a11yWindowId;

@@ -587,10 +588,12 @@ public class AccessibilityWindowManagerTest {
        verify(mMockA11yEventSender, times(2))
                .sendAccessibilityEventForCurrentUserLocked(captor.capture());
        assertThat(captor.getAllValues().get(0),
                allOf(a11yWindowId(currentActiveWindowId),
                allOf(displayId(Display.DEFAULT_DISPLAY),
                        a11yWindowId(currentActiveWindowId),
                        a11yWindowChanges(AccessibilityEvent.WINDOWS_CHANGE_ACTIVE)));
        assertThat(captor.getAllValues().get(1),
                allOf(a11yWindowId(eventWindowId),
                allOf(displayId(Display.DEFAULT_DISPLAY),
                        a11yWindowId(eventWindowId),
                        a11yWindowChanges(AccessibilityEvent.WINDOWS_CHANGE_ACTIVE)));
    }

@@ -600,9 +603,58 @@ public class AccessibilityWindowManagerTest {
                DEFAULT_FOCUSED_INDEX);
        final int currentA11yFocusedWindowId = mA11yWindowManager.getFocusedWindowId(
                AccessibilityNodeInfo.FOCUS_ACCESSIBILITY);
        assertThat(currentA11yFocusedWindowId, is(not(eventWindowId)));
        assertThat(currentA11yFocusedWindowId, is(AccessibilityWindowInfo.UNDEFINED_WINDOW_ID));

        final int noUse = 0;
        mA11yWindowManager.updateActiveAndAccessibilityFocusedWindowLocked(USER_SYSTEM_ID,
                eventWindowId,
                AccessibilityNodeInfo.ROOT_NODE_ID,
                AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED,
                noUse);
        assertThat(mA11yWindowManager.getFocusedWindowId(
                AccessibilityNodeInfo.FOCUS_ACCESSIBILITY), is(eventWindowId));
        final ArgumentCaptor<AccessibilityEvent> captor =
                ArgumentCaptor.forClass(AccessibilityEvent.class);
        verify(mMockA11yEventSender, times(1))
                .sendAccessibilityEventForCurrentUserLocked(captor.capture());
        assertThat(captor.getAllValues().get(0),
                allOf(displayId(Display.DEFAULT_DISPLAY),
                        a11yWindowId(eventWindowId),
                        a11yWindowChanges(
                                AccessibilityEvent.WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED)));
    }

    @Test
    public void updateActiveAndA11yFocusedWindow_a11yFocusEvent_multiDisplay_defaultToSecondary()
            throws RemoteException {
        runUpdateActiveAndA11yFocusedWindow_MultiDisplayTest(
                Display.DEFAULT_DISPLAY, SECONDARY_DISPLAY_ID);
    }

    @Test
    public void updateActiveAndA11yFocusedWindow_a11yFocusEvent_multiDisplay_SecondaryToDefault()
            throws RemoteException {
        runUpdateActiveAndA11yFocusedWindow_MultiDisplayTest(
                SECONDARY_DISPLAY_ID, Display.DEFAULT_DISPLAY);
    }

    private void runUpdateActiveAndA11yFocusedWindow_MultiDisplayTest(
            int initialDisplayId, int eventDisplayId) throws RemoteException {
        startTrackingPerDisplay(SECONDARY_DISPLAY_ID);
        final int initialWindowId = getWindowIdFromWindowInfosForDisplay(
                initialDisplayId, DEFAULT_FOCUSED_INDEX);
        final int noUse = 0;
        mA11yWindowManager.updateActiveAndAccessibilityFocusedWindowLocked(USER_SYSTEM_ID,
                initialWindowId,
                AccessibilityNodeInfo.ROOT_NODE_ID,
                AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED,
                noUse);
        assertThat(mA11yWindowManager.getFocusedWindowId(
                AccessibilityNodeInfo.FOCUS_ACCESSIBILITY), is(initialWindowId));
        Mockito.reset(mMockA11yEventSender);

        final int eventWindowId = getWindowIdFromWindowInfosForDisplay(
                eventDisplayId, DEFAULT_FOCUSED_INDEX);
        mA11yWindowManager.updateActiveAndAccessibilityFocusedWindowLocked(USER_SYSTEM_ID,
                eventWindowId,
                AccessibilityNodeInfo.ROOT_NODE_ID,
@@ -615,11 +667,13 @@ public class AccessibilityWindowManagerTest {
        verify(mMockA11yEventSender, times(2))
                .sendAccessibilityEventForCurrentUserLocked(captor.capture());
        assertThat(captor.getAllValues().get(0),
                allOf(a11yWindowId(currentA11yFocusedWindowId),
                allOf(displayId(initialDisplayId),
                        a11yWindowId(initialWindowId),
                        a11yWindowChanges(
                                AccessibilityEvent.WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED)));
        assertThat(captor.getAllValues().get(1),
                allOf(a11yWindowId(eventWindowId),
                allOf(displayId(eventDisplayId),
                        a11yWindowId(eventWindowId),
                        a11yWindowChanges(
                                AccessibilityEvent.WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED)));
    }
@@ -674,10 +728,12 @@ public class AccessibilityWindowManagerTest {
        verify(mMockA11yEventSender, times(2))
                .sendAccessibilityEventForCurrentUserLocked(captor.capture());
        assertThat(captor.getAllValues().get(0),
                allOf(a11yWindowId(eventWindowId),
                allOf(displayId(Display.DEFAULT_DISPLAY),
                        a11yWindowId(eventWindowId),
                        a11yWindowChanges(AccessibilityEvent.WINDOWS_CHANGE_ACTIVE)));
        assertThat(captor.getAllValues().get(1),
                allOf(a11yWindowId(currentActiveWindowId),
                allOf(displayId(Display.DEFAULT_DISPLAY),
                        a11yWindowId(currentActiveWindowId),
                        a11yWindowChanges(AccessibilityEvent.WINDOWS_CHANGE_ACTIVE)));
    }

@@ -861,6 +917,77 @@ public class AccessibilityWindowManagerTest {
        assertTrue(TextUtils.equals(layoutParams.accessibilityTitle, a11yWindow.getTitle()));
    }

    @Test
    public void sendAccessibilityEventOnWindowRemoval() {
        final ArrayList<WindowInfo> infos = mWindowInfos.get(Display.DEFAULT_DISPLAY);

        // Removing index 0 because it's not focused, and avoids unnecessary layer change.
        final int windowId =
                getWindowIdFromWindowInfosForDisplay(Display.DEFAULT_DISPLAY, 0);
        infos.remove(0);
        for (WindowInfo info : infos) {
            // Adjust layer number because it should start from 0.
            info.layer--;
        }

        onWindowsForAccessibilityChanged(Display.DEFAULT_DISPLAY, FORCE_SEND);

        final ArgumentCaptor<AccessibilityEvent> captor =
                ArgumentCaptor.forClass(AccessibilityEvent.class);
        verify(mMockA11yEventSender, times(1))
                .sendAccessibilityEventForCurrentUserLocked(captor.capture());
        assertThat(captor.getAllValues().get(0),
                allOf(displayId(Display.DEFAULT_DISPLAY),
                        a11yWindowId(windowId),
                        a11yWindowChanges(AccessibilityEvent.WINDOWS_CHANGE_REMOVED)));
    }

    @Test
    public void sendAccessibilityEventOnWindowAddition() throws RemoteException {
        final ArrayList<WindowInfo> infos = mWindowInfos.get(Display.DEFAULT_DISPLAY);

        for (WindowInfo info : infos) {
            // Adjust layer number because new window will have 0 so that layer number in
            // A11yWindowInfo in window won't be changed.
            info.layer++;
        }

        final IWindow token = addAccessibilityInteractionConnection(Display.DEFAULT_DISPLAY,
                false, USER_SYSTEM_ID);
        addWindowInfo(infos, token, 0);
        final int windowId =
                getWindowIdFromWindowInfosForDisplay(Display.DEFAULT_DISPLAY, infos.size() - 1);

        onWindowsForAccessibilityChanged(Display.DEFAULT_DISPLAY, FORCE_SEND);

        final ArgumentCaptor<AccessibilityEvent> captor =
                ArgumentCaptor.forClass(AccessibilityEvent.class);
        verify(mMockA11yEventSender, times(1))
                .sendAccessibilityEventForCurrentUserLocked(captor.capture());
        assertThat(captor.getAllValues().get(0),
                allOf(displayId(Display.DEFAULT_DISPLAY),
                        a11yWindowId(windowId),
                        a11yWindowChanges(AccessibilityEvent.WINDOWS_CHANGE_ADDED)));
    }

    @Test
    public void sendAccessibilityEventOnWindowChange() {
        final ArrayList<WindowInfo> infos = mWindowInfos.get(Display.DEFAULT_DISPLAY);
        infos.get(0).title = "new title";
        final int windowId = getWindowIdFromWindowInfosForDisplay(Display.DEFAULT_DISPLAY, 0);

        onWindowsForAccessibilityChanged(Display.DEFAULT_DISPLAY, FORCE_SEND);

        final ArgumentCaptor<AccessibilityEvent> captor =
                ArgumentCaptor.forClass(AccessibilityEvent.class);
        verify(mMockA11yEventSender, times(1))
                .sendAccessibilityEventForCurrentUserLocked(captor.capture());
        assertThat(captor.getAllValues().get(0),
                allOf(displayId(Display.DEFAULT_DISPLAY),
                        a11yWindowId(windowId),
                        a11yWindowChanges(AccessibilityEvent.WINDOWS_CHANGE_TITLE)));
    }

    private void registerLeashedTokenAndWindowId() {
        mA11yWindowManager.registerIdLocked(mMockHostToken, HOST_WINDOW_ID);
        mA11yWindowManager.registerIdLocked(mMockEmbeddedToken, EMBEDDED_WINDOW_ID);
@@ -1018,6 +1145,29 @@ public class AccessibilityWindowManagerTest {
        }
    }

    static class DisplayIdMatcher extends TypeSafeMatcher<AccessibilityEvent> {
        private final int mDisplayId;

        DisplayIdMatcher(int displayId) {
            super();
            mDisplayId = displayId;
        }

        static DisplayIdMatcher displayId(int displayId) {
            return new DisplayIdMatcher(displayId);
        }

        @Override
        protected boolean matchesSafely(AccessibilityEvent event) {
            return event.getDisplayId() == mDisplayId;
        }

        @Override
        public void describeTo(Description description) {
            description.appendText("Matching to displayId " + mDisplayId);
        }
    }

    static class WindowIdMatcher extends TypeSafeMatcher<AccessibilityEvent> {
        private int mWindowId;