Home | History | Annotate | Download | only in view
      1 /*
      2  * Copyright (C) 2012 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package android.view;
     18 
     19 import static android.view.accessibility.AccessibilityNodeInfo.INCLUDE_NOT_IMPORTANT_VIEWS;
     20 
     21 import android.os.Bundle;
     22 import android.os.Handler;
     23 import android.os.Looper;
     24 import android.os.Message;
     25 import android.os.Process;
     26 import android.os.RemoteException;
     27 import android.util.Pool;
     28 import android.util.Poolable;
     29 import android.util.PoolableManager;
     30 import android.util.Pools;
     31 import android.util.SparseLongArray;
     32 import android.view.accessibility.AccessibilityInteractionClient;
     33 import android.view.accessibility.AccessibilityNodeInfo;
     34 import android.view.accessibility.AccessibilityNodeProvider;
     35 import android.view.accessibility.IAccessibilityInteractionConnectionCallback;
     36 
     37 import java.util.ArrayList;
     38 import java.util.HashMap;
     39 import java.util.List;
     40 import java.util.Map;
     41 
     42 /**
     43  * Class for managing accessibility interactions initiated from the system
     44  * and targeting the view hierarchy. A *ClientThread method is to be
     45  * called from the interaction connection ViewAncestor gives the system to
     46  * talk to it and a corresponding *UiThread method that is executed on the
     47  * UI thread.
     48  */
     49 final class AccessibilityInteractionController {
     50     private static final int POOL_SIZE = 5;
     51 
     52     private ArrayList<AccessibilityNodeInfo> mTempAccessibilityNodeInfoList =
     53         new ArrayList<AccessibilityNodeInfo>();
     54 
     55     private final Handler mHandler;
     56 
     57     private final ViewRootImpl mViewRootImpl;
     58 
     59     private final AccessibilityNodePrefetcher mPrefetcher;
     60 
     61     private final long mMyLooperThreadId;
     62 
     63     private final int mMyProcessId;
     64 
     65     private final ArrayList<View> mTempArrayList = new ArrayList<View>();
     66 
     67     public AccessibilityInteractionController(ViewRootImpl viewRootImpl) {
     68         Looper looper =  viewRootImpl.mHandler.getLooper();
     69         mMyLooperThreadId = looper.getThread().getId();
     70         mMyProcessId = Process.myPid();
     71         mHandler = new PrivateHandler(looper);
     72         mViewRootImpl = viewRootImpl;
     73         mPrefetcher = new AccessibilityNodePrefetcher();
     74     }
     75 
     76     // Reusable poolable arguments for interacting with the view hierarchy
     77     // to fit more arguments than Message and to avoid sharing objects between
     78     // two messages since several threads can send messages concurrently.
     79     private final Pool<SomeArgs> mPool = Pools.synchronizedPool(Pools.finitePool(
     80             new PoolableManager<SomeArgs>() {
     81                 public SomeArgs newInstance() {
     82                     return new SomeArgs();
     83                 }
     84 
     85                 public void onAcquired(SomeArgs info) {
     86                     /* do nothing */
     87                 }
     88 
     89                 public void onReleased(SomeArgs info) {
     90                     info.clear();
     91                 }
     92             }, POOL_SIZE)
     93     );
     94 
     95     private class SomeArgs implements Poolable<SomeArgs> {
     96         private SomeArgs mNext;
     97         private boolean mIsPooled;
     98 
     99         public Object arg1;
    100         public Object arg2;
    101         public int argi1;
    102         public int argi2;
    103         public int argi3;
    104 
    105         public SomeArgs getNextPoolable() {
    106             return mNext;
    107         }
    108 
    109         public boolean isPooled() {
    110             return mIsPooled;
    111         }
    112 
    113         public void setNextPoolable(SomeArgs args) {
    114             mNext = args;
    115         }
    116 
    117         public void setPooled(boolean isPooled) {
    118             mIsPooled = isPooled;
    119         }
    120 
    121         private void clear() {
    122             arg1 = null;
    123             arg2 = null;
    124             argi1 = 0;
    125             argi2 = 0;
    126             argi3 = 0;
    127         }
    128     }
    129 
    130     private boolean isShown(View view) {
    131         // The first two checks are made also made by isShown() which
    132         // however traverses the tree up to the parent to catch that.
    133         // Therefore, we do some fail fast check to minimize the up
    134         // tree traversal.
    135         return (view.mAttachInfo != null
    136                 && view.mAttachInfo.mWindowVisibility == View.VISIBLE
    137                 && view.isShown());
    138     }
    139 
    140     public void findAccessibilityNodeInfoByAccessibilityIdClientThread(
    141             long accessibilityNodeId, int windowLeft, int windowTop, int interactionId,
    142             IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid,
    143             long interrogatingTid) {
    144         Message message = mHandler.obtainMessage();
    145         message.what = PrivateHandler.MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID;
    146         message.arg1 = flags;
    147 
    148         SomeArgs args = mPool.acquire();
    149         args.argi1 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId);
    150         args.argi2 = AccessibilityNodeInfo.getVirtualDescendantId(accessibilityNodeId);
    151         args.argi3 = interactionId;
    152         args.arg1 = callback;
    153 
    154         SomeArgs moreArgs = mPool.acquire();
    155         moreArgs.argi1 = windowLeft;
    156         moreArgs.argi2 = windowTop;
    157         args.arg2 = moreArgs;
    158 
    159         message.obj = args;
    160 
    161         // If the interrogation is performed by the same thread as the main UI
    162         // thread in this process, set the message as a static reference so
    163         // after this call completes the same thread but in the interrogating
    164         // client can handle the message to generate the result.
    165         if (interrogatingPid == mMyProcessId && interrogatingTid == mMyLooperThreadId) {
    166             AccessibilityInteractionClient.getInstanceForThread(
    167                     interrogatingTid).setSameThreadMessage(message);
    168         } else {
    169             mHandler.sendMessage(message);
    170         }
    171     }
    172 
    173     private void findAccessibilityNodeInfoByAccessibilityIdUiThread(Message message) {
    174         final int flags = message.arg1;
    175 
    176         SomeArgs args = (SomeArgs) message.obj;
    177         final int accessibilityViewId = args.argi1;
    178         final int virtualDescendantId = args.argi2;
    179         final int interactionId = args.argi3;
    180         final IAccessibilityInteractionConnectionCallback callback =
    181             (IAccessibilityInteractionConnectionCallback) args.arg1;
    182 
    183         SomeArgs moreArgs = (SomeArgs) args.arg2;
    184         mViewRootImpl.mAttachInfo.mActualWindowLeft = moreArgs.argi1;
    185         mViewRootImpl.mAttachInfo.mActualWindowTop = moreArgs.argi2;
    186 
    187         mPool.release(moreArgs);
    188         mPool.release(args);
    189 
    190         List<AccessibilityNodeInfo> infos = mTempAccessibilityNodeInfoList;
    191         infos.clear();
    192         try {
    193             if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
    194                 return;
    195             }
    196             mViewRootImpl.mAttachInfo.mIncludeNotImportantViews =
    197                 (flags & INCLUDE_NOT_IMPORTANT_VIEWS) != 0;
    198             View root = null;
    199             if (accessibilityViewId == AccessibilityNodeInfo.UNDEFINED) {
    200                 root = mViewRootImpl.mView;
    201             } else {
    202                 root = findViewByAccessibilityId(accessibilityViewId);
    203             }
    204             if (root != null && isShown(root)) {
    205                 mPrefetcher.prefetchAccessibilityNodeInfos(root, virtualDescendantId, flags, infos);
    206             }
    207         } finally {
    208             try {
    209                 mViewRootImpl.mAttachInfo.mIncludeNotImportantViews = false;
    210                 callback.setFindAccessibilityNodeInfosResult(infos, interactionId);
    211                 infos.clear();
    212             } catch (RemoteException re) {
    213                 /* ignore - the other side will time out */
    214             }
    215         }
    216     }
    217 
    218     public void findAccessibilityNodeInfoByViewIdClientThread(long accessibilityNodeId,
    219             int viewId, int windowLeft, int windowTop, int interactionId,
    220             IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid,
    221             long interrogatingTid) {
    222         Message message = mHandler.obtainMessage();
    223         message.what = PrivateHandler.MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_ID;
    224         message.arg1 = flags;
    225         message.arg2 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId);
    226 
    227         SomeArgs args = mPool.acquire();
    228         args.argi1 = viewId;
    229         args.argi2 = interactionId;
    230         args.arg1 = callback;
    231 
    232         SomeArgs moreArgs = mPool.acquire();
    233         moreArgs.argi1 = windowLeft;
    234         moreArgs.argi2 = windowTop;
    235         args.arg2 = moreArgs;
    236 
    237         message.obj = args;
    238 
    239         // If the interrogation is performed by the same thread as the main UI
    240         // thread in this process, set the message as a static reference so
    241         // after this call completes the same thread but in the interrogating
    242         // client can handle the message to generate the result.
    243         if (interrogatingPid == mMyProcessId && interrogatingTid == mMyLooperThreadId) {
    244             AccessibilityInteractionClient.getInstanceForThread(
    245                     interrogatingTid).setSameThreadMessage(message);
    246         } else {
    247             mHandler.sendMessage(message);
    248         }
    249     }
    250 
    251     private void findAccessibilityNodeInfoByViewIdUiThread(Message message) {
    252         final int flags = message.arg1;
    253         final int accessibilityViewId = message.arg2;
    254 
    255         SomeArgs args = (SomeArgs) message.obj;
    256         final int viewId = args.argi1;
    257         final int interactionId = args.argi2;
    258         final IAccessibilityInteractionConnectionCallback callback =
    259             (IAccessibilityInteractionConnectionCallback) args.arg1;
    260 
    261         SomeArgs moreArgs = (SomeArgs) args.arg2;
    262         mViewRootImpl.mAttachInfo.mActualWindowLeft = moreArgs.argi1;
    263         mViewRootImpl.mAttachInfo.mActualWindowTop = moreArgs.argi2;
    264 
    265         mPool.release(moreArgs);
    266         mPool.release(args);
    267 
    268         AccessibilityNodeInfo info = null;
    269         try {
    270             if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
    271                 return;
    272             }
    273             mViewRootImpl.mAttachInfo.mIncludeNotImportantViews =
    274                 (flags & INCLUDE_NOT_IMPORTANT_VIEWS) != 0;
    275             View root = null;
    276             if (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED) {
    277                 root = findViewByAccessibilityId(accessibilityViewId);
    278             } else {
    279                 root = mViewRootImpl.mView;
    280             }
    281             if (root != null) {
    282                 View target = root.findViewById(viewId);
    283                 if (target != null && isShown(target)) {
    284                     info = target.createAccessibilityNodeInfo();
    285                 }
    286             }
    287         } finally {
    288             try {
    289                 mViewRootImpl.mAttachInfo.mIncludeNotImportantViews = false;
    290                 callback.setFindAccessibilityNodeInfoResult(info, interactionId);
    291             } catch (RemoteException re) {
    292                 /* ignore - the other side will time out */
    293             }
    294         }
    295     }
    296 
    297     public void findAccessibilityNodeInfosByTextClientThread(long accessibilityNodeId,
    298             String text, int windowLeft, int windowTop, int interactionId,
    299             IAccessibilityInteractionConnectionCallback callback, int flags,
    300             int interrogatingPid, long interrogatingTid) {
    301         Message message = mHandler.obtainMessage();
    302         message.what = PrivateHandler.MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_TEXT;
    303         message.arg1 = flags;
    304 
    305         SomeArgs args = mPool.acquire();
    306         args.arg1 = text;
    307         args.argi1 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId);
    308         args.argi2 = AccessibilityNodeInfo.getVirtualDescendantId(accessibilityNodeId);
    309         args.argi3 = interactionId;
    310 
    311         SomeArgs moreArgs = mPool.acquire();
    312         moreArgs.arg1 = callback;
    313         moreArgs.argi1 = windowLeft;
    314         moreArgs.argi2 = windowTop;
    315         args.arg2 = moreArgs;
    316 
    317         message.obj = args;
    318 
    319         // If the interrogation is performed by the same thread as the main UI
    320         // thread in this process, set the message as a static reference so
    321         // after this call completes the same thread but in the interrogating
    322         // client can handle the message to generate the result.
    323         if (interrogatingPid == mMyProcessId && interrogatingTid == mMyLooperThreadId) {
    324             AccessibilityInteractionClient.getInstanceForThread(
    325                     interrogatingTid).setSameThreadMessage(message);
    326         } else {
    327             mHandler.sendMessage(message);
    328         }
    329     }
    330 
    331     private void findAccessibilityNodeInfosByTextUiThread(Message message) {
    332         final int flags = message.arg1;
    333 
    334         SomeArgs args = (SomeArgs) message.obj;
    335         final String text = (String) args.arg1;
    336         final int accessibilityViewId = args.argi1;
    337         final int virtualDescendantId = args.argi2;
    338         final int interactionId = args.argi3;
    339 
    340         SomeArgs moreArgs = (SomeArgs) args.arg2;
    341         final IAccessibilityInteractionConnectionCallback callback =
    342             (IAccessibilityInteractionConnectionCallback) moreArgs.arg1;
    343         mViewRootImpl.mAttachInfo.mActualWindowLeft = moreArgs.argi1;
    344         mViewRootImpl.mAttachInfo.mActualWindowTop = moreArgs.argi2;
    345 
    346         mPool.release(moreArgs);
    347         mPool.release(args);
    348 
    349         List<AccessibilityNodeInfo> infos = null;
    350         try {
    351             if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
    352                 return;
    353             }
    354             mViewRootImpl.mAttachInfo.mIncludeNotImportantViews =
    355                 (flags & INCLUDE_NOT_IMPORTANT_VIEWS) != 0;
    356             View root = null;
    357             if (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED) {
    358                 root = findViewByAccessibilityId(accessibilityViewId);
    359             } else {
    360                 root = mViewRootImpl.mView;
    361             }
    362             if (root != null && isShown(root)) {
    363                 AccessibilityNodeProvider provider = root.getAccessibilityNodeProvider();
    364                 if (provider != null) {
    365                     infos = provider.findAccessibilityNodeInfosByText(text,
    366                             virtualDescendantId);
    367                 } else if (virtualDescendantId == AccessibilityNodeInfo.UNDEFINED) {
    368                     ArrayList<View> foundViews = mTempArrayList;
    369                     foundViews.clear();
    370                     root.findViewsWithText(foundViews, text, View.FIND_VIEWS_WITH_TEXT
    371                             | View.FIND_VIEWS_WITH_CONTENT_DESCRIPTION
    372                             | View.FIND_VIEWS_WITH_ACCESSIBILITY_NODE_PROVIDERS);
    373                     if (!foundViews.isEmpty()) {
    374                         infos = mTempAccessibilityNodeInfoList;
    375                         infos.clear();
    376                         final int viewCount = foundViews.size();
    377                         for (int i = 0; i < viewCount; i++) {
    378                             View foundView = foundViews.get(i);
    379                             if (isShown(foundView)) {
    380                                 provider = foundView.getAccessibilityNodeProvider();
    381                                 if (provider != null) {
    382                                     List<AccessibilityNodeInfo> infosFromProvider =
    383                                         provider.findAccessibilityNodeInfosByText(text,
    384                                                 AccessibilityNodeInfo.UNDEFINED);
    385                                     if (infosFromProvider != null) {
    386                                         infos.addAll(infosFromProvider);
    387                                     }
    388                                 } else  {
    389                                     infos.add(foundView.createAccessibilityNodeInfo());
    390                                 }
    391                             }
    392                         }
    393                     }
    394                 }
    395             }
    396         } finally {
    397             try {
    398                 mViewRootImpl.mAttachInfo.mIncludeNotImportantViews = false;
    399                 callback.setFindAccessibilityNodeInfosResult(infos, interactionId);
    400             } catch (RemoteException re) {
    401                 /* ignore - the other side will time out */
    402             }
    403         }
    404     }
    405 
    406     public void findFocusClientThread(long accessibilityNodeId, int focusType, int windowLeft,
    407             int windowTop, int interactionId, IAccessibilityInteractionConnectionCallback callback,
    408             int flags, int interogatingPid, long interrogatingTid) {
    409         Message message = mHandler.obtainMessage();
    410         message.what = PrivateHandler.MSG_FIND_FOCUS;
    411         message.arg1 = flags;
    412         message.arg2 = focusType;
    413 
    414         SomeArgs args = mPool.acquire();
    415         args.argi1 = interactionId;
    416         args.argi2 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId);
    417         args.argi3 = AccessibilityNodeInfo.getVirtualDescendantId(accessibilityNodeId);
    418         args.arg1 = callback;
    419 
    420         SomeArgs moreArgs = mPool.acquire();
    421         moreArgs.argi1 = windowLeft;
    422         moreArgs.argi2 = windowTop;
    423         args.arg2 = moreArgs;
    424 
    425         message.obj = args;
    426 
    427         // If the interrogation is performed by the same thread as the main UI
    428         // thread in this process, set the message as a static reference so
    429         // after this call completes the same thread but in the interrogating
    430         // client can handle the message to generate the result.
    431         if (interogatingPid == mMyProcessId && interrogatingTid == mMyLooperThreadId) {
    432             AccessibilityInteractionClient.getInstanceForThread(
    433                     interrogatingTid).setSameThreadMessage(message);
    434         } else {
    435             mHandler.sendMessage(message);
    436         }
    437     }
    438 
    439     private void findFocusUiThread(Message message) {
    440         final int flags = message.arg1;
    441         final int focusType = message.arg2;
    442 
    443         SomeArgs args = (SomeArgs) message.obj;
    444         final int interactionId = args.argi1;
    445         final int accessibilityViewId = args.argi2;
    446         final int virtualDescendantId = args.argi3;
    447         final IAccessibilityInteractionConnectionCallback callback =
    448             (IAccessibilityInteractionConnectionCallback) args.arg1;
    449 
    450         SomeArgs moreArgs = (SomeArgs) args.arg2;
    451         mViewRootImpl.mAttachInfo.mActualWindowLeft = moreArgs.argi1;
    452         mViewRootImpl.mAttachInfo.mActualWindowTop = moreArgs.argi2;
    453 
    454         mPool.release(moreArgs);
    455         mPool.release(args);
    456 
    457         AccessibilityNodeInfo focused = null;
    458         try {
    459             if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
    460                 return;
    461             }
    462             mViewRootImpl.mAttachInfo.mIncludeNotImportantViews =
    463                 (flags & INCLUDE_NOT_IMPORTANT_VIEWS) != 0;
    464             View root = null;
    465             if (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED) {
    466                 root = findViewByAccessibilityId(accessibilityViewId);
    467             } else {
    468                 root = mViewRootImpl.mView;
    469             }
    470             if (root != null && isShown(root)) {
    471                 switch (focusType) {
    472                     case AccessibilityNodeInfo.FOCUS_ACCESSIBILITY: {
    473                         View host = mViewRootImpl.mAccessibilityFocusedHost;
    474                         // If there is no accessibility focus host or it is not a descendant
    475                         // of the root from which to start the search, then the search failed.
    476                         if (host == null || !ViewRootImpl.isViewDescendantOf(host, root)) {
    477                             break;
    478                         }
    479                         // If the host has a provider ask this provider to search for the
    480                         // focus instead fetching all provider nodes to do the search here.
    481                         AccessibilityNodeProvider provider = host.getAccessibilityNodeProvider();
    482                         if (provider != null) {
    483                             if (mViewRootImpl.mAccessibilityFocusedVirtualView != null) {
    484                                 focused = AccessibilityNodeInfo.obtain(
    485                                         mViewRootImpl.mAccessibilityFocusedVirtualView);
    486                             }
    487                         } else if (virtualDescendantId == View.NO_ID) {
    488                             focused = host.createAccessibilityNodeInfo();
    489                         }
    490                     } break;
    491                     case AccessibilityNodeInfo.FOCUS_INPUT: {
    492                         // Input focus cannot go to virtual views.
    493                         View target = root.findFocus();
    494                         if (target != null && isShown(target)) {
    495                             focused = target.createAccessibilityNodeInfo();
    496                         }
    497                     } break;
    498                     default:
    499                         throw new IllegalArgumentException("Unknown focus type: " + focusType);
    500                 }
    501             }
    502         } finally {
    503             try {
    504                 mViewRootImpl.mAttachInfo.mIncludeNotImportantViews = false;
    505                 callback.setFindAccessibilityNodeInfoResult(focused, interactionId);
    506             } catch (RemoteException re) {
    507                 /* ignore - the other side will time out */
    508             }
    509         }
    510     }
    511 
    512     public void focusSearchClientThread(long accessibilityNodeId, int direction, int windowLeft,
    513             int windowTop, int interactionId, IAccessibilityInteractionConnectionCallback callback,
    514             int flags, int interogatingPid, long interrogatingTid) {
    515         Message message = mHandler.obtainMessage();
    516         message.what = PrivateHandler.MSG_FOCUS_SEARCH;
    517         message.arg1 = flags;
    518         message.arg2 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId);
    519 
    520         SomeArgs args = mPool.acquire();
    521         args.argi1 = AccessibilityNodeInfo.getVirtualDescendantId(accessibilityNodeId);
    522         args.argi2 = direction;
    523         args.argi3 = interactionId;
    524         args.arg1 = callback;
    525 
    526         SomeArgs moreArgs = mPool.acquire();
    527         moreArgs.argi1 = windowLeft;
    528         moreArgs.argi2 = windowTop;
    529         args.arg2 = moreArgs;
    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 virtualDescendantId = args.argi1;
    551         final int direction = args.argi2;
    552         final int interactionId = args.argi3;
    553         final IAccessibilityInteractionConnectionCallback callback =
    554             (IAccessibilityInteractionConnectionCallback) args.arg1;
    555 
    556         SomeArgs moreArgs = (SomeArgs) args.arg2;
    557         mViewRootImpl.mAttachInfo.mActualWindowLeft = moreArgs.argi1;
    558         mViewRootImpl.mAttachInfo.mActualWindowTop = moreArgs.argi2;
    559 
    560         mPool.release(moreArgs);
    561         mPool.release(args);
    562 
    563         AccessibilityNodeInfo next = null;
    564         try {
    565             if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
    566                 return;
    567             }
    568             mViewRootImpl.mAttachInfo.mIncludeNotImportantViews =
    569                 (flags & INCLUDE_NOT_IMPORTANT_VIEWS) != 0;
    570             View root = null;
    571             if (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED) {
    572                 root = findViewByAccessibilityId(accessibilityViewId);
    573             } else {
    574                 root = mViewRootImpl.mView;
    575             }
    576             if (root != null && isShown(root)) {
    577                 if ((direction & View.FOCUS_ACCESSIBILITY) ==  View.FOCUS_ACCESSIBILITY) {
    578                     AccessibilityNodeProvider provider = root.getAccessibilityNodeProvider();
    579                     if (provider != null) {
    580                         next = provider.accessibilityFocusSearch(direction, virtualDescendantId);
    581                         if (next != null) {
    582                             return;
    583                         }
    584                     }
    585                     View nextView = root.focusSearch(direction);
    586                     while (nextView != null) {
    587                         // If the focus search reached a node with a provider
    588                         // we delegate to the provider to find the next one.
    589                         // If the provider does not return a virtual view to
    590                         // take accessibility focus we try the next view found
    591                         // by the focus search algorithm.
    592                         provider = nextView.getAccessibilityNodeProvider();
    593                         if (provider != null) {
    594                             next = provider.accessibilityFocusSearch(direction, View.NO_ID);
    595                             if (next != null) {
    596                                 break;
    597                             }
    598                             nextView = nextView.focusSearch(direction);
    599                         } else {
    600                             next = nextView.createAccessibilityNodeInfo();
    601                             break;
    602                         }
    603                     }
    604                 } else {
    605                     View nextView = root.focusSearch(direction);
    606                     if (nextView != null) {
    607                         next = nextView.createAccessibilityNodeInfo();
    608                     }
    609                 }
    610             }
    611         } finally {
    612             try {
    613                 mViewRootImpl.mAttachInfo.mIncludeNotImportantViews = false;
    614                 callback.setFindAccessibilityNodeInfoResult(next, interactionId);
    615             } catch (RemoteException re) {
    616                 /* ignore - the other side will time out */
    617             }
    618         }
    619     }
    620 
    621     public void performAccessibilityActionClientThread(long accessibilityNodeId, int action,
    622             Bundle arguments, int interactionId,
    623             IAccessibilityInteractionConnectionCallback callback, int flags, int interogatingPid,
    624             long interrogatingTid) {
    625         Message message = mHandler.obtainMessage();
    626         message.what = PrivateHandler.MSG_PERFORM_ACCESSIBILITY_ACTION;
    627         message.arg1 = flags;
    628         message.arg2 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId);
    629 
    630         SomeArgs args = mPool.acquire();
    631         args.argi1 = AccessibilityNodeInfo.getVirtualDescendantId(accessibilityNodeId);
    632         args.argi2 = action;
    633         args.argi3 = interactionId;
    634         args.arg1 = callback;
    635         args.arg2 = arguments;
    636 
    637         message.obj = args;
    638 
    639         // If the interrogation is performed by the same thread as the main UI
    640         // thread in this process, set the message as a static reference so
    641         // after this call completes the same thread but in the interrogating
    642         // client can handle the message to generate the result.
    643         if (interogatingPid == mMyProcessId && interrogatingTid == mMyLooperThreadId) {
    644             AccessibilityInteractionClient.getInstanceForThread(
    645                     interrogatingTid).setSameThreadMessage(message);
    646         } else {
    647             mHandler.sendMessage(message);
    648         }
    649     }
    650 
    651     private void perfromAccessibilityActionUiThread(Message message) {
    652         final int flags = message.arg1;
    653         final int accessibilityViewId = message.arg2;
    654 
    655         SomeArgs args = (SomeArgs) message.obj;
    656         final int virtualDescendantId = args.argi1;
    657         final int action = args.argi2;
    658         final int interactionId = args.argi3;
    659         final IAccessibilityInteractionConnectionCallback callback =
    660             (IAccessibilityInteractionConnectionCallback) args.arg1;
    661         Bundle arguments = (Bundle) args.arg2;
    662 
    663         mPool.release(args);
    664 
    665         boolean succeeded = false;
    666         try {
    667             if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
    668                 return;
    669             }
    670             mViewRootImpl.mAttachInfo.mIncludeNotImportantViews =
    671                 (flags & INCLUDE_NOT_IMPORTANT_VIEWS) != 0;
    672             View target = null;
    673             if (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED) {
    674                 target = findViewByAccessibilityId(accessibilityViewId);
    675             } else {
    676                 target = mViewRootImpl.mView;
    677             }
    678             if (target != null && isShown(target)) {
    679                 AccessibilityNodeProvider provider = target.getAccessibilityNodeProvider();
    680                 if (provider != null) {
    681                     succeeded = provider.performAction(virtualDescendantId, action,
    682                             arguments);
    683                 } else if (virtualDescendantId == View.NO_ID) {
    684                     succeeded = target.performAccessibilityAction(action, arguments);
    685                 }
    686             }
    687         } finally {
    688             try {
    689                 mViewRootImpl.mAttachInfo.mIncludeNotImportantViews = false;
    690                 callback.setPerformAccessibilityActionResult(succeeded, interactionId);
    691             } catch (RemoteException re) {
    692                 /* ignore - the other side will time out */
    693             }
    694         }
    695     }
    696 
    697     private View findViewByAccessibilityId(int accessibilityId) {
    698         View root = mViewRootImpl.mView;
    699         if (root == null) {
    700             return null;
    701         }
    702         View foundView = root.findViewByAccessibilityId(accessibilityId);
    703         if (foundView != null && !isShown(foundView)) {
    704             return null;
    705         }
    706         return foundView;
    707     }
    708 
    709     /**
    710      * This class encapsulates a prefetching strategy for the accessibility APIs for
    711      * querying window content. It is responsible to prefetch a batch of
    712      * AccessibilityNodeInfos in addition to the one for a requested node.
    713      */
    714     private class AccessibilityNodePrefetcher {
    715 
    716         private static final int MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE = 50;
    717 
    718         private final ArrayList<View> mTempViewList = new ArrayList<View>();
    719 
    720         public void prefetchAccessibilityNodeInfos(View view, int virtualViewId, int prefetchFlags,
    721                 List<AccessibilityNodeInfo> outInfos) {
    722             AccessibilityNodeProvider provider = view.getAccessibilityNodeProvider();
    723             if (provider == null) {
    724                 AccessibilityNodeInfo root = view.createAccessibilityNodeInfo();
    725                 if (root != null) {
    726                     outInfos.add(root);
    727                     if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS) != 0) {
    728                         prefetchPredecessorsOfRealNode(view, outInfos);
    729                     }
    730                     if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS) != 0) {
    731                         prefetchSiblingsOfRealNode(view, outInfos);
    732                     }
    733                     if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS) != 0) {
    734                         prefetchDescendantsOfRealNode(view, outInfos);
    735                     }
    736                 }
    737             } else {
    738                 AccessibilityNodeInfo root = provider.createAccessibilityNodeInfo(virtualViewId);
    739                 if (root != null) {
    740                     outInfos.add(root);
    741                     if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS) != 0) {
    742                         prefetchPredecessorsOfVirtualNode(root, view, provider, outInfos);
    743                     }
    744                     if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS) != 0) {
    745                         prefetchSiblingsOfVirtualNode(root, view, provider, outInfos);
    746                     }
    747                     if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS) != 0) {
    748                         prefetchDescendantsOfVirtualNode(root, provider, outInfos);
    749                     }
    750                 }
    751             }
    752         }
    753 
    754         private void prefetchPredecessorsOfRealNode(View view,
    755                 List<AccessibilityNodeInfo> outInfos) {
    756             ViewParent parent = view.getParentForAccessibility();
    757             while (parent instanceof View
    758                     && outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
    759                 View parentView = (View) parent;
    760                 AccessibilityNodeInfo info = parentView.createAccessibilityNodeInfo();
    761                 if (info != null) {
    762                     outInfos.add(info);
    763                 }
    764                 parent = parent.getParentForAccessibility();
    765             }
    766         }
    767 
    768         private void prefetchSiblingsOfRealNode(View current,
    769                 List<AccessibilityNodeInfo> outInfos) {
    770             ViewParent parent = current.getParentForAccessibility();
    771             if (parent instanceof ViewGroup) {
    772                 ViewGroup parentGroup = (ViewGroup) parent;
    773                 ArrayList<View> children = mTempViewList;
    774                 children.clear();
    775                 try {
    776                     parentGroup.addChildrenForAccessibility(children);
    777                     final int childCount = children.size();
    778                     for (int i = 0; i < childCount; i++) {
    779                         if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
    780                             return;
    781                         }
    782                         View child = children.get(i);
    783                         if (child.getAccessibilityViewId() != current.getAccessibilityViewId()
    784                                 &&  isShown(child)) {
    785                             AccessibilityNodeInfo info = null;
    786                             AccessibilityNodeProvider provider =
    787                                 child.getAccessibilityNodeProvider();
    788                             if (provider == null) {
    789                                 info = child.createAccessibilityNodeInfo();
    790                             } else {
    791                                 info = provider.createAccessibilityNodeInfo(
    792                                         AccessibilityNodeInfo.UNDEFINED);
    793                             }
    794                             if (info != null) {
    795                                 outInfos.add(info);
    796                             }
    797                         }
    798                     }
    799                 } finally {
    800                     children.clear();
    801                 }
    802             }
    803         }
    804 
    805         private void prefetchDescendantsOfRealNode(View root,
    806                 List<AccessibilityNodeInfo> outInfos) {
    807             if (!(root instanceof ViewGroup)) {
    808                 return;
    809             }
    810             HashMap<View, AccessibilityNodeInfo> addedChildren =
    811                 new HashMap<View, AccessibilityNodeInfo>();
    812             ArrayList<View> children = mTempViewList;
    813             children.clear();
    814             try {
    815                 root.addChildrenForAccessibility(children);
    816                 final int childCount = children.size();
    817                 for (int i = 0; i < childCount; i++) {
    818                     if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
    819                         return;
    820                     }
    821                     View child = children.get(i);
    822                     if (isShown(child)) {
    823                         AccessibilityNodeProvider provider = child.getAccessibilityNodeProvider();
    824                         if (provider == null) {
    825                             AccessibilityNodeInfo info = child.createAccessibilityNodeInfo();
    826                             if (info != null) {
    827                                 outInfos.add(info);
    828                                 addedChildren.put(child, null);
    829                             }
    830                         } else {
    831                             AccessibilityNodeInfo info = provider.createAccessibilityNodeInfo(
    832                                    AccessibilityNodeInfo.UNDEFINED);
    833                             if (info != null) {
    834                                 outInfos.add(info);
    835                                 addedChildren.put(child, info);
    836                             }
    837                         }
    838                     }
    839                 }
    840             } finally {
    841                 children.clear();
    842             }
    843             if (outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
    844                 for (Map.Entry<View, AccessibilityNodeInfo> entry : addedChildren.entrySet()) {
    845                     View addedChild = entry.getKey();
    846                     AccessibilityNodeInfo virtualRoot = entry.getValue();
    847                     if (virtualRoot == null) {
    848                         prefetchDescendantsOfRealNode(addedChild, outInfos);
    849                     } else {
    850                         AccessibilityNodeProvider provider =
    851                             addedChild.getAccessibilityNodeProvider();
    852                         prefetchDescendantsOfVirtualNode(virtualRoot, provider, outInfos);
    853                     }
    854                 }
    855             }
    856         }
    857 
    858         private void prefetchPredecessorsOfVirtualNode(AccessibilityNodeInfo root,
    859                 View providerHost, AccessibilityNodeProvider provider,
    860                 List<AccessibilityNodeInfo> outInfos) {
    861             long parentNodeId = root.getParentNodeId();
    862             int accessibilityViewId = AccessibilityNodeInfo.getAccessibilityViewId(parentNodeId);
    863             while (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED) {
    864                 if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
    865                     return;
    866                 }
    867                 final int virtualDescendantId =
    868                     AccessibilityNodeInfo.getVirtualDescendantId(parentNodeId);
    869                 if (virtualDescendantId != AccessibilityNodeInfo.UNDEFINED
    870                         || accessibilityViewId == providerHost.getAccessibilityViewId()) {
    871                     AccessibilityNodeInfo parent = provider.createAccessibilityNodeInfo(
    872                             virtualDescendantId);
    873                     if (parent != null) {
    874                         outInfos.add(parent);
    875                     }
    876                     parentNodeId = parent.getParentNodeId();
    877                     accessibilityViewId = AccessibilityNodeInfo.getAccessibilityViewId(
    878                             parentNodeId);
    879                 } else {
    880                     prefetchPredecessorsOfRealNode(providerHost, outInfos);
    881                     return;
    882                 }
    883             }
    884         }
    885 
    886         private void prefetchSiblingsOfVirtualNode(AccessibilityNodeInfo current, View providerHost,
    887                 AccessibilityNodeProvider provider, List<AccessibilityNodeInfo> outInfos) {
    888             final long parentNodeId = current.getParentNodeId();
    889             final int parentAccessibilityViewId =
    890                 AccessibilityNodeInfo.getAccessibilityViewId(parentNodeId);
    891             final int parentVirtualDescendantId =
    892                 AccessibilityNodeInfo.getVirtualDescendantId(parentNodeId);
    893             if (parentVirtualDescendantId != AccessibilityNodeInfo.UNDEFINED
    894                     || parentAccessibilityViewId == providerHost.getAccessibilityViewId()) {
    895                 AccessibilityNodeInfo parent =
    896                     provider.createAccessibilityNodeInfo(parentVirtualDescendantId);
    897                 if (parent != null) {
    898                     SparseLongArray childNodeIds = parent.getChildNodeIds();
    899                     final int childCount = childNodeIds.size();
    900                     for (int i = 0; i < childCount; i++) {
    901                         if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
    902                             return;
    903                         }
    904                         final long childNodeId = childNodeIds.get(i);
    905                         if (childNodeId != current.getSourceNodeId()) {
    906                             final int childVirtualDescendantId =
    907                                 AccessibilityNodeInfo.getVirtualDescendantId(childNodeId);
    908                             AccessibilityNodeInfo child = provider.createAccessibilityNodeInfo(
    909                                     childVirtualDescendantId);
    910                             if (child != null) {
    911                                 outInfos.add(child);
    912                             }
    913                         }
    914                     }
    915                 }
    916             } else {
    917                 prefetchSiblingsOfRealNode(providerHost, outInfos);
    918             }
    919         }
    920 
    921         private void prefetchDescendantsOfVirtualNode(AccessibilityNodeInfo root,
    922                 AccessibilityNodeProvider provider, List<AccessibilityNodeInfo> outInfos) {
    923             SparseLongArray childNodeIds = root.getChildNodeIds();
    924             final int initialOutInfosSize = outInfos.size();
    925             final int childCount = childNodeIds.size();
    926             for (int i = 0; i < childCount; i++) {
    927                 if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
    928                     return;
    929                 }
    930                 final long childNodeId = childNodeIds.get(i);
    931                 AccessibilityNodeInfo child = provider.createAccessibilityNodeInfo(
    932                         AccessibilityNodeInfo.getVirtualDescendantId(childNodeId));
    933                 if (child != null) {
    934                     outInfos.add(child);
    935                 }
    936             }
    937             if (outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
    938                 final int addedChildCount = outInfos.size() - initialOutInfosSize;
    939                 for (int i = 0; i < addedChildCount; i++) {
    940                     AccessibilityNodeInfo child = outInfos.get(initialOutInfosSize + i);
    941                     prefetchDescendantsOfVirtualNode(child, provider, outInfos);
    942                 }
    943             }
    944         }
    945     }
    946 
    947     private class PrivateHandler extends Handler {
    948         private final static int MSG_PERFORM_ACCESSIBILITY_ACTION = 1;
    949         private final static int MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID = 2;
    950         private final static int MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_ID = 3;
    951         private final static int MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_TEXT = 4;
    952         private final static int MSG_FIND_FOCUS = 5;
    953         private final static int MSG_FOCUS_SEARCH = 6;
    954 
    955         public PrivateHandler(Looper looper) {
    956             super(looper);
    957         }
    958 
    959         @Override
    960         public String getMessageName(Message message) {
    961             final int type = message.what;
    962             switch (type) {
    963                 case MSG_PERFORM_ACCESSIBILITY_ACTION:
    964                     return "MSG_PERFORM_ACCESSIBILITY_ACTION";
    965                 case MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID:
    966                     return "MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID";
    967                 case MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_ID:
    968                     return "MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_ID";
    969                 case MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_TEXT:
    970                     return "MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_TEXT";
    971                 case MSG_FIND_FOCUS:
    972                     return "MSG_FIND_FOCUS";
    973                 case MSG_FOCUS_SEARCH:
    974                     return "MSG_FOCUS_SEARCH";
    975                 default:
    976                     throw new IllegalArgumentException("Unknown message type: " + type);
    977             }
    978         }
    979 
    980         @Override
    981         public void handleMessage(Message message) {
    982             final int type = message.what;
    983             switch (type) {
    984                 case MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID: {
    985                     findAccessibilityNodeInfoByAccessibilityIdUiThread(message);
    986                 } break;
    987                 case MSG_PERFORM_ACCESSIBILITY_ACTION: {
    988                     perfromAccessibilityActionUiThread(message);
    989                 } break;
    990                 case MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_ID: {
    991                     findAccessibilityNodeInfoByViewIdUiThread(message);
    992                 } break;
    993                 case MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_TEXT: {
    994                     findAccessibilityNodeInfosByTextUiThread(message);
    995                 } break;
    996                 case MSG_FIND_FOCUS: {
    997                     findFocusUiThread(message);
    998                 } break;
    999                 case MSG_FOCUS_SEARCH: {
   1000                     focusSearchUiThread(message);
   1001                 } break;
   1002                 default:
   1003                     throw new IllegalArgumentException("Unknown message type: " + type);
   1004             }
   1005         }
   1006     }
   1007 }
   1008