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