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