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

Commit 8c43e818 authored by sallyyuen's avatar sallyyuen Committed by Sally
Browse files

Keep a separate accessibility focus for proxy-ed displays.

Proxy display a11y focus changes will not clear non-proxy displays,
and vice-versa.
The logic for a proxy display generally follows the logic for a
non-proxy display when deciding when to update
windows#isAccessibilityFocus/send WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED.

Bug: 254545943
Test: manual check with TalkBack on and a proxy-ed Exo
VirtualDisplay. Both can retain and set their own a11y focus.
atest AccessibilityDisplayProxyTest,
atest AccessibilityManagerServiceTest

Change-Id: I76f72acf4c72ae3ce23cd2240430de3e844d2e4b
parent ca9e2463
Loading
Loading
Loading
Loading
+4 −5
Original line number Diff line number Diff line
@@ -142,7 +142,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
    protected final SystemSupport mSystemSupport;
    protected final WindowManagerInternal mWindowManagerService;
    private final SystemActionPerformer mSystemActionPerformer;
    private final AccessibilityWindowManager mA11yWindowManager;
    final AccessibilityWindowManager mA11yWindowManager;
    private final DisplayManager mDisplayManager;
    private final PowerManager mPowerManager;
    private final IPlatformCompat mIPlatformCompat;
