diff --git a/core/java/android/view/AccessibilityInteractionController.java b/core/java/android/view/AccessibilityInteractionController.java index 16f9a1865014b..6dc31dd0c9d1b 100644 --- a/core/java/android/view/AccessibilityInteractionController.java +++ b/core/java/android/view/AccessibilityInteractionController.java @@ -29,7 +29,6 @@ import android.util.Poolable; import android.util.PoolableManager; import android.util.Pools; import android.util.SparseLongArray; -import android.view.ViewGroup.ChildListForAccessibility; import android.view.accessibility.AccessibilityInteractionClient; import android.view.accessibility.AccessibilityNodeInfo; import android.view.accessibility.AccessibilityNodeProvider; @@ -623,6 +622,8 @@ final class AccessibilityInteractionController { private static final int MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE = 50; + private final ArrayList mTempViewList = new ArrayList(); + public void prefetchAccessibilityNodeInfos(View view, int virtualViewId, int prefetchFlags, List outInfos) { AccessibilityNodeProvider provider = view.getAccessibilityNodeProvider(); @@ -663,8 +664,6 @@ final class AccessibilityInteractionController { while (parent instanceof View && outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) { View parentView = (View) parent; - final long parentNodeId = AccessibilityNodeInfo.makeNodeId( - parentView.getAccessibilityViewId(), AccessibilityNodeInfo.UNDEFINED); AccessibilityNodeInfo info = parentView.createAccessibilityNodeInfo(); if (info != null) { outInfos.add(info); @@ -678,19 +677,21 @@ final class AccessibilityInteractionController { ViewParent parent = current.getParentForAccessibility(); if (parent instanceof ViewGroup) { ViewGroup parentGroup = (ViewGroup) parent; - ChildListForAccessibility children = ChildListForAccessibility.obtain(parentGroup, - false); + ArrayList children = mTempViewList; + children.clear(); try { - final int childCount = children.getChildCount(); + parentGroup.addChildrenForAccessibility(children); + final int childCount = children.size(); for (int i = 0; i < childCount; i++) { if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) { return; } - View child = children.getChildAt(i); + View child = children.get(i); if (child.getAccessibilityViewId() != current.getAccessibilityViewId() && isShown(child)) { AccessibilityNodeInfo info = null; - AccessibilityNodeProvider provider = child.getAccessibilityNodeProvider(); + AccessibilityNodeProvider provider = + child.getAccessibilityNodeProvider(); if (provider == null) { info = child.createAccessibilityNodeInfo(); } else { @@ -703,7 +704,7 @@ final class AccessibilityInteractionController { } } } finally { - children.recycle(); + children.clear(); } } } @@ -716,14 +717,16 @@ final class AccessibilityInteractionController { ViewGroup rootGroup = (ViewGroup) root; HashMap addedChildren = new HashMap(); - ChildListForAccessibility children = ChildListForAccessibility.obtain(rootGroup, false); + ArrayList children = mTempViewList; + children.clear(); try { - final int childCount = children.getChildCount(); + root.addChildrenForAccessibility(children); + final int childCount = children.size(); for (int i = 0; i < childCount; i++) { if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) { return; } - View child = children.getChildAt(i); + View child = children.get(i); if (isShown(child)) { AccessibilityNodeProvider provider = child.getAccessibilityNodeProvider(); if (provider == null) { @@ -743,7 +746,7 @@ final class AccessibilityInteractionController { } } } finally { - children.recycle(); + children.clear(); } if (outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) { for (Map.Entry entry : addedChildren.entrySet()) { diff --git a/core/java/android/view/accessibility/AccessibilityInteractionClient.java b/core/java/android/view/accessibility/AccessibilityInteractionClient.java index bd341d0b10ebe..20b5f17405cb4 100644 --- a/core/java/android/view/accessibility/AccessibilityInteractionClient.java +++ b/core/java/android/view/accessibility/AccessibilityInteractionClient.java @@ -19,6 +19,7 @@ package android.view.accessibility; import android.accessibilityservice.IAccessibilityServiceConnection; import android.graphics.Rect; import android.os.Binder; +import android.os.Build; import android.os.Bundle; import android.os.Message; import android.os.Process; @@ -27,10 +28,14 @@ import android.os.SystemClock; import android.util.Log; import android.util.LongSparseArray; import android.util.SparseArray; +import android.util.SparseLongArray; import java.util.ArrayList; import java.util.Collections; +import java.util.HashSet; +import java.util.LinkedList; import java.util.List; +import java.util.Queue; import java.util.concurrent.atomic.AtomicInteger; /** @@ -74,6 +79,8 @@ public final class AccessibilityInteractionClient private static final boolean DEBUG = false; + private static final boolean CHECK_INTEGRITY = true; + private static final long TIMEOUT_INTERACTION_MILLIS = 5000; private static final Object sStaticLock = new Object(); @@ -491,6 +498,9 @@ public final class AccessibilityInteractionClient result = Collections.emptyList(); } clearResultLocked(); + if (Build.IS_DEBUGGABLE && CHECK_INTEGRITY) { + checkFindAccessibilityNodeInfoResultIntegrity(result); + } return result; } } @@ -696,4 +706,56 @@ public final class AccessibilityInteractionClient sConnectionCache.remove(connectionId); } } + + /** + * Checks whether the infos are a fully connected tree with no duplicates. + * + * @param infos The result list to check. + */ + private void checkFindAccessibilityNodeInfoResultIntegrity(List infos) { + if (infos.size() == 0) { + return; + } + // Find the root node. + AccessibilityNodeInfo root = infos.get(0); + final int infoCount = infos.size(); + for (int i = 1; i < infoCount; i++) { + for (int j = i; j < infoCount; j++) { + AccessibilityNodeInfo candidate = infos.get(j); + if (root.getParentNodeId() == candidate.getSourceNodeId()) { + root = candidate; + break; + } + } + } + if (root == null) { + Log.e(LOG_TAG, "No root."); + } + // Check for duplicates. + HashSet seen = new HashSet(); + Queue fringe = new LinkedList(); + fringe.add(root); + while (!fringe.isEmpty()) { + AccessibilityNodeInfo current = fringe.poll(); + if (!seen.add(current)) { + Log.e(LOG_TAG, "Duplicate node."); + return; + } + SparseLongArray childIds = current.getChildNodeIds(); + final int childCount = childIds.size(); + for (int i = 0; i < childCount; i++) { + final long childId = childIds.valueAt(i); + for (int j = 0; j < infoCount; j++) { + AccessibilityNodeInfo child = infos.get(j); + if (child.getSourceNodeId() == childId) { + fringe.add(child); + } + } + } + } + final int disconnectedCount = infos.size() - seen.size(); + if (disconnectedCount > 0) { + Log.e(LOG_TAG, disconnectedCount + " Disconnected nodes."); + } + } } diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfoCache.java b/core/java/android/view/accessibility/AccessibilityNodeInfoCache.java index 52b7772864f75..14954bea735ae 100644 --- a/core/java/android/view/accessibility/AccessibilityNodeInfoCache.java +++ b/core/java/android/view/accessibility/AccessibilityNodeInfoCache.java @@ -244,7 +244,7 @@ public class AccessibilityNodeInfoCache { /** * We are enforcing the invariant for a single accessibility focus. * - * @param currentInputFocusId The current input focused node. + * @param currentAccessibilityFocusId The current input focused node. */ private void clearSubtreeWithOldAccessibilityFocusLocked(long currentAccessibilityFocusId) { final int cacheSize = mCacheImpl.size(); diff --git a/core/java/android/widget/NumberPicker.java b/core/java/android/widget/NumberPicker.java index b825e1be75ca2..515f0c4bfb3f9 100644 --- a/core/java/android/widget/NumberPicker.java +++ b/core/java/android/widget/NumberPicker.java @@ -2490,7 +2490,7 @@ public class NumberPicker extends LinearLayout { info.addChild(NumberPicker.this, VIRTUAL_VIEW_ID_INCREMENT); } - info.setParent((View) getParent()); + info.setParent((View) getParentForAccessibility()); info.setEnabled(NumberPicker.this.isEnabled()); info.setScrollable(true); Rect boundsInParent = mTempRect;