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

Commit 9dffffc9 authored by Sally Yuen's avatar Sally Yuen Committed by Android (Google) Code Review
Browse files

Merge "Query the cache for AccessibilityInteractionClient's findFocus"

parents 11df2ecd 5fe5f8b8
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.