@@ -2006,15 +2006,14 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
        return accessibilityWindowId;
    }

    private int resolveAccessibilityWindowIdForFindFocusLocked(int windowId, int focusType) {
    int resolveAccessibilityWindowIdForFindFocusLocked(int windowId, int focusType) {
        if (windowId == AccessibilityWindowInfo.ANY_WINDOW_ID) {
            final int focusedWindowId = mA11yWindowManager.getFocusedWindowId(focusType);
            // If the caller is a proxy and the found window doesn't belong to a proxy display
            // (or vice versa), then return null. This doesn't work if there are multiple active
            // proxys, but in the future this code shouldn't be needed if input and a11y focus are
            // proxys, but in the future this code shouldn't be needed if input focus are
            // properly split. (so we will deal with the issues if we see them).
            //TODO(254545943): Remove this when there is user and proxy separation of input and a11y
            // focus
            //TODO(254545943): Remove this when there is user and proxy separation of input focus
            if (!mA11yWindowManager.windowIdBelongsToDisplayType(focusedWindowId, mDisplayTypes)) {
                return AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
            }
+1 −1
Original line number Diff line number Diff line
@@ -3823,7 +3823,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
        try {
            mProxyManager.registerProxy(client, displayId, mContext,
                    sIdCounter++, mMainHandler, mSecurityPolicy, this, getTraceManager(),
                    mWindowManagerService, mA11yWindowManager);
                    mWindowManagerService);

            synchronized (mLock) {
                notifyClearAccessibilityCacheLocked();
+159 −9
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@ 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;
import static com.android.server.accessibility.AbstractAccessibilityServiceConnection.DISPLAY_TYPE_DEFAULT;
@@ -104,6 +105,8 @@ public class AccessibilityWindowManager {

    private boolean mTouchInteractionInProgress;

    private boolean mHasProxy;

    /** List of Display Windows Observer, mapping from displayId -> DisplayWindowsObserver. */
    private final SparseArray<DisplayWindowsObserver> mDisplayWindowsObservers =
            new SparseArray<>();
@@ -159,6 +162,9 @@ public class AccessibilityWindowManager {
     * Returns {@code true} if the window belongs to a display of {@code displayTypes}.
     */
    public boolean windowIdBelongsToDisplayType(int focusedWindowId, int displayTypes) {
        if (!mHasProxy) {
            return true;
        }
        // UIAutomation wants focus from any display type.
        final int displayTypeMask = DISPLAY_TYPE_PROXY | DISPLAY_TYPE_DEFAULT;
        if ((displayTypes & displayTypeMask) == displayTypeMask) {
@@ -195,6 +201,7 @@ public class AccessibilityWindowManager {
        private List<AccessibilityWindowInfo> mWindows;
        private boolean mTrackingWindows = false;
        private boolean mHasWatchOutsideTouchWindow;
        private int mProxyDisplayAccessibilityFocusedWindow;
        private boolean mIsProxy;

        /**
@@ -608,8 +615,11 @@ public class AccessibilityWindowManager {

            final int windowCount = windows.size();
            final boolean isTopFocusedDisplay = mDisplayId == mTopFocusedDisplayId;
            // A proxy with an a11y-focused window is a11y-focused should use the proxy focus id.
            final boolean isAccessibilityFocusedDisplay =
                    mDisplayId == mAccessibilityFocusedDisplayId;
                    mDisplayId == mAccessibilityFocusedDisplayId
                            || (mIsProxy && mProxyDisplayAccessibilityFocusedWindow
                                    != AccessibilityWindowInfo.UNDEFINED_WINDOW_ID);
            // Modifies the value of top focused window, active window and a11y focused window
            // only if this display is top focused display which has the top focused window.
            if (isTopFocusedDisplay) {
@@ -635,8 +645,11 @@ public class AccessibilityWindowManager {

            // We'll clear accessibility focus if the window with focus is no longer visible to
            // accessibility services.
            int a11yFocusedWindowId = mIsProxy
                    ? mProxyDisplayAccessibilityFocusedWindow
                    : mAccessibilityFocusedWindowId;
            if (isAccessibilityFocusedDisplay) {
                shouldClearAccessibilityFocus = mAccessibilityFocusedWindowId
                shouldClearAccessibilityFocus = a11yFocusedWindowId
                        != AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
            }

@@ -701,7 +714,7 @@ public class AccessibilityWindowManager {
                if (isAccessibilityFocusedDisplay) {
                    for (int i = 0; i < accessibilityWindowCount; i++) {
                        final AccessibilityWindowInfo window = mWindows.get(i);
                        if (window.getId() == mAccessibilityFocusedWindowId) {
                        if (window.getId() == a11yFocusedWindowId) {
                            window.setAccessibilityFocused(true);
                            shouldClearAccessibilityFocus = false;
                            break;
@@ -718,7 +731,7 @@ public class AccessibilityWindowManager {
            }

            if (shouldClearAccessibilityFocus) {
                clearAccessibilityFocusLocked(mAccessibilityFocusedWindowId);
                clearAccessibilityFocusLocked(a11yFocusedWindowId);
            }
        }

@@ -1022,6 +1035,7 @@ public class AccessibilityWindowManager {
            }
            if (proxyed && !observer.mIsProxy) {
                observer.mIsProxy = true;
                mHasProxy = true;
            }
            if (observer.isTrackingWindowsLocked()) {
                return;
@@ -1044,6 +1058,7 @@ public class AccessibilityWindowManager {
                observer.stopTrackingWindowsLocked();
                mDisplayWindowsObservers.remove(displayId);
            }
            resetHasProxyIfNeededLocked();
        }
    }

@@ -1053,11 +1068,26 @@ public class AccessibilityWindowManager {
     */
    public void stopTrackingDisplayProxy(int displayId) {
        synchronized (mLock) {
            DisplayWindowsObserver observer = mDisplayWindowsObservers.get(displayId);
            final DisplayWindowsObserver proxyObserver = mDisplayWindowsObservers.get(displayId);
            if (proxyObserver != null) {
                proxyObserver.mIsProxy = false;
            }
            resetHasProxyIfNeededLocked();
        }
    }

    private void resetHasProxyIfNeededLocked() {
        boolean hasProxy = false;
        final int count = mDisplayWindowsObservers.size();
        for (int i = 0; i < count; i++) {
            final DisplayWindowsObserver observer = mDisplayWindowsObservers.valueAt(i);
            if (observer != null) {
                observer.mIsProxy = false;
                if (observer.mIsProxy) {
                    hasProxy = true;
                }
            }
        }
        mHasProxy = hasProxy;
    }

    /**
@@ -1490,6 +1520,11 @@ public class AccessibilityWindowManager {

            case AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED: {
                synchronized (mLock) {
                    // If window id belongs to a proxy display, then find the display, update the
                    // observer focus and send WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED events.
                    if (mHasProxy && setProxyFocusLocked(windowId)) {
                        return;
                    }
                    if (mAccessibilityFocusedWindowId != windowId) {
                        clearAccessibilityFocusLocked(mAccessibilityFocusedWindowId);
                        setAccessibilityFocusedWindowLocked(windowId);
@@ -1500,6 +1535,10 @@ public class AccessibilityWindowManager {

            case AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED: {
                synchronized (mLock) {
                    // If cleared happened on the proxy display, then clear the tracked focus.
                    if (mHasProxy && clearProxyFocusLocked(windowId, eventAction)) {
                        return;
                    }
                    if (mAccessibilityFocusNodeId == nodeId) {
                        mAccessibilityFocusNodeId = AccessibilityNodeInfo.UNDEFINED_ITEM_ID;
                    }
@@ -1599,9 +1638,10 @@ public class AccessibilityWindowManager {
            if (mAccessibilityFocusedDisplayId != Display.INVALID_DISPLAY
                    && mAccessibilityFocusedWindowId
                    != AccessibilityWindowInfo.UNDEFINED_WINDOW_ID) {
                // Previously focused window -> send a focused event for losing focus
                events.add(AccessibilityEvent.obtainWindowsChangedEvent(
                        mAccessibilityFocusedDisplayId, mAccessibilityFocusedWindowId,
                        AccessibilityEvent.WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED));
                        WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED));
            }

            mAccessibilityFocusedWindowId = windowId;
@@ -1611,8 +1651,9 @@ public class AccessibilityWindowManager {
                final DisplayWindowsObserver observer = mDisplayWindowsObservers.valueAt(i);
                if (observer != null && observer.setAccessibilityFocusedWindowLocked(windowId)) {
                    mAccessibilityFocusedDisplayId = observer.mDisplayId;
                    // Newly focused window -> send a focused event for gaining focus
                    events.add(AccessibilityEvent.obtainWindowsChangedEvent(observer.mDisplayId,
                            windowId, AccessibilityEvent.WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED));
                            windowId, WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED));
                }
            }

@@ -1662,6 +1703,33 @@ public class AccessibilityWindowManager {
     * @return The focused windowId
     */
    public int getFocusedWindowId(int focusType) {
        return getFocusedWindowId(focusType, Display.INVALID_DISPLAY);
    }

    /**
     * Returns focused windowId or accessibility focused windowId according to given focusType and
     * display id.
     * @param focusType {@link AccessibilityNodeInfo#FOCUS_INPUT} or
     * {@link AccessibilityNodeInfo#FOCUS_ACCESSIBILITY}
     * @param displayId the display id to check. If this display is proxy-ed, the proxy's a11y focus
     *                  will be returned.
     * @return The focused windowId
     */
    public int getFocusedWindowId(int focusType, int displayId) {
        if (displayId == Display.INVALID_DISPLAY || displayId == Display.DEFAULT_DISPLAY
                || !mHasProxy) {
            return getDefaultFocus(focusType);
        }

        final DisplayWindowsObserver observer = mDisplayWindowsObservers.get(displayId);
        if (observer != null && observer.mIsProxy) {
            return getProxyFocus(focusType, observer);
        } else {
            return getDefaultFocus(focusType);
        }
    }

    private int getDefaultFocus(int focusType) {
        if (focusType == AccessibilityNodeInfo.FOCUS_INPUT) {
            return mTopFocusedWindowId;
        } else if (focusType == AccessibilityNodeInfo.FOCUS_ACCESSIBILITY) {
@@ -1670,6 +1738,16 @@ public class AccessibilityWindowManager {
        return AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
    }

    private int getProxyFocus(int focusType, DisplayWindowsObserver observer) {
        if (focusType == AccessibilityNodeInfo.FOCUS_INPUT) {
            return mTopFocusedWindowId;
        } else if (focusType == AccessibilityNodeInfo.FOCUS_ACCESSIBILITY) {
            return observer.mProxyDisplayAccessibilityFocusedWindow;
        } else {
            return AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
        }
    }

    /**
     * Returns {@link AccessibilityWindowInfo} of PIP window.
     *
@@ -1987,6 +2065,78 @@ public class AccessibilityWindowManager {
        return mHostEmbeddedMap.get(token);
    }

    /**
     * Checks if the window belongs to a proxy display and if so clears the focused window id.
     * @param focusClearedWindowId the cleared window id.
     * @return true if an observer is proxy-ed and has cleared its focused window id.
     */
    private boolean clearProxyFocusLocked(int focusClearedWindowId, int eventAction) {
        // If we are just moving focus from one view to the other in the same window, do nothing.
        if (eventAction == AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS) {
            return false;
        }
        for (int i = 0; i < mDisplayWindowsObservers.size(); i++) {
            final DisplayWindowsObserver observer = mDisplayWindowsObservers.get(i);
            if (observer != null && observer.mWindows != null && observer.mIsProxy) {
                final int windowCount = observer.mWindows.size();
                for (int j = 0; j < windowCount; j++) {
                    AccessibilityWindowInfo window = observer.mWindows.get(j);
                    if (window.getId() == focusClearedWindowId) {
                        observer.mProxyDisplayAccessibilityFocusedWindow =
                                AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
                        // TODO(268754409): Look into sending a WINDOW_FOCUS_CHANGED event since
                        //  window no longer has focus (default window logic doesn't), and
                        //  whether the node id needs to be cached (default window logic does).
                        return true;
                    }
                }
            }
        }
        return false;
    }

    /**
     * Checks if the window belongs to a proxy display and if so sends
     * WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED for that window and the previously focused window.
     * @param focusedWindowId the focused window id.
     * @return true if an observer is proxy-ed and contains the focused window.
     */
    private boolean setProxyFocusLocked(int focusedWindowId) {
        for (int i = 0; i < mDisplayWindowsObservers.size(); i++) {
            final DisplayWindowsObserver observer = mDisplayWindowsObservers.valueAt(i);
            if (observer != null && observer.mIsProxy
                    && observer.setAccessibilityFocusedWindowLocked(focusedWindowId)) {
                final int previouslyFocusedWindowId =
                        observer.mProxyDisplayAccessibilityFocusedWindow;

                if (previouslyFocusedWindowId == focusedWindowId) {
                    // Don't send a focus event if the window is already focused.
                    return true;
                }

                // Previously focused window -> Clear focus on UI thread and send a focused event
                // for losing focus
                if (previouslyFocusedWindowId != AccessibilityWindowInfo.UNDEFINED_WINDOW_ID) {
                    clearAccessibilityFocusLocked(previouslyFocusedWindowId);
                    mAccessibilityEventSender.sendAccessibilityEventForCurrentUserLocked(
                            AccessibilityEvent.obtainWindowsChangedEvent(
                                    observer.mDisplayId, previouslyFocusedWindowId,
                                    WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED));
                }
                observer.mProxyDisplayAccessibilityFocusedWindow = focusedWindowId;
                // Newly focused window -> send a focused event for it gaining focus
                mAccessibilityEventSender.sendAccessibilityEventForCurrentUserLocked(
                        AccessibilityEvent.obtainWindowsChangedEvent(
                                observer.mDisplayId,
                                observer.mProxyDisplayAccessibilityFocusedWindow,
                                WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED));

                return true;
            }
        }
        return false;
    }

    /**
     * Dumps all {@link AccessibilityWindowInfo}s here.
     */
+18 −0
Original line number Diff line number Diff line
@@ -254,6 +254,24 @@ public class ProxyAccessibilityServiceConnection extends AccessibilityServiceCon
        return mFocusColor;
    }

    @Override
    int resolveAccessibilityWindowIdForFindFocusLocked(int windowId, int focusType) {
        if (windowId == AccessibilityWindowInfo.ANY_WINDOW_ID) {
            final int focusedWindowId = mA11yWindowManager.getFocusedWindowId(focusType,
                    mDisplayId);
            // If the caller is a proxy and the found window doesn't belong to a proxy display
            // (or vice versa), then return null. This doesn't work if there are multiple active
            // proxys, but in the future this code shouldn't be needed if input focus
            // properly split. (so we will deal with the issues if we see them).
            //TODO(254545943): Remove this when there is user and proxy separation of input
            if (!mA11yWindowManager.windowIdBelongsToDisplayType(focusedWindowId, mDisplayTypes)) {
                return AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
            }
            return focusedWindowId;
        }
        return windowId;
    }

    @Override
    public void binderDied() {
    }
+2 −3
Original line number Diff line number Diff line
@@ -78,8 +78,7 @@ public class ProxyManager {
            AccessibilitySecurityPolicy securityPolicy,
            AbstractAccessibilityServiceConnection.SystemSupport systemSupport,
            AccessibilityTrace trace,
            WindowManagerInternal windowManagerInternal,
            AccessibilityWindowManager awm) throws RemoteException {
            WindowManagerInternal windowManagerInternal) throws RemoteException {

        // Set a default AccessibilityServiceInfo that is used before the proxy's info is
        // populated. A proxy has the touch exploration and window capabilities.
@@ -93,7 +92,7 @@ public class ProxyManager {
                new ProxyAccessibilityServiceConnection(context, info.getComponentName(), info,
                        id, mainHandler, mLock, securityPolicy, systemSupport, trace,
                        windowManagerInternal,
                        awm, displayId);
                        mA11yWindowManager, displayId);

        synchronized (mLock) {
            mProxyA11yServiceConnections.put(displayId, connection);
Loading