Merge "[FUI18] Expose notifyNetworkStatus as system API" into sc-dev
diff --git a/core/java/android/view/AccessibilityInteractionController.java b/core/java/android/view/AccessibilityInteractionController.java
index a1ba3b7..09452828 100644
--- a/core/java/android/view/AccessibilityInteractionController.java
+++ b/core/java/android/view/AccessibilityInteractionController.java
@@ -86,19 +86,12 @@
     // 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 @@
     private AddNodeInfosForViewId mAddNodeInfosForViewId;
 
     @GuardedBy("mLock")
-    private ArrayList<Message> mPendingFindNodeByIdMessages;
-
-    @GuardedBy("mLock")
     private int mNumActiveRequestPreparers;
     @GuardedBy("mLock")
     private List<MessageHolder> mMessagesWaitingForRequestPreparer;
@@ -138,7 +128,6 @@
         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 @@
         args.arg4 = arguments;
         message.obj = args;
 
-        synchronized (mLock) {
-            mPendingFindNodeByIdMessages.add(message);
-        }
         scheduleMessage(message, interrogatingPid, interrogatingTid, CONSIDER_REQUEST_PREPARERS);
     }
 
@@ -329,9 +315,6 @@
     }
 
     private void findAccessibilityNodeInfoByAccessibilityIdUiThread(Message message) {
-        synchronized (mLock) {
-            mPendingFindNodeByIdMessages.remove(message);
-        }
         final int flags = message.arg1;
 
         SomeArgs args = (SomeArgs) message.obj;
@@ -346,58 +329,22 @@
 
         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);
+            updateInfosForViewportAndReturnFindNodeResult(
+                    infos, 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,
@@ -456,7 +403,6 @@
                 mAddNodeInfosForViewId.reset();
             }
         } finally {
-            mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
             updateInfosForViewportAndReturnFindNodeResult(
                     infos, callback, interactionId, spec, interactiveRegion);
         }
@@ -539,7 +485,6 @@
                 }
             }
         } finally {
-            mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
             updateInfosForViewportAndReturnFindNodeResult(
                     infos, callback, interactionId, spec, interactiveRegion);
         }
@@ -631,7 +576,6 @@
                 }
             }
         } finally {
-            mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
             updateInfoForViewportAndReturnFindNodeResult(
                     focused, callback, interactionId, spec, interactiveRegion);
         }
@@ -686,7 +630,6 @@
                 }
             }
         } finally {
-            mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
             updateInfoForViewportAndReturnFindNodeResult(
                     next, callback, interactionId, spec, interactiveRegion);
         }
@@ -843,6 +786,33 @@
         }
     }
 
+    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) {
@@ -863,6 +833,17 @@
         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;
@@ -910,6 +891,17 @@
         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;
@@ -983,46 +975,18 @@
         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();
@@ -1032,80 +996,22 @@
         }
     }
 
-    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(
@@ -1148,45 +1054,56 @@
 
         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();
@@ -1197,6 +1114,7 @@
 
             // 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;
@@ -1263,11 +1181,9 @@
 
         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) {
@@ -1279,9 +1195,6 @@
 
         private void prefetchSiblingsOfRealNode(View current,
                 List<AccessibilityNodeInfo> outInfos) {
-            if (shouldStopPrefetching(outInfos)) {
-                return;
-            }
             ViewParent parent = current.getParentForAccessibility();
             if (parent instanceof ViewGroup) {
                 ViewGroup parentGroup = (ViewGroup) parent;
@@ -1291,7 +1204,7 @@
                     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);
@@ -1319,7 +1232,7 @@
 
         private void prefetchDescendantsOfRealNode(View root,
                 List<AccessibilityNodeInfo> outInfos) {
-            if (shouldStopPrefetching(outInfos) || !(root instanceof ViewGroup)) {
+            if (!(root instanceof ViewGroup)) {
                 return;
             }
             HashMap<View, AccessibilityNodeInfo> addedChildren =
@@ -1330,7 +1243,7 @@
                 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);
@@ -1355,7 +1268,7 @@
             } 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();
@@ -1377,7 +1290,7 @@
             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 =
@@ -1422,7 +1335,7 @@
                 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);
@@ -1447,7 +1360,7 @@
             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);
@@ -1457,7 +1370,7 @@
                     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);
@@ -1566,10 +1479,6 @@
         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 9556c25..f63749b 100644
--- a/core/java/android/view/accessibility/AccessibilityInteractionClient.java
+++ b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
@@ -23,9 +23,7 @@
 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 @@
 
     private final Object mInstanceLock = new Object();
 
-    private Handler mMainHandler;
-
     private volatile int mInteractionId = -1;
 
     private AccessibilityNodeInfo mFindAccessibilityNodeInfoResult;
@@ -127,11 +123,6 @@
 
     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 @@
 
     private AccessibilityInteractionClient() {
         /* reducing constructor visibility */
-        if (Looper.getMainLooper() != null) {
-            mMainHandler = new Handler(Looper.getMainLooper());
-        }
     }
 
     /**
@@ -463,16 +451,16 @@
                     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 @@
         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 @@
     }
 
     /**
-     * {@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 231e75a..049bb31 100644
--- a/core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl
@@ -47,15 +47,6 @@
         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 7e1e7f4..ab24f89 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.runner.RunWith;
 import org.mockito.Mock;
 
+import java.util.Arrays;
+import java.util.List;
+
 /**
  * Tests for AccessibilityInteractionClient
  */
