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