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