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