Home | History | Annotate | Download | only in view
      1 /*
      2  * Copyright (C) 2012 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package android.view;
     18 
     19 import static android.view.accessibility.AccessibilityNodeInfo.ACTION_ARGUMENT_ACCESSIBLE_CLICKABLE_SPAN;
     20 import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY;
     21 
     22 import android.graphics.Point;
     23 import android.graphics.Rect;
     24 import android.graphics.RectF;
     25 import android.graphics.Region;
     26 import android.os.Binder;
     27 import android.os.Bundle;
     28 import android.os.Handler;
     29 import android.os.Looper;
     30 import android.os.Message;
     31 import android.os.Parcelable;
     32 import android.os.Process;
     33 import android.os.RemoteException;
     34 import android.text.style.AccessibilityClickableSpan;
     35 import android.text.style.ClickableSpan;
     36 import android.util.LongSparseArray;
     37 import android.view.View.AttachInfo;
     38 import android.view.accessibility.AccessibilityInteractionClient;
     39 import android.view.accessibility.AccessibilityNodeInfo;
     40 import android.view.accessibility.AccessibilityNodeProvider;
     41 import android.view.accessibility.IAccessibilityInteractionConnectionCallback;
     42 
     43 import com.android.internal.R;
     44 import com.android.internal.os.SomeArgs;
     45 
     46 import java.util.ArrayList;
     47 import java.util.HashMap;
     48 import java.util.HashSet;
     49 import java.util.LinkedList;
     50 import java.util.List;
     51 import java.util.Map;
     52 import java.util.Queue;
     53 import java.util.function.Predicate;
     54 
     55 /**
     56  * Class for managing accessibility interactions initiated from the system
     57  * and targeting the view hierarchy. A *ClientThread method is to be
     58  * called from the interaction connection ViewAncestor gives the system to
     59  * talk to it and a corresponding *UiThread method that is executed on the
     60  * UI thread.
     61  */
     62 final class AccessibilityInteractionController {
     63 
     64     private static final boolean ENFORCE_NODE_TREE_CONSISTENT = false;
     65 
     66     private final ArrayList<AccessibilityNodeInfo> mTempAccessibilityNodeInfoList =
     67         new ArrayList<AccessibilityNodeInfo>();
     68 
     69     private final Handler mHandler;
     70 
     71     private final ViewRootImpl mViewRootImpl;
     72 
     73     private final AccessibilityNodePrefetcher mPrefetcher;
     74 
     75     private final long mMyLooperThreadId;
     76 
     77     private final int mMyProcessId;
     78 
     79     private final ArrayList<View> mTempArrayList = new ArrayList<View>();
     80 
     81     private final Point mTempPoint = new Point();
     82     private final Rect mTempRect = new Rect();
     83     private final Rect mTempRect1 = new Rect();
     84     private final Rect mTempRect2 = new Rect();
     85 
     86     private AddNodeInfosForViewId mAddNodeInfosForViewId;
     87 
     88     public AccessibilityInteractionController(ViewRootImpl viewRootImpl) {
     89         Looper looper =  viewRootImpl.mHandler.getLooper();
     90         mMyLooperThreadId = looper.getThread().getId();
     91         mMyProcessId = Process.myPid();
     92         mHandler = new PrivateHandler(looper);
     93         mViewRootImpl = viewRootImpl;
     94         mPrefetcher = new AccessibilityNodePrefetcher();
     95     }
     96 
     97     private void scheduleMessage(Message message, int interrogatingPid, long interrogatingTid) {
     98         // If the interrogation is performed by the same thread as the main UI
     99         // thread in this process, set the message as a static reference so
    100         // after this call completes the same thread but in the interrogating
    101         // client can handle the message to generate the result.
    102         if (interrogatingPid == mMyProcessId && interrogatingTid == mMyLooperThreadId) {
    103             AccessibilityInteractionClient.getInstanceForThread(
    104                     interrogatingTid).setSameThreadMessage(message);
    105         } else {
    106             mHandler.sendMessage(message);
    107         }
    108     }
    109 
    110     private boolean isShown(View view) {
    111         // The first two checks are made also made by isShown() which
    112         // however traverses the tree up to the parent to catch that.
    113         // Therefore, we do some fail fast check to minimize the up
    114         // tree traversal.
    115         return (view.mAttachInfo != null
    116                 && view.mAttachInfo.mWindowVisibility == View.VISIBLE
    117                 && view.isShown());
    118     }
    119 
    120     public void findAccessibilityNodeInfoByAccessibilityIdClientThread(
    121             long accessibilityNodeId, Region interactiveRegion, int interactionId,
    122             IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid,
    123             long interrogatingTid, MagnificationSpec spec, Bundle arguments) {
    124         Message message = mHandler.obtainMessage();
    125         message.what = PrivateHandler.MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_ACCESSIBILITY_ID;
    126         message.arg1 = flags;
    127 
    128         SomeArgs args = SomeArgs.obtain();
    129         args.argi1 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId);
    130         args.argi2 = AccessibilityNodeInfo.getVirtualDescendantId(accessibilityNodeId);
    131         args.argi3 = interactionId;
    132         args.arg1 = callback;
    133         args.arg2 = spec;
    134         args.arg3 = interactiveRegion;
    135         args.arg4 = arguments;
    136         message.obj = args;
    137 
    138         scheduleMessage(message, interrogatingPid, interrogatingTid);
    139     }
    140 
    141     private void findAccessibilityNodeInfoByAccessibilityIdUiThread(Message message) {
    142         final int flags = message.arg1;
    143 
    144         SomeArgs args = (SomeArgs) message.obj;
    145         final int accessibilityViewId = args.argi1;
    146         final int virtualDescendantId = args.argi2;
    147         final int interactionId = args.argi3;
    148         final IAccessibilityInteractionConnectionCallback callback =
    149             (IAccessibilityInteractionConnectionCallback) args.arg1;
    150         final MagnificationSpec spec = (MagnificationSpec) args.arg2;
    151         final Region interactiveRegion = (Region) args.arg3;
    152         final Bundle arguments = (Bundle) args.arg4;
    153 
    154         args.recycle();
    155 
    156         List<AccessibilityNodeInfo> infos = mTempAccessibilityNodeInfoList;
    157         infos.clear();
    158         try {
    159             if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
    160                 return;
    161             }
    162             mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags;
    163             View root = null;
    164             if (accessibilityViewId == AccessibilityNodeInfo.ROOT_ITEM_ID) {
    165                 root = mViewRootImpl.mView;
    166             } else {
    167                 root = findViewByAccessibilityId(accessibilityViewId);
    168             }
    169             if (root != null && isShown(root)) {
    170                 mPrefetcher.prefetchAccessibilityNodeInfos(
    171                         root, virtualDescendantId, flags, infos, arguments);
    172             }
    173         } finally {
    174             updateInfosForViewportAndReturnFindNodeResult(
    175                     infos, callback, interactionId, spec, interactiveRegion);
    176         }
    177     }
    178 
    179     public void findAccessibilityNodeInfosByViewIdClientThread(long accessibilityNodeId,
    180             String viewId, Region interactiveRegion, int interactionId,
    181             IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid,
    182             long interrogatingTid, MagnificationSpec spec) {
    183         Message message = mHandler.obtainMessage();
    184         message.what = PrivateHandler.MSG_FIND_ACCESSIBILITY_NODE_INFOS_BY_VIEW_ID;
    185         message.arg1 = flags;
    186         message.arg2 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId);
    187 
    188         SomeArgs args = SomeArgs.obtain();
    189         args.argi1 = interactionId;
    190         args.arg1 = callback;
    191         args.arg2 = spec;
    192         args.arg3 = viewId;
    193         args.arg4 = interactiveRegion;
    194         message.obj = args;
    195 
    196         scheduleMessage(message, interrogatingPid, interrogatingTid);
    197     }
    198 
    199     private void findAccessibilityNodeInfosByViewIdUiThread(Message message) {
    200         final int flags = message.arg1;
    201         final int accessibilityViewId = message.arg2;
    202 
    203         SomeArgs args = (SomeArgs) message.obj;
    204         final int interactionId = args.argi1;
    205         final IAccessibilityInteractionConnectionCallback callback =
    206             (IAccessibilityInteractionConnectionCallback) args.arg1;
    207         final MagnificationSpec spec = (MagnificationSpec) args.arg2;
    208         final String viewId = (String) args.arg3;
    209         final Region interactiveRegion = (Region) args.arg4;
    210         args.recycle();
    211 
    212         final List<AccessibilityNodeInfo> infos = mTempAccessibilityNodeInfoList;
    213         infos.clear();
    214         try {
    215             if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
    216                 return;
    217             }
    218             mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags;
    219             View root = null;
    220             if (accessibilityViewId != AccessibilityNodeInfo.ROOT_ITEM_ID) {
    221                 root = findViewByAccessibilityId(accessibilityViewId);
    222             } else {
    223                 root = mViewRootImpl.mView;
    224             }
    225             if (root != null) {
    226                 final int resolvedViewId = root.getContext().getResources()
    227                         .getIdentifier(viewId, null, null);
    228                 if (resolvedViewId <= 0) {
    229                     return;
    230                 }
    231                 if (mAddNodeInfosForViewId == null) {
    232                     mAddNodeInfosForViewId = new AddNodeInfosForViewId();
    233                 }
    234                 mAddNodeInfosForViewId.init(resolvedViewId, infos);
    235                 root.findViewByPredicate(mAddNodeInfosForViewId);
    236                 mAddNodeInfosForViewId.reset();
    237             }
    238         } finally {
    239             updateInfosForViewportAndReturnFindNodeResult(
    240                     infos, callback, interactionId, spec, interactiveRegion);
    241         }
    242     }
    243 
    244     public void findAccessibilityNodeInfosByTextClientThread(long accessibilityNodeId,
    245             String text, Region interactiveRegion, int interactionId,
    246             IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid,
    247             long interrogatingTid, MagnificationSpec spec) {
    248         Message message = mHandler.obtainMessage();
    249         message.what = PrivateHandler.MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_TEXT;
    250         message.arg1 = flags;
    251 
    252         SomeArgs args = SomeArgs.obtain();
    253         args.arg1 = text;
    254         args.arg2 = callback;
    255         args.arg3 = spec;
    256         args.argi1 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId);
    257         args.argi2 = AccessibilityNodeInfo.getVirtualDescendantId(accessibilityNodeId);
    258         args.argi3 = interactionId;
    259         args.arg4 = interactiveRegion;
    260         message.obj = args;
    261 
    262         scheduleMessage(message, interrogatingPid, interrogatingTid);
    263     }
    264 
    265     private void findAccessibilityNodeInfosByTextUiThread(Message message) {
    266         final int flags = message.arg1;
    267 
    268         SomeArgs args = (SomeArgs) message.obj;
    269         final String text = (String) args.arg1;
    270         final IAccessibilityInteractionConnectionCallback callback =
    271             (IAccessibilityInteractionConnectionCallback) args.arg2;
    272         final MagnificationSpec spec = (MagnificationSpec) args.arg3;
    273         final int accessibilityViewId = args.argi1;
    274         final int virtualDescendantId = args.argi2;
    275         final int interactionId = args.argi3;
    276         final Region interactiveRegion = (Region) args.arg4;
    277         args.recycle();
    278 
    279         List<AccessibilityNodeInfo> infos = null;
    280         try {
    281             if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
    282                 return;
    283             }
    284             mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags;
    285             View root = null;
    286             if (accessibilityViewId != AccessibilityNodeInfo.ROOT_ITEM_ID) {
    287                 root = findViewByAccessibilityId(accessibilityViewId);
    288             } else {
    289                 root = mViewRootImpl.mView;
    290             }
    291             if (root != null && isShown(root)) {
    292                 AccessibilityNodeProvider provider = root.getAccessibilityNodeProvider();
    293                 if (provider != null) {
    294                     infos = provider.findAccessibilityNodeInfosByText(text,
    295                             virtualDescendantId);
    296                 } else if (virtualDescendantId == AccessibilityNodeProvider.HOST_VIEW_ID) {
    297                     ArrayList<View> foundViews = mTempArrayList;
    298                     foundViews.clear();
    299                     root.findViewsWithText(foundViews, text, View.FIND_VIEWS_WITH_TEXT
    300                             | View.FIND_VIEWS_WITH_CONTENT_DESCRIPTION
    301                             | View.FIND_VIEWS_WITH_ACCESSIBILITY_NODE_PROVIDERS);
    302                     if (!foundViews.isEmpty()) {
    303                         infos = mTempAccessibilityNodeInfoList;
    304                         infos.clear();
    305                         final int viewCount = foundViews.size();
    306                         for (int i = 0; i < viewCount; i++) {
    307                             View foundView = foundViews.get(i);
    308                             if (isShown(foundView)) {
    309                                 provider = foundView.getAccessibilityNodeProvider();
    310                                 if (provider != null) {
    311                                     List<AccessibilityNodeInfo> infosFromProvider =
    312                                         provider.findAccessibilityNodeInfosByText(text,
    313                                                 AccessibilityNodeProvider.HOST_VIEW_ID);
    314                                     if (infosFromProvider != null) {
    315                                         infos.addAll(infosFromProvider);
    316                                     }
    317                                 } else  {
    318                                     infos.add(foundView.createAccessibilityNodeInfo());
    319                                 }
    320                             }
    321                         }
    322                     }
    323                 }
    324             }
    325         } finally {
    326             updateInfosForViewportAndReturnFindNodeResult(
    327                     infos, callback, interactionId, spec, interactiveRegion);
    328         }
    329     }
    330 
    331     public void findFocusClientThread(long accessibilityNodeId, int focusType,
    332             Region interactiveRegion, int interactionId,
    333             IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid,
    334             long interrogatingTid, MagnificationSpec spec) {
    335         Message message = mHandler.obtainMessage();
    336         message.what = PrivateHandler.MSG_FIND_FOCUS;
    337         message.arg1 = flags;
    338         message.arg2 = focusType;
    339 
    340         SomeArgs args = SomeArgs.obtain();
    341         args.argi1 = interactionId;
    342         args.argi2 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId);
    343         args.argi3 = AccessibilityNodeInfo.getVirtualDescendantId(accessibilityNodeId);
    344         args.arg1 = callback;
    345         args.arg2 = spec;
    346         args.arg3 = interactiveRegion;
    347 
    348         message.obj = args;
    349 
    350         scheduleMessage(message, interrogatingPid, interrogatingTid);
    351     }
    352 
    353     private void findFocusUiThread(Message message) {
    354         final int flags = message.arg1;
    355         final int focusType = message.arg2;
    356 
    357         SomeArgs args = (SomeArgs) message.obj;
    358         final int interactionId = args.argi1;
    359         final int accessibilityViewId = args.argi2;
    360         final int virtualDescendantId = args.argi3;
    361         final IAccessibilityInteractionConnectionCallback callback =
    362             (IAccessibilityInteractionConnectionCallback) args.arg1;
    363         final MagnificationSpec spec = (MagnificationSpec) args.arg2;
    364         final Region interactiveRegion = (Region) args.arg3;
    365         args.recycle();
    366 
    367         AccessibilityNodeInfo focused = null;
    368         try {
    369             if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
    370                 return;
    371             }
    372             mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags;
    373             View root = null;
    374             if (accessibilityViewId != AccessibilityNodeInfo.ROOT_ITEM_ID) {
    375                 root = findViewByAccessibilityId(accessibilityViewId);
    376             } else {
    377                 root = mViewRootImpl.mView;
    378             }
    379             if (root != null && isShown(root)) {
    380                 switch (focusType) {
    381                     case AccessibilityNodeInfo.FOCUS_ACCESSIBILITY: {
    382                         View host = mViewRootImpl.mAccessibilityFocusedHost;
    383                         // If there is no accessibility focus host or it is not a descendant
    384                         // of the root from which to start the search, then the search failed.
    385                         if (host == null || !ViewRootImpl.isViewDescendantOf(host, root)) {
    386                             break;
    387                         }
    388                         // The focused view not shown, we failed.
    389                         if (!isShown(host)) {
    390                             break;
    391                         }
    392                         // If the host has a provider ask this provider to search for the
    393                         // focus instead fetching all provider nodes to do the search here.
    394                         AccessibilityNodeProvider provider = host.getAccessibilityNodeProvider();
    395                         if (provider != null) {
    396                             if (mViewRootImpl.mAccessibilityFocusedVirtualView != null) {
    397                                 focused = AccessibilityNodeInfo.obtain(
    398                                         mViewRootImpl.mAccessibilityFocusedVirtualView);
    399                             }
    400                         } else if (virtualDescendantId == AccessibilityNodeProvider.HOST_VIEW_ID) {
    401                             focused = host.createAccessibilityNodeInfo();
    402                         }
    403                     } break;
    404                     case AccessibilityNodeInfo.FOCUS_INPUT: {
    405                         View target = root.findFocus();
    406                         if (target == null || !isShown(target)) {
    407                             break;
    408                         }
    409                         AccessibilityNodeProvider provider = target.getAccessibilityNodeProvider();
    410                         if (provider != null) {
    411                             focused = provider.findFocus(focusType);
    412                         }
    413                         if (focused == null) {
    414                             focused = target.createAccessibilityNodeInfo();
    415                         }
    416                     } break;
    417                     default:
    418                         throw new IllegalArgumentException("Unknown focus type: " + focusType);
    419                 }
    420             }
    421         } finally {
    422             updateInfoForViewportAndReturnFindNodeResult(
    423                     focused, callback, interactionId, spec, interactiveRegion);
    424         }
    425     }
    426 
    427     public void focusSearchClientThread(long accessibilityNodeId, int direction,
    428             Region interactiveRegion, int interactionId,
    429             IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid,
    430             long interrogatingTid, MagnificationSpec spec) {
    431         Message message = mHandler.obtainMessage();
    432         message.what = PrivateHandler.MSG_FOCUS_SEARCH;
    433         message.arg1 = flags;
    434         message.arg2 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId);
    435 
    436         SomeArgs args = SomeArgs.obtain();
    437         args.argi2 = direction;
    438         args.argi3 = interactionId;
    439         args.arg1 = callback;
    440         args.arg2 = spec;
    441         args.arg3 = interactiveRegion;
    442 
    443         message.obj = args;
    444 
    445         scheduleMessage(message, interrogatingPid, interrogatingTid);
    446     }
    447 
    448     private void focusSearchUiThread(Message message) {
    449         final int flags = message.arg1;
    450         final int accessibilityViewId = message.arg2;
    451 
    452         SomeArgs args = (SomeArgs) message.obj;
    453         final int direction = args.argi2;
    454         final int interactionId = args.argi3;
    455         final IAccessibilityInteractionConnectionCallback callback =
    456             (IAccessibilityInteractionConnectionCallback) args.arg1;
    457         final MagnificationSpec spec = (MagnificationSpec) args.arg2;
    458         final Region interactiveRegion = (Region) args.arg3;
    459 
    460         args.recycle();
    461 
    462         AccessibilityNodeInfo next = null;
    463         try {
    464             if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
    465                 return;
    466             }
    467             mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags;
    468             View root = null;
    469             if (accessibilityViewId != AccessibilityNodeInfo.ROOT_ITEM_ID) {
    470                 root = findViewByAccessibilityId(accessibilityViewId);
    471             } else {
    472                 root = mViewRootImpl.mView;
    473             }
    474             if (root != null && isShown(root)) {
    475                 View nextView = root.focusSearch(direction);
    476                 if (nextView != null) {
    477                     next = nextView.createAccessibilityNodeInfo();
    478                 }
    479             }
    480         } finally {
    481             updateInfoForViewportAndReturnFindNodeResult(
    482                     next, callback, interactionId, spec, interactiveRegion);
    483         }
    484     }
    485 
    486     public void performAccessibilityActionClientThread(long accessibilityNodeId, int action,
    487             Bundle arguments, int interactionId,
    488             IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid,
    489             long interrogatingTid) {
    490         Message message = mHandler.obtainMessage();
    491         message.what = PrivateHandler.MSG_PERFORM_ACCESSIBILITY_ACTION;
    492         message.arg1 = flags;
    493         message.arg2 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId);
    494 
    495         SomeArgs args = SomeArgs.obtain();
    496         args.argi1 = AccessibilityNodeInfo.getVirtualDescendantId(accessibilityNodeId);
    497         args.argi2 = action;
    498         args.argi3 = interactionId;
    499         args.arg1 = callback;
    500         args.arg2 = arguments;
    501 
    502         message.obj = args;
    503 
    504         scheduleMessage(message, interrogatingPid, interrogatingTid);
    505     }
    506 
    507     private void performAccessibilityActionUiThread(Message message) {
    508         final int flags = message.arg1;
    509         final int accessibilityViewId = message.arg2;
    510 
    511         SomeArgs args = (SomeArgs) message.obj;
    512         final int virtualDescendantId = args.argi1;
    513         final int action = args.argi2;
    514         final int interactionId = args.argi3;
    515         final IAccessibilityInteractionConnectionCallback callback =
    516             (IAccessibilityInteractionConnectionCallback) args.arg1;
    517         Bundle arguments = (Bundle) args.arg2;
    518 
    519         args.recycle();
    520 
    521         boolean succeeded = false;
    522         try {
    523             if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null ||
    524                     mViewRootImpl.mStopped || mViewRootImpl.mPausedForTransition) {
    525                 return;
    526             }
    527             mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags;
    528             View target = null;
    529             if (accessibilityViewId != AccessibilityNodeInfo.ROOT_ITEM_ID) {
    530                 target = findViewByAccessibilityId(accessibilityViewId);
    531             } else {
    532                 target = mViewRootImpl.mView;
    533             }
    534             if (target != null && isShown(target)) {
    535                 if (action == R.id.accessibilityActionClickOnClickableSpan) {
    536                     // Handle this hidden action separately
    537                     succeeded = handleClickableSpanActionUiThread(
    538                             target, virtualDescendantId, arguments);
    539                 } else {
    540                     AccessibilityNodeProvider provider = target.getAccessibilityNodeProvider();
    541                     if (provider != null) {
    542                         succeeded = provider.performAction(virtualDescendantId, action,
    543                                 arguments);
    544                     } else if (virtualDescendantId == AccessibilityNodeProvider.HOST_VIEW_ID) {
    545                         succeeded = target.performAccessibilityAction(action, arguments);
    546                     }
    547                 }
    548             }
    549         } finally {
    550             try {
    551                 mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
    552                 callback.setPerformAccessibilityActionResult(succeeded, interactionId);
    553             } catch (RemoteException re) {
    554                 /* ignore - the other side will time out */
    555             }
    556         }
    557     }
    558 
    559     private View findViewByAccessibilityId(int accessibilityId) {
    560         View root = mViewRootImpl.mView;
    561         if (root == null) {
    562             return null;
    563         }
    564         View foundView = root.findViewByAccessibilityId(accessibilityId);
    565         if (foundView != null && !isShown(foundView)) {
    566             return null;
    567         }
    568         return foundView;
    569     }
    570 
    571     private void applyAppScaleAndMagnificationSpecIfNeeded(List<AccessibilityNodeInfo> infos,
    572             MagnificationSpec spec) {
    573         if (infos == null) {
    574             return;
    575         }
    576         final float applicationScale = mViewRootImpl.mAttachInfo.mApplicationScale;
    577         if (shouldApplyAppScaleAndMagnificationSpec(applicationScale, spec)) {
    578             final int infoCount = infos.size();
    579             for (int i = 0; i < infoCount; i++) {
    580                 AccessibilityNodeInfo info = infos.get(i);
    581                 applyAppScaleAndMagnificationSpecIfNeeded(info, spec);
    582             }
    583         }
    584     }
    585 
    586     private void adjustIsVisibleToUserIfNeeded(List<AccessibilityNodeInfo> infos,
    587             Region interactiveRegion) {
    588         if (interactiveRegion == null || infos == null) {
    589             return;
    590         }
    591         final int infoCount = infos.size();
    592         for (int i = 0; i < infoCount; i++) {
    593             AccessibilityNodeInfo info = infos.get(i);
    594             adjustIsVisibleToUserIfNeeded(info, interactiveRegion);
    595         }
    596     }
    597 
    598     private void adjustIsVisibleToUserIfNeeded(AccessibilityNodeInfo info,
    599             Region interactiveRegion) {
    600         if (interactiveRegion == null || info == null) {
    601             return;
    602         }
    603         Rect boundsInScreen = mTempRect;
    604         info.getBoundsInScreen(boundsInScreen);
    605         if (interactiveRegion.quickReject(boundsInScreen)) {
    606             info.setVisibleToUser(false);
    607         }
    608     }
    609 
    610     private void applyAppScaleAndMagnificationSpecIfNeeded(AccessibilityNodeInfo info,
    611             MagnificationSpec spec) {
    612         if (info == null) {
    613             return;
    614         }
    615 
    616         final float applicationScale = mViewRootImpl.mAttachInfo.mApplicationScale;
    617         if (!shouldApplyAppScaleAndMagnificationSpec(applicationScale, spec)) {
    618             return;
    619         }
    620 
    621         Rect boundsInParent = mTempRect;
    622         Rect boundsInScreen = mTempRect1;
    623 
    624         info.getBoundsInParent(boundsInParent);
    625         info.getBoundsInScreen(boundsInScreen);
    626         if (applicationScale != 1.0f) {
    627             boundsInParent.scale(applicationScale);
    628             boundsInScreen.scale(applicationScale);
    629         }
    630         if (spec != null) {
    631             boundsInParent.scale(spec.scale);
    632             // boundsInParent must not be offset.
    633             boundsInScreen.scale(spec.scale);
    634             boundsInScreen.offset((int) spec.offsetX, (int) spec.offsetY);
    635         }
    636         info.setBoundsInParent(boundsInParent);
    637         info.setBoundsInScreen(boundsInScreen);
    638 
    639         // Scale text locations if they are present
    640         if (info.hasExtras()) {
    641             Bundle extras = info.getExtras();
    642             Parcelable[] textLocations =
    643                     extras.getParcelableArray(EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY);
    644             if (textLocations != null) {
    645                 for (int i = 0; i < textLocations.length; i++) {
    646                     // Unchecked cast - an app that puts other objects in this bundle with this
    647                     // key will crash.
    648                     RectF textLocation = ((RectF) textLocations[i]);
    649                     textLocation.scale(applicationScale);
    650                     if (spec != null) {
    651                         textLocation.scale(spec.scale);
    652                         textLocation.offset(spec.offsetX, spec.offsetY);
    653                     }
    654                 }
    655             }
    656         }
    657 
    658         if (spec != null) {
    659             AttachInfo attachInfo = mViewRootImpl.mAttachInfo;
    660             if (attachInfo.mDisplay == null) {
    661                 return;
    662             }
    663 
    664             final float scale = attachInfo.mApplicationScale * spec.scale;
    665 
    666             Rect visibleWinFrame = mTempRect1;
    667             visibleWinFrame.left = (int) (attachInfo.mWindowLeft * scale + spec.offsetX);
    668             visibleWinFrame.top = (int) (attachInfo.mWindowTop * scale + spec.offsetY);
    669             visibleWinFrame.right = (int) (visibleWinFrame.left + mViewRootImpl.mWidth * scale);
    670             visibleWinFrame.bottom = (int) (visibleWinFrame.top + mViewRootImpl.mHeight * scale);
    671 
    672             attachInfo.mDisplay.getRealSize(mTempPoint);
    673             final int displayWidth = mTempPoint.x;
    674             final int displayHeight = mTempPoint.y;
    675 
    676             Rect visibleDisplayFrame = mTempRect2;
    677             visibleDisplayFrame.set(0, 0, displayWidth, displayHeight);
    678 
    679             if (!visibleWinFrame.intersect(visibleDisplayFrame)) {
    680                 // If there's no intersection with display, set visibleWinFrame empty.
    681                 visibleDisplayFrame.setEmpty();
    682             }
    683 
    684             if (!visibleWinFrame.intersects(boundsInScreen.left, boundsInScreen.top,
    685                     boundsInScreen.right, boundsInScreen.bottom)) {
    686                 info.setVisibleToUser(false);
    687             }
    688         }
    689     }
    690 
    691     private boolean shouldApplyAppScaleAndMagnificationSpec(float appScale,
    692             MagnificationSpec spec) {
    693         return (appScale != 1.0f || (spec != null && !spec.isNop()));
    694     }
    695 
    696     private void updateInfosForViewportAndReturnFindNodeResult(List<AccessibilityNodeInfo> infos,
    697             IAccessibilityInteractionConnectionCallback callback, int interactionId,
    698             MagnificationSpec spec, Region interactiveRegion) {
    699         try {
    700             mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
    701             applyAppScaleAndMagnificationSpecIfNeeded(infos, spec);
    702             adjustIsVisibleToUserIfNeeded(infos, interactiveRegion);
    703             callback.setFindAccessibilityNodeInfosResult(infos, interactionId);
    704             if (infos != null) {
    705                 infos.clear();
    706             }
    707         } catch (RemoteException re) {
    708             /* ignore - the other side will time out */
    709         } finally {
    710             recycleMagnificationSpecAndRegionIfNeeded(spec, interactiveRegion);
    711         }
    712     }
    713 
    714     private void updateInfoForViewportAndReturnFindNodeResult(AccessibilityNodeInfo info,
    715             IAccessibilityInteractionConnectionCallback callback, int interactionId,
    716             MagnificationSpec spec, Region interactiveRegion) {
    717         try {
    718             mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
    719             applyAppScaleAndMagnificationSpecIfNeeded(info, spec);
    720             adjustIsVisibleToUserIfNeeded(info, interactiveRegion);
    721             callback.setFindAccessibilityNodeInfoResult(info, interactionId);
    722         } catch (RemoteException re) {
    723                 /* ignore - the other side will time out */
    724         } finally {
    725             recycleMagnificationSpecAndRegionIfNeeded(spec, interactiveRegion);
    726         }
    727     }
    728 
    729     private void recycleMagnificationSpecAndRegionIfNeeded(MagnificationSpec spec, Region region) {
    730         if (android.os.Process.myPid() != Binder.getCallingPid()) {
    731             // Specs are cached in the system process and obtained from a pool when read from
    732             // a parcel, so only recycle the spec if called from another process.
    733             if (spec != null) {
    734                 spec.recycle();
    735             }
    736         } else {
    737             // Regions are obtained in the system process and instantiated when read from
    738             // a parcel, so only recycle the region if caled from the same process.
    739             if (region != null) {
    740                 region.recycle();
    741             }
    742         }
    743     }
    744 
    745     private boolean handleClickableSpanActionUiThread(
    746             View view, int virtualDescendantId, Bundle arguments) {
    747         Parcelable span = arguments.getParcelable(ACTION_ARGUMENT_ACCESSIBLE_CLICKABLE_SPAN);
    748         if (!(span instanceof AccessibilityClickableSpan)) {
    749             return false;
    750         }
    751 
    752         // Find the original ClickableSpan if it's still on the screen
    753         AccessibilityNodeInfo infoWithSpan = null;
    754         AccessibilityNodeProvider provider = view.getAccessibilityNodeProvider();
    755         if (provider != null) {
    756             infoWithSpan = provider.createAccessibilityNodeInfo(virtualDescendantId);
    757         } else if (virtualDescendantId == AccessibilityNodeProvider.HOST_VIEW_ID) {
    758             infoWithSpan = view.createAccessibilityNodeInfo();
    759         }
    760         if (infoWithSpan == null) {
    761             return false;
    762         }
    763 
    764         // Click on the corresponding span
    765         ClickableSpan clickableSpan = ((AccessibilityClickableSpan) span).findClickableSpan(
    766                 infoWithSpan.getOriginalText());
    767         if (clickableSpan != null) {
    768             clickableSpan.onClick(view);
    769             return true;
    770         }
    771         return false;
    772     }
    773 
    774     /**
    775      * This class encapsulates a prefetching strategy for the accessibility APIs for
    776      * querying window content. It is responsible to prefetch a batch of
    777      * AccessibilityNodeInfos in addition to the one for a requested node.
    778      */
    779     private class AccessibilityNodePrefetcher {
    780 
    781         private static final int MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE = 50;
    782 
    783         private final ArrayList<View> mTempViewList = new ArrayList<View>();
    784 
    785         public void prefetchAccessibilityNodeInfos(View view, int virtualViewId, int fetchFlags,
    786                 List<AccessibilityNodeInfo> outInfos, Bundle arguments) {
    787             AccessibilityNodeProvider provider = view.getAccessibilityNodeProvider();
    788             // Determine if we'll be populating extra data
    789             final String extraDataRequested = (arguments == null) ? null
    790                     : arguments.getString(AccessibilityNodeInfo.EXTRA_DATA_REQUESTED_KEY);
    791             if (provider == null) {
    792                 AccessibilityNodeInfo root = view.createAccessibilityNodeInfo();
    793                 if (root != null) {
    794                     if (extraDataRequested != null) {
    795                         view.addExtraDataToAccessibilityNodeInfo(
    796                                 root, extraDataRequested, arguments);
    797                     }
    798                     outInfos.add(root);
    799                     if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS) != 0) {
    800                         prefetchPredecessorsOfRealNode(view, outInfos);
    801                     }
    802                     if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS) != 0) {
    803                         prefetchSiblingsOfRealNode(view, outInfos);
    804                     }
    805                     if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS) != 0) {
    806                         prefetchDescendantsOfRealNode(view, outInfos);
    807                     }
    808                 }
    809             } else {
    810                 final AccessibilityNodeInfo root =
    811                         provider.createAccessibilityNodeInfo(virtualViewId);
    812                 if (root != null) {
    813                     if (extraDataRequested != null) {
    814                         provider.addExtraDataToAccessibilityNodeInfo(
    815                                 virtualViewId, root, extraDataRequested, arguments);
    816                     }
    817                     outInfos.add(root);
    818                     if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS) != 0) {
    819                         prefetchPredecessorsOfVirtualNode(root, view, provider, outInfos);
    820                     }
    821                     if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS) != 0) {
    822                         prefetchSiblingsOfVirtualNode(root, view, provider, outInfos);
    823                     }
    824                     if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS) != 0) {
    825                         prefetchDescendantsOfVirtualNode(root, provider, outInfos);
    826                     }
    827                 }
    828             }
    829             if (ENFORCE_NODE_TREE_CONSISTENT) {
    830                 enforceNodeTreeConsistent(outInfos);
    831             }
    832         }
    833 
    834         private void enforceNodeTreeConsistent(List<AccessibilityNodeInfo> nodes) {
    835             LongSparseArray<AccessibilityNodeInfo> nodeMap =
    836                     new LongSparseArray<AccessibilityNodeInfo>();
    837             final int nodeCount = nodes.size();
    838             for (int i = 0; i < nodeCount; i++) {
    839                 AccessibilityNodeInfo node = nodes.get(i);
    840                 nodeMap.put(node.getSourceNodeId(), node);
    841             }
    842 
    843             // If the nodes are a tree it does not matter from
    844             // which node we start to search for the root.
    845             AccessibilityNodeInfo root = nodeMap.valueAt(0);
    846             AccessibilityNodeInfo parent = root;
    847             while (parent != null) {
    848                 root = parent;
    849                 parent = nodeMap.get(parent.getParentNodeId());
    850             }
    851 
    852             // Traverse the tree and do some checks.
    853             AccessibilityNodeInfo accessFocus = null;
    854             AccessibilityNodeInfo inputFocus = null;
    855             HashSet<AccessibilityNodeInfo> seen = new HashSet<AccessibilityNodeInfo>();
    856             Queue<AccessibilityNodeInfo> fringe = new LinkedList<AccessibilityNodeInfo>();
    857             fringe.add(root);
    858 
    859             while (!fringe.isEmpty()) {
    860                 AccessibilityNodeInfo current = fringe.poll();
    861 
    862                 // Check for duplicates
    863                 if (!seen.add(current)) {
    864                     throw new IllegalStateException("Duplicate node: "
    865                             + current + " in window:"
    866                             + mViewRootImpl.mAttachInfo.mAccessibilityWindowId);
    867                 }
    868 
    869                 // Check for one accessibility focus.
    870                 if (current.isAccessibilityFocused()) {
    871                     if (accessFocus != null) {
    872                         throw new IllegalStateException("Duplicate accessibility focus:"
    873                                 + current
    874                                 + " in window:" + mViewRootImpl.mAttachInfo.mAccessibilityWindowId);
    875                     } else {
    876                         accessFocus = current;
    877                     }
    878                 }
    879 
    880                 // Check for one input focus.
    881                 if (current.isFocused()) {
    882                     if (inputFocus != null) {
    883                         throw new IllegalStateException("Duplicate input focus: "
    884                             + current + " in window:"
    885                             + mViewRootImpl.mAttachInfo.mAccessibilityWindowId);
    886                     } else {
    887                         inputFocus = current;
    888                     }
    889                 }
    890 
    891                 final int childCount = current.getChildCount();
    892                 for (int j = 0; j < childCount; j++) {
    893                     final long childId = current.getChildId(j);
    894                     final AccessibilityNodeInfo child = nodeMap.get(childId);
    895                     if (child != null) {
    896                         fringe.add(child);
    897                     }
    898                 }
    899             }
    900 
    901             // Check for disconnected nodes.
    902             for (int j = nodeMap.size() - 1; j >= 0; j--) {
    903                 AccessibilityNodeInfo info = nodeMap.valueAt(j);
    904                 if (!seen.contains(info)) {
    905                     throw new IllegalStateException("Disconnected node: " + info);
    906                 }
    907             }
    908         }
    909 
    910         private void prefetchPredecessorsOfRealNode(View view,
    911                 List<AccessibilityNodeInfo> outInfos) {
    912             ViewParent parent = view.getParentForAccessibility();
    913             while (parent instanceof View
    914                     && outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
    915                 View parentView = (View) parent;
    916                 AccessibilityNodeInfo info = parentView.createAccessibilityNodeInfo();
    917                 if (info != null) {
    918                     outInfos.add(info);
    919                 }
    920                 parent = parent.getParentForAccessibility();
    921             }
    922         }
    923 
    924         private void prefetchSiblingsOfRealNode(View current,
    925                 List<AccessibilityNodeInfo> outInfos) {
    926             ViewParent parent = current.getParentForAccessibility();
    927             if (parent instanceof ViewGroup) {
    928                 ViewGroup parentGroup = (ViewGroup) parent;
    929                 ArrayList<View> children = mTempViewList;
    930                 children.clear();
    931                 try {
    932                     parentGroup.addChildrenForAccessibility(children);
    933                     final int childCount = children.size();
    934                     for (int i = 0; i < childCount; i++) {
    935                         if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
    936                             return;
    937                         }
    938                         View child = children.get(i);
    939                         if (child.getAccessibilityViewId() != current.getAccessibilityViewId()
    940                                 &&  isShown(child)) {
    941                             AccessibilityNodeInfo info = null;
    942                             AccessibilityNodeProvider provider =
    943                                 child.getAccessibilityNodeProvider();
    944                             if (provider == null) {
    945                                 info = child.createAccessibilityNodeInfo();
    946                             } else {
    947                                 info = provider.createAccessibilityNodeInfo(
    948                                         AccessibilityNodeProvider.HOST_VIEW_ID);
    949                             }
    950                             if (info != null) {
    951                                 outInfos.add(info);
    952                             }
    953                         }
    954                     }
    955                 } finally {
    956                     children.clear();
    957                 }
    958             }
    959         }
    960 
    961         private void prefetchDescendantsOfRealNode(View root,
    962                 List<AccessibilityNodeInfo> outInfos) {
    963             if (!(root instanceof ViewGroup)) {
    964                 return;
    965             }
    966             HashMap<View, AccessibilityNodeInfo> addedChildren =
    967                 new HashMap<View, AccessibilityNodeInfo>();
    968             ArrayList<View> children = mTempViewList;
    969             children.clear();
    970             try {
    971                 root.addChildrenForAccessibility(children);
    972                 final int childCount = children.size();
    973                 for (int i = 0; i < childCount; i++) {
    974                     if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
    975                         return;
    976                     }
    977                     View child = children.get(i);
    978                     if (isShown(child)) {
    979                         AccessibilityNodeProvider provider = child.getAccessibilityNodeProvider();
    980                         if (provider == null) {
    981                             AccessibilityNodeInfo info = child.createAccessibilityNodeInfo();
    982                             if (info != null) {
    983                                 outInfos.add(info);
    984                                 addedChildren.put(child, null);
    985                             }
    986                         } else {
    987                             AccessibilityNodeInfo info = provider.createAccessibilityNodeInfo(
    988                                    AccessibilityNodeProvider.HOST_VIEW_ID);
    989                             if (info != null) {
    990                                 outInfos.add(info);
    991                                 addedChildren.put(child, info);
    992                             }
    993                         }
    994                     }
    995                 }
    996             } finally {
    997                 children.clear();
    998             }
    999             if (outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
   1000                 for (Map.Entry<View, AccessibilityNodeInfo> entry : addedChildren.entrySet()) {
   1001                     View addedChild = entry.getKey();
   1002                     AccessibilityNodeInfo virtualRoot = entry.getValue();
   1003                     if (virtualRoot == null) {
   1004                         prefetchDescendantsOfRealNode(addedChild, outInfos);
   1005                     } else {
   1006                         AccessibilityNodeProvider provider =
   1007                             addedChild.getAccessibilityNodeProvider();
   1008                         prefetchDescendantsOfVirtualNode(virtualRoot, provider, outInfos);
   1009                     }
   1010                 }
   1011             }
   1012         }
   1013 
   1014         private void prefetchPredecessorsOfVirtualNode(AccessibilityNodeInfo root,
   1015                 View providerHost, AccessibilityNodeProvider provider,
   1016                 List<AccessibilityNodeInfo> outInfos) {
   1017             final int initialResultSize = outInfos.size();
   1018             long parentNodeId = root.getParentNodeId();
   1019             int accessibilityViewId = AccessibilityNodeInfo.getAccessibilityViewId(parentNodeId);
   1020             while (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) {
   1021                 if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
   1022                     return;
   1023                 }
   1024                 final int virtualDescendantId =
   1025                     AccessibilityNodeInfo.getVirtualDescendantId(parentNodeId);
   1026                 if (virtualDescendantId != AccessibilityNodeProvider.HOST_VIEW_ID
   1027                         || accessibilityViewId == providerHost.getAccessibilityViewId()) {
   1028                     final AccessibilityNodeInfo parent;
   1029                     parent = provider.createAccessibilityNodeInfo(virtualDescendantId);
   1030                     if (parent == null) {
   1031                         // Going up the parent relation we found a null predecessor,
   1032                         // so remove these disconnected nodes form the result.
   1033                         final int currentResultSize = outInfos.size();
   1034                         for (int i = currentResultSize - 1; i >= initialResultSize; i--) {
   1035                             outInfos.remove(i);
   1036                         }
   1037                         // Couldn't obtain the parent, which means we have a
   1038                         // disconnected sub-tree. Abort prefetch immediately.
   1039                         return;
   1040                     }
   1041                     outInfos.add(parent);
   1042                     parentNodeId = parent.getParentNodeId();
   1043                     accessibilityViewId = AccessibilityNodeInfo.getAccessibilityViewId(
   1044                             parentNodeId);
   1045                 } else {
   1046                     prefetchPredecessorsOfRealNode(providerHost, outInfos);
   1047                     return;
   1048                 }
   1049             }
   1050         }
   1051 
   1052         private void prefetchSiblingsOfVirtualNode(AccessibilityNodeInfo current, View providerHost,
   1053                 AccessibilityNodeProvider provider, List<AccessibilityNodeInfo> outInfos) {
   1054             final long parentNodeId = current.getParentNodeId();
   1055             final int parentAccessibilityViewId =
   1056                 AccessibilityNodeInfo.getAccessibilityViewId(parentNodeId);
   1057             final int parentVirtualDescendantId =
   1058                 AccessibilityNodeInfo.getVirtualDescendantId(parentNodeId);
   1059             if (parentVirtualDescendantId != AccessibilityNodeProvider.HOST_VIEW_ID
   1060                     || parentAccessibilityViewId == providerHost.getAccessibilityViewId()) {
   1061                 final AccessibilityNodeInfo parent =
   1062                         provider.createAccessibilityNodeInfo(parentVirtualDescendantId);
   1063                 if (parent != null) {
   1064                     final int childCount = parent.getChildCount();
   1065                     for (int i = 0; i < childCount; i++) {
   1066                         if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
   1067                             return;
   1068                         }
   1069                         final long childNodeId = parent.getChildId(i);
   1070                         if (childNodeId != current.getSourceNodeId()) {
   1071                             final int childVirtualDescendantId =
   1072                                 AccessibilityNodeInfo.getVirtualDescendantId(childNodeId);
   1073                             AccessibilityNodeInfo child = provider.createAccessibilityNodeInfo(
   1074                                     childVirtualDescendantId);
   1075                             if (child != null) {
   1076                                 outInfos.add(child);
   1077                             }
   1078                         }
   1079                     }
   1080                 }
   1081             } else {
   1082                 prefetchSiblingsOfRealNode(providerHost, outInfos);
   1083             }
   1084         }
   1085 
   1086         private void prefetchDescendantsOfVirtualNode(AccessibilityNodeInfo root,
   1087                 AccessibilityNodeProvider provider, List<AccessibilityNodeInfo> outInfos) {
   1088             final int initialOutInfosSize = outInfos.size();
   1089             final int childCount = root.getChildCount();
   1090             for (int i = 0; i < childCount; i++) {
   1091                 if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
   1092                     return;
   1093                 }
   1094                 final long childNodeId = root.getChildId(i);
   1095                 AccessibilityNodeInfo child = provider.createAccessibilityNodeInfo(
   1096                         AccessibilityNodeInfo.getVirtualDescendantId(childNodeId));
   1097                 if (child != null) {
   1098                     outInfos.add(child);
   1099                 }
   1100             }
   1101             if (outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
   1102                 final int addedChildCount = outInfos.size() - initialOutInfosSize;
   1103                 for (int i = 0; i < addedChildCount; i++) {
   1104                     AccessibilityNodeInfo child = outInfos.get(initialOutInfosSize + i);
   1105                     prefetchDescendantsOfVirtualNode(child, provider, outInfos);
   1106                 }
   1107             }
   1108         }
   1109     }
   1110 
   1111     private class PrivateHandler extends Handler {
   1112         private static final int MSG_PERFORM_ACCESSIBILITY_ACTION = 1;
   1113         private static final int MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_ACCESSIBILITY_ID = 2;
   1114         private static final int MSG_FIND_ACCESSIBILITY_NODE_INFOS_BY_VIEW_ID = 3;
   1115         private static final int MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_TEXT = 4;
   1116         private static final int MSG_FIND_FOCUS = 5;
   1117         private static final int MSG_FOCUS_SEARCH = 6;
   1118 
   1119         public PrivateHandler(Looper looper) {
   1120             super(looper);
   1121         }
   1122 
   1123         @Override
   1124         public String getMessageName(Message message) {
   1125             final int type = message.what;
   1126             switch (type) {
   1127                 case MSG_PERFORM_ACCESSIBILITY_ACTION:
   1128                     return "MSG_PERFORM_ACCESSIBILITY_ACTION";
   1129                 case MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_ACCESSIBILITY_ID:
   1130                     return "MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_ACCESSIBILITY_ID";
   1131                 case MSG_FIND_ACCESSIBILITY_NODE_INFOS_BY_VIEW_ID:
   1132                     return "MSG_FIND_ACCESSIBILITY_NODE_INFOS_BY_VIEW_ID";
   1133                 case MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_TEXT:
   1134                     return "MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_TEXT";
   1135                 case MSG_FIND_FOCUS:
   1136                     return "MSG_FIND_FOCUS";
   1137                 case MSG_FOCUS_SEARCH:
   1138                     return "MSG_FOCUS_SEARCH";
   1139                 default:
   1140                     throw new IllegalArgumentException("Unknown message type: " + type);
   1141             }
   1142         }
   1143 
   1144         @Override
   1145         public void handleMessage(Message message) {
   1146             final int type = message.what;
   1147             switch (type) {
   1148                 case MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_ACCESSIBILITY_ID: {
   1149                     findAccessibilityNodeInfoByAccessibilityIdUiThread(message);
   1150                 } break;
   1151                 case MSG_PERFORM_ACCESSIBILITY_ACTION: {
   1152                     performAccessibilityActionUiThread(message);
   1153                 } break;
   1154                 case MSG_FIND_ACCESSIBILITY_NODE_INFOS_BY_VIEW_ID: {
   1155                     findAccessibilityNodeInfosByViewIdUiThread(message);
   1156                 } break;
   1157                 case MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_TEXT: {
   1158                     findAccessibilityNodeInfosByTextUiThread(message);
   1159                 } break;
   1160                 case MSG_FIND_FOCUS: {
   1161                     findFocusUiThread(message);
   1162                 } break;
   1163                 case MSG_FOCUS_SEARCH: {
   1164                     focusSearchUiThread(message);
   1165                 } break;
   1166                 default:
   1167                     throw new IllegalArgumentException("Unknown message type: " + type);
   1168             }
   1169         }
   1170     }
   1171 
   1172     private final class AddNodeInfosForViewId implements Predicate<View> {
   1173         private int mViewId = View.NO_ID;
   1174         private List<AccessibilityNodeInfo> mInfos;
   1175 
   1176         public void init(int viewId, List<AccessibilityNodeInfo> infos) {
   1177             mViewId = viewId;
   1178             mInfos = infos;
   1179         }
   1180 
   1181         public void reset() {
   1182             mViewId = View.NO_ID;
   1183             mInfos = null;
   1184         }
   1185 
   1186         @Override
   1187         public boolean test(View view) {
   1188             if (view.getId() == mViewId && isShown(view)) {
   1189                 mInfos.add(view.createAccessibilityNodeInfo());
   1190             }
   1191             return false;
   1192         }
   1193     }
   1194 }
   1195