AccessibilityInteractionController.java revision aa780c110922148a6a4ba06734bb2b0bb8c98f93
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                final int childCount = children.getChildCount();
666                for (int i = 0; i < childCount; i++) {
667                    if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
668                        children.recycle();
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                children.recycle();
688            }
689        }
690
691        private void prefetchDescendantsOfRealNode(View root,
692                List<AccessibilityNodeInfo> outInfos) {
693            if (!(root instanceof ViewGroup)) {
694                return;
695            }
696            ViewGroup rootGroup = (ViewGroup) root;
697            HashMap<View, AccessibilityNodeInfo> addedChildren =
698                new HashMap<View, AccessibilityNodeInfo>();
699            ChildListForAccessibility children = ChildListForAccessibility.obtain(rootGroup, false);
700            final int childCount = children.getChildCount();
701            for (int i = 0; i < childCount; i++) {
702                if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
703                    children.recycle();
704                    return;
705                }
706                View child = children.getChildAt(i);
707                if (child.isDisplayedOnScreen()) {
708                    AccessibilityNodeProvider provider = child.getAccessibilityNodeProvider();
709                    if (provider == null) {
710                        AccessibilityNodeInfo info = child.createAccessibilityNodeInfo();
711                        if (info != null) {
712                            outInfos.add(info);
713                            addedChildren.put(child, null);
714                        }
715                    } else {
716                        AccessibilityNodeInfo info = provider.createAccessibilityNodeInfo(
717                               AccessibilityNodeInfo.UNDEFINED);
718                        if (info != null) {
719                            outInfos.add(info);
720                            addedChildren.put(child, info);
721                        }
722                    }
723                }
724            }
725            children.recycle();
726            if (outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
727                for (Map.Entry<View, AccessibilityNodeInfo> entry : addedChildren.entrySet()) {
728                    View addedChild = entry.getKey();
729                    AccessibilityNodeInfo virtualRoot = entry.getValue();
730                    if (virtualRoot == null) {
731                        prefetchDescendantsOfRealNode(addedChild, outInfos);
732                    } else {
733                        AccessibilityNodeProvider provider =
734                            addedChild.getAccessibilityNodeProvider();
735                        prefetchDescendantsOfVirtualNode(virtualRoot, provider, outInfos);
736                    }
737                }
738            }
739        }
740
741        private void prefetchPredecessorsOfVirtualNode(AccessibilityNodeInfo root,
742                View providerHost, AccessibilityNodeProvider provider,
743                List<AccessibilityNodeInfo> outInfos) {
744            long parentNodeId = root.getParentNodeId();
745            int accessibilityViewId = AccessibilityNodeInfo.getAccessibilityViewId(parentNodeId);
746            while (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED) {
747                if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
748                    return;
749                }
750                final int virtualDescendantId =
751                    AccessibilityNodeInfo.getVirtualDescendantId(parentNodeId);
752                if (virtualDescendantId != AccessibilityNodeInfo.UNDEFINED
753                        || accessibilityViewId == providerHost.getAccessibilityViewId()) {
754                    AccessibilityNodeInfo parent = provider.createAccessibilityNodeInfo(
755                            virtualDescendantId);
756                    if (parent != null) {
757                        outInfos.add(parent);
758                    }
759                    parentNodeId = parent.getParentNodeId();
760                    accessibilityViewId = AccessibilityNodeInfo.getAccessibilityViewId(
761                            parentNodeId);
762                } else {
763                    prefetchPredecessorsOfRealNode(providerHost, outInfos);
764                    return;
765                }
766            }
767        }
768
769        private void prefetchSiblingsOfVirtualNode(AccessibilityNodeInfo current, View providerHost,
770                AccessibilityNodeProvider provider, List<AccessibilityNodeInfo> outInfos) {
771            final long parentNodeId = current.getParentNodeId();
772            final int parentAccessibilityViewId =
773                AccessibilityNodeInfo.getAccessibilityViewId(parentNodeId);
774            final int parentVirtualDescendantId =
775                AccessibilityNodeInfo.getVirtualDescendantId(parentNodeId);
776            if (parentVirtualDescendantId != AccessibilityNodeInfo.UNDEFINED
777                    || parentAccessibilityViewId == providerHost.getAccessibilityViewId()) {
778                AccessibilityNodeInfo parent =
779                    provider.createAccessibilityNodeInfo(parentVirtualDescendantId);
780                if (parent != null) {
781                    SparseLongArray childNodeIds = parent.getChildNodeIds();
782                    final int childCount = childNodeIds.size();
783                    for (int i = 0; i < childCount; i++) {
784                        if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
785                            return;
786                        }
787                        final long childNodeId = childNodeIds.get(i);
788                        if (childNodeId != current.getSourceNodeId()) {
789                            final int childVirtualDescendantId =
790                                AccessibilityNodeInfo.getVirtualDescendantId(childNodeId);
791                            AccessibilityNodeInfo child = provider.createAccessibilityNodeInfo(
792                                    childVirtualDescendantId);
793                            if (child != null) {
794                                outInfos.add(child);
795                            }
796                        }
797                    }
798                }
799            } else {
800                prefetchSiblingsOfRealNode(providerHost, outInfos);
801            }
802        }
803
804        private void prefetchDescendantsOfVirtualNode(AccessibilityNodeInfo root,
805                AccessibilityNodeProvider provider, List<AccessibilityNodeInfo> outInfos) {
806            SparseLongArray childNodeIds = root.getChildNodeIds();
807            final int initialOutInfosSize = outInfos.size();
808            final int childCount = childNodeIds.size();
809            for (int i = 0; i < childCount; i++) {
810                if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
811                    return;
812                }
813                final long childNodeId = childNodeIds.get(i);
814                AccessibilityNodeInfo child = provider.createAccessibilityNodeInfo(
815                        AccessibilityNodeInfo.getVirtualDescendantId(childNodeId));
816                if (child != null) {
817                    outInfos.add(child);
818                }
819            }
820            if (outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
821                final int addedChildCount = outInfos.size() - initialOutInfosSize;
822                for (int i = 0; i < addedChildCount; i++) {
823                    AccessibilityNodeInfo child = outInfos.get(initialOutInfosSize + i);
824                    prefetchDescendantsOfVirtualNode(child, provider, outInfos);
825                }
826            }
827        }
828    }
829
830    private class PrivateHandler extends Handler {
831        private final static int MSG_PERFORM_ACCESSIBILITY_ACTION = 1;
832        private final static int MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID = 2;
833        private final static int MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_ID = 3;
834        private final static int MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_TEXT = 4;
835        private final static int MSG_FIND_FOCUS = 5;
836        private final static int MSG_FOCUS_SEARCH = 6;
837
838        public PrivateHandler(Looper looper) {
839            super(looper);
840        }
841
842        @Override
843        public String getMessageName(Message message) {
844            final int type = message.what;
845            switch (type) {
846                case MSG_PERFORM_ACCESSIBILITY_ACTION:
847                    return "MSG_PERFORM_ACCESSIBILITY_ACTION";
848                case MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID:
849                    return "MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID";
850                case MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_ID:
851                    return "MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_ID";
852                case MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_TEXT:
853                    return "MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_TEXT";
854                case MSG_FIND_FOCUS:
855                    return "MSG_FIND_FOCUS";
856                case MSG_FOCUS_SEARCH:
857                    return "MSG_FOCUS_SEARCH";
858                default:
859                    throw new IllegalArgumentException("Unknown message type: " + type);
860            }
861        }
862
863        @Override
864        public void handleMessage(Message message) {
865            final int type = message.what;
866            switch (type) {
867                case MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID: {
868                    findAccessibilityNodeInfoByAccessibilityIdUiThread(message);
869                } break;
870                case MSG_PERFORM_ACCESSIBILITY_ACTION: {
871                    perfromAccessibilityActionUiThread(message);
872                } break;
873                case MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_ID: {
874                    findAccessibilityNodeInfoByViewIdUiThread(message);
875                } break;
876                case MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_TEXT: {
877                    findAccessibilityNodeInfosByTextUiThread(message);
878                } break;
879                case MSG_FIND_FOCUS: {
880                    findFocusUiThread(message);
881                } break;
882                case MSG_FOCUS_SEARCH: {
883                    focusSearchUiThread(message);
884                } break;
885                default:
886                    throw new IllegalArgumentException("Unknown message type: " + type);
887            }
888        }
889    }
890}
891