@@ -62,7 +65,7 @@
         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 @@
     }
 
     private static class MockConnection extends AccessibilityServiceConnectionImpl {
-        AccessibilityNodeInfo mInfoToReturn;
+        List<AccessibilityNodeInfo> mInfosToReturn;
 
         @Override
         public String[] findAccessibilityNodeInfoByAccessibilityId(int accessibilityWindowId,
@@ -80,7 +83,7 @@
                 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 6828dd9..bafb641 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 @@
     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 @@
         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 @@
 
     @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;
         }
-        replaceInfoActionsAndCallServiceIfReady();
+        if (readyForCallback) {
+            replaceInfoActionsAndCallService();
+        }
     }
 
     @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 @@
 
     private void replaceInfoActionsAndCallService() {
         final AccessibilityNodeInfo nodeToReturn;
-        boolean doCallback = false;
         synchronized (mLock) {
-            doCallback = mReplacementNodeIsReadyOrFailed
-                    && mSetFindNodeFromOriginalWindowCalled;
-            if (doCallback && mNodeFromOriginalWindow != null) {
-                replaceActionsOnInfoLocked(mNodeFromOriginalWindow);
-                mSetFindNodeFromOriginalWindowCalled = false;
-            }
-            nodeToReturn = mNodeFromOriginalWindow;
-        }
-        if (doCallback) {
-            try {
-                mServiceCallback.setFindAccessibilityNodeInfoResult(nodeToReturn, mInteractionId);
-            } catch (RemoteException re) {
+            if (mDone) {
                 if (DEBUG) {
-                    Slog.e(LOG_TAG, "Failed to setFindAccessibilityNodeInfoResult");
+                    Slog.e(LOG_TAG, "Extra callback");
                 }
+                return;
+            }
+            if (mNodeFromOriginalWindow != null) {
+                replaceActionsOnInfoLocked(mNodeFromOriginalWindow);
+            }
+            recycleReplaceActionNodesLocked();
+            nodeToReturn = mNodeFromOriginalWindow;
+            mDone = true;
+        }
+        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;
+            }
+            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;
         }
