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