diff options
| author | 2021-03-03 00:30:05 +0000 | |
|---|---|---|
| committer | 2021-03-03 00:30:05 +0000 | |
| commit | 4ce3fddf731fa6685539c5e8d51a81e1466112b6 (patch) | |
| tree | 2d40a9303f0480cfb57c3919ddcafcf12babc276 | |
| parent | f94c85b13021c83d50109d0feed25cf498f1cfbd (diff) | |
Revert "Prefetching can be interrupted by other service requests."
This reverts commit f94c85b13021c83d50109d0feed25cf498f1cfbd.
Reason for revert: Causing app crashes and runtime restarts in tests. See b/181701570 for details.
Change-Id: I2b5f7b80f07f5d8f564acdf20fcdb1e9b5b9da19
6 files changed, 238 insertions, 1026 deletions
diff --git a/core/java/android/view/AccessibilityInteractionController.java b/core/java/android/view/AccessibilityInteractionController.java index 0499f39f2fe4..9473845b15e6 100644 --- a/core/java/android/view/AccessibilityInteractionController.java +++ b/core/java/android/view/AccessibilityInteractionController.java @@ -86,19 +86,12 @@ public final class AccessibilityInteractionController { // accessibility from hanging private static final long REQUEST_PREPARER_TIMEOUT_MS = 500; - // Callbacks should have the same configuration of the flags below to allow satisfying a pending - // node request on prefetch - private static final int FLAGS_AFFECTING_REPORTED_DATA = - AccessibilityNodeInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS - | AccessibilityNodeInfo.FLAG_REPORT_VIEW_IDS; - private final ArrayList<AccessibilityNodeInfo> mTempAccessibilityNodeInfoList = new ArrayList<AccessibilityNodeInfo>(); private final Object mLock = new Object(); - @VisibleForTesting - public final PrivateHandler mHandler; + private final PrivateHandler mHandler; private final ViewRootImpl mViewRootImpl; @@ -121,9 +114,6 @@ public final class AccessibilityInteractionController { private AddNodeInfosForViewId mAddNodeInfosForViewId; @GuardedBy("mLock") - private ArrayList<Message> mPendingFindNodeByIdMessages; - - @GuardedBy("mLock") private int mNumActiveRequestPreparers; @GuardedBy("mLock") private List<MessageHolder> mMessagesWaitingForRequestPreparer; @@ -138,7 +128,6 @@ public final class AccessibilityInteractionController { mViewRootImpl = viewRootImpl; mPrefetcher = new AccessibilityNodePrefetcher(); mA11yManager = mViewRootImpl.mContext.getSystemService(AccessibilityManager.class); - mPendingFindNodeByIdMessages = new ArrayList<>(); } private void scheduleMessage(Message message, int interrogatingPid, long interrogatingTid, @@ -188,9 +177,6 @@ public final class AccessibilityInteractionController { args.arg4 = arguments; message.obj = args; - synchronized (mLock) { - mPendingFindNodeByIdMessages.add(message); - } scheduleMessage(message, interrogatingPid, interrogatingTid, CONSIDER_REQUEST_PREPARERS); } @@ -329,9 +315,6 @@ public final class AccessibilityInteractionController { } private void findAccessibilityNodeInfoByAccessibilityIdUiThread(Message message) { - synchronized (mLock) { - mPendingFindNodeByIdMessages.remove(message); - } final int flags = message.arg1; SomeArgs args = (SomeArgs) message.obj; @@ -346,58 +329,22 @@ public final class AccessibilityInteractionController { args.recycle(); - View rootView = null; - AccessibilityNodeInfo rootNode = null; + List<AccessibilityNodeInfo> infos = mTempAccessibilityNodeInfoList; + infos.clear(); try { if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) { return; } mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags; - rootView = findViewByAccessibilityId(accessibilityViewId); - if (rootView != null && isShown(rootView)) { - rootNode = populateAccessibilityNodeInfoForView( - rootView, arguments, virtualDescendantId); + final View root = findViewByAccessibilityId(accessibilityViewId); + if (root != null && isShown(root)) { + mPrefetcher.prefetchAccessibilityNodeInfos( + root, virtualDescendantId, flags, infos, arguments); } } finally { - updateInfoForViewportAndReturnFindNodeResult( - rootNode == null ? null : AccessibilityNodeInfo.obtain(rootNode), - callback, interactionId, spec, interactiveRegion); - } - ArrayList<AccessibilityNodeInfo> infos = mTempAccessibilityNodeInfoList; - infos.clear(); - mPrefetcher.prefetchAccessibilityNodeInfos( - rootView, rootNode == null ? null : AccessibilityNodeInfo.obtain(rootNode), - virtualDescendantId, flags, infos); - mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0; - updateInfosForViewPort(infos, spec, interactiveRegion); - returnPrefetchResult(interactionId, infos, callback); - returnPendingFindAccessibilityNodeInfosInPrefetch(rootNode, infos, flags); - } - - private AccessibilityNodeInfo populateAccessibilityNodeInfoForView( - View view, Bundle arguments, int virtualViewId) { - AccessibilityNodeProvider provider = view.getAccessibilityNodeProvider(); - // Determine if we'll be populating extra data - final String extraDataRequested = (arguments == null) ? null - : arguments.getString(EXTRA_DATA_REQUESTED_KEY); - AccessibilityNodeInfo root = null; - if (provider == null) { - root = view.createAccessibilityNodeInfo(); - if (root != null) { - if (extraDataRequested != null) { - view.addExtraDataToAccessibilityNodeInfo(root, extraDataRequested, arguments); - } - } - } else { - root = provider.createAccessibilityNodeInfo(virtualViewId); - if (root != null) { - if (extraDataRequested != null) { - provider.addExtraDataToAccessibilityNodeInfo( - virtualViewId, root, extraDataRequested, arguments); - } - } + updateInfosForViewportAndReturnFindNodeResult( + infos, callback, interactionId, spec, interactiveRegion); } - return root; } public void findAccessibilityNodeInfosByViewIdClientThread(long accessibilityNodeId, @@ -455,7 +402,6 @@ public final class AccessibilityInteractionController { mAddNodeInfosForViewId.reset(); } } finally { - mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0; updateInfosForViewportAndReturnFindNodeResult( infos, callback, interactionId, spec, interactiveRegion); } @@ -538,7 +484,6 @@ public final class AccessibilityInteractionController { } } } finally { - mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0; updateInfosForViewportAndReturnFindNodeResult( infos, callback, interactionId, spec, interactiveRegion); } @@ -630,7 +575,6 @@ public final class AccessibilityInteractionController { } } } finally { - mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0; updateInfoForViewportAndReturnFindNodeResult( focused, callback, interactionId, spec, interactiveRegion); } @@ -685,7 +629,6 @@ public final class AccessibilityInteractionController { } } } finally { - mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0; updateInfoForViewportAndReturnFindNodeResult( next, callback, interactionId, spec, interactiveRegion); } @@ -842,6 +785,33 @@ public final class AccessibilityInteractionController { } } + private void applyAppScaleAndMagnificationSpecIfNeeded(List<AccessibilityNodeInfo> infos, + MagnificationSpec spec) { + if (infos == null) { + return; + } + final float applicationScale = mViewRootImpl.mAttachInfo.mApplicationScale; + if (shouldApplyAppScaleAndMagnificationSpec(applicationScale, spec)) { + final int infoCount = infos.size(); + for (int i = 0; i < infoCount; i++) { + AccessibilityNodeInfo info = infos.get(i); + applyAppScaleAndMagnificationSpecIfNeeded(info, spec); + } + } + } + + private void adjustIsVisibleToUserIfNeeded(List<AccessibilityNodeInfo> infos, + Region interactiveRegion) { + if (interactiveRegion == null || infos == null) { + return; + } + final int infoCount = infos.size(); + for (int i = 0; i < infoCount; i++) { + AccessibilityNodeInfo info = infos.get(i); + adjustIsVisibleToUserIfNeeded(info, interactiveRegion); + } + } + private void adjustIsVisibleToUserIfNeeded(AccessibilityNodeInfo info, Region interactiveRegion) { if (interactiveRegion == null || info == null) { @@ -862,6 +832,17 @@ public final class AccessibilityInteractionController { return false; } + private void adjustBoundsInScreenIfNeeded(List<AccessibilityNodeInfo> infos) { + if (infos == null || shouldBypassAdjustBoundsInScreen()) { + return; + } + final int infoCount = infos.size(); + for (int i = 0; i < infoCount; i++) { + final AccessibilityNodeInfo info = infos.get(i); + adjustBoundsInScreenIfNeeded(info); + } + } + private void adjustBoundsInScreenIfNeeded(AccessibilityNodeInfo info) { if (info == null || shouldBypassAdjustBoundsInScreen()) { return; @@ -909,6 +890,17 @@ public final class AccessibilityInteractionController { return screenMatrix == null || screenMatrix.isIdentity(); } + private void associateLeashedParentIfNeeded(List<AccessibilityNodeInfo> infos) { + if (infos == null || shouldBypassAssociateLeashedParent()) { + return; + } + final int infoCount = infos.size(); + for (int i = 0; i < infoCount; i++) { + final AccessibilityNodeInfo info = infos.get(i); + associateLeashedParentIfNeeded(info); + } + } + private void associateLeashedParentIfNeeded(AccessibilityNodeInfo info) { if (info == null || shouldBypassAssociateLeashedParent()) { return; @@ -982,46 +974,18 @@ public final class AccessibilityInteractionController { return (appScale != 1.0f || (spec != null && !spec.isNop())); } - private void updateInfosForViewPort(List<AccessibilityNodeInfo> infos, MagnificationSpec spec, - Region interactiveRegion) { - for (int i = 0; i < infos.size(); i++) { - updateInfoForViewPort(infos.get(i), spec, interactiveRegion); - } - } - - private void updateInfoForViewPort(AccessibilityNodeInfo info, MagnificationSpec spec, - Region interactiveRegion) { - associateLeashedParentIfNeeded(info); - applyScreenMatrixIfNeeded(info); - adjustBoundsInScreenIfNeeded(info); - // To avoid applyAppScaleAndMagnificationSpecIfNeeded changing the bounds of node, - // then impact the visibility result, we need to adjust visibility before apply scale. - adjustIsVisibleToUserIfNeeded(info, interactiveRegion); - applyAppScaleAndMagnificationSpecIfNeeded(info, spec); - } - private void updateInfosForViewportAndReturnFindNodeResult(List<AccessibilityNodeInfo> infos, IAccessibilityInteractionConnectionCallback callback, int interactionId, MagnificationSpec spec, Region interactiveRegion) { - if (infos != null) { - updateInfosForViewPort(infos, spec, interactiveRegion); - } - returnFindNodesResult(infos, callback, interactionId); - } - - private void returnFindNodeResult(AccessibilityNodeInfo info, - IAccessibilityInteractionConnectionCallback callback, - int interactionId) { - try { - callback.setFindAccessibilityNodeInfoResult(info, interactionId); - } catch (RemoteException re) { - /* ignore - the other side will time out */ - } - } - - private void returnFindNodesResult(List<AccessibilityNodeInfo> infos, - IAccessibilityInteractionConnectionCallback callback, int interactionId) { try { + mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0; + associateLeashedParentIfNeeded(infos); + applyScreenMatrixIfNeeded(infos); + adjustBoundsInScreenIfNeeded(infos); + // To avoid applyAppScaleAndMagnificationSpecIfNeeded changing the bounds of node, + // then impact the visibility result, we need to adjust visibility before apply scale. + adjustIsVisibleToUserIfNeeded(infos, interactiveRegion); + applyAppScaleAndMagnificationSpecIfNeeded(infos, spec); callback.setFindAccessibilityNodeInfosResult(infos, interactionId); if (infos != null) { infos.clear(); @@ -1031,80 +995,22 @@ public final class AccessibilityInteractionController { } } - private void returnPendingFindAccessibilityNodeInfosInPrefetch(AccessibilityNodeInfo rootNode, - List<AccessibilityNodeInfo> infos, int flags) { - - AccessibilityNodeInfo satisfiedPendingRequestPrefetchedNode = null; - IAccessibilityInteractionConnectionCallback satisfiedPendingRequestCallback = null; - int satisfiedPendingRequestInteractionId = AccessibilityInteractionClient.NO_ID; - - synchronized (mLock) { - for (int i = 0; i < mPendingFindNodeByIdMessages.size(); i++) { - final Message pendingMessage = mPendingFindNodeByIdMessages.get(i); - final int pendingFlags = pendingMessage.arg1; - if ((pendingFlags & FLAGS_AFFECTING_REPORTED_DATA) - != (flags & FLAGS_AFFECTING_REPORTED_DATA)) { - continue; - } - SomeArgs args = (SomeArgs) pendingMessage.obj; - final int accessibilityViewId = args.argi1; - final int virtualDescendantId = args.argi2; - - satisfiedPendingRequestPrefetchedNode = nodeWithIdFromList(rootNode, - infos, AccessibilityNodeInfo.makeNodeId( - accessibilityViewId, virtualDescendantId)); - - if (satisfiedPendingRequestPrefetchedNode != null) { - satisfiedPendingRequestCallback = - (IAccessibilityInteractionConnectionCallback) args.arg1; - satisfiedPendingRequestInteractionId = args.argi3; - mHandler.removeMessages( - PrivateHandler.MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_ACCESSIBILITY_ID, - pendingMessage.obj); - args.recycle(); - break; - } - } - mPendingFindNodeByIdMessages.clear(); - } - - if (satisfiedPendingRequestPrefetchedNode != null) { - returnFindNodeResult( - AccessibilityNodeInfo.obtain(satisfiedPendingRequestPrefetchedNode), - satisfiedPendingRequestCallback, satisfiedPendingRequestInteractionId); - } - } - - private AccessibilityNodeInfo nodeWithIdFromList(AccessibilityNodeInfo rootNode, - List<AccessibilityNodeInfo> infos, long nodeId) { - if (rootNode != null && rootNode.getSourceNodeId() == nodeId) { - return rootNode; - } - for (int j = 0; j < infos.size(); j++) { - AccessibilityNodeInfo info = infos.get(j); - if (info.getSourceNodeId() == nodeId) { - return info; - } - } - return null; - } - - private void returnPrefetchResult(int interactionId, List<AccessibilityNodeInfo> infos, - IAccessibilityInteractionConnectionCallback callback) { - if (infos.size() > 0) { - try { - callback.setPrefetchAccessibilityNodeInfoResult(infos, interactionId); - } catch (RemoteException re) { - /* ignore - other side isn't too bothered if this doesn't arrive */ - } - } - } - private void updateInfoForViewportAndReturnFindNodeResult(AccessibilityNodeInfo info, IAccessibilityInteractionConnectionCallback callback, int interactionId, MagnificationSpec spec, Region interactiveRegion) { - updateInfoForViewPort(info, spec, interactiveRegion); - returnFindNodeResult(info, callback, interactionId); + try { + mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0; + associateLeashedParentIfNeeded(info); + applyScreenMatrixIfNeeded(info); + adjustBoundsInScreenIfNeeded(info); + // To avoid applyAppScaleAndMagnificationSpecIfNeeded changing the bounds of node, + // then impact the visibility result, we need to adjust visibility before apply scale. + adjustIsVisibleToUserIfNeeded(info, interactiveRegion); + applyAppScaleAndMagnificationSpecIfNeeded(info, spec); + callback.setFindAccessibilityNodeInfoResult(info, interactionId); + } catch (RemoteException re) { + /* ignore - the other side will time out */ + } } private boolean handleClickableSpanActionUiThread( @@ -1147,45 +1053,56 @@ public final class AccessibilityInteractionController { private final ArrayList<View> mTempViewList = new ArrayList<View>(); - public void prefetchAccessibilityNodeInfos(View view, AccessibilityNodeInfo root, - int virtualViewId, int fetchFlags, List<AccessibilityNodeInfo> outInfos) { - if (root == null) { - return; - } + public void prefetchAccessibilityNodeInfos(View view, int virtualViewId, int fetchFlags, + List<AccessibilityNodeInfo> outInfos, Bundle arguments) { AccessibilityNodeProvider provider = view.getAccessibilityNodeProvider(); + // Determine if we'll be populating extra data + final String extraDataRequested = (arguments == null) ? null + : arguments.getString(EXTRA_DATA_REQUESTED_KEY); if (provider == null) { - if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS) != 0) { - prefetchPredecessorsOfRealNode(view, outInfos); - } - if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS) != 0) { - prefetchSiblingsOfRealNode(view, outInfos); - } - if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS) != 0) { - prefetchDescendantsOfRealNode(view, outInfos); + AccessibilityNodeInfo root = view.createAccessibilityNodeInfo(); + if (root != null) { + if (extraDataRequested != null) { + view.addExtraDataToAccessibilityNodeInfo( + root, extraDataRequested, arguments); + } + outInfos.add(root); + if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS) != 0) { + prefetchPredecessorsOfRealNode(view, outInfos); + } + if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS) != 0) { + prefetchSiblingsOfRealNode(view, outInfos); + } + if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS) != 0) { + prefetchDescendantsOfRealNode(view, outInfos); + } } } else { - if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS) != 0) { - prefetchPredecessorsOfVirtualNode(root, view, provider, outInfos); - } - if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS) != 0) { - prefetchSiblingsOfVirtualNode(root, view, provider, outInfos); - } - if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS) != 0) { - prefetchDescendantsOfVirtualNode(root, provider, outInfos); + final AccessibilityNodeInfo root = + provider.createAccessibilityNodeInfo(virtualViewId); + if (root != null) { + if (extraDataRequested != null) { + provider.addExtraDataToAccessibilityNodeInfo( + virtualViewId, root, extraDataRequested, arguments); + } + outInfos.add(root); + if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS) != 0) { + prefetchPredecessorsOfVirtualNode(root, view, provider, outInfos); + } + if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS) != 0) { + prefetchSiblingsOfVirtualNode(root, view, provider, outInfos); + } + if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS) != 0) { + prefetchDescendantsOfVirtualNode(root, provider, outInfos); + } } } if (ENFORCE_NODE_TREE_CONSISTENT) { - enforceNodeTreeConsistent(root, outInfos); + enforceNodeTreeConsistent(outInfos); } } - private boolean shouldStopPrefetching(List prefetchededInfos) { - return mHandler.hasUserInteractiveMessagesWaiting() - || prefetchededInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE; - } - - private void enforceNodeTreeConsistent( - AccessibilityNodeInfo root, List<AccessibilityNodeInfo> nodes) { + private void enforceNodeTreeConsistent(List<AccessibilityNodeInfo> nodes) { LongSparseArray<AccessibilityNodeInfo> nodeMap = new LongSparseArray<AccessibilityNodeInfo>(); final int nodeCount = nodes.size(); @@ -1196,6 +1113,7 @@ public final class AccessibilityInteractionController { // If the nodes are a tree it does not matter from // which node we start to search for the root. + AccessibilityNodeInfo root = nodeMap.valueAt(0); AccessibilityNodeInfo parent = root; while (parent != null) { root = parent; @@ -1262,11 +1180,9 @@ public final class AccessibilityInteractionController { private void prefetchPredecessorsOfRealNode(View view, List<AccessibilityNodeInfo> outInfos) { - if (shouldStopPrefetching(outInfos)) { - return; - } ViewParent parent = view.getParentForAccessibility(); - while (parent instanceof View && !shouldStopPrefetching(outInfos)) { + while (parent instanceof View + && outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) { View parentView = (View) parent; AccessibilityNodeInfo info = parentView.createAccessibilityNodeInfo(); if (info != null) { @@ -1278,9 +1194,6 @@ public final class AccessibilityInteractionController { private void prefetchSiblingsOfRealNode(View current, List<AccessibilityNodeInfo> outInfos) { - if (shouldStopPrefetching(outInfos)) { - return; - } ViewParent parent = current.getParentForAccessibility(); if (parent instanceof ViewGroup) { ViewGroup parentGroup = (ViewGroup) parent; @@ -1290,7 +1203,7 @@ public final class AccessibilityInteractionController { parentGroup.addChildrenForAccessibility(children); final int childCount = children.size(); for (int i = 0; i < childCount; i++) { - if (shouldStopPrefetching(outInfos)) { + if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) { return; } View child = children.get(i); @@ -1318,7 +1231,7 @@ public final class AccessibilityInteractionController { private void prefetchDescendantsOfRealNode(View root, List<AccessibilityNodeInfo> outInfos) { - if (shouldStopPrefetching(outInfos) || !(root instanceof ViewGroup)) { + if (!(root instanceof ViewGroup)) { return; } HashMap<View, AccessibilityNodeInfo> addedChildren = @@ -1329,7 +1242,7 @@ public final class AccessibilityInteractionController { root.addChildrenForAccessibility(children); final int childCount = children.size(); for (int i = 0; i < childCount; i++) { - if (shouldStopPrefetching(outInfos)) { + if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) { return; } View child = children.get(i); @@ -1354,7 +1267,7 @@ public final class AccessibilityInteractionController { } finally { children.clear(); } - if (!shouldStopPrefetching(outInfos)) { + if (outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) { for (Map.Entry<View, AccessibilityNodeInfo> entry : addedChildren.entrySet()) { View addedChild = entry.getKey(); AccessibilityNodeInfo virtualRoot = entry.getValue(); @@ -1376,7 +1289,7 @@ public final class AccessibilityInteractionController { long parentNodeId = root.getParentNodeId(); int accessibilityViewId = AccessibilityNodeInfo.getAccessibilityViewId(parentNodeId); while (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) { - if (shouldStopPrefetching(outInfos)) { + if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) { return; } final int virtualDescendantId = @@ -1421,7 +1334,7 @@ public final class AccessibilityInteractionController { if (parent != null) { final int childCount = parent.getChildCount(); for (int i = 0; i < childCount; i++) { - if (shouldStopPrefetching(outInfos)) { + if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) { return; } final long childNodeId = parent.getChildId(i); @@ -1446,7 +1359,7 @@ public final class AccessibilityInteractionController { final int initialOutInfosSize = outInfos.size(); final int childCount = root.getChildCount(); for (int i = 0; i < childCount; i++) { - if (shouldStopPrefetching(outInfos)) { + if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) { return; } final long childNodeId = root.getChildId(i); @@ -1456,7 +1369,7 @@ public final class AccessibilityInteractionController { outInfos.add(child); } } - if (!shouldStopPrefetching(outInfos)) { + if (outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) { final int addedChildCount = outInfos.size() - initialOutInfosSize; for (int i = 0; i < addedChildCount; i++) { AccessibilityNodeInfo child = outInfos.get(initialOutInfosSize + i); @@ -1565,10 +1478,6 @@ public final class AccessibilityInteractionController { boolean hasAccessibilityCallback(Message message) { return message.what < FIRST_NO_ACCESSIBILITY_CALLBACK_MSG ? true : false; } - - boolean hasUserInteractiveMessagesWaiting() { - return hasMessagesOrCallbacks(); - } } private final class AddNodeInfosForViewId implements Predicate<View> { diff --git a/core/java/android/view/accessibility/AccessibilityInteractionClient.java b/core/java/android/view/accessibility/AccessibilityInteractionClient.java index 9556c25575cd..f63749be6df2 100644 --- a/core/java/android/view/accessibility/AccessibilityInteractionClient.java +++ b/core/java/android/view/accessibility/AccessibilityInteractionClient.java @@ -23,9 +23,7 @@ import android.compat.annotation.UnsupportedAppUsage; import android.os.Binder; import android.os.Build; import android.os.Bundle; -import android.os.Handler; import android.os.IBinder; -import android.os.Looper; import android.os.Message; import android.os.Process; import android.os.RemoteException; @@ -115,8 +113,6 @@ public final class AccessibilityInteractionClient private final Object mInstanceLock = new Object(); - private Handler mMainHandler; - private volatile int mInteractionId = -1; private AccessibilityNodeInfo mFindAccessibilityNodeInfoResult; @@ -127,11 +123,6 @@ public final class AccessibilityInteractionClient private Message mSameThreadMessage; - private int mInteractionIdWaitingForPrefetchResult; - private int mConnectionIdWaitingForPrefetchResult; - private String[] mPackageNamesForNextPrefetchResult; - private Runnable mPrefetchResultRunnable; - /** * @return The client for the current thread. */ @@ -206,9 +197,6 @@ public final class AccessibilityInteractionClient private AccessibilityInteractionClient() { /* reducing constructor visibility */ - if (Looper.getMainLooper() != null) { - mMainHandler = new Handler(Looper.getMainLooper()); - } } /** @@ -463,16 +451,16 @@ public final class AccessibilityInteractionClient Binder.restoreCallingIdentity(identityToken); } if (packageNames != null) { - AccessibilityNodeInfo info = - getFindAccessibilityNodeInfoResultAndClear(interactionId); - if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_MASK) != 0 - && info != null) { - setInteractionWaitingForPrefetchResult(interactionId, connectionId, - packageNames); - } - finalizeAndCacheAccessibilityNodeInfo(info, connectionId, + List<AccessibilityNodeInfo> infos = getFindAccessibilityNodeInfosResultAndClear( + interactionId); + finalizeAndCacheAccessibilityNodeInfos(infos, connectionId, bypassCache, packageNames); - return info; + if (infos != null && !infos.isEmpty()) { + for (int i = 1; i < infos.size(); i++) { + infos.get(i).recycle(); + } + return infos.get(0); + } } } else { if (DEBUG) { @@ -486,15 +474,6 @@ public final class AccessibilityInteractionClient return null; } - private void setInteractionWaitingForPrefetchResult(int interactionId, int connectionId, - String[] packageNames) { - synchronized (mInstanceLock) { - mInteractionIdWaitingForPrefetchResult = interactionId; - mConnectionIdWaitingForPrefetchResult = connectionId; - mPackageNamesForNextPrefetchResult = packageNames; - } - } - private static String idToString(int accessibilityWindowId, long accessibilityNodeId) { return accessibilityWindowId + "/" + AccessibilityNodeInfo.idToString(accessibilityNodeId); @@ -850,60 +829,6 @@ public final class AccessibilityInteractionClient } /** - * {@inheritDoc} - */ - @Override - public void setPrefetchAccessibilityNodeInfoResult(@NonNull List<AccessibilityNodeInfo> infos, - int interactionId) { - List<AccessibilityNodeInfo> infosCopy = null; - int mConnectionIdWaitingForPrefetchResultCopy = -1; - String[] mPackageNamesForNextPrefetchResultCopy = null; - - synchronized (mInstanceLock) { - if (!infos.isEmpty() && mInteractionIdWaitingForPrefetchResult == interactionId) { - if (mMainHandler != null) { - if (mPrefetchResultRunnable != null) { - mMainHandler.removeCallbacks(mPrefetchResultRunnable); - mPrefetchResultRunnable = null; - } - /** - * TODO(b/180957109): AccessibilityCache is prone to deadlocks - * We post caching the prefetched nodes in the main thread. Using the binder - * thread results in "Long monitor contention with owner main" logs where - * service response times may exceed 5 seconds. This is due to the cache calling - * out to the system when refreshing nodes with the lock held. - */ - mPrefetchResultRunnable = () -> finalizeAndCacheAccessibilityNodeInfos( - infos, mConnectionIdWaitingForPrefetchResult, false, - mPackageNamesForNextPrefetchResult); - mMainHandler.post(mPrefetchResultRunnable); - - } else { - for (AccessibilityNodeInfo info : infos) { - infosCopy.add(new AccessibilityNodeInfo(info)); - } - mConnectionIdWaitingForPrefetchResultCopy = - mConnectionIdWaitingForPrefetchResult; - mPackageNamesForNextPrefetchResultCopy = - new String[mPackageNamesForNextPrefetchResult.length]; - for (int i = 0; i < mPackageNamesForNextPrefetchResult.length; i++) { - mPackageNamesForNextPrefetchResultCopy[i] = - mPackageNamesForNextPrefetchResult[i]; - - } - } - } - - } - - if (infosCopy != null) { - finalizeAndCacheAccessibilityNodeInfos( - infosCopy, mConnectionIdWaitingForPrefetchResultCopy, false, - mPackageNamesForNextPrefetchResultCopy); - } - } - - /** * Gets the result of a request to perform an accessibility action. * * @param interactionId The interaction id to match the result with the request. diff --git a/core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl b/core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl index 231e75a19a06..049bb31adbb1 100644 --- a/core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl +++ b/core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl @@ -47,15 +47,6 @@ oneway interface IAccessibilityInteractionConnectionCallback { int interactionId); /** - * Sets the result of a prefetch request that returns {@link AccessibilityNodeInfo}s. - * - * @param root The {@link AccessibilityNodeInfo} for which the prefetching is based off of. - * @param infos The result {@link AccessibilityNodeInfo}s. - */ - void setPrefetchAccessibilityNodeInfoResult( - in List<AccessibilityNodeInfo> infos, int interactionId); - - /** * Sets the result of a request to perform an accessibility action. * * @param Whether the action was performed. diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityInteractionClientTest.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityInteractionClientTest.java index 7e1e7f4bdd7f..ab24f89015c7 100644 --- a/core/tests/coretests/src/android/view/accessibility/AccessibilityInteractionClientTest.java +++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityInteractionClientTest.java @@ -33,6 +33,9 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; +import java.util.Arrays; +import java.util.List; + /** * Tests for AccessibilityInteractionClient */ @@ -62,7 +65,7 @@ public class AccessibilityInteractionClientTest { final long accessibilityNodeId = 0x4321L; AccessibilityNodeInfo nodeFromConnection = AccessibilityNodeInfo.obtain(); nodeFromConnection.setSourceNodeId(accessibilityNodeId, windowId); - mMockConnection.mInfoToReturn = nodeFromConnection; + mMockConnection.mInfosToReturn = Arrays.asList(nodeFromConnection); AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance(); AccessibilityNodeInfo node = client.findAccessibilityNodeInfoByAccessibilityId( MOCK_CONNECTION_ID, windowId, accessibilityNodeId, true, 0, null); @@ -72,7 +75,7 @@ public class AccessibilityInteractionClientTest { } private static class MockConnection extends AccessibilityServiceConnectionImpl { - AccessibilityNodeInfo mInfoToReturn; + List<AccessibilityNodeInfo> mInfosToReturn; @Override public String[] findAccessibilityNodeInfoByAccessibilityId(int accessibilityWindowId, @@ -80,7 +83,7 @@ public class AccessibilityInteractionClientTest { IAccessibilityInteractionConnectionCallback callback, int flags, long threadId, Bundle arguments) { try { - callback.setFindAccessibilityNodeInfoResult(mInfoToReturn, interactionId); + callback.setFindAccessibilityNodeInfosResult(mInfosToReturn, interactionId); } catch (RemoteException e) { throw new RuntimeException(e); } diff --git a/services/accessibility/java/com/android/server/accessibility/ActionReplacingCallback.java b/services/accessibility/java/com/android/server/accessibility/ActionReplacingCallback.java index 6828dd916701..bafb641dcc9e 100644 --- a/services/accessibility/java/com/android/server/accessibility/ActionReplacingCallback.java +++ b/services/accessibility/java/com/android/server/accessibility/ActionReplacingCallback.java @@ -40,34 +40,29 @@ public class ActionReplacingCallback extends IAccessibilityInteractionConnection private final IAccessibilityInteractionConnectionCallback mServiceCallback; private final IAccessibilityInteractionConnection mConnectionWithReplacementActions; private final int mInteractionId; - private final int mNodeWithReplacementActionsInteractionId; private final Object mLock = new Object(); @GuardedBy("mLock") - private boolean mReplacementNodeIsReadyOrFailed; - - @GuardedBy("mLock") - AccessibilityNodeInfo mNodeWithReplacementActions; + List<AccessibilityNodeInfo> mNodesWithReplacementActions; @GuardedBy("mLock") List<AccessibilityNodeInfo> mNodesFromOriginalWindow; @GuardedBy("mLock") - boolean mSetFindNodeFromOriginalWindowCalled = false; - - @GuardedBy("mLock") AccessibilityNodeInfo mNodeFromOriginalWindow; + // Keep track of whether or not we've been called back for a single node @GuardedBy("mLock") - boolean mSetFindNodesFromOriginalWindowCalled = false; - + boolean mSingleNodeCallbackHappened; + // Keep track of whether or not we've been called back for multiple node @GuardedBy("mLock") - List<AccessibilityNodeInfo> mPrefetchedNodesFromOriginalWindow; + boolean mMultiNodeCallbackHappened; + // We shouldn't get any more callbacks after we've called back the original service, but + // keep track to make sure we catch such strange things @GuardedBy("mLock") - boolean mSetPrefetchFromOriginalWindowCalled = false; - + boolean mDone; public ActionReplacingCallback(IAccessibilityInteractionConnectionCallback serviceCallback, IAccessibilityInteractionConnection connectionWithReplacementActions, @@ -75,20 +70,19 @@ public class ActionReplacingCallback extends IAccessibilityInteractionConnection mServiceCallback = serviceCallback; mConnectionWithReplacementActions = connectionWithReplacementActions; mInteractionId = interactionId; - mNodeWithReplacementActionsInteractionId = interactionId + 1; // Request the root node of the replacing window final long identityToken = Binder.clearCallingIdentity(); try { mConnectionWithReplacementActions.findAccessibilityNodeInfoByAccessibilityId( - AccessibilityNodeInfo.ROOT_NODE_ID, null, - mNodeWithReplacementActionsInteractionId, this, 0, + AccessibilityNodeInfo.ROOT_NODE_ID, null, interactionId + 1, this, 0, interrogatingPid, interrogatingTid, null, null); } catch (RemoteException re) { if (DEBUG) { Slog.e(LOG_TAG, "Error calling findAccessibilityNodeInfoByAccessibilityId()"); } - mReplacementNodeIsReadyOrFailed = true; + // Pretend we already got a (null) list of replacement nodes + mMultiNodeCallbackHappened = true; } finally { Binder.restoreCallingIdentity(identityToken); } @@ -96,67 +90,46 @@ public class ActionReplacingCallback extends IAccessibilityInteractionConnection @Override public void setFindAccessibilityNodeInfoResult(AccessibilityNodeInfo info, int interactionId) { - synchronized (mLock) { + boolean readyForCallback; + synchronized(mLock) { if (interactionId == mInteractionId) { mNodeFromOriginalWindow = info; - mSetFindNodeFromOriginalWindowCalled = true; - } else if (interactionId == mNodeWithReplacementActionsInteractionId) { - mNodeWithReplacementActions = info; - mReplacementNodeIsReadyOrFailed = true; } else { Slog.e(LOG_TAG, "Callback with unexpected interactionId"); return; } + + mSingleNodeCallbackHappened = true; + readyForCallback = mMultiNodeCallbackHappened; + } + if (readyForCallback) { + replaceInfoActionsAndCallService(); } - replaceInfoActionsAndCallServiceIfReady(); } @Override public void setFindAccessibilityNodeInfosResult(List<AccessibilityNodeInfo> infos, int interactionId) { - synchronized (mLock) { + boolean callbackForSingleNode; + boolean callbackForMultipleNodes; + synchronized(mLock) { if (interactionId == mInteractionId) { mNodesFromOriginalWindow = infos; - mSetFindNodesFromOriginalWindowCalled = true; - } else if (interactionId == mNodeWithReplacementActionsInteractionId) { - setNodeWithReplacementActionsFromList(infos); - mReplacementNodeIsReadyOrFailed = true; + } else if (interactionId == mInteractionId + 1) { + mNodesWithReplacementActions = infos; } else { Slog.e(LOG_TAG, "Callback with unexpected interactionId"); return; } + callbackForSingleNode = mSingleNodeCallbackHappened; + callbackForMultipleNodes = mMultiNodeCallbackHappened; + mMultiNodeCallbackHappened = true; } - replaceInfoActionsAndCallServiceIfReady(); - } - - @Override - public void setPrefetchAccessibilityNodeInfoResult(List<AccessibilityNodeInfo> infos, - int interactionId) - throws RemoteException { - synchronized (mLock) { - if (interactionId == mInteractionId) { - mPrefetchedNodesFromOriginalWindow = infos; - mSetPrefetchFromOriginalWindowCalled = true; - } else { - Slog.e(LOG_TAG, "Callback with unexpected interactionId"); - return; - } + if (callbackForSingleNode) { + replaceInfoActionsAndCallService(); } - replaceInfoActionsAndCallServiceIfReady(); - } - - private void replaceInfoActionsAndCallServiceIfReady() { - replaceInfoActionsAndCallService(); - replaceInfosActionsAndCallService(); - replacePrefetchInfosActionsAndCallService(); - } - - private void setNodeWithReplacementActionsFromList(List<AccessibilityNodeInfo> infos) { - for (int i = 0; i < infos.size(); i++) { - AccessibilityNodeInfo info = infos.get(i); - if (info.getSourceNodeId() == AccessibilityNodeInfo.ROOT_NODE_ID) { - mNodeWithReplacementActions = info; - } + if (callbackForMultipleNodes) { + replaceInfosActionsAndCallService(); } } @@ -169,81 +142,55 @@ public class ActionReplacingCallback extends IAccessibilityInteractionConnection private void replaceInfoActionsAndCallService() { final AccessibilityNodeInfo nodeToReturn; - boolean doCallback = false; synchronized (mLock) { - doCallback = mReplacementNodeIsReadyOrFailed - && mSetFindNodeFromOriginalWindowCalled; - if (doCallback && mNodeFromOriginalWindow != null) { + if (mDone) { + if (DEBUG) { + Slog.e(LOG_TAG, "Extra callback"); + } + return; + } + if (mNodeFromOriginalWindow != null) { replaceActionsOnInfoLocked(mNodeFromOriginalWindow); - mSetFindNodeFromOriginalWindowCalled = false; } + recycleReplaceActionNodesLocked(); nodeToReturn = mNodeFromOriginalWindow; + mDone = true; } - if (doCallback) { - try { - mServiceCallback.setFindAccessibilityNodeInfoResult(nodeToReturn, mInteractionId); - } catch (RemoteException re) { - if (DEBUG) { - Slog.e(LOG_TAG, "Failed to setFindAccessibilityNodeInfoResult"); - } + try { + mServiceCallback.setFindAccessibilityNodeInfoResult(nodeToReturn, mInteractionId); + } catch (RemoteException re) { + if (DEBUG) { + Slog.e(LOG_TAG, "Failed to setFindAccessibilityNodeInfoResult"); } } } private void replaceInfosActionsAndCallService() { - List<AccessibilityNodeInfo> nodesToReturn = null; - boolean doCallback = false; + final List<AccessibilityNodeInfo> nodesToReturn; synchronized (mLock) { - doCallback = mReplacementNodeIsReadyOrFailed - && mSetFindNodesFromOriginalWindowCalled; - if (doCallback) { - nodesToReturn = replaceActionsLocked(mNodesFromOriginalWindow); - mSetFindNodesFromOriginalWindowCalled = false; - } - } - if (doCallback) { - try { - mServiceCallback.setFindAccessibilityNodeInfosResult(nodesToReturn, mInteractionId); - } catch (RemoteException re) { + if (mDone) { if (DEBUG) { - Slog.e(LOG_TAG, "Failed to setFindAccessibilityNodeInfosResult"); + Slog.e(LOG_TAG, "Extra callback"); } + return; } - } - } - - private void replacePrefetchInfosActionsAndCallService() { - List<AccessibilityNodeInfo> nodesToReturn = null; - boolean doCallback = false; - synchronized (mLock) { - doCallback = mReplacementNodeIsReadyOrFailed - && mSetPrefetchFromOriginalWindowCalled; - if (doCallback) { - nodesToReturn = replaceActionsLocked(mPrefetchedNodesFromOriginalWindow); - mSetPrefetchFromOriginalWindowCalled = false; - } - } - if (doCallback) { - try { - mServiceCallback.setPrefetchAccessibilityNodeInfoResult( - nodesToReturn, mInteractionId); - } catch (RemoteException re) { - if (DEBUG) { - Slog.e(LOG_TAG, "Failed to setFindAccessibilityNodeInfosResult"); + if (mNodesFromOriginalWindow != null) { + for (int i = 0; i < mNodesFromOriginalWindow.size(); i++) { + replaceActionsOnInfoLocked(mNodesFromOriginalWindow.get(i)); } } + recycleReplaceActionNodesLocked(); + nodesToReturn = (mNodesFromOriginalWindow == null) + ? null : new ArrayList<>(mNodesFromOriginalWindow); + mDone = true; } - } - - @GuardedBy("mLock") - private List<AccessibilityNodeInfo> replaceActionsLocked(List<AccessibilityNodeInfo> infos) { - if (infos != null) { - for (int i = 0; i < infos.size(); i++) { - replaceActionsOnInfoLocked(infos.get(i)); + try { + mServiceCallback.setFindAccessibilityNodeInfosResult(nodesToReturn, mInteractionId); + } catch (RemoteException re) { + if (DEBUG) { + Slog.e(LOG_TAG, "Failed to setFindAccessibilityNodeInfosResult"); } } - return (infos == null) - ? null : new ArrayList<>(infos); } @GuardedBy("mLock") @@ -257,22 +204,40 @@ public class ActionReplacingCallback extends IAccessibilityInteractionConnection info.setDismissable(false); // We currently only replace actions for the root node if ((info.getSourceNodeId() == AccessibilityNodeInfo.ROOT_NODE_ID) - && mNodeWithReplacementActions != null) { - List<AccessibilityAction> actions = mNodeWithReplacementActions.getActionList(); - if (actions != null) { - for (int j = 0; j < actions.size(); j++) { - info.addAction(actions.get(j)); + && mNodesWithReplacementActions != null) { + // This list should always contain a single node with the root ID + for (int i = 0; i < mNodesWithReplacementActions.size(); i++) { + AccessibilityNodeInfo nodeWithReplacementActions = + mNodesWithReplacementActions.get(i); + if (nodeWithReplacementActions.getSourceNodeId() + == AccessibilityNodeInfo.ROOT_NODE_ID) { + List<AccessibilityAction> actions = nodeWithReplacementActions.getActionList(); + if (actions != null) { + for (int j = 0; j < actions.size(); j++) { + info.addAction(actions.get(j)); + } + // The PIP needs to be able to take accessibility focus + info.addAction(AccessibilityAction.ACTION_ACCESSIBILITY_FOCUS); + info.addAction(AccessibilityAction.ACTION_CLEAR_ACCESSIBILITY_FOCUS); + } + info.setClickable(nodeWithReplacementActions.isClickable()); + info.setFocusable(nodeWithReplacementActions.isFocusable()); + info.setContextClickable(nodeWithReplacementActions.isContextClickable()); + info.setScrollable(nodeWithReplacementActions.isScrollable()); + info.setLongClickable(nodeWithReplacementActions.isLongClickable()); + info.setDismissable(nodeWithReplacementActions.isDismissable()); } - // The PIP needs to be able to take accessibility focus - info.addAction(AccessibilityAction.ACTION_ACCESSIBILITY_FOCUS); - info.addAction(AccessibilityAction.ACTION_CLEAR_ACCESSIBILITY_FOCUS); } - info.setClickable(mNodeWithReplacementActions.isClickable()); - info.setFocusable(mNodeWithReplacementActions.isFocusable()); - info.setContextClickable(mNodeWithReplacementActions.isContextClickable()); - info.setScrollable(mNodeWithReplacementActions.isScrollable()); - info.setLongClickable(mNodeWithReplacementActions.isLongClickable()); - info.setDismissable(mNodeWithReplacementActions.isDismissable()); } } + + @GuardedBy("mLock") + private void recycleReplaceActionNodesLocked() { + if (mNodesWithReplacementActions == null) return; + for (int i = mNodesWithReplacementActions.size() - 1; i >= 0; i--) { + AccessibilityNodeInfo nodeWithReplacementAction = mNodesWithReplacementActions.get(i); + nodeWithReplacementAction.recycle(); + } + mNodesWithReplacementActions = null; + } } diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInteractionControllerNodeRequestsTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInteractionControllerNodeRequestsTest.java deleted file mode 100644 index 170f561aa2da..000000000000 --- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInteractionControllerNodeRequestsTest.java +++ /dev/null @@ -1,581 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.accessibility; - - -import static android.view.accessibility.AccessibilityNodeInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS; -import static android.view.accessibility.AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS; -import static android.view.accessibility.AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS; -import static android.view.accessibility.AccessibilityNodeInfo.ROOT_NODE_ID; - -import static org.junit.Assert.assertEquals; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.ArgumentMatchers.anyList; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; - -import android.app.Instrumentation; -import android.content.Context; -import android.os.RemoteException; -import android.view.AccessibilityInteractionController; -import android.view.View; -import android.view.ViewRootImpl; -import android.view.WindowManager; -import android.view.accessibility.AccessibilityNodeIdManager; -import android.view.accessibility.AccessibilityNodeInfo; -import android.view.accessibility.AccessibilityNodeProvider; -import android.view.accessibility.IAccessibilityInteractionConnectionCallback; -import android.widget.FrameLayout; -import android.widget.TextView; - -import androidx.test.platform.app.InstrumentationRegistry; -import androidx.test.runner.AndroidJUnit4; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; -import org.mockito.Captor; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -import java.util.ArrayList; -import java.util.List; - -/** - * Tests that verify expected node and prefetched node results when finding a view by node id. We - * send some requests to the controller via View methods to control message timing. - */ -@RunWith(AndroidJUnit4.class) -public class AccessibilityInteractionControllerNodeRequestsTest { - private AccessibilityInteractionController mAccessibilityInteractionController; - @Mock - private IAccessibilityInteractionConnectionCallback mMockClientCallback1; - @Mock - private IAccessibilityInteractionConnectionCallback mMockClientCallback2; - - @Captor - private ArgumentCaptor<AccessibilityNodeInfo> mFindInfoCaptor; - @Captor private ArgumentCaptor<List<AccessibilityNodeInfo>> mPrefetchInfoListCaptor; - - private final Instrumentation mInstrumentation = InstrumentationRegistry.getInstrumentation(); - private static final int MOCK_CLIENT_1_THREAD_AND_PROCESS_ID = 1; - private static final int MOCK_CLIENT_2_THREAD_AND_PROCESS_ID = 2; - - private static final String FRAME_LAYOUT_DESCRIPTION = "frameLayout"; - private static final String TEXT_VIEW_1_DESCRIPTION = "textView1"; - private static final String TEXT_VIEW_2_DESCRIPTION = "textView2"; - - private TestFrameLayout mFrameLayout; - private TestTextView mTextView1; - private TestTextView2 mTextView2; - - private boolean mSendClient1RequestForTextAfterTextPrefetched; - private boolean mSendClient2RequestForTextAfterTextPrefetched; - private boolean mSendRequestForTextAndIncludeUnImportantViews; - private int mMockClient1InteractionId; - private int mMockClient2InteractionId; - - @Before - public void setUp() throws Throwable { - MockitoAnnotations.initMocks(this); - - mInstrumentation.runOnMainSync(() -> { - final Context context = mInstrumentation.getTargetContext(); - final ViewRootImpl viewRootImpl = new ViewRootImpl(context, context.getDisplay()); - - mFrameLayout = new TestFrameLayout(context); - mTextView1 = new TestTextView(context); - mTextView2 = new TestTextView2(context); - - mFrameLayout.addView(mTextView1); - mFrameLayout.addView(mTextView2); - - // The controller retrieves views through this manager, and registration happens on - // when attached to a window, which we don't have. We can simply reference FrameLayout - // with ROOT_NODE_ID - AccessibilityNodeIdManager.getInstance().registerViewWithId( - mTextView1, mTextView1.getAccessibilityViewId()); - AccessibilityNodeIdManager.getInstance().registerViewWithId( - mTextView2, mTextView2.getAccessibilityViewId()); - - try { - viewRootImpl.setView(mFrameLayout, new WindowManager.LayoutParams(), null); - - } catch (WindowManager.BadTokenException e) { - // activity isn't running, we will ignore BadTokenException. - } - - mAccessibilityInteractionController = - new AccessibilityInteractionController(viewRootImpl); - }); - - } - - @After - public void tearDown() throws Throwable { - AccessibilityNodeIdManager.getInstance().unregisterViewWithId( - mTextView1.getAccessibilityViewId()); - AccessibilityNodeIdManager.getInstance().unregisterViewWithId( - mTextView2.getAccessibilityViewId()); - } - - /** - * Tests a basic request for the root node with prefetch flag - * {@link AccessibilityNodeInfo#FLAG_PREFETCH_DESCENDANTS} - * - * @throws RemoteException - */ - @Test - public void testFindRootView_withOneClient_shouldReturnRootNodeAndPrefetchDescendants() - throws RemoteException { - // Request for our FrameLayout - sendNodeRequestToController(ROOT_NODE_ID, mMockClientCallback1, - mMockClient1InteractionId, FLAG_PREFETCH_DESCENDANTS); - mInstrumentation.waitForIdleSync(); - - // Verify we get FrameLayout - verify(mMockClientCallback1).setFindAccessibilityNodeInfoResult( - mFindInfoCaptor.capture(), eq(mMockClient1InteractionId)); - AccessibilityNodeInfo infoSentToService = mFindInfoCaptor.getValue(); - assertEquals(FRAME_LAYOUT_DESCRIPTION, infoSentToService.getContentDescription()); - - verify(mMockClientCallback1).setPrefetchAccessibilityNodeInfoResult( - mPrefetchInfoListCaptor.capture(), eq(mMockClient1InteractionId)); - // The descendants are our two TextViews - List<AccessibilityNodeInfo> prefetchedNodes = mPrefetchInfoListCaptor.getValue(); - assertEquals(2, prefetchedNodes.size()); - assertEquals(TEXT_VIEW_1_DESCRIPTION, prefetchedNodes.get(0).getContentDescription()); - assertEquals(TEXT_VIEW_2_DESCRIPTION, prefetchedNodes.get(1).getContentDescription()); - - } - - /** - * Tests a basic request for TestTextView1's node with prefetch flag - * {@link AccessibilityNodeInfo#FLAG_PREFETCH_SIBLINGS} - * - * @throws RemoteException - */ - @Test - public void testFindTextView_withOneClient_shouldReturnNodeAndPrefetchedSiblings() - throws RemoteException { - // Request for TextView1 - sendNodeRequestToController(AccessibilityNodeInfo.makeNodeId( - mTextView1.getAccessibilityViewId(), AccessibilityNodeProvider.HOST_VIEW_ID), - mMockClientCallback1, mMockClient1InteractionId, FLAG_PREFETCH_SIBLINGS); - mInstrumentation.waitForIdleSync(); - - // Verify we get TextView1 - verify(mMockClientCallback1).setFindAccessibilityNodeInfoResult( - mFindInfoCaptor.capture(), eq(mMockClient1InteractionId)); - AccessibilityNodeInfo infoSentToService = mFindInfoCaptor.getValue(); - assertEquals(TEXT_VIEW_1_DESCRIPTION, infoSentToService.getContentDescription()); - - // Verify the prefetched sibling of TextView1 is TextView2 - verify(mMockClientCallback1).setPrefetchAccessibilityNodeInfoResult( - mPrefetchInfoListCaptor.capture(), eq(mMockClient1InteractionId)); - // TextView2 is the prefetched sibling - List<AccessibilityNodeInfo> prefetchedNodes = mPrefetchInfoListCaptor.getValue(); - assertEquals(1, prefetchedNodes.size()); - assertEquals(TEXT_VIEW_2_DESCRIPTION, prefetchedNodes.get(0).getContentDescription()); - } - - /** - * Tests a series of controller requests to prevent prefetching. - * Request 1: Client 1 requests the root node - * Request 2: When the root node is initialized in - * {@link TestFrameLayout#onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo)}, - * Client 2 requests TestTextView1's node - * - * Request 2 on the queue prevents prefetching for Request 1. - * - * @throws RemoteException - */ - @Test - public void testFindRootAndTextNodes_withTwoClients_shouldPreventClient1Prefetch() - throws RemoteException { - mFrameLayout.setAccessibilityDelegate(new View.AccessibilityDelegate() { - @Override - public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) { - super.onInitializeAccessibilityNodeInfo(host, info); - final long nodeId = AccessibilityNodeInfo.makeNodeId( - mTextView1.getAccessibilityViewId(), - AccessibilityNodeProvider.HOST_VIEW_ID); - - // Enqueue a request when this node is found from a different service for - // TextView1 - sendNodeRequestToController(nodeId, mMockClientCallback2, - mMockClient2InteractionId, FLAG_PREFETCH_SIBLINGS); - } - }); - // Client 1 request for FrameLayout - sendNodeRequestToController(ROOT_NODE_ID, mMockClientCallback1, - mMockClient1InteractionId, FLAG_PREFETCH_DESCENDANTS); - - mInstrumentation.waitForIdleSync(); - - // Verify client 1 gets FrameLayout - verify(mMockClientCallback1).setFindAccessibilityNodeInfoResult( - mFindInfoCaptor.capture(), eq(mMockClient1InteractionId)); - AccessibilityNodeInfo infoSentToService = mFindInfoCaptor.getValue(); - assertEquals(FRAME_LAYOUT_DESCRIPTION, infoSentToService.getContentDescription()); - - // The second request is put in the queue in the FrameLayout's onInitializeA11yNodeInfo, - // meaning prefetching is interrupted and does not even begin for the first request - verify(mMockClientCallback1, never()) - .setPrefetchAccessibilityNodeInfoResult(anyList(), anyInt()); - - // Verify client 2 gets TextView1 - verify(mMockClientCallback2).setFindAccessibilityNodeInfoResult( - mFindInfoCaptor.capture(), eq(mMockClient2InteractionId)); - infoSentToService = mFindInfoCaptor.getValue(); - assertEquals(TEXT_VIEW_1_DESCRIPTION, infoSentToService.getContentDescription()); - - // Verify the prefetched sibling of TextView1 is TextView2 (FLAG_PREFETCH_SIBLINGS) - verify(mMockClientCallback2).setPrefetchAccessibilityNodeInfoResult( - mPrefetchInfoListCaptor.capture(), eq(mMockClient2InteractionId)); - List<AccessibilityNodeInfo> prefetchedNodes = mPrefetchInfoListCaptor.getValue(); - assertEquals(1, prefetchedNodes.size()); - assertEquals(TEXT_VIEW_2_DESCRIPTION, prefetchedNodes.get(0).getContentDescription()); - } - - /** - * Tests a series of controller same-service requests to interrupt prefetching and satisfy a - * pending node request. - * Request 1: Request the root node - * Request 2: When TextTextView1's node is initialized as part of Request 1's prefetching, - * request TestTextView1's node - * - * Request 1 prefetches TestTextView1's node, is interrupted by a pending request, and checks - * if its prefetched nodes satisfy any pending requests. It satisfies Request 2's request for - * TestTextView1's node. Request 2 is fulfilled, so it is removed from queue and does not - * prefetch. - * - * @throws RemoteException - */ - @Test - public void testFindRootAndTextNode_withOneClient_shouldInterruptPrefetchAndSatisfyPendingMsg() - throws RemoteException { - mSendClient1RequestForTextAfterTextPrefetched = true; - - mTextView1.setAccessibilityDelegate(new View.AccessibilityDelegate(){ - @Override - public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) { - super.onInitializeAccessibilityNodeInfo(host, info); - info.setContentDescription(TEXT_VIEW_1_DESCRIPTION); - final long nodeId = AccessibilityNodeInfo.makeNodeId( - mTextView1.getAccessibilityViewId(), - AccessibilityNodeProvider.HOST_VIEW_ID); - - if (mSendClient1RequestForTextAfterTextPrefetched) { - // Prevent a loop when processing second request - mSendClient1RequestForTextAfterTextPrefetched = false; - // TextView1 is prefetched here after the FrameLayout is found. Now enqueue a - // same-client request for TextView1 - sendNodeRequestToController(nodeId, mMockClientCallback1, - ++mMockClient1InteractionId, FLAG_PREFETCH_SIBLINGS); - - } - } - }); - // Client 1 requests FrameLayout - sendNodeRequestToController(ROOT_NODE_ID, mMockClientCallback1, - mMockClient1InteractionId, FLAG_PREFETCH_DESCENDANTS); - - // Flush out all messages - mInstrumentation.waitForIdleSync(); - - // When TextView1 is prefetched for FrameLayout, we put a message on the queue in - // TextView1's onInitializeA11yNodeInfo that requests for TextView1. The service thus get - // two node results for FrameLayout and TextView1. - verify(mMockClientCallback1, times(2)) - .setFindAccessibilityNodeInfoResult(mFindInfoCaptor.capture(), anyInt()); - - List<AccessibilityNodeInfo> foundNodes = mFindInfoCaptor.getAllValues(); - assertEquals(FRAME_LAYOUT_DESCRIPTION, foundNodes.get(0).getContentDescription()); - assertEquals(TEXT_VIEW_1_DESCRIPTION, foundNodes.get(1).getContentDescription()); - - // The controller will look at FrameLayout's prefetched nodes and find matching nodes in - // pending requests. The prefetched TextView1 matches the second request. The second - // request was removed from queue and prefetching for this request never occurred. - verify(mMockClientCallback1, times(1)) - .setPrefetchAccessibilityNodeInfoResult(mPrefetchInfoListCaptor.capture(), - eq(mMockClient1InteractionId - 1)); - List<AccessibilityNodeInfo> prefetchedNodes = mPrefetchInfoListCaptor.getValue(); - assertEquals(1, prefetchedNodes.size()); - assertEquals(TEXT_VIEW_1_DESCRIPTION, prefetchedNodes.get(0).getContentDescription()); - } - - /** - * Like above, but tests a series of controller requests from different services to interrupt - * prefetching and satisfy a pending node request. - * - * @throws RemoteException - */ - @Test - public void testFindRootAndTextNode_withTwoClients_shouldInterruptPrefetchAndSatisfyPendingMsg() - throws RemoteException { - mSendClient2RequestForTextAfterTextPrefetched = true; - mTextView1.setAccessibilityDelegate(new View.AccessibilityDelegate(){ - @Override - public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) { - super.onInitializeAccessibilityNodeInfo(host, info); - info.setContentDescription(TEXT_VIEW_1_DESCRIPTION); - final long nodeId = AccessibilityNodeInfo.makeNodeId( - mTextView1.getAccessibilityViewId(), - AccessibilityNodeProvider.HOST_VIEW_ID); - - if (mSendClient2RequestForTextAfterTextPrefetched) { - mSendClient2RequestForTextAfterTextPrefetched = false; - // TextView1 is prefetched here. Now enqueue client 2's request for - // TextView1 - sendNodeRequestToController(nodeId, mMockClientCallback2, - mMockClient2InteractionId, FLAG_PREFETCH_SIBLINGS); - } - } - }); - // Client 1 requests FrameLayout - sendNodeRequestToController(ROOT_NODE_ID, mMockClientCallback1, - mMockClient1InteractionId, FLAG_PREFETCH_DESCENDANTS); - - mInstrumentation.waitForIdleSync(); - - // Verify client 1 gets FrameLayout - verify(mMockClientCallback1, times(1)) - .setFindAccessibilityNodeInfoResult(mFindInfoCaptor.capture(), anyInt()); - assertEquals(FRAME_LAYOUT_DESCRIPTION, - mFindInfoCaptor.getValue().getContentDescription()); - - // Verify client 1 has prefetched nodes - verify(mMockClientCallback1, times(1)) - .setPrefetchAccessibilityNodeInfoResult(mPrefetchInfoListCaptor.capture(), - eq(mMockClient1InteractionId)); - - // Verify client 1's only prefetched node is TextView1 - List<AccessibilityNodeInfo> prefetchedNodes = mPrefetchInfoListCaptor.getValue(); - assertEquals(1, prefetchedNodes.size()); - assertEquals(TEXT_VIEW_1_DESCRIPTION, prefetchedNodes.get(0).getContentDescription()); - - // Verify client 2 gets TextView1 - verify(mMockClientCallback2, times(1)) - .setFindAccessibilityNodeInfoResult(mFindInfoCaptor.capture(), anyInt()); - - assertEquals(TEXT_VIEW_1_DESCRIPTION, mFindInfoCaptor.getValue().getContentDescription()); - - // The second request was removed from queue and prefetching for this client request never - // occurred as it was satisfied. - verify(mMockClientCallback2, never()) - .setPrefetchAccessibilityNodeInfoResult(anyList(), anyInt()); - - } - - @Test - public void testFindNodeById_withTwoDifferentPrefetchFlags_shouldNotSatisfyPendingRequest() - throws RemoteException { - mSendRequestForTextAndIncludeUnImportantViews = true; - mTextView1.setAccessibilityDelegate(new View.AccessibilityDelegate(){ - @Override - public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) { - super.onInitializeAccessibilityNodeInfo(host, info); - info.setContentDescription(TEXT_VIEW_1_DESCRIPTION); - final long nodeId = AccessibilityNodeInfo.makeNodeId( - mTextView1.getAccessibilityViewId(), - AccessibilityNodeProvider.HOST_VIEW_ID); - - if (mSendRequestForTextAndIncludeUnImportantViews) { - mSendRequestForTextAndIncludeUnImportantViews = false; - // TextView1 is prefetched here for client 1. Now enqueue a request from a - // different client that holds different fetch flags for TextView1 - sendNodeRequestToController(nodeId, mMockClientCallback2, - mMockClient2InteractionId, - FLAG_PREFETCH_SIBLINGS | FLAG_INCLUDE_NOT_IMPORTANT_VIEWS); - } - } - }); - - // Mockito does not make copies of objects when called. It holds references, so - // the captor would point to client 2's results after all requests are processed. Verify - // prefetched node immediately - doAnswer(invocation -> { - List<AccessibilityNodeInfo> prefetched = invocation.getArgument(0); - assertEquals(TEXT_VIEW_1_DESCRIPTION, prefetched.get(0).getContentDescription()); - return null; - }).when(mMockClientCallback1).setPrefetchAccessibilityNodeInfoResult(anyList(), - eq(mMockClient1InteractionId)); - - // Client 1 requests FrameLayout - sendNodeRequestToController(ROOT_NODE_ID, mMockClientCallback1, - mMockClient1InteractionId, FLAG_PREFETCH_DESCENDANTS); - - mInstrumentation.waitForIdleSync(); - - // Verify client 1 gets FrameLayout - verify(mMockClientCallback1, times(1)) - .setFindAccessibilityNodeInfoResult(mFindInfoCaptor.capture(), - eq(mMockClient1InteractionId)); - - assertEquals(FRAME_LAYOUT_DESCRIPTION, - mFindInfoCaptor.getValue().getContentDescription()); - - // Verify client 1 has prefetched results. The only prefetched node is TextView1 - // (from above doAnswer) - verify(mMockClientCallback1, times(1)) - .setPrefetchAccessibilityNodeInfoResult(mPrefetchInfoListCaptor.capture(), - eq(mMockClient1InteractionId)); - - // Verify client 2 gets TextView1 - verify(mMockClientCallback2, times(1)) - .setFindAccessibilityNodeInfoResult(mFindInfoCaptor.capture(), - eq(mMockClient2InteractionId)); - assertEquals(TEXT_VIEW_1_DESCRIPTION, - mFindInfoCaptor.getValue().getContentDescription()); - // Verify client 2 has TextView2 as a prefetched node - verify(mMockClientCallback2, times(1)) - .setPrefetchAccessibilityNodeInfoResult(mPrefetchInfoListCaptor.capture(), - eq(mMockClient2InteractionId)); - List<AccessibilityNodeInfo> prefetchedNode = mPrefetchInfoListCaptor.getValue(); - assertEquals(1, prefetchedNode.size()); - assertEquals(TEXT_VIEW_2_DESCRIPTION, prefetchedNode.get(0).getContentDescription()); - } - - private void sendNodeRequestToController(long requestedNodeId, - IAccessibilityInteractionConnectionCallback callback, int interactionId, - int prefetchFlags) { - final int processAndThreadId = callback == mMockClientCallback1 - ? MOCK_CLIENT_1_THREAD_AND_PROCESS_ID - : MOCK_CLIENT_2_THREAD_AND_PROCESS_ID; - - mAccessibilityInteractionController.findAccessibilityNodeInfoByAccessibilityIdClientThread( - requestedNodeId, - null, interactionId, - callback, prefetchFlags, - processAndThreadId, - processAndThreadId, null, null); - - } - - private class TestFrameLayout extends FrameLayout { - - TestFrameLayout(Context context) { - super(context); - } - - @Override - public int getWindowVisibility() { - // We aren't attached to a window so let's pretend - return VISIBLE; - } - - @Override - public boolean isShown() { - // Controller check - return true; - } - - @Override - public int getAccessibilityViewId() { - // static id doesn't reset after tests so return the same one - return 0; - } - - @Override - public void addChildrenForAccessibility(ArrayList<View> outChildren) { - // ViewGroup#addChildrenForAccessbility sorting logic will switch these two - outChildren.add(mTextView1); - outChildren.add(mTextView2); - } - - @Override - public boolean includeForAccessibility() { - return true; - } - - @Override - public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { - super.onInitializeAccessibilityNodeInfo(info); - info.setContentDescription(FRAME_LAYOUT_DESCRIPTION); - } - } - - private class TestTextView extends TextView { - TestTextView(Context context) { - super(context); - } - - @Override - public int getWindowVisibility() { - return VISIBLE; - } - - @Override - public boolean isShown() { - return true; - } - - @Override - public int getAccessibilityViewId() { - return 1; - } - - @Override - public boolean includeForAccessibility() { - return true; - } - - @Override - public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { - super.onInitializeAccessibilityNodeInfo(info); - info.setContentDescription(TEXT_VIEW_1_DESCRIPTION); - } - } - - private class TestTextView2 extends TextView { - TestTextView2(Context context) { - super(context); - } - - @Override - public int getWindowVisibility() { - return VISIBLE; - } - - @Override - public boolean isShown() { - return true; - } - - @Override - public int getAccessibilityViewId() { - return 2; - } - - @Override - public boolean includeForAccessibility() { - return true; - } - - @Override - public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { - super.onInitializeAccessibilityNodeInfo(info); - info.setContentDescription(TEXT_VIEW_2_DESCRIPTION); - } - } -} |