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

Commit 5fe5f8b8 authored by sallyyuen's avatar sallyyuen Committed by Sally
Browse files

Query the cache for AccessibilityInteractionClient's findFocus

TalkBack is making a lot of findFocus calls which is impacting
performance. We track focus in the cache so reuse it here.

AccessibilityManagerService does not need a cache, so
only initialize a cache when needed

Bug: 187731774
Test: atest CtsAccessibilityServiceTestCases  CtsAccessibilityTestCases CtsUiAutomationTestCases FrameworksServicesTests:com.android.server.accessibility FrameworksCoreTests:com.android.internal.accessibility FrameworksCoreTests:android.view.accessibility
 atest AccessibilityCacheTest, manual
Change-Id: Ie6f693345d31a3f82ffa479428f76a09fa972a83
parent 719079be
Loading
Loading
Loading
Loading
+2 −1
Original line number Original line Diff line number Diff line
@@ -151,7 +151,8 @@ public final class AccessibilityInteractionController {
            if (interrogatingPid == mMyProcessId && interrogatingTid == mMyLooperThreadId
            if (interrogatingPid == mMyProcessId && interrogatingTid == mMyLooperThreadId
                    && mHandler.hasAccessibilityCallback(message)) {
                    && mHandler.hasAccessibilityCallback(message)) {
                AccessibilityInteractionClient.getInstanceForThread(
                AccessibilityInteractionClient.getInstanceForThread(
                        interrogatingTid).setSameThreadMessage(message);
                        interrogatingTid, /* initializeCache= */true)
                        .setSameThreadMessage(message);
            } else {
            } else {
                // For messages without callback of interrogating client, just handle the
                // For messages without callback of interrogating client, just handle the
                // message immediately if this is UI thread.
                // message immediately if this is UI thread.
+88 −0
Original line number Original line Diff line number Diff line
@@ -16,6 +16,9 @@


package android.view.accessibility;
package android.view.accessibility;



import static android.view.accessibility.AccessibilityNodeInfo.FOCUS_ACCESSIBILITY;

import android.os.Build;
import android.os.Build;
import android.util.ArraySet;
import android.util.ArraySet;
import android.util.Log;
import android.util.Log;
@@ -70,6 +73,7 @@ public class AccessibilityCache {
    private long mInputFocus = AccessibilityNodeInfo.UNDEFINED_ITEM_ID;
    private long mInputFocus = AccessibilityNodeInfo.UNDEFINED_ITEM_ID;


    private int mAccessibilityFocusedWindow = AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
    private int mAccessibilityFocusedWindow = AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
    private int mInputFocusWindow = AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;


    private boolean mIsAllWindowsCached;
    private boolean mIsAllWindowsCached;


@@ -190,6 +194,7 @@ public class AccessibilityCache {
                        removeCachedNodeLocked(event.getWindowId(), mInputFocus);
                        removeCachedNodeLocked(event.getWindowId(), mInputFocus);
                    }
                    }
                    mInputFocus = event.getSourceNodeId();
                    mInputFocus = event.getSourceNodeId();
                    mInputFocusWindow = event.getWindowId();
                    nodeToRefresh = removeCachedNodeLocked(event.getWindowId(), mInputFocus);
                    nodeToRefresh = removeCachedNodeLocked(event.getWindowId(), mInputFocus);
                } break;
                } break;


@@ -439,6 +444,7 @@ public class AccessibilityCache {
            }
            }
            if (clone.isFocused()) {
            if (clone.isFocused()) {
                mInputFocus = sourceId;
                mInputFocus = sourceId;
                mInputFocusWindow = windowId;
            }
            }
        }
        }
    }
    }
@@ -462,6 +468,7 @@ public class AccessibilityCache {
            mInputFocus = AccessibilityNodeInfo.UNDEFINED_ITEM_ID;
            mInputFocus = AccessibilityNodeInfo.UNDEFINED_ITEM_ID;


            mAccessibilityFocusedWindow = AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
            mAccessibilityFocusedWindow = AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
            mInputFocusWindow = AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
        }
        }
    }
    }


