Merge "Eliminate potential deadlock in AccessibilityCache"

This commit is contained in:
Jacky Kao
2021-06-16 03:14:27 +00:00
committed by Gerrit Code Review
2 changed files with 34 additions and 60 deletions

View File

@@ -158,6 +158,7 @@ public class AccessibilityCache {
* @param event An event. * @param event An event.
*/ */
public void onAccessibilityEvent(AccessibilityEvent event) { public void onAccessibilityEvent(AccessibilityEvent event) {
AccessibilityNodeInfo nodeToRefresh = null;
synchronized (mLock) { synchronized (mLock) {
if (DEBUG) { if (DEBUG) {
Log.i(LOG_TAG, "onAccessibilityEvent(" + event + ")"); Log.i(LOG_TAG, "onAccessibilityEvent(" + event + ")");
@@ -166,17 +167,19 @@ public class AccessibilityCache {
switch (eventType) { switch (eventType) {
case AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED: { case AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED: {
if (mAccessibilityFocus != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) { if (mAccessibilityFocus != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) {
refreshCachedNodeLocked(mAccessibilityFocusedWindow, mAccessibilityFocus); removeCachedNodeLocked(mAccessibilityFocusedWindow, mAccessibilityFocus);
} }
mAccessibilityFocus = event.getSourceNodeId(); mAccessibilityFocus = event.getSourceNodeId();
mAccessibilityFocusedWindow = event.getWindowId(); mAccessibilityFocusedWindow = event.getWindowId();
refreshCachedNodeLocked(mAccessibilityFocusedWindow, mAccessibilityFocus); nodeToRefresh = removeCachedNodeLocked(mAccessibilityFocusedWindow,
mAccessibilityFocus);
} break; } break;
case AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED: { case AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED: {
if (mAccessibilityFocus == event.getSourceNodeId() if (mAccessibilityFocus == event.getSourceNodeId()
&& mAccessibilityFocusedWindow == event.getWindowId()) { && mAccessibilityFocusedWindow == event.getWindowId()) {
refreshCachedNodeLocked(mAccessibilityFocusedWindow, mAccessibilityFocus); nodeToRefresh = removeCachedNodeLocked(mAccessibilityFocusedWindow,
mAccessibilityFocus);
mAccessibilityFocus = AccessibilityNodeInfo.UNDEFINED_ITEM_ID; mAccessibilityFocus = AccessibilityNodeInfo.UNDEFINED_ITEM_ID;
mAccessibilityFocusedWindow = AccessibilityWindowInfo.UNDEFINED_WINDOW_ID; mAccessibilityFocusedWindow = AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
} }
@@ -184,17 +187,18 @@ public class AccessibilityCache {
case AccessibilityEvent.TYPE_VIEW_FOCUSED: { case AccessibilityEvent.TYPE_VIEW_FOCUSED: {
if (mInputFocus != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) { if (mInputFocus != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) {
refreshCachedNodeLocked(event.getWindowId(), mInputFocus); removeCachedNodeLocked(event.getWindowId(), mInputFocus);
} }
mInputFocus = event.getSourceNodeId(); mInputFocus = event.getSourceNodeId();
refreshCachedNodeLocked(event.getWindowId(), mInputFocus); nodeToRefresh = removeCachedNodeLocked(event.getWindowId(), mInputFocus);
} break; } break;
case AccessibilityEvent.TYPE_VIEW_SELECTED: case AccessibilityEvent.TYPE_VIEW_SELECTED:
case AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED: case AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED:
case AccessibilityEvent.TYPE_VIEW_CLICKED: case AccessibilityEvent.TYPE_VIEW_CLICKED:
case AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED: { case AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED: {
refreshCachedNodeLocked(event.getWindowId(), event.getSourceNodeId()); nodeToRefresh = removeCachedNodeLocked(event.getWindowId(),
event.getSourceNodeId());
} break; } break;
case AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED: { case AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED: {
@@ -205,7 +209,7 @@ public class AccessibilityCache {
& AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE) != 0) { & AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE) != 0) {
clearSubTreeLocked(windowId, sourceId); clearSubTreeLocked(windowId, sourceId);
} else { } else {
refreshCachedNodeLocked(windowId, sourceId); nodeToRefresh = removeCachedNodeLocked(windowId, sourceId);
} }
} }
} break; } break;
@@ -218,8 +222,8 @@ public class AccessibilityCache {
if (event.getWindowChanges() if (event.getWindowChanges()
== AccessibilityEvent.WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED) { == AccessibilityEvent.WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED) {
// Don't need to clear all cache. Unless the changes are related to // Don't need to clear all cache. Unless the changes are related to
// content, we won't clear all cache here. // content, we won't clear all cache here with clear().
refreshCachedWindowLocked(event.getWindowId()); clearWindowCacheLocked();
break; break;
} }
case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED: { case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED: {
@@ -228,59 +232,34 @@ public class AccessibilityCache {
} }
} }
if (nodeToRefresh != null) {
if (DEBUG) {
Log.i(LOG_TAG, "Refreshing and re-adding cached node.");
}
if (mAccessibilityNodeRefresher.refreshNode(nodeToRefresh, true)) {
add(nodeToRefresh);
}
}
if (CHECK_INTEGRITY) { if (CHECK_INTEGRITY) {
checkIntegrity(); checkIntegrity();
} }
} }
private void refreshCachedNodeLocked(int windowId, long sourceId) { private AccessibilityNodeInfo removeCachedNodeLocked(int windowId, long sourceId) {
if (DEBUG) { if (DEBUG) {
Log.i(LOG_TAG, "Refreshing cached node."); Log.i(LOG_TAG, "Removing cached node.");
} }
LongSparseArray<AccessibilityNodeInfo> nodes = mNodeCache.get(windowId); LongSparseArray<AccessibilityNodeInfo> nodes = mNodeCache.get(windowId);
if (nodes == null) { if (nodes == null) {
return; return null;
} }
AccessibilityNodeInfo cachedInfo = nodes.get(sourceId); AccessibilityNodeInfo cachedInfo = nodes.get(sourceId);
// If the source is not in the cache - nothing to do. // If the source is not in the cache - nothing to do.
if (cachedInfo == null) { if (cachedInfo == null) {
return; return null;
}
// The node changed so we will just refresh it right now.
if (mAccessibilityNodeRefresher.refreshNode(cachedInfo, true)) {
return;
}
// Weird, we could not refresh. Just evict the entire sub-tree.
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;
} }
nodes.remove(sourceId);
return cachedInfo;
} }
/** /**
@@ -450,7 +429,7 @@ public class AccessibilityCache {
if (clone.isAccessibilityFocused()) { if (clone.isAccessibilityFocused()) {
if (mAccessibilityFocus != AccessibilityNodeInfo.UNDEFINED_ITEM_ID if (mAccessibilityFocus != AccessibilityNodeInfo.UNDEFINED_ITEM_ID
&& mAccessibilityFocus != sourceId) { && mAccessibilityFocus != sourceId) {
refreshCachedNodeLocked(windowId, mAccessibilityFocus); removeCachedNodeLocked(windowId, mAccessibilityFocus);
} }
mAccessibilityFocus = sourceId; mAccessibilityFocus = sourceId;
mAccessibilityFocusedWindow = windowId; mAccessibilityFocusedWindow = windowId;

View File

@@ -563,7 +563,7 @@ public class AccessibilityCacheTest {
} }
@Test @Test
public void nodeWithA11yFocusWhenAnotherNodeGetsFocus_getsRefreshed() { public void nodeWithA11yFocusWhenAnotherNodeGetsFocus_getsRemoved() {
AccessibilityNodeInfo nodeInfo = getNodeWithA11yAndWindowId(SINGLE_VIEW_ID, WINDOW_ID_1); AccessibilityNodeInfo nodeInfo = getNodeWithA11yAndWindowId(SINGLE_VIEW_ID, WINDOW_ID_1);
nodeInfo.setAccessibilityFocused(true); nodeInfo.setAccessibilityFocused(true);
mAccessibilityCache.add(nodeInfo); mAccessibilityCache.add(nodeInfo);
@@ -573,7 +573,7 @@ public class AccessibilityCacheTest {
mAccessibilityCache.onAccessibilityEvent(event); mAccessibilityCache.onAccessibilityEvent(event);
event.recycle(); event.recycle();
try { try {
verify(mAccessibilityNodeRefresher).refreshNode(nodeInfo, true); assertNull(mAccessibilityCache.getNode(WINDOW_ID_1, SINGLE_VIEW_ID));
} finally { } finally {
nodeInfo.recycle(); nodeInfo.recycle();
} }
@@ -614,7 +614,7 @@ public class AccessibilityCacheTest {
} }
@Test @Test
public void nodeWithInputFocusWhenAnotherNodeGetsFocus_getsRefreshed() { public void nodeWithInputFocusWhenAnotherNodeGetsFocus_getsRemoved() {
AccessibilityNodeInfo nodeInfo = getNodeWithA11yAndWindowId(SINGLE_VIEW_ID, WINDOW_ID_1); AccessibilityNodeInfo nodeInfo = getNodeWithA11yAndWindowId(SINGLE_VIEW_ID, WINDOW_ID_1);
nodeInfo.setFocused(true); nodeInfo.setFocused(true);
mAccessibilityCache.add(nodeInfo); mAccessibilityCache.add(nodeInfo);
@@ -624,7 +624,7 @@ public class AccessibilityCacheTest {
mAccessibilityCache.onAccessibilityEvent(event); mAccessibilityCache.onAccessibilityEvent(event);
event.recycle(); event.recycle();
try { try {
verify(mAccessibilityNodeRefresher).refreshNode(nodeInfo, true); assertNull(mAccessibilityCache.getNode(WINDOW_ID_1, SINGLE_VIEW_ID));
} finally { } finally {
nodeInfo.recycle(); nodeInfo.recycle();
} }
@@ -733,20 +733,15 @@ public class AccessibilityCacheTest {
} }
@Test @Test
public void addA11yFocusNodeBeforeFocusClearedEvent_previousA11yFocusNodeGetsRefreshed() { public void addA11yFocusNodeBeforeFocusClearedEvent_previousA11yFocusNodeGetsRemoved() {
AccessibilityNodeInfo nodeInfo1 = getNodeWithA11yAndWindowId(SINGLE_VIEW_ID, WINDOW_ID_1); AccessibilityNodeInfo nodeInfo1 = getNodeWithA11yAndWindowId(SINGLE_VIEW_ID, WINDOW_ID_1);
nodeInfo1.setAccessibilityFocused(true); nodeInfo1.setAccessibilityFocused(true);
mAccessibilityCache.add(nodeInfo1); mAccessibilityCache.add(nodeInfo1);
AccessibilityNodeInfo nodeInfo2 = getNodeWithA11yAndWindowId(OTHER_VIEW_ID, WINDOW_ID_1); AccessibilityNodeInfo nodeInfo2 = getNodeWithA11yAndWindowId(OTHER_VIEW_ID, WINDOW_ID_1);
nodeInfo2.setAccessibilityFocused(true); nodeInfo2.setAccessibilityFocused(true);
mAccessibilityCache.add(nodeInfo2); mAccessibilityCache.add(nodeInfo2);
AccessibilityEvent event = AccessibilityEvent.obtain(
AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED);
event.setSource(getMockViewWithA11yAndWindowIds(SINGLE_VIEW_ID, WINDOW_ID_1));
mAccessibilityCache.onAccessibilityEvent(event);
event.recycle();
try { try {
verify(mAccessibilityNodeRefresher).refreshNode(nodeInfo1, true); assertNull(mAccessibilityCache.getNode(WINDOW_ID_1, SINGLE_VIEW_ID));
} finally { } finally {
nodeInfo1.recycle(); nodeInfo1.recycle();
nodeInfo2.recycle(); nodeInfo2.recycle();