AccessibilityInteractionController.java revision 749e796eb3a42e21613a3b360000373601a8f50d
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
17package android.view;
18
19import static android.view.accessibility.AccessibilityNodeInfo.INCLUDE_NOT_IMPORTANT_VIEWS;
20
21import android.os.Handler;
22import android.os.Looper;
23import android.os.Message;
24import android.os.Process;
25import android.os.RemoteException;
26import android.util.Pool;
27import android.util.Poolable;
28import android.util.PoolableManager;
29import android.util.Pools;
30import android.util.SparseLongArray;
31import android.view.ViewGroup.ChildListForAccessibility;
32import android.view.accessibility.AccessibilityInteractionClient;
33import android.view.accessibility.AccessibilityNodeInfo;
34import android.view.accessibility.AccessibilityNodeProvider;
35import android.view.accessibility.IAccessibilityInteractionConnectionCallback;
36
37import java.util.ArrayList;
38import java.util.HashMap;
39import java.util.List;
40import 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 */
49final 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    public AccessibilityInteractionController(ViewRootImpl viewRootImpl) {
66        Looper looper =  viewRootImpl.mHandler.getLooper();
67        mMyLooperThreadId = looper.getThread().getId();
68        mMyProcessId = Process.myPid();
69        mHandler = new PrivateHandler(looper);
70        mViewRootImpl = viewRootImpl;
71        mPrefetcher = new AccessibilityNodePrefetcher();
72    }
73
74    // Reusable poolable arguments for interacting with the view hierarchy
75    // to fit more arguments than Message and to avoid sharing objects between
76    // two messages since several threads can send messages concurrently.
77    private final Pool<SomeArgs> mPool = Pools.synchronizedPool(Pools.finitePool(
78            new PoolableManager<SomeArgs>() {
79                public SomeArgs newInstance() {
80                    return new SomeArgs();
81                }
82
83                public void onAcquired(SomeArgs info) {
84                    /* do nothing */
85                }
86
87                public void onReleased(SomeArgs info) {
88                    info.clear();
89                }
90            }, POOL_SIZE)
91    );
92
93    private class SomeArgs implements Poolable<SomeArgs> {
94        private SomeArgs mNext;
95        private boolean mIsPooled;
96
97        public Object arg1;
98        public Object arg2;
99        public int argi1;
100        public int argi2;
101        public int argi3;
102
103        public SomeArgs getNextPoolable() {
104            return mNext;
105        }
106
107        public boolean isPooled() {
108            return mIsPooled;
109        }
110
111        public void setNextPoolable(SomeArgs args) {
112            mNext = args;
113        }
114
115        public void setPooled(boolean isPooled) {
116            mIsPooled = isPooled;
117        }
118
119        private void clear() {
120            arg1 = null;
121            arg2 = null;
122            argi1 = 0;
123            argi2 = 0;
124            argi3 = 0;
125        }
126    }
127
128    public void findAccessibilityNodeInfoByAccessibilityIdClientThread(
129            long accessibilityNodeId, int interactionId,
130            IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid,
131            long interrogatingTid) {
132        Message message = mHandler.obtainMessage();
133        message.what = PrivateHandler.MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID;
134        message.arg1 = flags;
135        SomeArgs args = mPool.acquire();
136        args.argi1 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId);
137        args.argi2 = AccessibilityNodeInfo.getVirtualDescendantId(accessibilityNodeId);
138        args.argi3 = interactionId;
139        args.arg1 = callback;
140        message.obj = args;
141        // If the interrogation is performed by the same thread as the main UI
142        // thread in this process, set the message as a static reference so
143        // after this call completes the same thread but in the interrogating
144        // client can handle the message to generate the result.
145        if (interrogatingPid == mMyProcessId && interrogatingTid == mMyLooperThreadId) {
146            AccessibilityInteractionClient.getInstanceForThread(
147                    interrogatingTid).setSameThreadMessage(message);
148        } else {
149            mHandler.sendMessage(message);
150        }
151    }
152
153    private void findAccessibilityNodeInfoByAccessibilityIdUiThread(Message message) {
154        final int flags = message.arg1;
155        SomeArgs args = (SomeArgs) message.obj;
156        final int accessibilityViewId = args.argi1;
157        final int virtualDescendantId = args.argi2;
158        final int interactionId = args.argi3;
159        final IAccessibilityInteractionConnectionCallback callback =
160            (IAccessibilityInteractionConnectionCallback) args.arg1;
161        mPool.release(args);
162        List<AccessibilityNodeInfo> infos = mTempAccessibilityNodeInfoList;
163        infos.clear();
164        try {
165            if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
166                return;
167            }
168            mViewRootImpl.mAttachInfo.mIncludeNotImportantViews =
169                (flags & INCLUDE_NOT_IMPORTANT_VIEWS) != 0;
170            View root = null;
171            if (accessibilityViewId == AccessibilityNodeInfo.UNDEFINED) {
172                root = mViewRootImpl.mView;
173            } else {
174                root = findViewByAccessibilityId(accessibilityViewId);
175            }
176            if (root != null && root.isDisplayedOnScreen()) {
177                mPrefetcher.prefetchAccessibilityNodeInfos(root, virtualDescendantId, flags, infos);
178            }
179        } finally {
180            try {
181                mViewRootImpl.mAttachInfo.mIncludeNotImportantViews = false;
182                callback.setFindAccessibilityNodeInfosResult(infos, interactionId);
183                infos.clear();
184            } catch (RemoteException re) {
185                /* ignore - the other side will time out */
186            }
187        }
188    }
189
190    public void findAccessibilityNodeInfoByViewIdClientThread(long accessibilityNodeId,
191            int viewId, int interactionId, IAccessibilityInteractionConnectionCallback callback,
192            int flags, int interrogatingPid, long interrogatingTid) {
193        Message message = mHandler.obtainMessage();
194        message.what = PrivateHandler.MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_ID;
195        message.arg1 = flags;
196        message.arg2 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId);
197        SomeArgs args = mPool.acquire();
198        args.argi1 = viewId;
199        args.argi2 = interactionId;
200        args.arg1 = callback;
201        message.obj = args;
202        // If the interrogation is performed by the same thread as the main UI
203        // thread in this process, set the message as a static reference so
204        // after this call completes the same thread but in the interrogating
205        // client can handle the message to generate the result.
206        if (interrogatingPid == mMyProcessId && interrogatingTid == mMyLooperThreadId) {
207            AccessibilityInteractionClient.getInstanceForThread(
208                    interrogatingTid).setSameThreadMessage(message);
209        } else {
210            mHandler.sendMessage(message);
211        }
212    }
213
214    private void findAccessibilityNodeInfoByViewIdUiThread(Message message) {
215        final int flags = message.arg1;
216        final int accessibilityViewId = message.arg2;
217        SomeArgs args = (SomeArgs) message.obj;
218        final int viewId = args.argi1;
219        final int interactionId = args.argi2;
220        final IAccessibilityInteractionConnectionCallback callback =
221            (IAccessibilityInteractionConnectionCallback) args.arg1;
222        mPool.release(args);
223        AccessibilityNodeInfo info = null;
224        try {
225            if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
226                return;
227            }
228            mViewRootImpl.mAttachInfo.mIncludeNotImportantViews =
229                (flags & INCLUDE_NOT_IMPORTANT_VIEWS) != 0;
230            View root = null;
231            if (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED) {
232                root = findViewByAccessibilityId(accessibilityViewId);
233            } else {
234                root = mViewRootImpl.mView;
235            }
236            if (root != null) {
237                View target = root.findViewById(viewId);
238                if (target != null && target.isDisplayedOnScreen()) {
239                    info = target.createAccessibilityNodeInfo();
240                }
241            }
242        } finally {
243            try {
244                mViewRootImpl.mAttachInfo.mIncludeNotImportantViews = false;
245                callback.setFindAccessibilityNodeInfoResult(info, interactionId);
246            } catch (RemoteException re) {
247                /* ignore - the other side will time out */
248            }
249        }
250    }
251
252    public void findAccessibilityNodeInfosByTextClientThread(long accessibilityNodeId,
253            String text, int interactionId, IAccessibilityInteractionConnectionCallback callback,
254            int flags,  int interrogatingPid, long interrogatingTid) {
255        Message message = mHandler.obtainMessage();
256        message.what = PrivateHandler.MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_TEXT;
257        message.arg1 = flags;
258        SomeArgs args = mPool.acquire();
259        args.arg1 = text;
260        args.argi1 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId);
261        args.argi2 = AccessibilityNodeInfo.getVirtualDescendantId(accessibilityNodeId);
262        args.argi3 = interactionId;
263        args.arg2 = callback;
264        message.obj = args;
265        // If the interrogation is performed by the same thread as the main UI
266        // thread in this process, set the message as a static reference so
267        // after this call completes the same thread but in the interrogating
268        // client can handle the message to generate the result.
269        if (interrogatingPid == mMyProcessId && interrogatingTid == mMyLooperThreadId) {
270            AccessibilityInteractionClient.getInstanceForThread(
271                    interrogatingTid).setSameThreadMessage(message);
272        } else {
273            mHandler.sendMessage(message);
274        }
275    }
276
277    private void findAccessibilityNodeInfosByTextUiThread(Message message) {
278        final int flags = message.arg1;
279        SomeArgs args = (SomeArgs) message.obj;
280        final String text = (String) args.arg1;
281        final int accessibilityViewId = args.argi1;
282        final int virtualDescendantId = args.argi2;
283        final int interactionId = args.argi3;
284        final IAccessibilityInteractionConnectionCallback callback =
285            (IAccessibilityInteractionConnectionCallback) args.arg2;
286        mPool.release(args);
287        List<AccessibilityNodeInfo> infos = null;
288        try {
289            if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
290                return;
291            }
292            mViewRootImpl.mAttachInfo.mIncludeNotImportantViews =
293                (flags & INCLUDE_NOT_IMPORTANT_VIEWS) != 0;
294            View root = null;
295            if (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED) {
296                root = findViewByAccessibilityId(accessibilityViewId);
297            } else {
298                root = mViewRootImpl.mView;
299            }
300            if (root != null && root.isDisplayedOnScreen()) {
301                AccessibilityNodeProvider provider = root.getAccessibilityNodeProvider();
302                if (provider != null) {
303                    infos = provider.findAccessibilityNodeInfosByText(text,
304                            virtualDescendantId);
305                } else if (virtualDescendantId == AccessibilityNodeInfo.UNDEFINED) {
306                    ArrayList<View> foundViews = mViewRootImpl.mAttachInfo.mTempArrayList;
307                    foundViews.clear();
308                    root.findViewsWithText(foundViews, text, View.FIND_VIEWS_WITH_TEXT
309                            | View.FIND_VIEWS_WITH_CONTENT_DESCRIPTION
310                            | View.FIND_VIEWS_WITH_ACCESSIBILITY_NODE_PROVIDERS);
311                    if (!foundViews.isEmpty()) {
312                        infos = mTempAccessibilityNodeInfoList;
313                        infos.clear();
314                        final int viewCount = foundViews.size();
315                        for (int i = 0; i < viewCount; i++) {
316                            View foundView = foundViews.get(i);
317                            if (foundView.isDisplayedOnScreen()) {
318                                provider = foundView.getAccessibilityNodeProvider();
319                                if (provider != null) {
320                                    List<AccessibilityNodeInfo> infosFromProvider =
321                                        provider.findAccessibilityNodeInfosByText(text,
322                                                virtualDescendantId);
323                                    if (infosFromProvider != null) {
324                                        infos.addAll(infosFromProvider);
325                                    }
326                                } else  {
327                                    infos.add(foundView.createAccessibilityNodeInfo());
328                                }
329                            }
330                        }
331                    }
332                }
333            }
334        } finally {
335            try {
336                mViewRootImpl.mAttachInfo.mIncludeNotImportantViews = false;
337                callback.setFindAccessibilityNodeInfosResult(infos, interactionId);
338            } catch (RemoteException re) {
339                /* ignore - the other side will time out */
340            }
341        }
342    }
343
344    public void findFocusClientThread(long accessibilityNodeId, int interactionId, int focusType,
345            IAccessibilityInteractionConnectionCallback callback,  int flags, int interogatingPid,
346            long interrogatingTid) {
347        Message message = mHandler.obtainMessage();
348        message.what = PrivateHandler.MSG_FIND_FOCUS;
349        message.arg1 = flags;
350        message.arg2 = focusType;
351        SomeArgs args = mPool.acquire();
352        args.argi1 = interactionId;
353        args.argi2 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId);
354        args.argi3 = AccessibilityNodeInfo.getVirtualDescendantId(accessibilityNodeId);
355        args.arg1 = callback;
356        message.obj = args;
357        // If the interrogation is performed by the same thread as the main UI
358        // thread in this process, set the message as a static reference so
359        // after this call completes the same thread but in the interrogating
360        // client can handle the message to generate the result.
361        if (interogatingPid == mMyProcessId && interrogatingTid == mMyLooperThreadId) {
362            AccessibilityInteractionClient.getInstanceForThread(
363                    interrogatingTid).setSameThreadMessage(message);
364        } else {
365            mHandler.sendMessage(message);
366        }
367    }
368
369    private void findFocusUiThread(Message message) {
370        final int flags = message.arg1;
371        final int focusType = message.arg2;
372        SomeArgs args = (SomeArgs) message.obj;
373        final int interactionId = args.argi1;
374        final int accessibilityViewId = args.argi2;
375        final int virtualDescendantId = args.argi3;
376        final IAccessibilityInteractionConnectionCallback callback =
377            (IAccessibilityInteractionConnectionCallback) args.arg1;
378        mPool.release(args);
379        AccessibilityNodeInfo focused = null;
380        try {
381            if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
382                return;
383            }
384            mViewRootImpl.mAttachInfo.mIncludeNotImportantViews =
385                (flags & INCLUDE_NOT_IMPORTANT_VIEWS) != 0;
386            View root = null;
387            if (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED) {
388                root = findViewByAccessibilityId(accessibilityViewId);
389            } else {
390                root = mViewRootImpl.mView;
391            }
392            if (root != null && root.isDisplayedOnScreen()) {
393                switch (focusType) {
394                    case AccessibilityNodeInfo.FOCUS_ACCESSIBILITY: {
395                        View host = mViewRootImpl.mAccessibilityFocusedHost;
396                        // If there is no accessibility focus host or it is not a descendant
397                        // of the root from which to start the search, then the search failed.
398                        if (host == null || !ViewRootImpl.isViewDescendantOf(host, root)) {
399                            break;
400                        }
401                        // If the host has a provider ask this provider to search for the
402                        // focus instead fetching all provider nodes to do the search here.
403                        AccessibilityNodeProvider provider = host.getAccessibilityNodeProvider();
404                        if (provider != null) {
405                            focused = provider.findAccessibilitiyFocus(virtualDescendantId);
406                        } else if (virtualDescendantId == View.NO_ID) {
407                            focused = host.createAccessibilityNodeInfo();
408                        }
409                    } break;
410                    case AccessibilityNodeInfo.FOCUS_INPUT: {
411                        // Input focus cannot go to virtual views.
412                        View target = root.findFocus();
413                        if (target != null && target.isDisplayedOnScreen()) {
414                            focused = target.createAccessibilityNodeInfo();
415                        }
416                    } break;
417                    default:
418                        throw new IllegalArgumentException("Unknown focus type: " + focusType);
419                }
420            }
421        } finally {
422            try {
423                mViewRootImpl.mAttachInfo.mIncludeNotImportantViews = false;
424                callback.setFindAccessibilityNodeInfoResult(focused, interactionId);
425            } catch (RemoteException re) {
426                /* ignore - the other side will time out */
427            }
428        }
429    }
430
431    public void focusSearchClientThread(long accessibilityNodeId, int interactionId, int direction,
432            IAccessibilityInteractionConnectionCallback callback, int flags, int interogatingPid,
433            long interrogatingTid) {
434        Message message = mHandler.obtainMessage();
435        message.what = PrivateHandler.MSG_FOCUS_SEARCH;
436        message.arg1 = flags;
437        message.arg2 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId);
438        SomeArgs args = mPool.acquire();
439        args.argi1 = AccessibilityNodeInfo.getVirtualDescendantId(accessibilityNodeId);
440        args.argi2 = direction;
441        args.argi3 = interactionId;
442        args.arg1 = callback;
443        message.obj = args;
444        // If the interrogation is performed by the same thread as the main UI
445        // thread in this process, set the message as a static reference so
446        // after this call completes the same thread but in the interrogating
447        // client can handle the message to generate the result.
448        if (interogatingPid == mMyProcessId && interrogatingTid == mMyLooperThreadId) {
449            AccessibilityInteractionClient.getInstanceForThread(
450                    interrogatingTid).setSameThreadMessage(message);
451        } else {
452            mHandler.sendMessage(message);
453        }
454    }
455
456    private void focusSearchUiThread(Message message) {
457        final int flags = message.arg1;
458        final int accessibilityViewId = message.arg2;
459        SomeArgs args = (SomeArgs) message.obj;
460        final int virtualDescendantId = args.argi1;
461        final int direction = args.argi2;
462        final int interactionId = args.argi3;
463        final IAccessibilityInteractionConnectionCallback callback =
464            (IAccessibilityInteractionConnectionCallback) args.arg1;
465        mPool.release(args);
466        AccessibilityNodeInfo next = null;
467        try {
468            if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
469                return;
470            }
471            mViewRootImpl.mAttachInfo.mIncludeNotImportantViews =
472                (flags & INCLUDE_NOT_IMPORTANT_VIEWS) != 0;
473            View root = null;
474            if (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED) {
475                root = findViewByAccessibilityId(accessibilityViewId);
476            } else {
477                root = mViewRootImpl.mView;
478            }
479            if (root != null && root.isDisplayedOnScreen()) {
480                if ((direction & View.FOCUS_ACCESSIBILITY) ==  View.FOCUS_ACCESSIBILITY) {
481                    AccessibilityNodeProvider provider = root.getAccessibilityNodeProvider();
482                    if (provider != null) {
483                        next = provider.accessibilityFocusSearch(direction,
484                                virtualDescendantId);
485                    } else if (virtualDescendantId == View.NO_ID) {
486                        View nextView = root.focusSearch(direction);
487                        if (nextView != null) {
488                            // If the focus search reached a node with a provider
489                            // we delegate to the provider to find the next one.
490                            provider = nextView.getAccessibilityNodeProvider();
491                            if (provider != null) {
492                                next = provider.accessibilityFocusSearch(direction,
493                                        virtualDescendantId);
494                            } else {
495                                next = nextView.createAccessibilityNodeInfo();
496                             }
497                        }
498                    }
499                } else {
500                    View nextView = root.focusSearch(direction);
501                    if (nextView != null) {
502                        next = nextView.createAccessibilityNodeInfo();
503                    }
504                }
505            }
506        } finally {
507            try {
508                mViewRootImpl.mAttachInfo.mIncludeNotImportantViews = false;
509                callback.setFindAccessibilityNodeInfoResult(next, interactionId);
510            } catch (RemoteException re) {
511                /* ignore - the other side will time out */
512            }
513        }
514    }
515
516    public void performAccessibilityActionClientThread(long accessibilityNodeId, int action,
517            int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags,
518            int interogatingPid, long interrogatingTid) {
519        Message message = mHandler.obtainMessage();
520        message.what = PrivateHandler.MSG_PERFORM_ACCESSIBILITY_ACTION;
521        message.arg1 = flags;
522        message.arg2 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId);
523        SomeArgs args = mPool.acquire();
524        args.argi1 = AccessibilityNodeInfo.getVirtualDescendantId(accessibilityNodeId);
525        args.argi2 = action;
526        args.argi3 = interactionId;
527        args.arg1 = callback;
528        message.obj = args;
529        // If the interrogation is performed by the same thread as the main UI
530        // thread in this process, set the message as a static reference so
531        // after this call completes the same thread but in the interrogating
532        // client can handle the message to generate the result.
533        if (interogatingPid == mMyProcessId && interrogatingTid == mMyLooperThreadId) {
534            AccessibilityInteractionClient.getInstanceForThread(
535                    interrogatingTid).setSameThreadMessage(message);
536        } else {
537            mHandler.sendMessage(message);
538        }
539    }
540
541    private void perfromAccessibilityActionUiThread(Message message) {
542        final int flags = message.arg1;
543        final int accessibilityViewId = message.arg2;
544        SomeArgs args = (SomeArgs) message.obj;
545        final int virtualDescendantId = args.argi1;
546        final int action = args.argi2;
547        final int interactionId = args.argi3;
548        final IAccessibilityInteractionConnectionCallback callback =
549            (IAccessibilityInteractionConnectionCallback) args.arg1;
550        mPool.release(args);
551        boolean succeeded = false;
552        try {
553            if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
554                return;
555            }
556            mViewRootImpl.mAttachInfo.mIncludeNotImportantViews =
557                (flags & INCLUDE_NOT_IMPORTANT_VIEWS) != 0;
558            View target = null;
559            if (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED) {
560                target = findViewByAccessibilityId(accessibilityViewId);
561            } else {
562                target = mViewRootImpl.mView;
563            }
564            if (target != null && target.isDisplayedOnScreen()) {
565                AccessibilityNodeProvider provider = target.getAccessibilityNodeProvider();
566                if (provider != null) {
567                    succeeded = provider.performAccessibilityAction(action, virtualDescendantId);
568                } else if (virtualDescendantId == View.NO_ID) {
569                    succeeded = target.performAccessibilityAction(action);
570                }
571            }
572        } finally {
573            try {
574                mViewRootImpl.mAttachInfo.mIncludeNotImportantViews = false;
575                callback.setPerformAccessibilityActionResult(succeeded, interactionId);
576            } catch (RemoteException re) {
577                /* ignore - the other side will time out */
578            }
579        }
580    }
581
582    private View findViewByAccessibilityId(int accessibilityId) {
583        View root = mViewRootImpl.mView;
584        if (root == null) {
585            return null;
586        }
587        View foundView = root.findViewByAccessibilityId(accessibilityId);
588        if (foundView != null && !foundView.isDisplayedOnScreen()) {
589            return null;
590        }
591        return foundView;
592    }
593
594    /**
595     * This class encapsulates a prefetching strategy for the accessibility APIs for
596     * querying window content. It is responsible to prefetch a batch of
597     * AccessibilityNodeInfos in addition to the one for a requested node.
598     */
599    private class AccessibilityNodePrefetcher {
600
601        private static final int MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE = 50;
602
603        public void prefetchAccessibilityNodeInfos(View view, int virtualViewId, int prefetchFlags,
604                List<AccessibilityNodeInfo> outInfos) {
605            AccessibilityNodeProvider provider = view.getAccessibilityNodeProvider();
606            if (provider == null) {
607                AccessibilityNodeInfo root = view.createAccessibilityNodeInfo();
608                if (root != null) {
609                    outInfos.add(root);
610                    if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS) != 0) {
611                        prefetchPredecessorsOfRealNode(view, outInfos);
612                    }
613                    if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS) != 0) {
614                        prefetchSiblingsOfRealNode(view, outInfos);
615                    }
616                    if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS) != 0) {
617                        prefetchDescendantsOfRealNode(view, outInfos);
618                    }
619                }
620            } else {
621                AccessibilityNodeInfo root = provider.createAccessibilityNodeInfo(virtualViewId);
622                if (root != null) {
623                    outInfos.add(root);
624                    if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS) != 0) {
625                        prefetchPredecessorsOfVirtualNode(root, view, provider, outInfos);
626                    }
627                    if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS) != 0) {
628                        prefetchSiblingsOfVirtualNode(root, view, provider, outInfos);
629                    }
630                    if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS) != 0) {
631                        prefetchDescendantsOfVirtualNode(root, provider, outInfos);
632                    }
633                }
634            }
635        }
636
637        private void prefetchPredecessorsOfRealNode(View view,
638                List<AccessibilityNodeInfo> outInfos) {
639            ViewParent parent = view.getParentForAccessibility();
640            while (parent instanceof View
641                    && outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
642                View parentView = (View) parent;
643                final long parentNodeId = AccessibilityNodeInfo.makeNodeId(
644                        parentView.getAccessibilityViewId(), AccessibilityNodeInfo.UNDEFINED);
645                AccessibilityNodeInfo info = parentView.createAccessibilityNodeInfo();
646                if (info != null) {
647                    outInfos.add(info);
648                }
649                parent = parent.getParentForAccessibility();
650            }
651        }
652
653        private void prefetchSiblingsOfRealNode(View current,
654                List<AccessibilityNodeInfo> outInfos) {
655            ViewParent parent = current.getParentForAccessibility();
656            if (parent instanceof ViewGroup) {
657                ViewGroup parentGroup = (ViewGroup) parent;
658                ChildListForAccessibility children = ChildListForAccessibility.obtain(parentGroup,
659                        false);
660                final int childCount = children.getChildCount();
661                for (int i = 0; i < childCount; i++) {
662                    if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
663                        children.recycle();
664                        return;
665                    }
666                    View child = children.getChildAt(i);
667                    if (child.getAccessibilityViewId() != current.getAccessibilityViewId()
668                            &&  child.isDisplayedOnScreen()) {
669                        AccessibilityNodeInfo info = null;
670                        AccessibilityNodeProvider provider = child.getAccessibilityNodeProvider();
671                        if (provider == null) {
672                            info = child.createAccessibilityNodeInfo();
673                        } else {
674                            info = provider.createAccessibilityNodeInfo(
675                                    AccessibilityNodeInfo.UNDEFINED);
676                        }
677                        if (info != null) {
678                            outInfos.add(info);
679                        }
680                    }
681                }
682                children.recycle();
683            }
684        }
685
686        private void prefetchDescendantsOfRealNode(View root,
687                List<AccessibilityNodeInfo> outInfos) {
688            if (!(root instanceof ViewGroup)) {
689                return;
690            }
691            ViewGroup rootGroup = (ViewGroup) root;
692            HashMap<View, AccessibilityNodeInfo> addedChildren =
693                new HashMap<View, AccessibilityNodeInfo>();
694            ChildListForAccessibility children = ChildListForAccessibility.obtain(rootGroup, false);
695            final int childCount = children.getChildCount();
696            for (int i = 0; i < childCount; i++) {
697                if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
698                    children.recycle();
699                    return;
700                }
701                View child = children.getChildAt(i);
702                if (child.isDisplayedOnScreen()) {
703                    AccessibilityNodeProvider provider = child.getAccessibilityNodeProvider();
704                    if (provider == null) {
705                        AccessibilityNodeInfo info = child.createAccessibilityNodeInfo();
706                        if (info != null) {
707                            outInfos.add(info);
708                            addedChildren.put(child, null);
709                        }
710                    } else {
711                        AccessibilityNodeInfo info = provider.createAccessibilityNodeInfo(
712                               AccessibilityNodeInfo.UNDEFINED);
713                        if (info != null) {
714                            outInfos.add(info);
715                            addedChildren.put(child, info);
716                        }
717                    }
718                }
719            }
720            children.recycle();
721            if (outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
722                for (Map.Entry<View, AccessibilityNodeInfo> entry : addedChildren.entrySet()) {
723                    View addedChild = entry.getKey();
724                    AccessibilityNodeInfo virtualRoot = entry.getValue();
725                    if (virtualRoot == null) {
726                        prefetchDescendantsOfRealNode(addedChild, outInfos);
727                    } else {
728                        AccessibilityNodeProvider provider =
729                            addedChild.getAccessibilityNodeProvider();
730                        prefetchDescendantsOfVirtualNode(virtualRoot, provider, outInfos);
731                    }
732                }
733            }
734        }
735
736        private void prefetchPredecessorsOfVirtualNode(AccessibilityNodeInfo root,
737                View providerHost, AccessibilityNodeProvider provider,
738                List<AccessibilityNodeInfo> outInfos) {
739            long parentNodeId = root.getParentNodeId();
740            int accessibilityViewId = AccessibilityNodeInfo.getAccessibilityViewId(parentNodeId);
741            while (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED) {
742                if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
743                    return;
744                }
745                final int virtualDescendantId =
746                    AccessibilityNodeInfo.getVirtualDescendantId(parentNodeId);
747                if (virtualDescendantId != AccessibilityNodeInfo.UNDEFINED
748                        || accessibilityViewId == providerHost.getAccessibilityViewId()) {
749                    AccessibilityNodeInfo parent = provider.createAccessibilityNodeInfo(
750                            virtualDescendantId);
751                    if (parent != null) {
752                        outInfos.add(parent);
753                    }
754                    parentNodeId = parent.getParentNodeId();
755                    accessibilityViewId = AccessibilityNodeInfo.getAccessibilityViewId(
756                            parentNodeId);
757                } else {
758                    prefetchPredecessorsOfRealNode(providerHost, outInfos);
759                    return;
760                }
761            }
762        }
763
764        private void prefetchSiblingsOfVirtualNode(AccessibilityNodeInfo current, View providerHost,
765                AccessibilityNodeProvider provider, List<AccessibilityNodeInfo> outInfos) {
766            final long parentNodeId = current.getParentNodeId();
767            final int parentAccessibilityViewId =
768                AccessibilityNodeInfo.getAccessibilityViewId(parentNodeId);
769            final int parentVirtualDescendantId =
770                AccessibilityNodeInfo.getVirtualDescendantId(parentNodeId);
771            if (parentVirtualDescendantId != AccessibilityNodeInfo.UNDEFINED
772                    || parentAccessibilityViewId == providerHost.getAccessibilityViewId()) {
773                AccessibilityNodeInfo parent =
774                    provider.createAccessibilityNodeInfo(parentVirtualDescendantId);
775                if (parent != null) {
776                    SparseLongArray childNodeIds = parent.getChildNodeIds();
777                    final int childCount = childNodeIds.size();
778                    for (int i = 0; i < childCount; i++) {
779                        if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
780                            return;
781                        }
782                        final long childNodeId = childNodeIds.get(i);
783                        if (childNodeId != current.getSourceNodeId()) {
784                            final int childVirtualDescendantId =
785                                AccessibilityNodeInfo.getVirtualDescendantId(childNodeId);
786                            AccessibilityNodeInfo child = provider.createAccessibilityNodeInfo(
787                                    childVirtualDescendantId);
788                            if (child != null) {
789                                outInfos.add(child);
790                            }
791                        }
792                    }
793                }
794            } else {
795                prefetchSiblingsOfRealNode(providerHost, outInfos);
796            }
797        }
798
799        private void prefetchDescendantsOfVirtualNode(AccessibilityNodeInfo root,
800                AccessibilityNodeProvider provider, List<AccessibilityNodeInfo> outInfos) {
801            SparseLongArray childNodeIds = root.getChildNodeIds();
802            final int initialOutInfosSize = outInfos.size();
803            final int childCount = childNodeIds.size();
804            for (int i = 0; i < childCount; i++) {
805                if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
806                    return;
807                }
808                final long childNodeId = childNodeIds.get(i);
809                AccessibilityNodeInfo child = provider.createAccessibilityNodeInfo(
810                        AccessibilityNodeInfo.getVirtualDescendantId(childNodeId));
811                if (child != null) {
812                    outInfos.add(child);
813                }
814            }
815            if (outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
816                final int addedChildCount = outInfos.size() - initialOutInfosSize;
817                for (int i = 0; i < addedChildCount; i++) {
818                    AccessibilityNodeInfo child = outInfos.get(initialOutInfosSize + i);
819                    prefetchDescendantsOfVirtualNode(child, provider, outInfos);
820                }
821            }
822        }
823    }
824
825    private class PrivateHandler extends Handler {
826        private final static int MSG_PERFORM_ACCESSIBILITY_ACTION = 1;
827        private final static int MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID = 2;
828        private final static int MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_ID = 3;
829        private final static int MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_TEXT = 4;
830        private final static int MSG_FIND_FOCUS = 5;
831        private final static int MSG_FOCUS_SEARCH = 6;
832
833        public PrivateHandler(Looper looper) {
834            super(looper);
835        }
836
837        @Override
838        public String getMessageName(Message message) {
839            final int type = message.what;
840            switch (type) {
841                case MSG_PERFORM_ACCESSIBILITY_ACTION:
842                    return "MSG_PERFORM_ACCESSIBILITY_ACTION";
843                case MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID:
844                    return "MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID";
845                case MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_ID:
846                    return "MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_ID";
847                case MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_TEXT:
848                    return "MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_TEXT";
849                case MSG_FIND_FOCUS:
850                    return "MSG_FIND_FOCUS";
851                case MSG_FOCUS_SEARCH:
852                    return "MSG_FOCUS_SEARCH";
853                default:
854                    throw new IllegalArgumentException("Unknown message type: " + type);
855            }
856        }
857
858        @Override
859        public void handleMessage(Message message) {
860            final int type = message.what;
861            switch (type) {
862                case MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID: {
863                    findAccessibilityNodeInfoByAccessibilityIdUiThread(message);
864                } break;
865                case MSG_PERFORM_ACCESSIBILITY_ACTION: {
866                    perfromAccessibilityActionUiThread(message);
867                } break;
868                case MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_ID: {
869                    findAccessibilityNodeInfoByViewIdUiThread(message);
870                } break;
871                case MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_TEXT: {
872                    findAccessibilityNodeInfosByTextUiThread(message);
873                } break;
874                case MSG_FIND_FOCUS: {
875                    findFocusUiThread(message);
876                } break;
877                case MSG_FOCUS_SEARCH: {
878                    focusSearchUiThread(message);
879                } break;
880                default:
881                    throw new IllegalArgumentException("Unknown message type: " + type);
882            }
883        }
884    }
885}
886