@@ -485,6 +492,87 @@ public class AccessibilityCache {
        mIsAllWindowsCached = false;
        mIsAllWindowsCached = false;
    }
    }


    /**
     * Gets a cached {@link AccessibilityNodeInfo} with focus according to focus type.
     *
     * Note: {@link android.view.accessibility.AccessibilityWindowInfo#ACTIVE_WINDOW_ID} will return
     * null.
     *
     * @param focusType The focus type.
     * @param windowId A unique window id.
     * @param initialNodeId A unique view id or virtual descendant id from where to start the
     *                      search.
     * @return The cached {@link AccessibilityNodeInfo} if it has a11y focus or null if such not
     * found.
     */
    public AccessibilityNodeInfo getFocus(int focusType, long initialNodeId, int windowId) {
        synchronized (mLock) {
            int currentFocusWindowId;
            long currentFocusId;
            if (focusType == FOCUS_ACCESSIBILITY) {
                currentFocusWindowId = mAccessibilityFocusedWindow;
                currentFocusId = mAccessibilityFocus;
            } else {
                currentFocusWindowId = mInputFocusWindow;
                currentFocusId = mInputFocus;
            }

            if (currentFocusWindowId == AccessibilityWindowInfo.UNDEFINED_WINDOW_ID) {
                return null;
            }

            if (windowId != AccessibilityWindowInfo.ANY_WINDOW_ID
                    && windowId != currentFocusWindowId) {
                return null;
            }

            LongSparseArray<AccessibilityNodeInfo> nodes =
                    mNodeCache.get(currentFocusWindowId);
            if (nodes == null) {
                return null;
            }

            final AccessibilityNodeInfo currentFocusedNode = nodes.get(currentFocusId);
            if (currentFocusedNode == null) {
                return null;
            }

            if (initialNodeId == currentFocusId || (isCachedNodeOrDescendantLocked(
                    currentFocusedNode.getParentNodeId(), initialNodeId, nodes))) {
                if (VERBOSE) {
                    Log.i(LOG_TAG, "getFocus(0x" + Long.toHexString(currentFocusId) + ") = "
                            + currentFocusedNode + " with type: "
                            + (focusType == FOCUS_ACCESSIBILITY
                            ? "FOCUS_ACCESSIBILITY"
                            : "FOCUS_INPUT"));
                }
                // Return a copy since the client calls to AccessibilityNodeInfo#recycle()
                // will wipe the data of the cached info.
                return new AccessibilityNodeInfo(currentFocusedNode);
            }

            if (VERBOSE) {
                Log.i(LOG_TAG, "getFocus is null with type: "
                        + (focusType == FOCUS_ACCESSIBILITY
                        ? "FOCUS_ACCESSIBILITY"
                        : "FOCUS_INPUT"));
            }
            return null;
        }
    }

    private boolean isCachedNodeOrDescendantLocked(long nodeId, long ancestorId,
            LongSparseArray<AccessibilityNodeInfo> nodes) {
        if (ancestorId == nodeId) {
            return true;
        }
        AccessibilityNodeInfo node = nodes.get(nodeId);
        if (node == null) {
            return false;
        }
        return isCachedNodeOrDescendantLocked(node.getParentNodeId(), ancestorId,  nodes);
    }

    /**
    /**
     * Clears nodes for the window with the given id
     * Clears nodes for the window with the given id
     */
     */
+91 −30
Original line number Original line Diff line number Diff line
@@ -22,6 +22,7 @@ import static android.accessibilityservice.AccessibilityTrace.FLAGS_ACCESSIBILIT
import android.accessibilityservice.IAccessibilityServiceConnection;
import android.accessibilityservice.IAccessibilityServiceConnection;
import android.annotation.NonNull;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.compat.annotation.UnsupportedAppUsage;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.Context;
import android.os.Binder;
import android.os.Binder;
@@ -114,8 +115,7 @@ public final class AccessibilityInteractionClient
        from a window, mapping from windowId -> timestamp. */
        from a window, mapping from windowId -> timestamp. */
    private static final SparseLongArray sScrollingWindows = new SparseLongArray();
    private static final SparseLongArray sScrollingWindows = new SparseLongArray();


    private static AccessibilityCache sAccessibilityCache =
    private static AccessibilityCache sAccessibilityCache;
            new AccessibilityCache(new AccessibilityCache.AccessibilityNodeRefresher());


    private final AtomicInteger mInteractionIdCounter = new AtomicInteger();
    private final AtomicInteger mInteractionIdCounter = new AtomicInteger();


