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

Commit 24a1ac51 authored by Jackal Guo's avatar Jackal Guo
Browse files

Don't clear cache when changing accessibility focus

Instead of clearing all cache, refreshing the accessibility windows
and the nodes.

Bug: 145562808
Test: a11y CTS & unit tests
Change-Id: I3048bffeb970712e43b82843acd452c147ca5054
parent 993b3ed5
Loading
Loading
Loading
Loading
+54 −4
Original line number Diff line number Diff line
@@ -69,6 +69,8 @@ public class AccessibilityCache {
    private long mAccessibilityFocus = AccessibilityNodeInfo.UNDEFINED_ITEM_ID;
    private long mInputFocus = AccessibilityNodeInfo.UNDEFINED_ITEM_ID;

    private int mAccessibilityFocusedWindow = AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;

    private boolean mIsAllWindowsCached;

    // The SparseArray of all {@link AccessibilityWindowInfo}s on all displays.
@@ -164,16 +166,19 @@ public class AccessibilityCache {
            switch (eventType) {
                case AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED: {
                    if (mAccessibilityFocus != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) {
                        refreshCachedNodeLocked(event.getWindowId(), mAccessibilityFocus);
                        refreshCachedNodeLocked(mAccessibilityFocusedWindow, mAccessibilityFocus);
                    }
                    mAccessibilityFocus = event.getSourceNodeId();
                    refreshCachedNodeLocked(event.getWindowId(), mAccessibilityFocus);
                    mAccessibilityFocusedWindow = event.getWindowId();
                    refreshCachedNodeLocked(mAccessibilityFocusedWindow, mAccessibilityFocus);
                } break;

                case AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED: {
                    if (mAccessibilityFocus == event.getSourceNodeId()) {
                        refreshCachedNodeLocked(event.getWindowId(), mAccessibilityFocus);
                    if (mAccessibilityFocus == event.getSourceNodeId()
                            && mAccessibilityFocusedWindow == event.getWindowId()) {
                        refreshCachedNodeLocked(mAccessibilityFocusedWindow, mAccessibilityFocus);
                        mAccessibilityFocus = AccessibilityNodeInfo.UNDEFINED_ITEM_ID;
                        mAccessibilityFocusedWindow = AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
                    }
                } break;

@@ -210,6 +215,13 @@ public class AccessibilityCache {
                } break;

                case AccessibilityEvent.TYPE_WINDOWS_CHANGED:
                    if (event.getWindowChanges()
                            == AccessibilityEvent.WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED) {
                        // Don't need to clear all cache. Unless the changes are related to
                        // content, we won't clear all cache here.
                        refreshCachedWindowLocked(event.getWindowId());
                        break;
                    }
                case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED: {
                    clear();
                } break;
@@ -243,6 +255,34 @@ public class AccessibilityCache {
        clearSubTreeLocked(windowId, sourceId);
    }

    private void refreshCachedWindowLocked(int windowId) {
        if (DEBUG) {
            Log.i(LOG_TAG, "Refreshing cached window.");
        }

        if (windowId == AccessibilityWindowInfo.UNDEFINED_WINDOW_ID) {
            return;
        }

        final int displayCounts = mWindowCacheByDisplay.size();
        for (int i = 0; i < displayCounts; i++) {
            final SparseArray<AccessibilityWindowInfo> windowsOfDisplay =
                    mWindowCacheByDisplay.valueAt(i);
            if (windowsOfDisplay == null) {
                continue;
            }
            final AccessibilityWindowInfo window = windowsOfDisplay.get(windowId);
            if (window == null) {
                continue;
            }
            if (!mAccessibilityNodeRefresher.refreshWindow(window)) {
                // If we fail to refresh the window, clear all windows.
                clearWindowCacheLocked();
            }
            return;
        }
    }

    /**
     * Gets a cached {@link AccessibilityNodeInfo} given the id of the hosting
     * window and the accessibility id of the node.
@@ -413,8 +453,10 @@ public class AccessibilityCache {
                    refreshCachedNodeLocked(windowId, mAccessibilityFocus);
                }
                mAccessibilityFocus = sourceId;
                mAccessibilityFocusedWindow = windowId;
            } else if (mAccessibilityFocus == sourceId) {
                mAccessibilityFocus = AccessibilityNodeInfo.UNDEFINED_ITEM_ID;
                mAccessibilityFocusedWindow = AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
            }
            if (clone.isFocused()) {
                mInputFocus = sourceId;
@@ -439,6 +481,8 @@ public class AccessibilityCache {

            mAccessibilityFocus = AccessibilityNodeInfo.UNDEFINED_ITEM_ID;
            mInputFocus = AccessibilityNodeInfo.UNDEFINED_ITEM_ID;

            mAccessibilityFocusedWindow = AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
        }
    }

@@ -653,8 +697,14 @@ public class AccessibilityCache {

    // Layer of indirection included to break dependency chain for testing
    public static class AccessibilityNodeRefresher {
        /** Refresh the given AccessibilityNodeInfo object. */
        public boolean refreshNode(AccessibilityNodeInfo info, boolean bypassCache) {
            return info.refresh(null, bypassCache);
        }

        /** Refresh the given AccessibilityWindowInfo object. */
        public boolean refreshWindow(AccessibilityWindowInfo info) {
            return info.refresh();
        }
    }
}
+28 −9
Original line number Diff line number Diff line
@@ -223,11 +223,27 @@ public final class AccessibilityInteractionClient
     * @return The {@link AccessibilityWindowInfo}.
     */
    public AccessibilityWindowInfo getWindow(int connectionId, int accessibilityWindowId) {
        return getWindow(connectionId, accessibilityWindowId, /* bypassCache */ false);
    }

    /**
     * Gets the info for a window.
     *
     * @param connectionId The id of a connection for interacting with the system.
     * @param accessibilityWindowId A unique window id. Use
     *     {@link android.view.accessibility.AccessibilityWindowInfo#ACTIVE_WINDOW_ID}
     *     to query the currently active window.
     * @param bypassCache Whether to bypass the cache.
     * @return The {@link AccessibilityWindowInfo}.
     */
    public AccessibilityWindowInfo getWindow(int connectionId, int accessibilityWindowId,
            boolean bypassCache) {
        try {
            IAccessibilityServiceConnection connection = getConnection(connectionId);
            if (connection != null) {
                AccessibilityWindowInfo window = sAccessibilityCache.getWindow(
                        accessibilityWindowId);
                AccessibilityWindowInfo window;
                if (!bypassCache) {
                    window = sAccessibilityCache.getWindow(accessibilityWindowId);
                    if (window != null) {
                        if (DEBUG) {
                            Log.i(LOG_TAG, "Window cache hit");
@@ -237,6 +253,7 @@ public final class AccessibilityInteractionClient
                    if (DEBUG) {
                        Log.i(LOG_TAG, "Window cache miss");
                    }
                }
                final long identityToken = Binder.clearCallingIdentity();
                try {
                    window = connection.getWindow(accessibilityWindowId);
@@ -244,7 +261,9 @@ public final class AccessibilityInteractionClient
                    Binder.restoreCallingIdentity(identityToken);
                }
                if (window != null) {
                    if (!bypassCache) {
                        sAccessibilityCache.addWindow(window);
                    }
                    return window;
                }
            } else {
+28 −1
Original line number Diff line number Diff line
@@ -87,6 +87,8 @@ public final class AccessibilityWindowInfo implements Parcelable {
    /** @hide */
    public static final int ACTIVE_WINDOW_ID = Integer.MAX_VALUE;
    /** @hide */
    public static final int UNDEFINED_CONNECTION_ID = -1;
    /** @hide */
    public static final int UNDEFINED_WINDOW_ID = -1;
    /** @hide */
    public static final int ANY_WINDOW_ID = -2;
@@ -117,7 +119,7 @@ public final class AccessibilityWindowInfo implements Parcelable {
    private CharSequence mTitle;
    private long mAnchorId = AccessibilityNodeInfo.UNDEFINED_NODE_ID;

    private int mConnectionId = UNDEFINED_WINDOW_ID;
    private int mConnectionId = UNDEFINED_CONNECTION_ID;

    /**
     * Creates a new {@link AccessibilityWindowInfo}.
@@ -539,6 +541,30 @@ public final class AccessibilityWindowInfo implements Parcelable {
        }
    }

    /**
     * Refreshes this window with the latest state of the window it represents.
     * <p>
     * <strong>Note:</strong> If this method returns false this info is obsolete
     * since it represents a window that is no longer exist.
     * </p>
     *
     * @hide
     */
    public boolean refresh() {
        if (mConnectionId == UNDEFINED_CONNECTION_ID || mId == UNDEFINED_WINDOW_ID) {
            return false;
        }
        final AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
        final AccessibilityWindowInfo refreshedInfo = client.getWindow(mConnectionId,
                mId, /* bypassCache */true);
        if (refreshedInfo == null) {
            return false;
        }
        init(refreshedInfo);
        refreshedInfo.recycle();
        return true;
    }

    @Override
    public int describeContents() {
        return 0;
@@ -586,6 +612,7 @@ public final class AccessibilityWindowInfo implements Parcelable {
        mTitle = other.mTitle;
        mAnchorId = other.mAnchorId;

        if (mChildIds != null) mChildIds.clear();
        if (other.mChildIds != null && other.mChildIds.size() > 0) {
            if (mChildIds == null) {
                mChildIds = other.mChildIds.clone();
+16 −0
Original line number Diff line number Diff line
@@ -435,6 +435,22 @@ public class AccessibilityCacheTest {
        assertEventTypeClearsNode(AccessibilityEvent.TYPE_WINDOWS_CHANGED);
    }

    @Test
    public void windowsChangedWithWindowsChangeA11yFocusedEvent_dontClearCache() {
        AccessibilityNodeInfo nodeInfo = getNodeWithA11yAndWindowId(SINGLE_VIEW_ID, WINDOW_ID_1);
        mAccessibilityCache.add(nodeInfo);
        AccessibilityEvent event = new AccessibilityEvent(AccessibilityEvent.TYPE_WINDOWS_CHANGED);
        event.setWindowChanges(AccessibilityEvent.WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED);
        mAccessibilityCache.onAccessibilityEvent(event);
        AccessibilityNodeInfo cachedNode = mAccessibilityCache.getNode(WINDOW_ID_1,
                nodeInfo.getSourceNodeId());
        try {
            assertNotNull(cachedNode);
        } finally {
            nodeInfo.recycle();
        }
    }

    @Test
    public void subTreeChangeEvent_clearsNodeAndChild() {
        AccessibilityEvent event = AccessibilityEvent