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:
Jackal Guo
2019-11-28 09:56:33 +08:00
parent 993b3ed571
commit 24a1ac51bf
4 changed files with 127 additions and 15 deletions

View File

@@ -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();
}
}
}

View File

@@ -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 {

View File

@@ -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();

View File

@@ -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