Methods Summary |
---|
public void | add(AccessibilityNodeInfo info)Caches an {@link AccessibilityNodeInfo}.
synchronized(mLock) {
if (DEBUG) {
Log.i(LOG_TAG, "add(" + info + ")");
}
final int windowId = info.getWindowId();
LongSparseArray<AccessibilityNodeInfo> nodes = mNodeCache.get(windowId);
if (nodes == null) {
nodes = new LongSparseArray<>();
mNodeCache.put(windowId, nodes);
}
final long sourceId = info.getSourceNodeId();
AccessibilityNodeInfo oldInfo = nodes.get(sourceId);
if (oldInfo != null) {
// If the added node is in the cache we have to be careful if
// the new one represents a source state where some of the
// children have been removed to remove the descendants that
// are no longer present.
final LongArray newChildrenIds = info.getChildNodeIds();
final int oldChildCount = oldInfo.getChildCount();
for (int i = 0; i < oldChildCount; i++) {
final long oldChildId = oldInfo.getChildId(i);
// If the child is no longer present, remove the sub-tree.
if (newChildrenIds == null || newChildrenIds.indexOf(oldChildId) < 0) {
clearSubTreeLocked(windowId, oldChildId);
}
}
// Also be careful if the parent has changed since the new
// parent may be a predecessor of the old parent which will
// add cyclse to the cache.
final long oldParentId = oldInfo.getParentNodeId();
if (info.getParentNodeId() != oldParentId) {
clearSubTreeLocked(windowId, oldParentId);
}
}
// Cache a copy since the client calls to AccessibilityNodeInfo#recycle()
// will wipe the data of the cached info.
AccessibilityNodeInfo clone = AccessibilityNodeInfo.obtain(info);
nodes.put(sourceId, clone);
}
|
public void | addWindow(AccessibilityWindowInfo window)
synchronized (mLock) {
if (DEBUG) {
Log.i(LOG_TAG, "Caching window: " + window.getId());
}
final int windowId = window.getId();
AccessibilityWindowInfo oldWindow = mWindowCache.get(windowId);
if (oldWindow != null) {
oldWindow.recycle();
}
mWindowCache.put(windowId, AccessibilityWindowInfo.obtain(window));
}
|
public void | checkIntegrity()Check the integrity of the cache which is nodes from different windows
are not mixed, there is a single active window, there is a single focused
window, for every window there are no duplicates nodes, all nodes for a
window are connected, for every window there is a single input focused
node, and for every window there is a single accessibility focused node.
synchronized (mLock) {
// Get the root.
if (mWindowCache.size() <= 0 && mNodeCache.size() == 0) {
return;
}
AccessibilityWindowInfo focusedWindow = null;
AccessibilityWindowInfo activeWindow = null;
final int windowCount = mWindowCache.size();
for (int i = 0; i < windowCount; i++) {
AccessibilityWindowInfo window = mWindowCache.valueAt(i);
// Check for one active window.
if (window.isActive()) {
if (activeWindow != null) {
Log.e(LOG_TAG, "Duplicate active window:" + window);
} else {
activeWindow = window;
}
}
// Check for one focused window.
if (window.isFocused()) {
if (focusedWindow != null) {
Log.e(LOG_TAG, "Duplicate focused window:" + window);
} else {
focusedWindow = window;
}
}
}
// Traverse the tree and do some checks.
AccessibilityNodeInfo accessFocus = null;
AccessibilityNodeInfo inputFocus = null;
final int nodesForWindowCount = mNodeCache.size();
for (int i = 0; i < nodesForWindowCount; i++) {
LongSparseArray<AccessibilityNodeInfo> nodes = mNodeCache.valueAt(i);
if (nodes.size() <= 0) {
continue;
}
ArraySet<AccessibilityNodeInfo> seen = new ArraySet<>();
final int windowId = mNodeCache.keyAt(i);
final int nodeCount = nodes.size();
for (int j = 0; j < nodeCount; j++) {
AccessibilityNodeInfo node = nodes.valueAt(j);
// Check for duplicates
if (!seen.add(node)) {
Log.e(LOG_TAG, "Duplicate node: " + node
+ " in window:" + windowId);
// Stop now as we potentially found a loop.
continue;
}
// Check for one accessibility focus.
if (node.isAccessibilityFocused()) {
if (accessFocus != null) {
Log.e(LOG_TAG, "Duplicate accessibility focus:" + node
+ " in window:" + windowId);
} else {
accessFocus = node;
}
}
// Check for one input focus.
if (node.isFocused()) {
if (inputFocus != null) {
Log.e(LOG_TAG, "Duplicate input focus: " + node
+ " in window:" + windowId);
} else {
inputFocus = node;
}
}
// The node should be a child of its parent if we have the parent.
AccessibilityNodeInfo nodeParent = nodes.get(node.getParentNodeId());
if (nodeParent != null) {
boolean childOfItsParent = false;
final int childCount = nodeParent.getChildCount();
for (int k = 0; k < childCount; k++) {
AccessibilityNodeInfo child = nodes.get(nodeParent.getChildId(k));
if (child == node) {
childOfItsParent = true;
break;
}
}
if (!childOfItsParent) {
Log.e(LOG_TAG, "Invalid parent-child relation between parent: "
+ nodeParent + " and child: " + node);
}
}
// The node should be the parent of its child if we have the child.
final int childCount = node.getChildCount();
for (int k = 0; k < childCount; k++) {
AccessibilityNodeInfo child = nodes.get(node.getChildId(k));
if (child != null) {
AccessibilityNodeInfo parent = nodes.get(child.getParentNodeId());
if (parent != node) {
Log.e(LOG_TAG, "Invalid child-parent relation between child: "
+ node + " and parent: " + nodeParent);
}
}
}
}
}
}
|
public void | clear()Clears the cache.
synchronized(mLock) {
if (DEBUG) {
Log.i(LOG_TAG, "clear()");
}
final int windowCount = mWindowCache.size();
for (int i = windowCount - 1; i >= 0; i--) {
AccessibilityWindowInfo window = mWindowCache.valueAt(i);
window.recycle();
mWindowCache.removeAt(i);
}
final int nodesForWindowCount = mNodeCache.size();
for (int i = 0; i < nodesForWindowCount; i++) {
final int windowId = mNodeCache.keyAt(i);
clearNodesForWindowLocked(windowId);
}
mAccessibilityFocus = AccessibilityNodeInfo.UNDEFINED_ITEM_ID;
mInputFocus = AccessibilityNodeInfo.UNDEFINED_ITEM_ID;
}
|
private void | clearNodesForWindowLocked(int windowId)
if (DEBUG) {
Log.i(LOG_TAG, "clearNodesForWindowLocked(" + windowId + ")");
}
LongSparseArray<AccessibilityNodeInfo> nodes = mNodeCache.get(windowId);
if (nodes == null) {
return;
}
// Recycle the nodes before clearing the cache.
final int nodeCount = nodes.size();
for (int i = nodeCount - 1; i >= 0; i--) {
AccessibilityNodeInfo info = nodes.valueAt(i);
nodes.removeAt(i);
info.recycle();
}
mNodeCache.remove(windowId);
|
private void | clearSubTreeLocked(int windowId, long rootNodeId)Clears a subtree rooted at the node with the given id that is
hosted in a given window.
if (DEBUG) {
Log.i(LOG_TAG, "Clearing cached subtree.");
}
LongSparseArray<AccessibilityNodeInfo> nodes = mNodeCache.get(windowId);
if (nodes != null) {
clearSubTreeRecursiveLocked(nodes, rootNodeId);
}
|
private void | clearSubTreeRecursiveLocked(android.util.LongSparseArray nodes, long rootNodeId)Clears a subtree given a pointer to the root id and the nodes
in the hosting window.
AccessibilityNodeInfo current = nodes.get(rootNodeId);
if (current == null) {
return;
}
nodes.remove(rootNodeId);
final int childCount = current.getChildCount();
for (int i = 0; i < childCount; i++) {
final long childNodeId = current.getChildId(i);
clearSubTreeRecursiveLocked(nodes, childNodeId);
}
|
public AccessibilityNodeInfo | getNode(int windowId, long accessibilityNodeId)Gets a cached {@link AccessibilityNodeInfo} given the id of the hosting
window and the accessibility id of the node.
synchronized(mLock) {
LongSparseArray<AccessibilityNodeInfo> nodes = mNodeCache.get(windowId);
if (nodes == null) {
return null;
}
AccessibilityNodeInfo info = nodes.get(accessibilityNodeId);
if (info != null) {
// Return a copy since the client calls to AccessibilityNodeInfo#recycle()
// will wipe the data of the cached info.
info = AccessibilityNodeInfo.obtain(info);
}
if (DEBUG) {
Log.i(LOG_TAG, "get(" + accessibilityNodeId + ") = " + info);
}
return info;
}
|
public AccessibilityWindowInfo | getWindow(int windowId)
synchronized (mLock) {
AccessibilityWindowInfo window = mWindowCache.get(windowId);
if (window != null) {
return AccessibilityWindowInfo.obtain(window);
}
return null;
}
|
public java.util.List | getWindows()
synchronized (mLock) {
final int windowCount = mWindowCache.size();
if (windowCount > 0) {
// Careful to return the windows in a decreasing layer order.
SparseArray<AccessibilityWindowInfo> sortedWindows = mTempWindowArray;
sortedWindows.clear();
for (int i = 0; i < windowCount; i++) {
AccessibilityWindowInfo window = mWindowCache.valueAt(i);
sortedWindows.put(window.getLayer(), window);
}
List<AccessibilityWindowInfo> windows = new ArrayList<>(windowCount);
for (int i = windowCount - 1; i >= 0; i--) {
AccessibilityWindowInfo window = sortedWindows.valueAt(i);
windows.add(AccessibilityWindowInfo.obtain(window));
sortedWindows.removeAt(i);
}
return windows;
}
return null;
}
|
public void | onAccessibilityEvent(AccessibilityEvent event)Notifies the cache that the something in the UI changed. As a result
the cache will either refresh some nodes or evict some nodes.
synchronized (mLock) {
final int eventType = event.getEventType();
switch (eventType) {
case AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED: {
if (mAccessibilityFocus != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) {
refreshCachedNodeLocked(event.getWindowId(), mAccessibilityFocus);
}
mAccessibilityFocus = event.getSourceNodeId();
refreshCachedNodeLocked(event.getWindowId(), mAccessibilityFocus);
} break;
case AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED: {
if (mAccessibilityFocus == event.getSourceNodeId()) {
refreshCachedNodeLocked(event.getWindowId(), mAccessibilityFocus);
mAccessibilityFocus = AccessibilityNodeInfo.UNDEFINED_ITEM_ID;
}
} break;
case AccessibilityEvent.TYPE_VIEW_FOCUSED: {
if (mInputFocus != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) {
refreshCachedNodeLocked(event.getWindowId(), mInputFocus);
}
mInputFocus = event.getSourceNodeId();
refreshCachedNodeLocked(event.getWindowId(), mInputFocus);
} break;
case AccessibilityEvent.TYPE_VIEW_SELECTED:
case AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED:
case AccessibilityEvent.TYPE_VIEW_CLICKED:
case AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED: {
refreshCachedNodeLocked(event.getWindowId(), event.getSourceNodeId());
} break;
case AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED: {
synchronized (mLock) {
final int windowId = event.getWindowId();
final long sourceId = event.getSourceNodeId();
if ((event.getContentChangeTypes()
& AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE) != 0) {
clearSubTreeLocked(windowId, sourceId);
} else {
refreshCachedNodeLocked(windowId, sourceId);
}
}
} break;
case AccessibilityEvent.TYPE_VIEW_SCROLLED: {
clearSubTreeLocked(event.getWindowId(), event.getSourceNodeId());
} break;
case AccessibilityEvent.TYPE_WINDOWS_CHANGED:
case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED: {
clear();
} break;
}
}
if (CHECK_INTEGRITY) {
checkIntegrity();
}
|
private void | refreshCachedNodeLocked(int windowId, long sourceId)
if (DEBUG) {
Log.i(LOG_TAG, "Refreshing cached node.");
}
LongSparseArray<AccessibilityNodeInfo> nodes = mNodeCache.get(windowId);
if (nodes == null) {
return;
}
AccessibilityNodeInfo cachedInfo = nodes.get(sourceId);
// If the source is not in the cache - nothing to do.
if (cachedInfo == null) {
return;
}
// The node changed so we will just refresh it right now.
if (cachedInfo.refresh(true)) {
return;
}
// Weird, we could not refresh. Just evict the entire sub-tree.
clearSubTreeLocked(windowId, sourceId);
|