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