diff options
| author | Sally <sallyyuen@google.com> | 2021-03-04 19:56:52 +0000 |
|---|---|---|
| committer | Sally <sallyyuen@google.com> | 2021-03-04 23:01:50 +0000 |
| commit | 327de4b45cfb6c5eb6ec1de51e6977950eb80121 (patch) | |
| tree | 1354dbd9690b138f1a01b02a966751a50dd91bd7 /core/java | |
| parent | 4e9240747944659c4c9b704db15966771e9d35f8 (diff) | |
Service requests can interrupt node prefetching
This is the re-merging of ag/12923546 (where most of that original
message is posted below), which includes various bug fixes.
Slow prefetch requests would block user interactive requests, creating
noticeable sluggishness and unresponsiveness in accessibility services,
especially on the web.
Let's make it so a user interactive requests stops prefetching.
We can't interrupt an API call, but we can stop in between API calls.
On the service side, we have to separate the prefetch callbacks from the
find callback. And we have to make it asynchronous. It does dispatch
intothe main thread, so the AccessibilityCache can remain single threaded.
When the calls are interrupted on the application side,
returnPendingFindAccessibilityNodeInfosInPrefetch checks the find
requests that are waiting in the queue, to see if they can be addressed
by the prefetch results. If they can be, we don't have to call into
potentially non-performant application code. We don't check requests
that have differing prefetch flags (FLAG_INCLUDE_NOT_IMPORTANT_VIEWS,
FLAG_REPORT_VIEW_IDS) that would result in different caches.
We also make mPendingFindNodeByIdMessages thread-safe and ensure in
ActionReplacingCallback we don't return null results. Merged
ag/13246536, ag/13256330
Messages should be added to PrivateHandler and
mPendingFindNodeIdMessages at the same time to avoid a race condition
where we try removing a message from the handler before it's actually
enqueued. This was causing double recycling
UiAutomation does't require a main thread, so getMainLooper may
return null. In this case, instead of posting to the main looper,
we cache nodes on the binder thread (which is our ultimate goal).
Added tests to verify AccessibilityInteractionController interactions
Test: atest AccessibilityInteractionControllerNodeRequestsTest,
FrameworksServicesTests FrameworksCoreTests, CtsAccessibility, Manual
testing
Bug: b/176195360, b/175877007, b/175884343, b/178726546, b/175832139,
b/176195505, b/181701570
Change-Id: I66902f995f33f0236003faa439925ec72fcf6952
Diffstat (limited to 'core/java')
3 files changed, 314 insertions, 139 deletions
diff --git a/core/java/android/view/AccessibilityInteractionController.java b/core/java/android/view/AccessibilityInteractionController.java index 09452828057e..e5a137cd13f3 100644 --- a/core/java/android/view/AccessibilityInteractionController.java +++ b/core/java/android/view/AccessibilityInteractionController.java @@ -86,6 +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>(); @@ -114,6 +120,9 @@ public final class AccessibilityInteractionController { private AddNodeInfosForViewId mAddNodeInfosForViewId; @GuardedBy("mLock") + private ArrayList<Message> mPendingFindNodeByIdMessages; + + @GuardedBy("mLock") private int mNumActiveRequestPreparers; @GuardedBy("mLock") private List<MessageHolder> mMessagesWaitingForRequestPreparer; @@ -128,6 +137,7 @@ 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, @@ -177,7 +187,11 @@ public final class AccessibilityInteractionController { args.arg4 = arguments; message.obj = args; - scheduleMessage(message, interrogatingPid, interrogatingTid, CONSIDER_REQUEST_PREPARERS); + synchronized (mLock) { + mPendingFindNodeByIdMessages.add(message); + scheduleMessage(message, interrogatingPid, interrogatingTid, + CONSIDER_REQUEST_PREPARERS); + } } /** @@ -315,6 +329,9 @@ public final class AccessibilityInteractionController { } private void findAccessibilityNodeInfoByAccessibilityIdUiThread(Message message) { + synchronized (mLock) { + mPendingFindNodeByIdMessages.remove(message); + } final int flags = message.arg1; SomeArgs args = (SomeArgs) message.obj; @@ -329,22 +346,58 @@ public final class AccessibilityInteractionController { args.recycle(); - List<AccessibilityNodeInfo> infos = mTempAccessibilityNodeInfoList; - infos.clear(); + View rootView = null; + AccessibilityNodeInfo rootNode = null; try { if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) { return; } mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags; - final View root = findViewByAccessibilityId(accessibilityViewId); - if (root != null && isShown(root)) { - mPrefetcher.prefetchAccessibilityNodeInfos( - root, virtualDescendantId, flags, infos, arguments); + rootView = findViewByAccessibilityId(accessibilityViewId); + if (rootView != null && isShown(rootView)) { + rootNode = populateAccessibilityNodeInfoForView( + rootView, arguments, virtualDescendantId); } } finally { - updateInfosForViewportAndReturnFindNodeResult( - infos, callback, interactionId, spec, interactiveRegion); + 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); + } + } + } + return root; } public void findAccessibilityNodeInfosByViewIdClientThread(long accessibilityNodeId, @@ -403,6 +456,7 @@ public final class AccessibilityInteractionController { mAddNodeInfosForViewId.reset(); } } finally { + mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0; updateInfosForViewportAndReturnFindNodeResult( infos, callback, interactionId, spec, interactiveRegion); } @@ -485,6 +539,7 @@ public final class AccessibilityInteractionController { } } } finally { + mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0; updateInfosForViewportAndReturnFindNodeResult( infos, callback, interactionId, spec, interactiveRegion); } @@ -576,6 +631,7 @@ public final class AccessibilityInteractionController { } } } finally { + mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0; updateInfoForViewportAndReturnFindNodeResult( focused, callback, interactionId, spec, interactiveRegion); } @@ -630,6 +686,7 @@ public final class AccessibilityInteractionController { } } } finally { + mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0; updateInfoForViewportAndReturnFindNodeResult( next, callback, interactionId, spec, interactiveRegion); } @@ -786,33 +843,6 @@ 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) { @@ -833,17 +863,6 @@ 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; @@ -891,17 +910,6 @@ 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; @@ -975,18 +983,46 @@ 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(); @@ -996,22 +1032,80 @@ 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) { - 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 */ - } + updateInfoForViewPort(info, spec, interactiveRegion); + returnFindNodeResult(info, callback, interactionId); } private boolean handleClickableSpanActionUiThread( @@ -1054,56 +1148,45 @@ public final class AccessibilityInteractionController { private final ArrayList<View> mTempViewList = new ArrayList<View>(); - public void prefetchAccessibilityNodeInfos(View view, int virtualViewId, int fetchFlags, - List<AccessibilityNodeInfo> outInfos, Bundle arguments) { + public void prefetchAccessibilityNodeInfos(View view, AccessibilityNodeInfo root, + int virtualViewId, int fetchFlags, List<AccessibilityNodeInfo> outInfos) { + if (root == null) { + return; + } 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) { - 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); - } + 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 { - 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 ((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(outInfos); + enforceNodeTreeConsistent(root, outInfos); } } - private void enforceNodeTreeConsistent(List<AccessibilityNodeInfo> nodes) { + private boolean shouldStopPrefetching(List prefetchededInfos) { + return mHandler.hasUserInteractiveMessagesWaiting() + || prefetchededInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE; + } + + private void enforceNodeTreeConsistent( + AccessibilityNodeInfo root, List<AccessibilityNodeInfo> nodes) { LongSparseArray<AccessibilityNodeInfo> nodeMap = new LongSparseArray<AccessibilityNodeInfo>(); final int nodeCount = nodes.size(); @@ -1114,7 +1197,6 @@ 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; @@ -1181,9 +1263,11 @@ public final class AccessibilityInteractionController { private void prefetchPredecessorsOfRealNode(View view, List<AccessibilityNodeInfo> outInfos) { + if (shouldStopPrefetching(outInfos)) { + return; + } ViewParent parent = view.getParentForAccessibility(); - while (parent instanceof View - && outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) { + while (parent instanceof View && !shouldStopPrefetching(outInfos)) { View parentView = (View) parent; AccessibilityNodeInfo info = parentView.createAccessibilityNodeInfo(); if (info != null) { @@ -1195,6 +1279,9 @@ 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; @@ -1204,7 +1291,7 @@ public final class AccessibilityInteractionController { parentGroup.addChildrenForAccessibility(children); final int childCount = children.size(); for (int i = 0; i < childCount; i++) { - if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) { + if (shouldStopPrefetching(outInfos)) { return; } View child = children.get(i); @@ -1232,7 +1319,7 @@ public final class AccessibilityInteractionController { private void prefetchDescendantsOfRealNode(View root, List<AccessibilityNodeInfo> outInfos) { - if (!(root instanceof ViewGroup)) { + if (shouldStopPrefetching(outInfos) || !(root instanceof ViewGroup)) { return; } HashMap<View, AccessibilityNodeInfo> addedChildren = @@ -1243,7 +1330,7 @@ public final class AccessibilityInteractionController { root.addChildrenForAccessibility(children); final int childCount = children.size(); for (int i = 0; i < childCount; i++) { - if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) { + if (shouldStopPrefetching(outInfos)) { return; } View child = children.get(i); @@ -1268,7 +1355,7 @@ public final class AccessibilityInteractionController { } finally { children.clear(); } - if (outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) { + if (!shouldStopPrefetching(outInfos)) { for (Map.Entry<View, AccessibilityNodeInfo> entry : addedChildren.entrySet()) { View addedChild = entry.getKey(); AccessibilityNodeInfo virtualRoot = entry.getValue(); @@ -1290,7 +1377,7 @@ public final class AccessibilityInteractionController { long parentNodeId = root.getParentNodeId(); int accessibilityViewId = AccessibilityNodeInfo.getAccessibilityViewId(parentNodeId); while (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) { - if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) { + if (shouldStopPrefetching(outInfos)) { return; } final int virtualDescendantId = @@ -1335,7 +1422,7 @@ public final class AccessibilityInteractionController { if (parent != null) { final int childCount = parent.getChildCount(); for (int i = 0; i < childCount; i++) { - if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) { + if (shouldStopPrefetching(outInfos)) { return; } final long childNodeId = parent.getChildId(i); @@ -1360,7 +1447,7 @@ public final class AccessibilityInteractionController { final int initialOutInfosSize = outInfos.size(); final int childCount = root.getChildCount(); for (int i = 0; i < childCount; i++) { - if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) { + if (shouldStopPrefetching(outInfos)) { return; } final long childNodeId = root.getChildId(i); @@ -1370,7 +1457,7 @@ public final class AccessibilityInteractionController { outInfos.add(child); } } - if (outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) { + if (!shouldStopPrefetching(outInfos)) { final int addedChildCount = outInfos.size() - initialOutInfosSize; for (int i = 0; i < addedChildCount; i++) { AccessibilityNodeInfo child = outInfos.get(initialOutInfosSize + i); @@ -1479,6 +1566,10 @@ 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 f63749be6df2..8d1271d7311c 100644 --- a/core/java/android/view/accessibility/AccessibilityInteractionClient.java +++ b/core/java/android/view/accessibility/AccessibilityInteractionClient.java @@ -23,7 +23,9 @@ 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; @@ -113,6 +115,8 @@ public final class AccessibilityInteractionClient private final Object mInstanceLock = new Object(); + private Handler mMainHandler; + private volatile int mInteractionId = -1; private AccessibilityNodeInfo mFindAccessibilityNodeInfoResult; @@ -123,6 +127,11 @@ 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. */ @@ -197,6 +206,10 @@ public final class AccessibilityInteractionClient private AccessibilityInteractionClient() { /* reducing constructor visibility */ + Looper mainLooper = Looper.getMainLooper(); + if (mainLooper != null) { + mMainHandler = new Handler(mainLooper); + } } /** @@ -451,16 +464,16 @@ public final class AccessibilityInteractionClient Binder.restoreCallingIdentity(identityToken); } if (packageNames != null) { - List<AccessibilityNodeInfo> infos = getFindAccessibilityNodeInfosResultAndClear( - interactionId); - finalizeAndCacheAccessibilityNodeInfos(infos, connectionId, - bypassCache, packageNames); - if (infos != null && !infos.isEmpty()) { - for (int i = 1; i < infos.size(); i++) { - infos.get(i).recycle(); - } - return infos.get(0); + AccessibilityNodeInfo info = + getFindAccessibilityNodeInfoResultAndClear(interactionId); + if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_MASK) != 0 + && info != null) { + setInteractionWaitingForPrefetchResult(interactionId, connectionId, + packageNames); } + finalizeAndCacheAccessibilityNodeInfo(info, connectionId, + bypassCache, packageNames); + return info; } } else { if (DEBUG) { @@ -474,6 +487,15 @@ 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); @@ -829,6 +851,59 @@ 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 049bb31adbb1..231e75a19a06 100644 --- a/core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl +++ b/core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl @@ -47,6 +47,15 @@ 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. |
