Don't clear cache when changing accessibility focus
Instead of clearing all cache, refreshing the accessibility windows and the nodes. Bug: 145562808 Test: a11y CTS & unit tests Change-Id: I3048bffeb970712e43b82843acd452c147ca5054
This commit is contained in:
@@ -69,6 +69,8 @@ public class AccessibilityCache {
|
||||
private long mAccessibilityFocus = AccessibilityNodeInfo.UNDEFINED_ITEM_ID;
|
||||
private long mInputFocus = AccessibilityNodeInfo.UNDEFINED_ITEM_ID;
|
||||
|
||||
private int mAccessibilityFocusedWindow = AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
|
||||
|
||||
private boolean mIsAllWindowsCached;
|
||||
|
||||
// The SparseArray of all {@link AccessibilityWindowInfo}s on all displays.
|
||||
@@ -164,16 +166,19 @@ public class AccessibilityCache {
|
||||
switch (eventType) {
|
||||
case AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED: {
|
||||
if (mAccessibilityFocus != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) {
|
||||
refreshCachedNodeLocked(event.getWindowId(), mAccessibilityFocus);
|
||||
refreshCachedNodeLocked(mAccessibilityFocusedWindow, mAccessibilityFocus);
|
||||
}
|
||||
mAccessibilityFocus = event.getSourceNodeId();
|
||||
refreshCachedNodeLocked(event.getWindowId(), mAccessibilityFocus);
|
||||
mAccessibilityFocusedWindow = event.getWindowId();
|
||||
refreshCachedNodeLocked(mAccessibilityFocusedWindow, mAccessibilityFocus);
|
||||
} break;
|
||||
|
||||
case AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED: {
|
||||
if (mAccessibilityFocus == event.getSourceNodeId()) {
|
||||
refreshCachedNodeLocked(event.getWindowId(), mAccessibilityFocus);
|
||||
if (mAccessibilityFocus == event.getSourceNodeId()
|
||||
&& mAccessibilityFocusedWindow == event.getWindowId()) {
|
||||
refreshCachedNodeLocked(mAccessibilityFocusedWindow, mAccessibilityFocus);
|
||||
mAccessibilityFocus = AccessibilityNodeInfo.UNDEFINED_ITEM_ID;
|
||||
mAccessibilityFocusedWindow = AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
|
||||
}
|
||||
} break;
|
||||
|
||||
@@ -210,6 +215,13 @@ public class AccessibilityCache {
|
||||
} break;
|
||||
|
||||
case AccessibilityEvent.TYPE_WINDOWS_CHANGED:
|
||||
if (event.getWindowChanges()
|
||||
== AccessibilityEvent.WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED) {
|
||||
// Don't need to clear all cache. Unless the changes are related to
|
||||
// content, we won't clear all cache here.
|
||||
refreshCachedWindowLocked(event.getWindowId());
|
||||
break;
|
||||
}
|
||||
case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED: {
|
||||
clear();
|
||||
} break;
|
||||
@@ -243,6 +255,34 @@ public class AccessibilityCache {
|
||||
clearSubTreeLocked(windowId, sourceId);
|
||||
}
|
||||
|
||||
private void refreshCachedWindowLocked(int windowId) {
|
||||
if (DEBUG) {
|
||||
Log.i(LOG_TAG, "Refreshing cached window.");
|
||||
}
|
||||
|
||||
if (windowId == AccessibilityWindowInfo.UNDEFINED_WINDOW_ID) {
|
||||
return;
|
||||
}
|
||||
|
||||
final int displayCounts = mWindowCacheByDisplay.size();
|
||||
for (int i = 0; i < displayCounts; i++) {
|
||||
final SparseArray<AccessibilityWindowInfo> windowsOfDisplay =
|
||||
mWindowCacheByDisplay.valueAt(i);
|
||||
if (windowsOfDisplay == null) {
|
||||
continue;
|
||||
}
|
||||
final AccessibilityWindowInfo window = windowsOfDisplay.get(windowId);
|
||||
if (window == null) {
|
||||
continue;
|
||||
}
|
||||
if (!mAccessibilityNodeRefresher.refreshWindow(window)) {
|
||||
// If we fail to refresh the window, clear all windows.
|
||||
clearWindowCacheLocked();
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a cached {@link AccessibilityNodeInfo} given the id of the hosting
|
||||
* window and the accessibility id of the node.
|
||||
@@ -413,8 +453,10 @@ public class AccessibilityCache {
|
||||
refreshCachedNodeLocked(windowId, mAccessibilityFocus);
|
||||
}
|
||||
mAccessibilityFocus = sourceId;
|
||||
mAccessibilityFocusedWindow = windowId;
|
||||
} else if (mAccessibilityFocus == sourceId) {
|
||||
mAccessibilityFocus = AccessibilityNodeInfo.UNDEFINED_ITEM_ID;
|
||||
mAccessibilityFocusedWindow = AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
|
||||
}
|
||||
if (clone.isFocused()) {
|
||||
mInputFocus = sourceId;
|
||||
@@ -439,6 +481,8 @@ public class AccessibilityCache {
|
||||
|
||||
mAccessibilityFocus = AccessibilityNodeInfo.UNDEFINED_ITEM_ID;
|
||||
mInputFocus = AccessibilityNodeInfo.UNDEFINED_ITEM_ID;
|
||||
|
||||
mAccessibilityFocusedWindow = AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -653,8 +697,14 @@ public class AccessibilityCache {
|
||||
|
||||
// Layer of indirection included to break dependency chain for testing
|
||||
public static class AccessibilityNodeRefresher {
|
||||
/** Refresh the given AccessibilityNodeInfo object. */
|
||||
public boolean refreshNode(AccessibilityNodeInfo info, boolean bypassCache) {
|
||||
return info.refresh(null, bypassCache);
|
||||
}
|
||||
|
||||
/** Refresh the given AccessibilityWindowInfo object. */
|
||||
public boolean refreshWindow(AccessibilityWindowInfo info) {
|
||||
return info.refresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -223,19 +223,36 @@ 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);
|
||||
if (window != null) {
|
||||
if (DEBUG) {
|
||||
Log.i(LOG_TAG, "Window cache hit");
|
||||
AccessibilityWindowInfo window;
|
||||
if (!bypassCache) {
|
||||
window = sAccessibilityCache.getWindow(accessibilityWindowId);
|
||||
if (window != null) {
|
||||
if (DEBUG) {
|
||||
Log.i(LOG_TAG, "Window cache hit");
|
||||
}
|
||||
return window;
|
||||
}
|
||||
if (DEBUG) {
|
||||
Log.i(LOG_TAG, "Window cache miss");
|
||||
}
|
||||
return window;
|
||||
}
|
||||
if (DEBUG) {
|
||||
Log.i(LOG_TAG, "Window cache miss");
|
||||
}
|
||||
final long identityToken = Binder.clearCallingIdentity();
|
||||
try {
|
||||
@@ -244,7 +261,9 @@ public final class AccessibilityInteractionClient
|
||||
Binder.restoreCallingIdentity(identityToken);
|
||||
}
|
||||
if (window != null) {
|
||||
sAccessibilityCache.addWindow(window);
|
||||
if (!bypassCache) {
|
||||
sAccessibilityCache.addWindow(window);
|
||||
}
|
||||
return window;
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -87,6 +87,8 @@ public final class AccessibilityWindowInfo implements Parcelable {
|
||||
/** @hide */
|
||||
public static final int ACTIVE_WINDOW_ID = Integer.MAX_VALUE;
|
||||
/** @hide */
|
||||
public static final int UNDEFINED_CONNECTION_ID = -1;
|
||||
/** @hide */
|
||||
public static final int UNDEFINED_WINDOW_ID = -1;
|
||||
/** @hide */
|
||||
public static final int ANY_WINDOW_ID = -2;
|
||||
@@ -117,7 +119,7 @@ public final class AccessibilityWindowInfo implements Parcelable {
|
||||
private CharSequence mTitle;
|
||||
private long mAnchorId = AccessibilityNodeInfo.UNDEFINED_NODE_ID;
|
||||
|
||||
private int mConnectionId = UNDEFINED_WINDOW_ID;
|
||||
private int mConnectionId = UNDEFINED_CONNECTION_ID;
|
||||
|
||||
/**
|
||||
* Creates a new {@link AccessibilityWindowInfo}.
|
||||
@@ -539,6 +541,30 @@ public final class AccessibilityWindowInfo implements Parcelable {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Refreshes this window with the latest state of the window it represents.
|
||||
* <p>
|
||||
* <strong>Note:</strong> If this method returns false this info is obsolete
|
||||
* since it represents a window that is no longer exist.
|
||||
* </p>
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public boolean refresh() {
|
||||
if (mConnectionId == UNDEFINED_CONNECTION_ID || mId == UNDEFINED_WINDOW_ID) {
|
||||
return false;
|
||||
}
|
||||
final AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
|
||||
final AccessibilityWindowInfo refreshedInfo = client.getWindow(mConnectionId,
|
||||
mId, /* bypassCache */true);
|
||||
if (refreshedInfo == null) {
|
||||
return false;
|
||||
}
|
||||
init(refreshedInfo);
|
||||
refreshedInfo.recycle();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
@@ -586,6 +612,7 @@ public final class AccessibilityWindowInfo implements Parcelable {
|
||||
mTitle = other.mTitle;
|
||||
mAnchorId = other.mAnchorId;
|
||||
|
||||
if (mChildIds != null) mChildIds.clear();
|
||||
if (other.mChildIds != null && other.mChildIds.size() > 0) {
|
||||
if (mChildIds == null) {
|
||||
mChildIds = other.mChildIds.clone();
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user