Loading core/java/android/view/accessibility/AccessibilityCache.java +54 −4 Original line number Diff line number Diff line Loading @@ -69,6 +69,8 @@ public class AccessibilityCache { private long mAccessibilityFocus = AccessibilityNodeInfo.UNDEFINED_ITEM_ID; private long mInputFocus = AccessibilityNodeInfo.UNDEFINED_ITEM_ID; private int mAccessibilityFocusedWindow = AccessibilityWindowInfo.UNDEFINED_WINDOW_ID; private boolean mIsAllWindowsCached; // The SparseArray of all {@link AccessibilityWindowInfo}s on all displays. Loading Loading @@ -164,16 +166,19 @@ public class AccessibilityCache { switch (eventType) { case AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED: { if (mAccessibilityFocus != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) { refreshCachedNodeLocked(event.getWindowId(), mAccessibilityFocus); refreshCachedNodeLocked(mAccessibilityFocusedWindow, mAccessibilityFocus); } mAccessibilityFocus = event.getSourceNodeId(); refreshCachedNodeLocked(event.getWindowId(), mAccessibilityFocus); mAccessibilityFocusedWindow = event.getWindowId(); refreshCachedNodeLocked(mAccessibilityFocusedWindow, mAccessibilityFocus); } break; case AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED: { if (mAccessibilityFocus == event.getSourceNodeId()) { refreshCachedNodeLocked(event.getWindowId(), mAccessibilityFocus); if (mAccessibilityFocus == event.getSourceNodeId() && mAccessibilityFocusedWindow == event.getWindowId()) { refreshCachedNodeLocked(mAccessibilityFocusedWindow, mAccessibilityFocus); mAccessibilityFocus = AccessibilityNodeInfo.UNDEFINED_ITEM_ID; mAccessibilityFocusedWindow = AccessibilityWindowInfo.UNDEFINED_WINDOW_ID; } } break; Loading Loading @@ -210,6 +215,13 @@ public class AccessibilityCache { } break; case AccessibilityEvent.TYPE_WINDOWS_CHANGED: if (event.getWindowChanges() == AccessibilityEvent.WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED) { // Don't need to clear all cache. Unless the changes are related to // content, we won't clear all cache here. refreshCachedWindowLocked(event.getWindowId()); break; } case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED: { clear(); } break; Loading Loading @@ -243,6 +255,34 @@ public class AccessibilityCache { clearSubTreeLocked(windowId, sourceId); } private void refreshCachedWindowLocked(int windowId) { if (DEBUG) { Log.i(LOG_TAG, "Refreshing cached window."); } if (windowId == AccessibilityWindowInfo.UNDEFINED_WINDOW_ID) { return; } final int displayCounts = mWindowCacheByDisplay.size(); for (int i = 0; i < displayCounts; i++) { final SparseArray<AccessibilityWindowInfo> windowsOfDisplay = mWindowCacheByDisplay.valueAt(i); if (windowsOfDisplay == null) { continue; } final AccessibilityWindowInfo window = windowsOfDisplay.get(windowId); if (window == null) { continue; } if (!mAccessibilityNodeRefresher.refreshWindow(window)) { // If we fail to refresh the window, clear all windows. clearWindowCacheLocked(); } return; } } /** * Gets a cached {@link AccessibilityNodeInfo} given the id of the hosting * window and the accessibility id of the node. Loading Loading @@ -413,8 +453,10 @@ public class AccessibilityCache { refreshCachedNodeLocked(windowId, mAccessibilityFocus); } mAccessibilityFocus = sourceId; mAccessibilityFocusedWindow = windowId; } else if (mAccessibilityFocus == sourceId) { mAccessibilityFocus = AccessibilityNodeInfo.UNDEFINED_ITEM_ID; mAccessibilityFocusedWindow = AccessibilityWindowInfo.UNDEFINED_WINDOW_ID; } if (clone.isFocused()) { mInputFocus = sourceId; Loading @@ -439,6 +481,8 @@ public class AccessibilityCache { mAccessibilityFocus = AccessibilityNodeInfo.UNDEFINED_ITEM_ID; mInputFocus = AccessibilityNodeInfo.UNDEFINED_ITEM_ID; mAccessibilityFocusedWindow = AccessibilityWindowInfo.UNDEFINED_WINDOW_ID; } } Loading Loading @@ -653,8 +697,14 @@ public class AccessibilityCache { // Layer of indirection included to break dependency chain for testing public static class AccessibilityNodeRefresher { /** Refresh the given AccessibilityNodeInfo object. */ public boolean refreshNode(AccessibilityNodeInfo info, boolean bypassCache) { return info.refresh(null, bypassCache); } /** Refresh the given AccessibilityWindowInfo object. */ public boolean refreshWindow(AccessibilityWindowInfo info) { return info.refresh(); } } } core/java/android/view/accessibility/AccessibilityInteractionClient.java +28 −9 Original line number Diff line number Diff line Loading @@ -223,11 +223,27 @@ public final class AccessibilityInteractionClient * @return The {@link AccessibilityWindowInfo}. */ public AccessibilityWindowInfo getWindow(int connectionId, int accessibilityWindowId) { return getWindow(connectionId, accessibilityWindowId, /* bypassCache */ false); } /** * Gets the info for a window. * * @param connectionId The id of a connection for interacting with the system. * @param accessibilityWindowId A unique window id. Use * {@link android.view.accessibility.AccessibilityWindowInfo#ACTIVE_WINDOW_ID} * to query the currently active window. * @param bypassCache Whether to bypass the cache. * @return The {@link AccessibilityWindowInfo}. */ public AccessibilityWindowInfo getWindow(int connectionId, int accessibilityWindowId, boolean bypassCache) { try { IAccessibilityServiceConnection connection = getConnection(connectionId); if (connection != null) { AccessibilityWindowInfo window = sAccessibilityCache.getWindow( accessibilityWindowId); AccessibilityWindowInfo window; if (!bypassCache) { window = sAccessibilityCache.getWindow(accessibilityWindowId); if (window != null) { if (DEBUG) { Log.i(LOG_TAG, "Window cache hit"); Loading @@ -237,6 +253,7 @@ public final class AccessibilityInteractionClient if (DEBUG) { Log.i(LOG_TAG, "Window cache miss"); } } final long identityToken = Binder.clearCallingIdentity(); try { window = connection.getWindow(accessibilityWindowId); Loading @@ -244,7 +261,9 @@ public final class AccessibilityInteractionClient Binder.restoreCallingIdentity(identityToken); } if (window != null) { if (!bypassCache) { sAccessibilityCache.addWindow(window); } return window; } } else { Loading core/java/android/view/accessibility/AccessibilityWindowInfo.java +28 −1 Original line number Diff line number Diff line Loading @@ -87,6 +87,8 @@ public final class AccessibilityWindowInfo implements Parcelable { /** @hide */ public static final int ACTIVE_WINDOW_ID = Integer.MAX_VALUE; /** @hide */ public static final int UNDEFINED_CONNECTION_ID = -1; /** @hide */ public static final int UNDEFINED_WINDOW_ID = -1; /** @hide */ public static final int ANY_WINDOW_ID = -2; Loading Loading @@ -117,7 +119,7 @@ public final class AccessibilityWindowInfo implements Parcelable { private CharSequence mTitle; private long mAnchorId = AccessibilityNodeInfo.UNDEFINED_NODE_ID; private int mConnectionId = UNDEFINED_WINDOW_ID; private int mConnectionId = UNDEFINED_CONNECTION_ID; /** * Creates a new {@link AccessibilityWindowInfo}. Loading Loading @@ -539,6 +541,30 @@ public final class AccessibilityWindowInfo implements Parcelable { } } /** * Refreshes this window with the latest state of the window it represents. * <p> * <strong>Note:</strong> If this method returns false this info is obsolete * since it represents a window that is no longer exist. * </p> * * @hide */ public boolean refresh() { if (mConnectionId == UNDEFINED_CONNECTION_ID || mId == UNDEFINED_WINDOW_ID) { return false; } final AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance(); final AccessibilityWindowInfo refreshedInfo = client.getWindow(mConnectionId, mId, /* bypassCache */true); if (refreshedInfo == null) { return false; } init(refreshedInfo); refreshedInfo.recycle(); return true; } @Override public int describeContents() { return 0; Loading Loading @@ -586,6 +612,7 @@ public final class AccessibilityWindowInfo implements Parcelable { mTitle = other.mTitle; mAnchorId = other.mAnchorId; if (mChildIds != null) mChildIds.clear(); if (other.mChildIds != null && other.mChildIds.size() > 0) { if (mChildIds == null) { mChildIds = other.mChildIds.clone(); Loading core/tests/coretests/src/android/view/accessibility/AccessibilityCacheTest.java +16 −0 Original line number Diff line number Diff line Loading @@ -435,6 +435,22 @@ public class AccessibilityCacheTest { assertEventTypeClearsNode(AccessibilityEvent.TYPE_WINDOWS_CHANGED); } @Test public void windowsChangedWithWindowsChangeA11yFocusedEvent_dontClearCache() { AccessibilityNodeInfo nodeInfo = getNodeWithA11yAndWindowId(SINGLE_VIEW_ID, WINDOW_ID_1); mAccessibilityCache.add(nodeInfo); AccessibilityEvent event = new AccessibilityEvent(AccessibilityEvent.TYPE_WINDOWS_CHANGED); event.setWindowChanges(AccessibilityEvent.WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED); mAccessibilityCache.onAccessibilityEvent(event); AccessibilityNodeInfo cachedNode = mAccessibilityCache.getNode(WINDOW_ID_1, nodeInfo.getSourceNodeId()); try { assertNotNull(cachedNode); } finally { nodeInfo.recycle(); } } @Test public void subTreeChangeEvent_clearsNodeAndChild() { AccessibilityEvent event = AccessibilityEvent Loading Loading
core/java/android/view/accessibility/AccessibilityCache.java +54 −4 Original line number Diff line number Diff line Loading @@ -69,6 +69,8 @@ public class AccessibilityCache { private long mAccessibilityFocus = AccessibilityNodeInfo.UNDEFINED_ITEM_ID; private long mInputFocus = AccessibilityNodeInfo.UNDEFINED_ITEM_ID; private int mAccessibilityFocusedWindow = AccessibilityWindowInfo.UNDEFINED_WINDOW_ID; private boolean mIsAllWindowsCached; // The SparseArray of all {@link AccessibilityWindowInfo}s on all displays. Loading Loading @@ -164,16 +166,19 @@ public class AccessibilityCache { switch (eventType) { case AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED: { if (mAccessibilityFocus != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) { refreshCachedNodeLocked(event.getWindowId(), mAccessibilityFocus); refreshCachedNodeLocked(mAccessibilityFocusedWindow, mAccessibilityFocus); } mAccessibilityFocus = event.getSourceNodeId(); refreshCachedNodeLocked(event.getWindowId(), mAccessibilityFocus); mAccessibilityFocusedWindow = event.getWindowId(); refreshCachedNodeLocked(mAccessibilityFocusedWindow, mAccessibilityFocus); } break; case AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED: { if (mAccessibilityFocus == event.getSourceNodeId()) { refreshCachedNodeLocked(event.getWindowId(), mAccessibilityFocus); if (mAccessibilityFocus == event.getSourceNodeId() && mAccessibilityFocusedWindow == event.getWindowId()) { refreshCachedNodeLocked(mAccessibilityFocusedWindow, mAccessibilityFocus); mAccessibilityFocus = AccessibilityNodeInfo.UNDEFINED_ITEM_ID; mAccessibilityFocusedWindow = AccessibilityWindowInfo.UNDEFINED_WINDOW_ID; } } break; Loading Loading @@ -210,6 +215,13 @@ public class AccessibilityCache { } break; case AccessibilityEvent.TYPE_WINDOWS_CHANGED: if (event.getWindowChanges() == AccessibilityEvent.WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED) { // Don't need to clear all cache. Unless the changes are related to // content, we won't clear all cache here. refreshCachedWindowLocked(event.getWindowId()); break; } case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED: { clear(); } break; Loading Loading @@ -243,6 +255,34 @@ public class AccessibilityCache { clearSubTreeLocked(windowId, sourceId); } private void refreshCachedWindowLocked(int windowId) { if (DEBUG) { Log.i(LOG_TAG, "Refreshing cached window."); } if (windowId == AccessibilityWindowInfo.UNDEFINED_WINDOW_ID) { return; } final int displayCounts = mWindowCacheByDisplay.size(); for (int i = 0; i < displayCounts; i++) { final SparseArray<AccessibilityWindowInfo> windowsOfDisplay = mWindowCacheByDisplay.valueAt(i); if (windowsOfDisplay == null) { continue; } final AccessibilityWindowInfo window = windowsOfDisplay.get(windowId); if (window == null) { continue; } if (!mAccessibilityNodeRefresher.refreshWindow(window)) { // If we fail to refresh the window, clear all windows. clearWindowCacheLocked(); } return; } } /** * Gets a cached {@link AccessibilityNodeInfo} given the id of the hosting * window and the accessibility id of the node. Loading Loading @@ -413,8 +453,10 @@ public class AccessibilityCache { refreshCachedNodeLocked(windowId, mAccessibilityFocus); } mAccessibilityFocus = sourceId; mAccessibilityFocusedWindow = windowId; } else if (mAccessibilityFocus == sourceId) { mAccessibilityFocus = AccessibilityNodeInfo.UNDEFINED_ITEM_ID; mAccessibilityFocusedWindow = AccessibilityWindowInfo.UNDEFINED_WINDOW_ID; } if (clone.isFocused()) { mInputFocus = sourceId; Loading @@ -439,6 +481,8 @@ public class AccessibilityCache { mAccessibilityFocus = AccessibilityNodeInfo.UNDEFINED_ITEM_ID; mInputFocus = AccessibilityNodeInfo.UNDEFINED_ITEM_ID; mAccessibilityFocusedWindow = AccessibilityWindowInfo.UNDEFINED_WINDOW_ID; } } Loading Loading @@ -653,8 +697,14 @@ public class AccessibilityCache { // Layer of indirection included to break dependency chain for testing public static class AccessibilityNodeRefresher { /** Refresh the given AccessibilityNodeInfo object. */ public boolean refreshNode(AccessibilityNodeInfo info, boolean bypassCache) { return info.refresh(null, bypassCache); } /** Refresh the given AccessibilityWindowInfo object. */ public boolean refreshWindow(AccessibilityWindowInfo info) { return info.refresh(); } } }
core/java/android/view/accessibility/AccessibilityInteractionClient.java +28 −9 Original line number Diff line number Diff line Loading @@ -223,11 +223,27 @@ public final class AccessibilityInteractionClient * @return The {@link AccessibilityWindowInfo}. */ public AccessibilityWindowInfo getWindow(int connectionId, int accessibilityWindowId) { return getWindow(connectionId, accessibilityWindowId, /* bypassCache */ false); } /** * Gets the info for a window. * * @param connectionId The id of a connection for interacting with the system. * @param accessibilityWindowId A unique window id. Use * {@link android.view.accessibility.AccessibilityWindowInfo#ACTIVE_WINDOW_ID} * to query the currently active window. * @param bypassCache Whether to bypass the cache. * @return The {@link AccessibilityWindowInfo}. */ public AccessibilityWindowInfo getWindow(int connectionId, int accessibilityWindowId, boolean bypassCache) { try { IAccessibilityServiceConnection connection = getConnection(connectionId); if (connection != null) { AccessibilityWindowInfo window = sAccessibilityCache.getWindow( accessibilityWindowId); AccessibilityWindowInfo window; if (!bypassCache) { window = sAccessibilityCache.getWindow(accessibilityWindowId); if (window != null) { if (DEBUG) { Log.i(LOG_TAG, "Window cache hit"); Loading @@ -237,6 +253,7 @@ public final class AccessibilityInteractionClient if (DEBUG) { Log.i(LOG_TAG, "Window cache miss"); } } final long identityToken = Binder.clearCallingIdentity(); try { window = connection.getWindow(accessibilityWindowId); Loading @@ -244,7 +261,9 @@ public final class AccessibilityInteractionClient Binder.restoreCallingIdentity(identityToken); } if (window != null) { if (!bypassCache) { sAccessibilityCache.addWindow(window); } return window; } } else { Loading
core/java/android/view/accessibility/AccessibilityWindowInfo.java +28 −1 Original line number Diff line number Diff line Loading @@ -87,6 +87,8 @@ public final class AccessibilityWindowInfo implements Parcelable { /** @hide */ public static final int ACTIVE_WINDOW_ID = Integer.MAX_VALUE; /** @hide */ public static final int UNDEFINED_CONNECTION_ID = -1; /** @hide */ public static final int UNDEFINED_WINDOW_ID = -1; /** @hide */ public static final int ANY_WINDOW_ID = -2; Loading Loading @@ -117,7 +119,7 @@ public final class AccessibilityWindowInfo implements Parcelable { private CharSequence mTitle; private long mAnchorId = AccessibilityNodeInfo.UNDEFINED_NODE_ID; private int mConnectionId = UNDEFINED_WINDOW_ID; private int mConnectionId = UNDEFINED_CONNECTION_ID; /** * Creates a new {@link AccessibilityWindowInfo}. Loading Loading @@ -539,6 +541,30 @@ public final class AccessibilityWindowInfo implements Parcelable { } } /** * Refreshes this window with the latest state of the window it represents. * <p> * <strong>Note:</strong> If this method returns false this info is obsolete * since it represents a window that is no longer exist. * </p> * * @hide */ public boolean refresh() { if (mConnectionId == UNDEFINED_CONNECTION_ID || mId == UNDEFINED_WINDOW_ID) { return false; } final AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance(); final AccessibilityWindowInfo refreshedInfo = client.getWindow(mConnectionId, mId, /* bypassCache */true); if (refreshedInfo == null) { return false; } init(refreshedInfo); refreshedInfo.recycle(); return true; } @Override public int describeContents() { return 0; Loading Loading @@ -586,6 +612,7 @@ public final class AccessibilityWindowInfo implements Parcelable { mTitle = other.mTitle; mAnchorId = other.mAnchorId; if (mChildIds != null) mChildIds.clear(); if (other.mChildIds != null && other.mChildIds.size() > 0) { if (mChildIds == null) { mChildIds = other.mChildIds.clone(); Loading
core/tests/coretests/src/android/view/accessibility/AccessibilityCacheTest.java +16 −0 Original line number Diff line number Diff line Loading @@ -435,6 +435,22 @@ public class AccessibilityCacheTest { assertEventTypeClearsNode(AccessibilityEvent.TYPE_WINDOWS_CHANGED); } @Test public void windowsChangedWithWindowsChangeA11yFocusedEvent_dontClearCache() { AccessibilityNodeInfo nodeInfo = getNodeWithA11yAndWindowId(SINGLE_VIEW_ID, WINDOW_ID_1); mAccessibilityCache.add(nodeInfo); AccessibilityEvent event = new AccessibilityEvent(AccessibilityEvent.TYPE_WINDOWS_CHANGED); event.setWindowChanges(AccessibilityEvent.WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED); mAccessibilityCache.onAccessibilityEvent(event); AccessibilityNodeInfo cachedNode = mAccessibilityCache.getNode(WINDOW_ID_1, nodeInfo.getSourceNodeId()); try { assertNotNull(cachedNode); } finally { nodeInfo.recycle(); } } @Test public void subTreeChangeEvent_clearsNodeAndChild() { AccessibilityEvent event = AccessibilityEvent Loading