-    }
-
-    private void replacePrefetchInfosActionsAndCallService() {
-        List<AccessibilityNodeInfo> nodesToReturn = null;
-        boolean doCallback = false;
-        synchronized (mLock) {
-            doCallback = mReplacementNodeIsReadyOrFailed
-                    && mSetPrefetchFromOriginalWindowCalled;
-            if (doCallback) {
-                nodesToReturn = replaceActionsLocked(mPrefetchedNodesFromOriginalWindow);
-                mSetPrefetchFromOriginalWindowCalled = false;
+        try {
+            mServiceCallback.setFindAccessibilityNodeInfosResult(nodesToReturn, mInteractionId);
+        } catch (RemoteException re) {
+            if (DEBUG) {
+                Slog.e(LOG_TAG, "Failed to setFindAccessibilityNodeInfosResult");
             }
         }
-        if (doCallback) {
-            try {
-                mServiceCallback.setPrefetchAccessibilityNodeInfoResult(
-                        nodesToReturn, mInteractionId);
-            } catch (RemoteException re) {
-                if (DEBUG) {
-                    Slog.e(LOG_TAG, "Failed to setFindAccessibilityNodeInfosResult");
-                }
-            }
-        }
-    }
-
-    @GuardedBy("mLock")
-    private List<AccessibilityNodeInfo> replaceActionsLocked(List<AccessibilityNodeInfo> infos) {
-        if (infos != null) {
-            for (int i = 0; i < infos.size(); i++) {
-                replaceActionsOnInfoLocked(infos.get(i));
-            }
-        }
-        return (infos == null)
-                ? null : new ArrayList<>(infos);
     }
 
     @GuardedBy("mLock")
@@ -257,22 +204,40 @@
         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/core/java/com/android/server/policy/ModifierShortcutManager.java b/services/core/java/com/android/server/policy/ModifierShortcutManager.java
new file mode 100644
index 0000000..a0771c0
--- /dev/null
+++ b/services/core/java/com/android/server/policy/ModifierShortcutManager.java
@@ -0,0 +1,380 @@
+/*
+ * 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.policy;
+
+import android.content.ActivityNotFoundException;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.res.XmlResourceParser;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.LongSparseArray;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.view.KeyCharacterMap;
+import android.view.KeyEvent;
+
+import com.android.internal.policy.IShortcutService;
+import com.android.internal.util.XmlUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+
+/**
+ * Manages quick launch shortcuts by:
+ * <li> Keeping the local copy in sync with the database (this is an observer)
+ * <li> Returning a shortcut-matching intent to clients
+ * <li> Returning particular kind of application intent by special key.
+ */
+class ModifierShortcutManager {
+    private static final String TAG = "ShortcutManager";
+
+    private static final String TAG_BOOKMARKS = "bookmarks";
+    private static final String TAG_BOOKMARK = "bookmark";
+
+    private static final String ATTRIBUTE_PACKAGE = "package";
+    private static final String ATTRIBUTE_CLASS = "class";
+    private static final String ATTRIBUTE_SHORTCUT = "shortcut";
+    private static final String ATTRIBUTE_CATEGORY = "category";
+    private static final String ATTRIBUTE_SHIFT = "shift";
+
+    private final SparseArray<ShortcutInfo> mIntentShortcuts = new SparseArray<>();
+    private final SparseArray<ShortcutInfo> mShiftShortcuts = new SparseArray<>();
+
+    private LongSparseArray<IShortcutService> mShortcutKeyServices = new LongSparseArray<>();
+
+    /* Table of Application Launch keys.  Maps from key codes to intent categories.
+     *
+     * These are special keys that are used to launch particular kinds of applications,
+     * such as a web browser.  HID defines nearly a hundred of them in the Consumer (0x0C)
+     * usage page.  We don't support quite that many yet...
+     */
+    static SparseArray<String> sApplicationLaunchKeyCategories;
+    static {
+        sApplicationLaunchKeyCategories = new SparseArray<String>();
+        sApplicationLaunchKeyCategories.append(
+                KeyEvent.KEYCODE_EXPLORER, Intent.CATEGORY_APP_BROWSER);
+        sApplicationLaunchKeyCategories.append(
+                KeyEvent.KEYCODE_ENVELOPE, Intent.CATEGORY_APP_EMAIL);
+        sApplicationLaunchKeyCategories.append(
+                KeyEvent.KEYCODE_CONTACTS, Intent.CATEGORY_APP_CONTACTS);
+        sApplicationLaunchKeyCategories.append(
+                KeyEvent.KEYCODE_CALENDAR, Intent.CATEGORY_APP_CALENDAR);
+        sApplicationLaunchKeyCategories.append(
+                KeyEvent.KEYCODE_MUSIC, Intent.CATEGORY_APP_MUSIC);
+        sApplicationLaunchKeyCategories.append(
+                KeyEvent.KEYCODE_CALCULATOR, Intent.CATEGORY_APP_CALCULATOR);
+    }
+
+    private final Context mContext;
+    private boolean mSearchKeyShortcutPending = false;
+    private boolean mConsumeSearchKeyUp = true;
+
+    ModifierShortcutManager(Context context) {
+        mContext = context;
+        loadShortcuts();
+    }
+
+    /**
+     * Gets the shortcut intent for a given keycode+modifier. Make sure you
+     * strip whatever modifier is used for invoking shortcuts (for example,
+     * if 'Sym+A' should invoke a shortcut on 'A', you should strip the
+     * 'Sym' bit from the modifiers before calling this method.
+     * <p>
+     * This will first try an exact match (with modifiers), and then try a
+     * match without modifiers (primary character on a key).
+     *
+     * @param kcm The key character map of the device on which the key was pressed.
+     * @param keyCode The key code.
+     * @param metaState The meta state, omitting any modifiers that were used
+     * to invoke the shortcut.
+     * @return The intent that matches the shortcut, or null if not found.
+     */
+    private Intent getIntent(KeyCharacterMap kcm, int keyCode, int metaState) {
+        ShortcutInfo shortcut = null;
+
+        // If the Shift key is pressed, then search for the shift shortcuts.
+        boolean isShiftOn = (metaState & KeyEvent.META_SHIFT_ON) == KeyEvent.META_SHIFT_ON;
+        SparseArray<ShortcutInfo> shortcutMap = isShiftOn ? mShiftShortcuts : mIntentShortcuts;
+
+        // First try the exact keycode (with modifiers).
+        int shortcutChar = kcm.get(keyCode, metaState);
+        if (shortcutChar != 0) {
+            shortcut = shortcutMap.get(shortcutChar);
+        }
+
+        // Next try the primary character on that key.
+        if (shortcut == null) {
+            shortcutChar = Character.toLowerCase(kcm.getDisplayLabel(keyCode));
+            if (shortcutChar != 0) {
+                shortcut = shortcutMap.get(shortcutChar);
+            }
+        }
+
+        return (shortcut != null) ? shortcut.intent : null;
+    }
+
+    private void loadShortcuts() {
+        PackageManager packageManager = mContext.getPackageManager();
+        try {
+            XmlResourceParser parser = mContext.getResources().getXml(
+                    com.android.internal.R.xml.bookmarks);
+            XmlUtils.beginDocument(parser, TAG_BOOKMARKS);
+
+            while (true) {
+                XmlUtils.nextElement(parser);
+
+                if (parser.getEventType() == XmlPullParser.END_DOCUMENT) {
+                    break;
+                }
+
+                if (!TAG_BOOKMARK.equals(parser.getName())) {
+                    break;
+                }
+
+                String packageName = parser.getAttributeValue(null, ATTRIBUTE_PACKAGE);
+                String className = parser.getAttributeValue(null, ATTRIBUTE_CLASS);
+                String shortcutName = parser.getAttributeValue(null, ATTRIBUTE_SHORTCUT);
+                String categoryName = parser.getAttributeValue(null, ATTRIBUTE_CATEGORY);
+                String shiftName = parser.getAttributeValue(null, ATTRIBUTE_SHIFT);
+
+                if (TextUtils.isEmpty(shortcutName)) {
+                    Log.w(TAG, "Unable to get shortcut for: " + packageName + "/" + className);
+                    continue;
+                }
+
+                final int shortcutChar = shortcutName.charAt(0);
+                final boolean isShiftShortcut = (shiftName != null && shiftName.equals("true"));
+
+                final Intent intent;
+                final String title;
+                if (packageName != null && className != null) {
+                    ActivityInfo info = null;
+                    ComponentName componentName = new ComponentName(packageName, className);
+                    try {
+                        info = packageManager.getActivityInfo(componentName,
+                                PackageManager.MATCH_DIRECT_BOOT_AWARE
+                                        | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
+                                        | PackageManager.MATCH_UNINSTALLED_PACKAGES);
+                    } catch (PackageManager.NameNotFoundException e) {
+                        String[] packages = packageManager.canonicalToCurrentPackageNames(
+                                new String[] { packageName });
+                        componentName = new ComponentName(packages[0], className);
+                        try {
+                            info = packageManager.getActivityInfo(componentName,
+                                    PackageManager.MATCH_DIRECT_BOOT_AWARE
+                                            | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
+                                            | PackageManager.MATCH_UNINSTALLED_PACKAGES);
+                        } catch (PackageManager.NameNotFoundException e1) {
+                            Log.w(TAG, "Unable to add bookmark: " + packageName
+                                    + "/" + className, e);
+                            continue;
+                        }
+                    }
+
+                    intent = new Intent(Intent.ACTION_MAIN);
+                    intent.addCategory(Intent.CATEGORY_LAUNCHER);
+                    intent.setComponent(componentName);
+                    title = info.loadLabel(packageManager).toString();
+                } else if (categoryName != null) {
+                    intent = Intent.makeMainSelectorActivity(Intent.ACTION_MAIN, categoryName);
+                    title = "";
+                } else {
+                    Log.w(TAG, "Unable to add bookmark for shortcut " + shortcutName
+                            + ": missing package/class or category attributes");
+                    continue;
+                }
+
+                ShortcutInfo shortcut = new ShortcutInfo(title, intent);
+                if (isShiftShortcut) {
+                    mShiftShortcuts.put(shortcutChar, shortcut);
+                } else {
+                    mIntentShortcuts.put(shortcutChar, shortcut);
+                }
+            }
+        } catch (XmlPullParserException e) {
+            Log.w(TAG, "Got exception parsing bookmarks.", e);
+        } catch (IOException e) {
+            Log.w(TAG, "Got exception parsing bookmarks.", e);
+        }
+    }
+
+    void registerShortcutKey(long shortcutCode, IShortcutService shortcutService)
+            throws RemoteException {
+        IShortcutService service = mShortcutKeyServices.get(shortcutCode);
+        if (service != null && service.asBinder().pingBinder()) {
+            throw new RemoteException("Key already exists.");
+        }
+
+        mShortcutKeyServices.put(shortcutCode, shortcutService);
+    }
+
+    /**
+     * Handle the shortcut to {@link IShortcutService}
+     * @param keyCode The key code of the event.
+     * @param metaState The meta key modifier state.
+     * @return True if invoked the shortcut, otherwise false.
+     */
+    private boolean handleShortcutService(int keyCode, int metaState) {
+        long shortcutCode = keyCode;
+        if ((metaState & KeyEvent.META_CTRL_ON) != 0) {
+            shortcutCode |= ((long) KeyEvent.META_CTRL_ON) << Integer.SIZE;
+        }
+
+        if ((metaState & KeyEvent.META_ALT_ON) != 0) {
+            shortcutCode |= ((long) KeyEvent.META_ALT_ON) << Integer.SIZE;
+        }
+
+        if ((metaState & KeyEvent.META_SHIFT_ON) != 0) {
+            shortcutCode |= ((long) KeyEvent.META_SHIFT_ON) << Integer.SIZE;
+        }
+
+        if ((metaState & KeyEvent.META_META_ON) != 0) {
+            shortcutCode |= ((long) KeyEvent.META_META_ON) << Integer.SIZE;
+        }
+
+        IShortcutService shortcutService = mShortcutKeyServices.get(shortcutCode);
+        if (shortcutService != null) {
+            try {
+                shortcutService.notifyShortcutKeyPressed(shortcutCode);
+            } catch (RemoteException e) {
+                mShortcutKeyServices.delete(shortcutCode);
+            }
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Handle the shortcut to {@link Intent}
+     *
+     * @param kcm the {@link KeyCharacterMap} associated with the keyboard device.
+     * @param keyCode The key code of the event.
+     * @param metaState The meta key modifier state.
+     * @return True if invoked the shortcut, otherwise false.
+     */
+    private boolean handleIntentShortcut(KeyCharacterMap kcm, int keyCode, int metaState) {
+        // Shortcuts are invoked through Search+key, so intercept those here
+        // Any printing key that is chorded with Search should be consumed
+        // even if no shortcut was invoked.  This prevents text from being
+        // inadvertently inserted when using a keyboard that has built-in macro
+        // shortcut keys (that emit Search+x) and some of them are not registered.
+        if (mSearchKeyShortcutPending) {
+            if (kcm.isPrintingKey(keyCode)) {
+                mConsumeSearchKeyUp = true;
+                mSearchKeyShortcutPending = false;
+            } else {
+                return false;
+            }
+        } else if ((metaState & KeyEvent.META_META_MASK) != 0) {
+            // Invoke shortcuts using Meta.
+            metaState &= ~KeyEvent.META_META_MASK;
+        } else {
+            // Handle application launch keys.
+            String category = sApplicationLaunchKeyCategories.get(keyCode);
+            if (category != null) {
+                Intent intent = Intent.makeMainSelectorActivity(Intent.ACTION_MAIN, category);
+                intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+                try {
+                    mContext.startActivityAsUser(intent, UserHandle.CURRENT);
+                } catch (ActivityNotFoundException ex) {
+                    Slog.w(TAG, "Dropping application launch key because "
+                            + "the activity to which it is registered was not found: "
+                            + "keyCode=" + KeyEvent.keyCodeToString(keyCode) + ","
+                            + " category=" + category, ex);
+                }
+                return true;
+            } else {
+                return false;
+            }
+        }
+
+        final Intent shortcutIntent = getIntent(kcm, keyCode, metaState);
+        if (shortcutIntent != null) {
+            shortcutIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+            try {
+                mContext.startActivityAsUser(shortcutIntent, UserHandle.CURRENT);
+            } catch (ActivityNotFoundException ex) {
+                Slog.w(TAG, "Dropping shortcut key combination because "
+                        + "the activity to which it is registered was not found: "
+                        + "META+ or SEARCH" + KeyEvent.keyCodeToString(keyCode), ex);
+            }
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Handle the shortcut from {@link KeyEvent}
+     *
+     * @param event Description of the key event.
+     * @return True if invoked the shortcut, otherwise false.
+     */
+    boolean interceptKey(KeyEvent event) {
+        if (event.getRepeatCount() != 0) {
+            return false;
+        }
+
+        final int metaState = event.getModifiers();
+        final int keyCode = event.getKeyCode();
+        if (keyCode == KeyEvent.KEYCODE_SEARCH) {
+            if (event.getAction() == KeyEvent.ACTION_DOWN) {
+                mSearchKeyShortcutPending = true;
+                mConsumeSearchKeyUp = false;
+            } else {
+                mSearchKeyShortcutPending = false;
+                if (mConsumeSearchKeyUp) {
+                    mConsumeSearchKeyUp = false;
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        if (event.getAction() != KeyEvent.ACTION_DOWN) {
+            return false;
+        }
+
+        final KeyCharacterMap kcm = event.getKeyCharacterMap();
+        if (handleIntentShortcut(kcm, keyCode, metaState)) {
+            return true;
+        }
+
+        if (handleShortcutService(keyCode, metaState)) {
+            return true;
+        }
+
+        return false;
+    }
+
+    private static final class ShortcutInfo {
+        public final String title;
+        public final Intent intent;
+
+        ShortcutInfo(String title, Intent intent) {
+            this.title = title;
+            this.intent = intent;
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index c50efc7..bce218f 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -163,7 +163,6 @@
 import android.speech.RecognizerIntent;
 import android.telecom.TelecomManager;
 import android.util.Log;
-import android.util.LongSparseArray;
 import android.util.MutableBoolean;
 import android.util.PrintWriterPrinter;
 import android.util.Slog;
@@ -318,29 +317,6 @@
      */
     private boolean mKeyguardDrawnOnce;
 
-    /* Table of Application Launch keys.  Maps from key codes to intent categories.
-     *
-     * These are special keys that are used to launch particular kinds of applications,
-     * such as a web browser.  HID defines nearly a hundred of them in the Consumer (0x0C)
-     * usage page.  We don't support quite that many yet...
-     */
-    static SparseArray<String> sApplicationLaunchKeyCategories;
-    static {
-        sApplicationLaunchKeyCategories = new SparseArray<String>();
-        sApplicationLaunchKeyCategories.append(
-                KeyEvent.KEYCODE_EXPLORER, Intent.CATEGORY_APP_BROWSER);
-        sApplicationLaunchKeyCategories.append(
-                KeyEvent.KEYCODE_ENVELOPE, Intent.CATEGORY_APP_EMAIL);
-        sApplicationLaunchKeyCategories.append(
-                KeyEvent.KEYCODE_CONTACTS, Intent.CATEGORY_APP_CONTACTS);
-        sApplicationLaunchKeyCategories.append(
-                KeyEvent.KEYCODE_CALENDAR, Intent.CATEGORY_APP_CALENDAR);
-        sApplicationLaunchKeyCategories.append(
-                KeyEvent.KEYCODE_MUSIC, Intent.CATEGORY_APP_MUSIC);
-        sApplicationLaunchKeyCategories.append(
-                KeyEvent.KEYCODE_CALCULATOR, Intent.CATEGORY_APP_CALCULATOR);
-    }
-
     /** Amount of time (in milliseconds) to wait for windows drawn before powering on. */
     static final int WAITING_FOR_DRAWN_TIMEOUT = 1000;
 
@@ -419,8 +395,6 @@
     boolean mSafeMode;
     private WindowState mKeyguardCandidate = null;
 
-    private LongSparseArray<IShortcutService> mShortcutKeyServices = new LongSparseArray<>();
-
     // Whether to allow dock apps with METADATA_DOCK_HOME to temporarily take over the Home key.
     // This is for car dock and this is updated from resource.
     private boolean mEnableCarDockHomeCapture = true;
@@ -516,8 +490,6 @@
     Intent mCarDockIntent;
     Intent mDeskDockIntent;
     Intent mVrHeadsetHomeIntent;
-    boolean mSearchKeyShortcutPending;
-    boolean mConsumeSearchKeyUp;
     boolean mPendingMetaAction;
     boolean mPendingCapsLockToggle;
 
@@ -578,7 +550,7 @@
     private static final int BRIGHTNESS_STEPS = 10;
 
     SettingsObserver mSettingsObserver;
-    ShortcutManager mShortcutManager;
+    ModifierShortcutManager mModifierShortcutManager;
     PowerManager.WakeLock mBroadcastWakeLock;
     PowerManager.WakeLock mPowerKeyWakeLock;
     boolean mHavePendingMediaKeyRepeatWithWakeLock;
@@ -1772,7 +1744,7 @@
         mWakeGestureListener = new MyWakeGestureListener(mContext, mHandler);
         mSettingsObserver = new SettingsObserver(mHandler);
         mSettingsObserver.observe();
-        mShortcutManager = new ShortcutManager(context);
+        mModifierShortcutManager = new ModifierShortcutManager(context);
         mUiMode = context.getResources().getInteger(
                 com.android.internal.R.integer.config_defaultUiModeType);
         mHomeIntent =  new Intent(Intent.ACTION_MAIN, null);
@@ -2574,6 +2546,15 @@
             mPendingCapsLockToggle = false;
         }
 
+        if (isUserSetupComplete() && !keyguardOn) {
+            if (mModifierShortcutManager.interceptKey(event)) {
+                dismissKeyboardShortcutsMenu();
+                mPendingMetaAction = false;
+                mPendingCapsLockToggle = false;
+                return key_consumed;
+            }
+        }
+
         switch(keyCode) {
             case KeyEvent.KEYCODE_HOME:
                 // First we always handle the home key here, so applications
@@ -2599,20 +2580,6 @@
                     }
                 }
                 break;
-            case  KeyEvent.KEYCODE_SEARCH:
-                if (down) {
-                    if (repeatCount == 0) {
-                        mSearchKeyShortcutPending = true;
-                        mConsumeSearchKeyUp = false;
-                    }
-                } else {
-                    mSearchKeyShortcutPending = false;
-                    if (mConsumeSearchKeyUp) {
-                        mConsumeSearchKeyUp = false;
-                        return key_consumed;
-                    }
-                }
-                return 0;
             case KeyEvent.KEYCODE_APP_SWITCH:
                 if (!keyguardOn) {
                     if (down && repeatCount == 0) {
@@ -2820,114 +2787,11 @@
                 break;
         }
 
-        // Shortcuts are invoked through Search+key, so intercept those here
-        // Any printing key that is chorded with Search should be consumed
-        // even if no shortcut was invoked.  This prevents text from being
-        // inadvertently inserted when using a keyboard that has built-in macro
-        // shortcut keys (that emit Search+x) and some of them are not registered.
-        if (mSearchKeyShortcutPending) {
-            final KeyCharacterMap kcm = event.getKeyCharacterMap();
-            if (kcm.isPrintingKey(keyCode)) {
-                mConsumeSearchKeyUp = true;
-                mSearchKeyShortcutPending = false;
-                if (down && repeatCount == 0 && !keyguardOn) {
-                    Intent shortcutIntent = mShortcutManager.getIntent(kcm, keyCode, metaState);
-                    if (shortcutIntent != null) {
-                        shortcutIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-                        try {
-                            startActivityAsUser(shortcutIntent, UserHandle.CURRENT);
-                            dismissKeyboardShortcutsMenu();
-                        } catch (ActivityNotFoundException ex) {
-                            Slog.w(TAG, "Dropping shortcut key combination because "
-                                    + "the activity to which it is registered was not found: "
-                                    + "SEARCH+" + KeyEvent.keyCodeToString(keyCode), ex);
-                        }
-                    } else {
-                        Slog.i(TAG, "Dropping unregistered shortcut key combination: "
-                                + "SEARCH+" + KeyEvent.keyCodeToString(keyCode));
-                    }
-                }
-                return key_consumed;
-            }
-        }
-
-        // Invoke shortcuts using Meta.
-        if (down && repeatCount == 0 && !keyguardOn
-                && (metaState & KeyEvent.META_META_ON) != 0) {
-            final KeyCharacterMap kcm = event.getKeyCharacterMap();
-            if (kcm.isPrintingKey(keyCode)) {
-                Intent shortcutIntent = mShortcutManager.getIntent(kcm, keyCode,
-                        metaState & ~(KeyEvent.META_META_ON
-                                | KeyEvent.META_META_LEFT_ON | KeyEvent.META_META_RIGHT_ON));
-                if (shortcutIntent != null) {
-                    shortcutIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-                    try {
-                        startActivityAsUser(shortcutIntent, UserHandle.CURRENT);
-                        dismissKeyboardShortcutsMenu();
-                    } catch (ActivityNotFoundException ex) {
-                        Slog.w(TAG, "Dropping shortcut key combination because "
-                                + "the activity to which it is registered was not found: "
-                                + "META+" + KeyEvent.keyCodeToString(keyCode), ex);
-                    }
-                    return key_consumed;
-                }
-            }
-        }
-
-        // Handle application launch keys.
-        if (down && repeatCount == 0 && !keyguardOn) {
-            String category = sApplicationLaunchKeyCategories.get(keyCode);
-            if (category != null) {
-                Intent intent = Intent.makeMainSelectorActivity(Intent.ACTION_MAIN, category);
-                intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-                try {
-                    startActivityAsUser(intent, UserHandle.CURRENT);
-                    dismissKeyboardShortcutsMenu();
-                } catch (ActivityNotFoundException ex) {
-                    Slog.w(TAG, "Dropping application launch key because "
-                            + "the activity to which it is registered was not found: "
-                            + "keyCode=" + keyCode + ", category=" + category, ex);
-                }
-                return key_consumed;
-            }
-        }
-
         if (isValidGlobalKey(keyCode)
                 && mGlobalKeyManager.handleGlobalKey(mContext, keyCode, event)) {
             return key_consumed;
         }
 
-        if (down) {
-            long shortcutCode = keyCode;
-            if (event.isCtrlPressed()) {
-                shortcutCode |= ((long) KeyEvent.META_CTRL_ON) << Integer.SIZE;
-            }
-
-            if (event.isAltPressed()) {
-                shortcutCode |= ((long) KeyEvent.META_ALT_ON) << Integer.SIZE;
-            }
-
-            if (event.isShiftPressed()) {
-                shortcutCode |= ((long) KeyEvent.META_SHIFT_ON) << Integer.SIZE;
-            }
-
-            if (event.isMetaPressed()) {
-                shortcutCode |= ((long) KeyEvent.META_META_ON) << Integer.SIZE;
-            }
-
-            IShortcutService shortcutService = mShortcutKeyServices.get(shortcutCode);
-            if (shortcutService != null) {
-                try {
-                    if (isUserSetupComplete()) {
-                        shortcutService.notifyShortcutKeyPressed(shortcutCode);
-                    }
-                } catch (RemoteException e) {
-                    mShortcutKeyServices.delete(shortcutCode);
-                }
-                return key_consumed;
-            }
-        }
-
         // Reserve all the META modifier combos for system behavior
         if ((metaState & KeyEvent.META_META_ON) != 0) {
             return key_consumed;
@@ -3113,12 +2977,7 @@
     public void registerShortcutKey(long shortcutCode, IShortcutService shortcutService)
             throws RemoteException {
         synchronized (mLock) {
-            IShortcutService service = mShortcutKeyServices.get(shortcutCode);
-            if (service != null && service.asBinder().pingBinder()) {
-                throw new RemoteException("Key already exists.");
-            }
-
-            mShortcutKeyServices.put(shortcutCode, shortcutService);
+            mModifierShortcutManager.registerShortcutKey(shortcutCode, shortcutService);
         }
     }
 
diff --git a/services/core/java/com/android/server/policy/ShortcutManager.java b/services/core/java/com/android/server/policy/ShortcutManager.java
deleted file mode 100644
index ab404db..0000000
--- a/services/core/java/com/android/server/policy/ShortcutManager.java
+++ /dev/null
@@ -1,198 +0,0 @@
-/*
- * Copyright (C) 2007 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.policy;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ActivityInfo;
-import android.content.pm.PackageManager;
-import android.content.res.XmlResourceParser;
-import android.text.TextUtils;
-import android.util.Log;
-import android.util.SparseArray;
-import android.view.KeyCharacterMap;
-import android.view.KeyEvent;
-
-import com.android.internal.util.XmlUtils;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
-import java.io.IOException;
-
-/**
- * Manages quick launch shortcuts by:
- * <li> Keeping the local copy in sync with the database (this is an observer)
- * <li> Returning a shortcut-matching intent to clients
- */
-class ShortcutManager {
-    private static final String TAG = "ShortcutManager";
-
-    private static final String TAG_BOOKMARKS = "bookmarks";
-    private static final String TAG_BOOKMARK = "bookmark";
-
-    private static final String ATTRIBUTE_PACKAGE = "package";
-    private static final String ATTRIBUTE_CLASS = "class";
-    private static final String ATTRIBUTE_SHORTCUT = "shortcut";
-    private static final String ATTRIBUTE_CATEGORY = "category";
-    private static final String ATTRIBUTE_SHIFT = "shift";
-
-    private final SparseArray<ShortcutInfo> mShortcuts = new SparseArray<>();
-    private final SparseArray<ShortcutInfo> mShiftShortcuts = new SparseArray<>();
-
-    private final Context mContext;
-    
-    public ShortcutManager(Context context) {
-        mContext = context;
-        loadShortcuts();
-    }
-
-    /**
-     * Gets the shortcut intent for a given keycode+modifier. Make sure you
-     * strip whatever modifier is used for invoking shortcuts (for example,
-     * if 'Sym+A' should invoke a shortcut on 'A', you should strip the
-     * 'Sym' bit from the modifiers before calling this method.
-     * <p>
-     * This will first try an exact match (with modifiers), and then try a
-     * match without modifiers (primary character on a key).
-     * 
-     * @param kcm The key character map of the device on which the key was pressed.
-     * @param keyCode The key code.
-     * @param metaState The meta state, omitting any modifiers that were used
-     * to invoke the shortcut.
-     * @return The intent that matches the shortcut, or null if not found.
-     */
-    public Intent getIntent(KeyCharacterMap kcm, int keyCode, int metaState) {
-        ShortcutInfo shortcut = null;
-
-        // If the Shift key is pressed, then search for the shift shortcuts.
-        boolean isShiftOn = (metaState & KeyEvent.META_SHIFT_ON) == KeyEvent.META_SHIFT_ON;
-        SparseArray<ShortcutInfo> shortcutMap = isShiftOn ? mShiftShortcuts : mShortcuts;
-
-        // First try the exact keycode (with modifiers).
-        int shortcutChar = kcm.get(keyCode, metaState);
-        if (shortcutChar != 0) {
-            shortcut = shortcutMap.get(shortcutChar);
-        }
-
-        // Next try the primary character on that key.
-        if (shortcut == null) {
-            shortcutChar = Character.toLowerCase(kcm.getDisplayLabel(keyCode));
-            if (shortcutChar != 0) {
-                shortcut = shortcutMap.get(shortcutChar);
-            }
-        }
-
-        return (shortcut != null) ? shortcut.intent : null;
-    }
-
-    private void loadShortcuts() {
-        PackageManager packageManager = mContext.getPackageManager();
-        try {
-            XmlResourceParser parser = mContext.getResources().getXml(
-                    com.android.internal.R.xml.bookmarks);
-            XmlUtils.beginDocument(parser, TAG_BOOKMARKS);
-
-            while (true) {
-                XmlUtils.nextElement(parser);
-
-                if (parser.getEventType() == XmlPullParser.END_DOCUMENT) {
-                    break;
-                }
-
-                if (!TAG_BOOKMARK.equals(parser.getName())) {
-                    break;
-                }
-
-                String packageName = parser.getAttributeValue(null, ATTRIBUTE_PACKAGE);
-                String className = parser.getAttributeValue(null, ATTRIBUTE_CLASS);
-                String shortcutName = parser.getAttributeValue(null, ATTRIBUTE_SHORTCUT);
-                String categoryName = parser.getAttributeValue(null, ATTRIBUTE_CATEGORY);
-                String shiftName = parser.getAttributeValue(null, ATTRIBUTE_SHIFT);
-
-                if (TextUtils.isEmpty(shortcutName)) {
-                    Log.w(TAG, "Unable to get shortcut for: " + packageName + "/" + className);
-                    continue;
-                }
-
-                final int shortcutChar = shortcutName.charAt(0);
-                final boolean isShiftShortcut = (shiftName != null && shiftName.equals("true"));
-
-                final Intent intent;
-                final String title;
-                if (packageName != null && className != null) {
-                    ActivityInfo info = null;
-                    ComponentName componentName = new ComponentName(packageName, className);
-                    try {
-                        info = packageManager.getActivityInfo(componentName,
-                                PackageManager.MATCH_DIRECT_BOOT_AWARE
-                                        | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
-                                        | PackageManager.MATCH_UNINSTALLED_PACKAGES);
-                    } catch (PackageManager.NameNotFoundException e) {
-                        String[] packages = packageManager.canonicalToCurrentPackageNames(
-                                new String[] { packageName });
-                        componentName = new ComponentName(packages[0], className);
-                        try {
-                            info = packageManager.getActivityInfo(componentName,
-                                    PackageManager.MATCH_DIRECT_BOOT_AWARE
-                                            | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
-                                            | PackageManager.MATCH_UNINSTALLED_PACKAGES);
-                        } catch (PackageManager.NameNotFoundException e1) {
-                            Log.w(TAG, "Unable to add bookmark: " + packageName
-                                    + "/" + className, e);
-                            continue;
-                        }
-                    }
-
-                    intent = new Intent(Intent.ACTION_MAIN);
-                    intent.addCategory(Intent.CATEGORY_LAUNCHER);
-                    intent.setComponent(componentName);
-                    title = info.loadLabel(packageManager).toString();
-                } else if (categoryName != null) {
-                    intent = Intent.makeMainSelectorActivity(Intent.ACTION_MAIN, categoryName);
-                    title = "";
-                } else {
-                    Log.w(TAG, "Unable to add bookmark for shortcut " + shortcutName
-                            + ": missing package/class or category attributes");
-                    continue;
-                }
-
-                ShortcutInfo shortcut = new ShortcutInfo(title, intent);
-                if (isShiftShortcut) {
-                    mShiftShortcuts.put(shortcutChar, shortcut);
-                } else {
-                    mShortcuts.put(shortcutChar, shortcut);
-                }
-            }
-        } catch (XmlPullParserException e) {
-            Log.w(TAG, "Got exception parsing bookmarks.", e);
-        } catch (IOException e) {
-            Log.w(TAG, "Got exception parsing bookmarks.", e);
-        }
-    }
-
-    private static final class ShortcutInfo {
-        public final String title;
-        public final Intent intent;
-
-        public ShortcutInfo(String title, Intent intent) {
-            this.title = title;
-            this.intent = intent;
-        }
-    }
-}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 80173b5..68f5c58 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -5058,6 +5058,10 @@
             }
         } else {
             // No longer managed by any organizer.
+            final TaskDisplayArea taskDisplayArea = getDisplayArea();
+            if (taskDisplayArea != null) {
+                taskDisplayArea.removeLaunchRootTask(this);
+            }
             setForceHidden(FLAG_FORCE_HIDDEN_FOR_TASK_ORG, false /* set */);
             if (mCreatedByOrganizer) {
                 removeImmediately("setTaskOrganizer");
diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java
index badd7fd..76869e5 100644
--- a/services/core/java/com/android/server/wm/TaskDisplayArea.java
+++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java
@@ -1135,12 +1135,7 @@
                     "Can't set not mCreatedByOrganizer as launch root tr=" + rootTask);
         }
 
-        LaunchRootTaskDef def = null;
-        for (int i = mLaunchRootTasks.size() - 1; i >= 0; --i) {
-            if (mLaunchRootTasks.get(i).task.mTaskId != rootTask.mTaskId) continue;
-            def = mLaunchRootTasks.get(i);
-        }
-
+        LaunchRootTaskDef def = getLaunchRootTaskDef(rootTask);
         if (def != null) {
             // Remove so we add to the end of the list.
             mLaunchRootTasks.remove(def);
@@ -1156,6 +1151,23 @@
         }
     }
 
+    void removeLaunchRootTask(Task rootTask) {
+        LaunchRootTaskDef def = getLaunchRootTaskDef(rootTask);
+        if (def != null) {
+            mLaunchRootTasks.remove(def);
+        }
+    }
+
+    private @Nullable LaunchRootTaskDef getLaunchRootTaskDef(Task rootTask) {
+        LaunchRootTaskDef def = null;
+        for (int i = mLaunchRootTasks.size() - 1; i >= 0; --i) {
+            if (mLaunchRootTasks.get(i).task.mTaskId != rootTask.mTaskId) continue;
+            def = mLaunchRootTasks.get(i);
+            break;
+        }
+        return def;
+    }
+
     Task getLaunchRootTask(int windowingMode, int activityType, ActivityOptions options) {
         // Try to use the launch root task in options if available.
         if (options != 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 170f561..0000000
--- 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);
-        }
-    }
-}