summaryrefslogtreecommitdiff
path: root/core/java/android/view
diff options
context:
space:
mode:
Diffstat (limited to 'core/java/android/view')
-rw-r--r--core/java/android/view/View.java11
-rw-r--r--core/java/android/view/ViewGroup.java3
-rw-r--r--core/java/android/view/accessibility/AccessibilityInteractionClient.java6
-rw-r--r--core/java/android/view/accessibility/AccessibilityNodeInfoCache.java232
4 files changed, 198 insertions, 54 deletions
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index bf7d037969cb..85fd8fe40de4 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -4723,11 +4723,9 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
getBoundsOnScreen(bounds);
info.setBoundsInScreen(bounds);
- if ((mPrivateFlags & IS_ROOT_NAMESPACE) == 0) {
- ViewParent parent = getParentForAccessibility();
- if (parent instanceof View) {
- info.setParent((View) parent);
- }
+ ViewParent parent = getParentForAccessibility();
+ if (parent instanceof View) {
+ info.setParent((View) parent);
}
info.setVisibleToUser(isVisibleToUser());
@@ -6503,6 +6501,9 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
* that is interesting for accessilility purposes.
*/
public void notifyAccessibilityStateChanged() {
+ if (!AccessibilityManager.getInstance(mContext).isEnabled()) {
+ return;
+ }
if ((mPrivateFlags2 & ACCESSIBILITY_STATE_CHANGED) == 0) {
mPrivateFlags2 |= ACCESSIBILITY_STATE_CHANGED;
if (mParent != null) {
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index b3c8895a9f9e..f55b7acca9a0 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -1635,8 +1635,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
final int childrenCount = children.getChildCount();
for (int i = 0; i < childrenCount; i++) {
View child = children.getChildAt(i);
- if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE
- && (child.mPrivateFlags & IS_ROOT_NAMESPACE) == 0) {
+ if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) {
if (child.includeForAccessibility()) {
childrenForAccessibility.add(child);
} else {
diff --git a/core/java/android/view/accessibility/AccessibilityInteractionClient.java b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
index 24e90fdfe160..bd341d0b10eb 100644
--- a/core/java/android/view/accessibility/AccessibilityInteractionClient.java
+++ b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
@@ -441,10 +441,6 @@ public final class AccessibilityInteractionClient
sAccessibilityNodeInfoCache.clear();
}
- public void removeCachedNode(long accessibilityNodeId) {
- sAccessibilityNodeInfoCache.remove(accessibilityNodeId);
- }
-
public void onAccessibilityEvent(AccessibilityEvent event) {
sAccessibilityNodeInfoCache.onAccessibilityEvent(event);
}
@@ -630,7 +626,7 @@ public final class AccessibilityInteractionClient
applyCompatibilityScaleIfNeeded(info, windowScale);
info.setConnectionId(connectionId);
info.setSealed(true);
- sAccessibilityNodeInfoCache.put(info.getSourceNodeId(), info);
+ sAccessibilityNodeInfoCache.add(info);
}
}
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfoCache.java b/core/java/android/view/accessibility/AccessibilityNodeInfoCache.java
index d2609bb42d1d..52b7772864f7 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfoCache.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfoCache.java
@@ -16,10 +16,15 @@
package android.view.accessibility;
+import android.os.Build;
import android.util.Log;
import android.util.LongSparseArray;
import android.util.SparseLongArray;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.Queue;
+
/**
* Simple cache for AccessibilityNodeInfos. The cache is mapping an
* accessibility id to an info. The cache allows storing of
@@ -36,10 +41,14 @@ public class AccessibilityNodeInfoCache {
private static final boolean DEBUG = false;
+ private static final boolean CHECK_INTEGRITY = true;
+
private final Object mLock = new Object();
private final LongSparseArray<AccessibilityNodeInfo> mCacheImpl;
+ private int mWindowId;
+
public AccessibilityNodeInfoCache() {
if (ENABLED) {
mCacheImpl = new LongSparseArray<AccessibilityNodeInfo>();
@@ -59,21 +68,49 @@ public class AccessibilityNodeInfoCache {
final int eventType = event.getEventType();
switch (eventType) {
case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED: {
+ // New window so we clear the cache.
+ mWindowId = event.getWindowId();
clear();
} break;
+ case AccessibilityEvent.TYPE_VIEW_HOVER_ENTER:
+ case AccessibilityEvent.TYPE_VIEW_HOVER_EXIT: {
+ final int windowId = event.getWindowId();
+ if (mWindowId != windowId) {
+ // New window so we clear the cache.
+ mWindowId = windowId;
+ clear();
+ }
+ } break;
case AccessibilityEvent.TYPE_VIEW_FOCUSED:
+ case AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED:
case AccessibilityEvent.TYPE_VIEW_SELECTED:
case AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED:
case AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED: {
- final long accessibilityNodeId = event.getSourceNodeId();
- remove(accessibilityNodeId);
+ // Since we prefetch the descendants of a node we
+ // just remove the entire subtree since when the node
+ // is fetched we will gets its descendant anyway.
+ synchronized (mLock) {
+ final long sourceId = event.getSourceNodeId();
+ clearSubTreeLocked(sourceId);
+ if (eventType == AccessibilityEvent.TYPE_VIEW_FOCUSED) {
+ clearSubtreeWithOldInputFocusLocked(sourceId);
+ }
+ if (eventType == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED) {
+ clearSubtreeWithOldAccessibilityFocusLocked(sourceId);
+ }
+ }
} break;
case AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED:
case AccessibilityEvent.TYPE_VIEW_SCROLLED: {
- final long accessibilityNodeId = event.getSourceNodeId();
- clearSubTree(accessibilityNodeId);
+ synchronized (mLock) {
+ final long accessibilityNodeId = event.getSourceNodeId();
+ clearSubTreeLocked(accessibilityNodeId);
+ }
} break;
}
+ if (Build.IS_DEBUGGABLE && CHECK_INTEGRITY) {
+ checkIntegrity();
+ }
}
}
@@ -105,51 +142,45 @@ public class AccessibilityNodeInfoCache {
/**
* Caches an {@link AccessibilityNodeInfo} given its accessibility node id.
*
- * @param accessibilityNodeId The info accessibility node id.
* @param info The {@link AccessibilityNodeInfo} to cache.
*/
- public void put(long accessibilityNodeId, AccessibilityNodeInfo info) {
+ public void add(AccessibilityNodeInfo info) {
if (ENABLED) {
synchronized(mLock) {
if (DEBUG) {
- Log.i(LOG_TAG, "put(" + accessibilityNodeId + ", " + info + ")");
+ Log.i(LOG_TAG, "add(" + info + ")");
}
- // Cache a copy since the client calls to AccessibilityNodeInfo#recycle()
- // will wipe the data of the cached info.
- AccessibilityNodeInfo clone = AccessibilityNodeInfo.obtain(info);
- mCacheImpl.put(accessibilityNodeId, clone);
- }
- }
- }
- /**
- * Returns whether the cache contains an accessibility node id key.
- *
- * @param accessibilityNodeId The key for which to check.
- * @return True if the key is in the cache.
- */
- public boolean containsKey(long accessibilityNodeId) {
- if (ENABLED) {
- synchronized(mLock) {
- return (mCacheImpl.indexOfKey(accessibilityNodeId) >= 0);
- }
- } else {
- return false;
- }
- }
+ final long sourceId = info.getSourceNodeId();
+ AccessibilityNodeInfo oldInfo = mCacheImpl.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 avoid having disconnected
+ // subtrees in the cache.
+ SparseLongArray oldChildrenIds = oldInfo.getChildNodeIds();
+ SparseLongArray newChildrenIds = info.getChildNodeIds();
+ final int oldChildCount = oldChildrenIds.size();
+ for (int i = 0; i < oldChildCount; i++) {
+ final long oldChildId = oldChildrenIds.valueAt(i);
+ if (newChildrenIds.indexOfValue(oldChildId) < 0) {
+ clearSubTreeLocked(oldChildId);
+ }
+ }
- /**
- * Removes a cached {@link AccessibilityNodeInfo}.
- *
- * @param accessibilityNodeId The info accessibility node id.
- */
- public void remove(long accessibilityNodeId) {
- if (ENABLED) {
- synchronized(mLock) {
- if (DEBUG) {
- Log.i(LOG_TAG, "remove(" + accessibilityNodeId + ")");
+ // Also be careful if the parent has changed since the new
+ // parent may be a predecessor of the old parent which will
+ // make the cached tree cyclic.
+ final long oldParentId = oldInfo.getParentNodeId();
+ if (info.getParentNodeId() != oldParentId) {
+ clearSubTreeLocked(oldParentId);
+ }
}
- mCacheImpl.remove(accessibilityNodeId);
+
+ // Cache a copy since the client calls to AccessibilityNodeInfo#recycle()
+ // will wipe the data of the cached info.
+ AccessibilityNodeInfo clone = AccessibilityNodeInfo.obtain(info);
+ mCacheImpl.put(sourceId, clone);
}
}
}
@@ -179,7 +210,7 @@ public class AccessibilityNodeInfoCache {
*
* @param rootNodeId The root id.
*/
- private void clearSubTree(long rootNodeId) {
+ private void clearSubTreeLocked(long rootNodeId) {
AccessibilityNodeInfo current = mCacheImpl.get(rootNodeId);
if (current == null) {
return;
@@ -189,7 +220,124 @@ public class AccessibilityNodeInfoCache {
final int childCount = childNodeIds.size();
for (int i = 0; i < childCount; i++) {
final long childNodeId = childNodeIds.valueAt(i);
- clearSubTree(childNodeId);
+ clearSubTreeLocked(childNodeId);
+ }
+ }
+
+ /**
+ * We are enforcing the invariant for a single input focus.
+ *
+ * @param currentInputFocusId The current input focused node.
+ */
+ private void clearSubtreeWithOldInputFocusLocked(long currentInputFocusId) {
+ final int cacheSize = mCacheImpl.size();
+ for (int i = 0; i < cacheSize; i++) {
+ AccessibilityNodeInfo info = mCacheImpl.valueAt(i);
+ final long infoSourceId = info.getSourceNodeId();
+ if (infoSourceId != currentInputFocusId && info.isFocused()) {
+ clearSubTreeLocked(infoSourceId);
+ return;
+ }
+ }
+ }
+
+ /**
+ * We are enforcing the invariant for a single accessibility focus.
+ *
+ * @param currentInputFocusId The current input focused node.
+ */
+ private void clearSubtreeWithOldAccessibilityFocusLocked(long currentAccessibilityFocusId) {
+ final int cacheSize = mCacheImpl.size();
+ for (int i = 0; i < cacheSize; i++) {
+ AccessibilityNodeInfo info = mCacheImpl.valueAt(i);
+ final long infoSourceId = info.getSourceNodeId();
+ if (infoSourceId != currentAccessibilityFocusId && info.isAccessibilityFocused()) {
+ clearSubTreeLocked(infoSourceId);
+ return;
+ }
+ }
+ }
+
+ /**
+ * Check the integrity of the cache which is it does not have nodes
+ * from more than one window, there are no duplicates, all nodes are
+ * connected, there is a single input focused node, and there is a
+ * single accessibility focused node.
+ */
+ private void checkIntegrity() {
+ synchronized (mLock) {
+ // Get the root.
+ if (mCacheImpl.size() <= 0) {
+ return;
+ }
+
+ // If the cache is a tree it does not matter from
+ // which node we start to search for the root.
+ AccessibilityNodeInfo root = mCacheImpl.valueAt(0);
+ AccessibilityNodeInfo parent = root;
+ while (parent != null) {
+ root = parent;
+ parent = mCacheImpl.get(parent.getParentNodeId());
+ }
+
+ // Traverse the tree and do some checks.
+ final int windowId = root.getWindowId();
+ AccessibilityNodeInfo accessFocus = null;
+ AccessibilityNodeInfo inputFocus = null;
+ HashSet<AccessibilityNodeInfo> seen = new HashSet<AccessibilityNodeInfo>();
+ Queue<AccessibilityNodeInfo> fringe = new LinkedList<AccessibilityNodeInfo>();
+ fringe.add(root);
+
+ while (!fringe.isEmpty()) {
+ AccessibilityNodeInfo current = fringe.poll();
+ // Check for duplicates
+ if (!seen.add(current)) {
+ Log.e(LOG_TAG, "Duplicate node: " + current);
+ return;
+ }
+
+ // Check for one accessibility focus.
+ if (current.isAccessibilityFocused()) {
+ if (accessFocus != null) {
+ Log.e(LOG_TAG, "Duplicate accessibility focus:" + current);
+ } else {
+ accessFocus = current;
+ }
+ }
+
+ // Check for one input focus.
+ if (current.isFocused()) {
+ if (inputFocus != null) {
+ Log.e(LOG_TAG, "Duplicate input focus: " + current);
+ } else {
+ inputFocus = current;
+ }
+ }
+
+ SparseLongArray childIds = current.getChildNodeIds();
+ final int childCount = childIds.size();
+ for (int i = 0; i < childCount; i++) {
+ final long childId = childIds.valueAt(i);
+ AccessibilityNodeInfo child = mCacheImpl.get(childId);
+ if (child != null) {
+ fringe.add(child);
+ }
+ }
+ }
+
+ // Check for disconnected nodes or ones from another window.
+ final int cacheSize = mCacheImpl.size();
+ for (int i = 0; i < cacheSize; i++) {
+ AccessibilityNodeInfo info = mCacheImpl.valueAt(i);
+ if (!seen.contains(info)) {
+ if (info.getWindowId() == windowId) {
+ Log.e(LOG_TAG, "Disconneced node: ");
+ } else {
+ Log.e(LOG_TAG, "Node from: " + info.getWindowId() + " not from:"
+ + windowId + " " + info);
+ }
+ }
+ }
}
}
}