Loading core/java/android/view/AccessibilityInteractionController.java +2 −1 Original line number Original line Diff line number Diff line Loading @@ -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. Loading core/java/android/view/accessibility/AccessibilityCache.java +88 −0 Original line number Original line Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -439,6 +444,7 @@ public class AccessibilityCache { } } if (clone.isFocused()) { if (clone.isFocused()) { mInputFocus = sourceId; mInputFocus = sourceId; mInputFocusWindow = windowId; } } } } } } Loading @@ -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; } } } } Loading @@ -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 */ */ Loading core/java/android/view/accessibility/AccessibilityInteractionClient.java +91 −30 Original line number Original line Diff line number Diff line Loading @@ -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; Loading Loading @@ -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(); Loading Loading @@ -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); } } /** /** Loading @@ -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; Loading @@ -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); } } /** /** Loading @@ -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; Loading Loading @@ -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. Loading Loading @@ -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) { Loading Loading @@ -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); } } Loading Loading @@ -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"); Loading @@ -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(); Loading @@ -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 { Loading Loading @@ -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) { Loading Loading @@ -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} Loading @@ -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", Loading Loading @@ -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()) { Loading @@ -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}. Loading Loading @@ -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); } } } } Loading core/tests/coretests/src/android/view/accessibility/AccessibilityCacheTest.java +177 −0 Original line number Original line Diff line number Diff line Loading @@ -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; Loading Loading @@ -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); Loading Loading @@ -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); Loading services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +2 −1 Original line number Original line Diff line number Diff line Loading @@ -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. Loading Loading
core/java/android/view/AccessibilityInteractionController.java +2 −1 Original line number Original line Diff line number Diff line Loading @@ -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. Loading
core/java/android/view/accessibility/AccessibilityCache.java +88 −0 Original line number Original line Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -439,6 +444,7 @@ public class AccessibilityCache { } } if (clone.isFocused()) { if (clone.isFocused()) { mInputFocus = sourceId; mInputFocus = sourceId; mInputFocusWindow = windowId; } } } } } } Loading @@ -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; } } } } Loading @@ -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 */ */ Loading
core/java/android/view/accessibility/AccessibilityInteractionClient.java +91 −30 Original line number Original line Diff line number Diff line Loading @@ -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; Loading Loading @@ -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(); Loading Loading @@ -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); } } /** /** Loading @@ -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; Loading @@ -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); } } /** /** Loading @@ -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; Loading Loading @@ -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. Loading Loading @@ -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) { Loading Loading @@ -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); } } Loading Loading @@ -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"); Loading @@ -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(); Loading @@ -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 { Loading Loading @@ -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) { Loading Loading @@ -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} Loading @@ -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", Loading Loading @@ -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()) { Loading @@ -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}. Loading Loading @@ -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); } } } } Loading
core/tests/coretests/src/android/view/accessibility/AccessibilityCacheTest.java +177 −0 Original line number Original line Diff line number Diff line Loading @@ -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; Loading Loading @@ -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); Loading Loading @@ -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); Loading
services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +2 −1 Original line number Original line Diff line number Diff line Loading @@ -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. Loading