@@ -150,7 +150,7 @@ public final class AccessibilityInteractionClient
    @UnsupportedAppUsage()
    @UnsupportedAppUsage()
    public static AccessibilityInteractionClient getInstance() {
    public static AccessibilityInteractionClient getInstance() {
        final long threadId = Thread.currentThread().getId();
        final long threadId = Thread.currentThread().getId();
        return getInstanceForThread(threadId);
        return getInstanceForThread(threadId, true);
    }
    }


    /**
    /**
@@ -161,11 +161,17 @@ public final class AccessibilityInteractionClient
     *
     *
     * @return The client for a given <code>threadId</code>.
     * @return The client for a given <code>threadId</code>.
     */
     */
    public static AccessibilityInteractionClient getInstanceForThread(long threadId) {
    public static AccessibilityInteractionClient getInstanceForThread(long threadId,
            boolean initializeCache) {
        synchronized (sStaticLock) {
        synchronized (sStaticLock) {
            AccessibilityInteractionClient client = sClients.get(threadId);
            AccessibilityInteractionClient client = sClients.get(threadId);
            if (client == null) {
            if (client == null) {
                client = new AccessibilityInteractionClient();
                if (Binder.getCallingUid() == Process.SYSTEM_UID) {
                    // Don't initialize a cache for the system process
                    client = new AccessibilityInteractionClient(false);
                } else {
                    client = new AccessibilityInteractionClient(initializeCache);
                }
                sClients.put(threadId, client);
                sClients.put(threadId, client);
            }
            }
            return client;
            return client;
@@ -176,11 +182,20 @@ public final class AccessibilityInteractionClient
     * @return The client for the current thread.
     * @return The client for the current thread.
     */
     */
    public static AccessibilityInteractionClient getInstance(Context context) {
    public static AccessibilityInteractionClient getInstance(Context context) {
        return getInstance(/* initializeCache= */true, context);
    }

    /**
     * @param initializeCache whether to initialize the cache in a new client instance
     * @return The client for the current thread.
     */
    public static AccessibilityInteractionClient getInstance(boolean initializeCache,
            Context context) {
        final long threadId = Thread.currentThread().getId();
        final long threadId = Thread.currentThread().getId();
        if (context != null) {
        if (context != null) {
            return getInstanceForThread(threadId, context);
            return getInstanceForThread(threadId, initializeCache, context);
        }
        }
        return getInstanceForThread(threadId);
        return getInstanceForThread(threadId, initializeCache);
    }
    }


    /**
    /**
@@ -189,14 +204,19 @@ public final class AccessibilityInteractionClient
     * We do not have a thread local variable since other threads should be able to
     * We do not have a thread local variable since other threads should be able to
     * look up the correct client knowing a thread id. See ViewRootImpl for details.
     * look up the correct client knowing a thread id. See ViewRootImpl for details.
     *
     *
     * @param initializeCache whether to initialize the cache in a new client instance
     * @return The client for a given <code>threadId</code>.
     * @return The client for a given <code>threadId</code>.
     */
     */
    public static AccessibilityInteractionClient getInstanceForThread(
    public static AccessibilityInteractionClient getInstanceForThread(
            long threadId, Context context) {
            long threadId, boolean initializeCache, Context context) {
        synchronized (sStaticLock) {
        synchronized (sStaticLock) {
            AccessibilityInteractionClient client = sClients.get(threadId);
            AccessibilityInteractionClient client = sClients.get(threadId);
            if (client == null) {
            if (client == null) {
                client = new AccessibilityInteractionClient(context);
                if (Binder.getCallingUid() == Process.SYSTEM_UID) {
                    client = new AccessibilityInteractionClient(false, context);
                } else {
                    client = new AccessibilityInteractionClient(initializeCache, context);
                }
                sClients.put(threadId, client);
                sClients.put(threadId, client);
            }
            }
            return client;
            return client;
@@ -249,13 +269,26 @@ public final class AccessibilityInteractionClient


    private AccessibilityInteractionClient() {
    private AccessibilityInteractionClient() {
        /* reducing constructor visibility */
        /* reducing constructor visibility */
        this(true);
    }

    private AccessibilityInteractionClient(boolean initializeCache) {
        initializeCache(initializeCache);
        mAccessibilityManager = null;
        mAccessibilityManager = null;
    }
    }


    private AccessibilityInteractionClient(Context context) {
    private AccessibilityInteractionClient(boolean initializeCache, Context context) {
        initializeCache(initializeCache);
        mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
        mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
    }
    }


    private static void initializeCache(boolean initialize) {
        if (initialize && sAccessibilityCache == null) {
            sAccessibilityCache = new AccessibilityCache(
                    new AccessibilityCache.AccessibilityNodeRefresher());
        }
    }

    /**
    /**
     * Sets the message to be processed if the interacted view hierarchy
     * Sets the message to be processed if the interacted view hierarchy
     * and the interacting client are running in the same thread.
     * and the interacting client are running in the same thread.
@@ -311,7 +344,7 @@ public final class AccessibilityInteractionClient
            IAccessibilityServiceConnection connection = getConnection(connectionId);
            IAccessibilityServiceConnection connection = getConnection(connectionId);
            if (connection != null) {
            if (connection != null) {
                AccessibilityWindowInfo window;
                AccessibilityWindowInfo window;
                if (!bypassCache) {
                if (!bypassCache && sAccessibilityCache != null) {
                    window = sAccessibilityCache.getWindow(accessibilityWindowId);
                    window = sAccessibilityCache.getWindow(accessibilityWindowId);
                    if (window != null) {
                    if (window != null) {
                        if (DEBUG) {
                        if (DEBUG) {
@@ -341,7 +374,7 @@ public final class AccessibilityInteractionClient
                            + bypassCache);
                            + bypassCache);
                }
                }


                if (window != null) {
                if (window != null && sAccessibilityCache != null) {
                    if (!bypassCache) {
                    if (!bypassCache) {
                        sAccessibilityCache.addWindow(window);
                        sAccessibilityCache.addWindow(window);
                    }
                    }
@@ -384,8 +417,9 @@ public final class AccessibilityInteractionClient
        try {
        try {
            IAccessibilityServiceConnection connection = getConnection(connectionId);
            IAccessibilityServiceConnection connection = getConnection(connectionId);
            if (connection != null) {
            if (connection != null) {
                SparseArray<List<AccessibilityWindowInfo>> windows =
                SparseArray<List<AccessibilityWindowInfo>> windows;
                        sAccessibilityCache.getWindowsOnAllDisplays();
                if (sAccessibilityCache != null) {
                    windows = sAccessibilityCache.getWindowsOnAllDisplays();
                    if (windows != null) {
                    if (windows != null) {
                        if (DEBUG) {
                        if (DEBUG) {
                            Log.i(LOG_TAG, "Windows cache hit");
                            Log.i(LOG_TAG, "Windows cache hit");
@@ -399,6 +433,8 @@ public final class AccessibilityInteractionClient
                    if (DEBUG) {
                    if (DEBUG) {
                        Log.i(LOG_TAG, "Windows cache miss");
                        Log.i(LOG_TAG, "Windows cache miss");
                    }
                    }
                }

                final long identityToken = Binder.clearCallingIdentity();
                final long identityToken = Binder.clearCallingIdentity();
                try {
                try {
                    windows = connection.getWindows();
                    windows = connection.getWindows();
@@ -409,7 +445,9 @@ public final class AccessibilityInteractionClient
                    logTraceClient(connection, "getWindows", "connectionId=" + connectionId);
                    logTraceClient(connection, "getWindows", "connectionId=" + connectionId);
                }
                }
                if (windows != null) {
                if (windows != null) {
                    if (sAccessibilityCache != null) {
                        sAccessibilityCache.setWindowsOnAllDisplays(windows);
                        sAccessibilityCache.setWindowsOnAllDisplays(windows);
                    }
                    return windows;
                    return windows;
                }
                }
            } else {
            } else {
@@ -493,7 +531,7 @@ public final class AccessibilityInteractionClient
        try {
        try {
            IAccessibilityServiceConnection connection = getConnection(connectionId);
            IAccessibilityServiceConnection connection = getConnection(connectionId);
            if (connection != null) {
            if (connection != null) {
                if (!bypassCache) {
                if (!bypassCache && sAccessibilityCache != null) {
                    AccessibilityNodeInfo cachedInfo = sAccessibilityCache.getNode(
                    AccessibilityNodeInfo cachedInfo = sAccessibilityCache.getNode(
                            accessibilityWindowId, accessibilityNodeId);
                            accessibilityWindowId, accessibilityNodeId);
                    if (cachedInfo != null) {
                    if (cachedInfo != null) {
@@ -725,7 +763,9 @@ public final class AccessibilityInteractionClient
     * @param connectionId The id of a connection for interacting with the system.
     * @param connectionId The id of a connection for interacting with the system.
     * @param accessibilityWindowId A unique window id. Use
     * @param accessibilityWindowId A unique window id. Use
     *     {@link android.view.accessibility.AccessibilityWindowInfo#ACTIVE_WINDOW_ID}
     *     {@link android.view.accessibility.AccessibilityWindowInfo#ACTIVE_WINDOW_ID}
     *     to query the currently active window.
     *     to query the currently active window. Use
     *     {@link android.view.accessibility.AccessibilityWindowInfo#ANY_WINDOW_ID} to query all
     *     windows
     * @param accessibilityNodeId A unique view id or virtual descendant id from
     * @param accessibilityNodeId A unique view id or virtual descendant id from
     *     where to start the search. Use
     *     where to start the search. Use
     *     {@link android.view.accessibility.AccessibilityNodeInfo#ROOT_NODE_ID}
     *     {@link android.view.accessibility.AccessibilityNodeInfo#ROOT_NODE_ID}
@@ -733,11 +773,28 @@ public final class AccessibilityInteractionClient
     * @param focusType The focus type.
     * @param focusType The focus type.
     * @return The accessibility focused {@link AccessibilityNodeInfo}.
     * @return The accessibility focused {@link AccessibilityNodeInfo}.
     */
     */
    @SuppressLint("LongLogTag")
    public AccessibilityNodeInfo findFocus(int connectionId, int accessibilityWindowId,
    public AccessibilityNodeInfo findFocus(int connectionId, int accessibilityWindowId,
            long accessibilityNodeId, int focusType) {
            long accessibilityNodeId, int focusType) {
        try {
        try {
            IAccessibilityServiceConnection connection = getConnection(connectionId);
            IAccessibilityServiceConnection connection = getConnection(connectionId);
            if (connection != null) {
            if (connection != null) {
                if (sAccessibilityCache != null) {
                    AccessibilityNodeInfo cachedInfo = sAccessibilityCache.getFocus(focusType,
                            accessibilityNodeId, accessibilityWindowId);
                    if (cachedInfo != null) {
                        if (DEBUG) {
                            Log.i(LOG_TAG, "Focused node cache hit retrieved"
                                    + idToString(cachedInfo.getWindowId(),
                                    cachedInfo.getSourceNodeId()));
                        }
                        return cachedInfo;
                    }
                    if (DEBUG) {
                        Log.i(LOG_TAG, "Focused node cache miss with "
                                + idToString(accessibilityWindowId, accessibilityNodeId));
                    }
                }
                final int interactionId = mInteractionIdCounter.getAndIncrement();
                final int interactionId = mInteractionIdCounter.getAndIncrement();
                if (shouldTraceClient()) {
                if (shouldTraceClient()) {
                    logTraceClient(connection, "findFocus",
                    logTraceClient(connection, "findFocus",
@@ -901,8 +958,10 @@ public final class AccessibilityInteractionClient
     */
     */
    @UnsupportedAppUsage()
    @UnsupportedAppUsage()
    public void clearCache() {
    public void clearCache() {
        if (sAccessibilityCache != null) {
            sAccessibilityCache.clear();
            sAccessibilityCache.clear();
        }
        }
    }


    public void onAccessibilityEvent(AccessibilityEvent event) {
    public void onAccessibilityEvent(AccessibilityEvent event) {
        switch (event.getEventType()) {
        switch (event.getEventType()) {
@@ -917,8 +976,10 @@ public final class AccessibilityInteractionClient
            default:
            default:
                break;
                break;
        }
        }
        if (sAccessibilityCache != null) {
            sAccessibilityCache.onAccessibilityEvent(event);
            sAccessibilityCache.onAccessibilityEvent(event);
        }
        }
    }


    /**
    /**
     * Gets the the result of an async request that returns an {@link AccessibilityNodeInfo}.
     * Gets the the result of an async request that returns an {@link AccessibilityNodeInfo}.
@@ -1153,7 +1214,7 @@ public final class AccessibilityInteractionClient
                }
                }
            }
            }
            info.setSealed(true);
            info.setSealed(true);
            if (!bypassCache) {
            if (!bypassCache && sAccessibilityCache != null) {
                sAccessibilityCache.add(info);
                sAccessibilityCache.add(info);
            }
            }
        }
        }
+177 −0
Original line number Original line Diff line number Diff line
@@ -16,6 +16,12 @@


package android.view.accessibility;
package android.view.accessibility;


import static android.view.accessibility.AccessibilityNodeInfo.FOCUS_ACCESSIBILITY;
import static android.view.accessibility.AccessibilityNodeInfo.FOCUS_INPUT;
import static android.view.accessibility.AccessibilityNodeInfo.ROOT_ITEM_ID;
import static android.view.accessibility.AccessibilityNodeInfo.ROOT_NODE_ID;
import static android.view.accessibility.AccessibilityWindowInfo.ANY_WINDOW_ID;

import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertNull;
import static junit.framework.Assert.assertNull;
@@ -596,6 +602,97 @@ public class AccessibilityCacheTest {
        }
        }
    }
    }


    @Test
    public void getFocus_focusedNodeAsInitialNode_returnsNodeWithA11yFocus() {
        AccessibilityNodeInfo nodeInfo = addFocusedNode(FOCUS_ACCESSIBILITY);
        assertFocus(nodeInfo, FOCUS_ACCESSIBILITY, nodeInfo.getSourceNodeId(),
                nodeInfo.getWindowId());
    }

    @Test
    public void getFocus_focusedNodeAsInitialNode_returnsNodeWithInputFocus() {
        AccessibilityNodeInfo nodeInfo = addFocusedNode(FOCUS_INPUT);
        assertFocus(nodeInfo, FOCUS_INPUT, nodeInfo.getSourceNodeId(), nodeInfo.getWindowId());
    }


    @Test
    public void getFocus_fromAnyWindow_returnsNodeWithA11yFocus() {
        AccessibilityNodeInfo parentNodeInfo =
                getNodeWithA11yAndWindowId(ROOT_ITEM_ID, WINDOW_ID_1);
        AccessibilityNodeInfo nodeInfo = getNodeWithA11yAndWindowId(SINGLE_VIEW_ID, WINDOW_ID_1);
        nodeInfo.setParent(getMockViewWithA11yAndWindowIds(ROOT_ITEM_ID, WINDOW_ID_1));
        setFocus(nodeInfo, FOCUS_ACCESSIBILITY);
        mAccessibilityCache.add(parentNodeInfo);
        mAccessibilityCache.add(nodeInfo);

        AccessibilityNodeInfo focusedNodeInfo =
                mAccessibilityCache.getFocus(FOCUS_ACCESSIBILITY, ROOT_NODE_ID, ANY_WINDOW_ID);
        try {
            assertEquals(focusedNodeInfo, nodeInfo);
        } finally {
            nodeInfo.recycle();
            parentNodeInfo.recycle();
        }
    }

    @Test
    public void getFocus_fromAnyWindow_returnsNodeWithInputFocus() {
        AccessibilityNodeInfo parentNodeInfo =
                getNodeWithA11yAndWindowId(ROOT_ITEM_ID, WINDOW_ID_1);
        AccessibilityNodeInfo nodeInfo = getNodeWithA11yAndWindowId(SINGLE_VIEW_ID, WINDOW_ID_1);
        nodeInfo.setParent(getMockViewWithA11yAndWindowIds(ROOT_ITEM_ID, WINDOW_ID_1));
        setFocus(nodeInfo, FOCUS_INPUT);
        mAccessibilityCache.add(parentNodeInfo);
        mAccessibilityCache.add(nodeInfo);

        AccessibilityNodeInfo focusedNodeInfo =
                mAccessibilityCache.getFocus(FOCUS_INPUT, ROOT_NODE_ID, ANY_WINDOW_ID);
        try {
            assertEquals(focusedNodeInfo, nodeInfo);
        } finally {
            nodeInfo.recycle();
            parentNodeInfo.recycle();
        }
    }


    @Test
    public void getFocus_parentNodeAsInitialNode_returnsNodeWithA11yFocus() {
        findFocusReturnsFocus_initialNodeIsParent(FOCUS_ACCESSIBILITY);
    }

    @Test
    public void getFocus_parentNodeAsInitialNode_returnsNodeWithInputFocus() {
        findFocusReturnsFocus_initialNodeIsParent(FOCUS_INPUT);

    }

    @Test
    public void getFocus_nonFocusedChildNodeAsInitialNode_returnsNullA11yFocus() {
        findFocusReturnNull_initialNodeIsNotFocusOrFocusAncestor(FOCUS_ACCESSIBILITY);
    }

    @Test
    public void getFocus_nonFocusedChildNodeAsInitialNode_returnsNullInputFocus() {
        findFocusReturnNull_initialNodeIsNotFocusOrFocusAncestor(FOCUS_INPUT);
    }

    @Test
    public void getFocus_cacheCleared_returnsNullA11yFocus() {
        AccessibilityNodeInfo nodeInfo = addFocusedNode(FOCUS_ACCESSIBILITY);
        mAccessibilityCache.clear();
        assertFocus(null, FOCUS_ACCESSIBILITY, nodeInfo.getSourceNodeId(),
                nodeInfo.getWindowId());
    }

    @Test
    public void getFocus_cacheCleared_returnsNullInputFocus() {
        AccessibilityNodeInfo nodeInfo = addFocusedNode(FOCUS_INPUT);
        mAccessibilityCache.clear();
        assertFocus(null, FOCUS_INPUT, nodeInfo.getSourceNodeId(), nodeInfo.getWindowId());
    }

    @Test
    @Test
    public void nodeSourceOfInputFocusEvent_getsRefreshed() {
    public void nodeSourceOfInputFocusEvent_getsRefreshed() {
        AccessibilityNodeInfo nodeInfo = getNodeWithA11yAndWindowId(SINGLE_VIEW_ID, WINDOW_ID_1);
        AccessibilityNodeInfo nodeInfo = getNodeWithA11yAndWindowId(SINGLE_VIEW_ID, WINDOW_ID_1);
@@ -786,6 +883,86 @@ public class AccessibilityCacheTest {
        }
        }
    }
    }


    private AccessibilityNodeInfo addFocusedNode(int focusType) {
        AccessibilityNodeInfo nodeInfo = getNodeWithA11yAndWindowId(SINGLE_VIEW_ID, WINDOW_ID_1);
        setFocus(nodeInfo, focusType);
        mAccessibilityCache.add(nodeInfo);
        return nodeInfo;
    }

    private void assertFocus(AccessibilityNodeInfo focus, int focusType, long nodeId,
            int windowId) {
        AccessibilityNodeInfo focusedNodeInfo = mAccessibilityCache.getFocus(
                focusType, nodeId, windowId);
        try {
            assertEquals(focusedNodeInfo, focus);
        } finally {
            if (focus != null) {
                focus.recycle();
            }
            if (focusedNodeInfo != null) {
                focusedNodeInfo.recycle();
            }
        }
    }


    private void findFocusReturnNull_initialNodeIsNotFocusOrFocusAncestor(int focusType) {
        AccessibilityNodeInfo parentNodeInfo =
                getNodeWithA11yAndWindowId(PARENT_VIEW_ID, WINDOW_ID_1);
        AccessibilityNodeInfo childNodeInfo =
                getNodeWithA11yAndWindowId(CHILD_VIEW_ID, WINDOW_ID_1);
        AccessibilityNodeInfo otherChildNodeInfo =
                getNodeWithA11yAndWindowId(OTHER_CHILD_VIEW_ID, WINDOW_ID_1);
        childNodeInfo.setParent(getMockViewWithA11yAndWindowIds(PARENT_VIEW_ID, WINDOW_ID_1));
        otherChildNodeInfo.setParent(getMockViewWithA11yAndWindowIds(PARENT_VIEW_ID, WINDOW_ID_1));
        setFocus(childNodeInfo, focusType);
        mAccessibilityCache.add(parentNodeInfo);
        mAccessibilityCache.add(childNodeInfo);
        mAccessibilityCache.add(otherChildNodeInfo);

        AccessibilityNodeInfo focusedNodeInfo = mAccessibilityCache.getFocus(
                focusType, otherChildNodeInfo.getSourceNodeId(), WINDOW_ID_1);

        try {
            assertNull(focusedNodeInfo);

        } finally {
            parentNodeInfo.recycle();
            childNodeInfo.recycle();
        }
    }

    private void findFocusReturnsFocus_initialNodeIsParent(int focusType) {
        AccessibilityNodeInfo parentNodeInfo =
                getNodeWithA11yAndWindowId(PARENT_VIEW_ID, WINDOW_ID_1);
        AccessibilityNodeInfo childNodeInfo =
                getNodeWithA11yAndWindowId(CHILD_VIEW_ID, WINDOW_ID_1);
        childNodeInfo.setParent(getMockViewWithA11yAndWindowIds(PARENT_VIEW_ID, WINDOW_ID_1));
        setFocus(childNodeInfo, focusType);
        mAccessibilityCache.add(parentNodeInfo);
        mAccessibilityCache.add(childNodeInfo);

        AccessibilityNodeInfo focusedNodeInfo = mAccessibilityCache.getFocus(
                focusType, parentNodeInfo.getSourceNodeId(), WINDOW_ID_1);

        try {
            assertEquals(focusedNodeInfo, childNodeInfo);

        } finally {
            parentNodeInfo.recycle();
            childNodeInfo.recycle();
        }
    }

    private void setFocus(AccessibilityNodeInfo info, int focusType) {
        if (focusType == FOCUS_ACCESSIBILITY) {
            info.setAccessibilityFocused(true);
        } else {
            info.setFocused(true);
        }
    }

    private AccessibilityWindowInfo obtainAccessibilityWindowInfo(int windowId, int layer) {
    private AccessibilityWindowInfo obtainAccessibilityWindowInfo(int windowId, int layer) {
        AccessibilityWindowInfo windowInfo = AccessibilityWindowInfo.obtain();
        AccessibilityWindowInfo windowInfo = AccessibilityWindowInfo.obtain();
        windowInfo.setId(windowId);
        windowInfo.setId(windowId);
+2 −1
Original line number Original line Diff line number Diff line
@@ -3432,7 +3432,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub


            mConnectionId = service.mId;
            mConnectionId = service.mId;


            mClient = AccessibilityInteractionClient.getInstance(mContext);
            mClient = AccessibilityInteractionClient.getInstance(/* initializeCache= */false,
                    mContext);
            mClient.addConnection(mConnectionId, service);
            mClient.addConnection(mConnectionId, service);


            //TODO: (multi-display) We need to support multiple displays.
            //TODO: (multi-display) We need to support multiple displays.