AccessibilityInteractionController.java revision 86783474fdec98a22bc22e224462767eab13e273
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                            focused = provider.findAccessibilityFocus(virtualDescendantId);
484                        } else if (virtualDescendantId == View.NO_ID) {
485                            focused = host.createAccessibilityNodeInfo();
486                        }
487                    } break;
488                    case AccessibilityNodeInfo.FOCUS_INPUT: {
489                        // Input focus cannot go to virtual views.
490                        View target = root.findFocus();
491                        if (target != null && isShown(target)) {
492                            focused = target.createAccessibilityNodeInfo();
493                        }
494                    } break;
495                    default:
496                        throw new IllegalArgumentException("Unknown focus type: " + focusType);
497                }
498            }
499        } finally {
500            try {
501                mViewRootImpl.mAttachInfo.mIncludeNotImportantViews = false;
502                callback.setFindAccessibilityNodeInfoResult(focused, interactionId);
503            } catch (RemoteException re) {
504                /* ignore - the other side will time out */
505            }
506        }
507    }
508
509    public void focusSearchClientThread(long accessibilityNodeId, int direction, int windowLeft,
510            int windowTop, int interactionId, IAccessibilityInteractionConnectionCallback callback,
511            int flags, int interogatingPid, long interrogatingTid) {
512        Message message = mHandler.obtainMessage();
513        message.what = PrivateHandler.MSG_FOCUS_SEARCH;
514        message.arg1 = flags;
515        message.arg2 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId);
516
517        SomeArgs args = mPool.acquire();
518        args.argi1 = AccessibilityNodeInfo.getVirtualDescendantId(accessibilityNodeId);
519        args.argi2 = direction;
520        args.argi3 = interactionId;
521        args.arg1 = callback;
522
523        SomeArgs moreArgs = mPool.acquire();
524        moreArgs.argi1 = windowLeft;
525        moreArgs.argi2 = windowTop;
526        args.arg2 = moreArgs;
527
528        message.obj = args;
529
530        // If the interrogation is performed by the same thread as the main UI
531        // thread in this process, set the message as a static reference so
532        // after this call completes the same thread but in the interrogating
533        // client can handle the message to generate the result.
534        if (interogatingPid == mMyProcessId && interrogatingTid == mMyLooperThreadId) {
535            AccessibilityInteractionClient.getInstanceForThread(
536                    interrogatingTid).setSameThreadMessage(message);
537        } else {
538            mHandler.sendMessage(message);
539        }
540    }
541
542    private void focusSearchUiThread(Message message) {
543        final int flags = message.arg1;
544        final int accessibilityViewId = message.arg2;
545
546        SomeArgs args = (SomeArgs) message.obj;
547        final int virtualDescendantId = args.argi1;
548        final int direction = args.argi2;
549        final int interactionId = args.argi3;
550        final IAccessibilityInteractionConnectionCallback callback =
551            (IAccessibilityInteractionConnectionCallback) args.arg1;
552
553        SomeArgs moreArgs = (SomeArgs) args.arg2;
554        mViewRootImpl.mAttachInfo.mActualWindowLeft = moreArgs.argi1;
555        mViewRootImpl.mAttachInfo.mActualWindowTop = moreArgs.argi2;
556
557        mPool.release(moreArgs);
558        mPool.release(args);
559
560        AccessibilityNodeInfo next = null;
561        try {
562            if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
563                return;
564            }
565            mViewRootImpl.mAttachInfo.mIncludeNotImportantViews =
566                (flags & INCLUDE_NOT_IMPORTANT_VIEWS) != 0;
567            View root = null;
568            if (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED) {
569                root = findViewByAccessibilityId(accessibilityViewId);
570            } else {
571                root = mViewRootImpl.mView;
572            }
573            if (root != null && isShown(root)) {
574                if ((direction & View.FOCUS_ACCESSIBILITY) ==  View.FOCUS_ACCESSIBILITY) {
575                    AccessibilityNodeProvider provider = root.getAccessibilityNodeProvider();
576                    if (provider != null) {
577                        next = provider.accessibilityFocusSearch(direction, virtualDescendantId);
578                        if (next != null) {
579                            return;
580                        }
581                    }
582                    View nextView = root.focusSearch(direction);
583                    while (nextView != null) {
584                        // If the focus search reached a node with a provider
585                        // we delegate to the provider to find the next one.
586                        // If the provider does not return a virtual view to
587                        // take accessibility focus we try the next view found
588                        // by the focus search algorithm.
589                        provider = nextView.getAccessibilityNodeProvider();
590                        if (provider != null) {
591                            next = provider.accessibilityFocusSearch(direction, View.NO_ID);
592                            if (next != null) {
593                                break;
594                            }
595                            nextView = nextView.focusSearch(direction);
596                        } else {
597                            next = nextView.createAccessibilityNodeInfo();
598                            break;
599                        }
600                    }
601                } else {
602                    View nextView = root.focusSearch(direction);
603                    if (nextView != null) {
604                        next = nextView.createAccessibilityNodeInfo();
605                    }
606                }
607            }
608        } finally {
609            try {
610                mViewRootImpl.mAttachInfo.mIncludeNotImportantViews = false;
611                callback.setFindAccessibilityNodeInfoResult(next, interactionId);
612            } catch (RemoteException re) {
613                /* ignore - the other side will time out */
614            }
615        }
616    }
617
618    public void performAccessibilityActionClientThread(long accessibilityNodeId, int action,
619            Bundle arguments, int interactionId,
620            IAccessibilityInteractionConnectionCallback callback, int flags, int interogatingPid,
621            long interrogatingTid) {
622        Message message = mHandler.obtainMessage();
623        message.what = PrivateHandler.MSG_PERFORM_ACCESSIBILITY_ACTION;
624        message.arg1 = flags;
625        message.arg2 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId);
626
627        SomeArgs args = mPool.acquire();
628        args.argi1 = AccessibilityNodeInfo.getVirtualDescendantId(accessibilityNodeId);
629        args.argi2 = action;
630        args.argi3 = interactionId;
631        args.arg1 = callback;
632        args.arg2 = arguments;
633
634        message.obj = args;
635
636        // If the interrogation is performed by the same thread as the main UI
637        // thread in this process, set the message as a static reference so
638        // after this call completes the same thread but in the interrogating
639        // client can handle the message to generate the result.
640        if (interogatingPid == mMyProcessId && interrogatingTid == mMyLooperThreadId) {
641            AccessibilityInteractionClient.getInstanceForThread(
642                    interrogatingTid).setSameThreadMessage(message);
643        } else {
644            mHandler.sendMessage(message);
645        }
646    }
647
648    private void perfromAccessibilityActionUiThread(Message message) {
649        final int flags = message.arg1;
650        final int accessibilityViewId = message.arg2;
651
652        SomeArgs args = (SomeArgs) message.obj;
653        final int virtualDescendantId = args.argi1;
654        final int action = args.argi2;
655        final int interactionId = args.argi3;
656        final IAccessibilityInteractionConnectionCallback callback =
657            (IAccessibilityInteractionConnectionCallback) args.arg1;
658        Bundle arguments = (Bundle) args.arg2;
659
660        mPool.release(args);
661
662        boolean succeeded = false;
663        try {
664            if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
665                return;
666            }
667            mViewRootImpl.mAttachInfo.mIncludeNotImportantViews =
668                (flags & INCLUDE_NOT_IMPORTANT_VIEWS) != 0;
669            View target = null;
670            if (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED) {
671                target = findViewByAccessibilityId(accessibilityViewId);
672            } else {
673                target = mViewRootImpl.mView;
674            }
675            if (target != null && isShown(target)) {
676                AccessibilityNodeProvider provider = target.getAccessibilityNodeProvider();
677                if (provider != null) {
678                    succeeded = provider.performAction(virtualDescendantId, action,
679                            arguments);
680                } else if (virtualDescendantId == View.NO_ID) {
681                    succeeded = target.performAccessibilityAction(action, arguments);
682                }
683            }
684        } finally {
685            try {
686                mViewRootImpl.mAttachInfo.mIncludeNotImportantViews = false;
687                callback.setPerformAccessibilityActionResult(succeeded, interactionId);
688            } catch (RemoteException re) {
689                /* ignore - the other side will time out */
690            }
691        }
692    }
693
694    private View findViewByAccessibilityId(int accessibilityId) {
695        View root = mViewRootImpl.mView;
696        if (root == null) {
697            return null;
698        }
699        View foundView = root.findViewByAccessibilityId(accessibilityId);
700        if (foundView != null && !isShown(foundView)) {
701            return null;
702        }
703        return foundView;
704    }
705
706    /**
707     * This class encapsulates a prefetching strategy for the accessibility APIs for
708     * querying window content. It is responsible to prefetch a batch of
709     * AccessibilityNodeInfos in addition to the one for a requested node.
710     */
711    private class AccessibilityNodePrefetcher {
712
713        private static final int MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE = 50;
714
715        private final ArrayList<View> mTempViewList = new ArrayList<View>();
716
717        public void prefetchAccessibilityNodeInfos(View view, int virtualViewId, int prefetchFlags,
718                List<AccessibilityNodeInfo> outInfos) {
719            AccessibilityNodeProvider provider = view.getAccessibilityNodeProvider();
720            if (provider == null) {
721                AccessibilityNodeInfo root = view.createAccessibilityNodeInfo();
722                if (root != null) {
723                    outInfos.add(root);
724                    if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS) != 0) {
725                        prefetchPredecessorsOfRealNode(view, outInfos);
726                    }
727                    if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS) != 0) {
728                        prefetchSiblingsOfRealNode(view, outInfos);
729                    }
730                    if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS) != 0) {
731                        prefetchDescendantsOfRealNode(view, outInfos);
732                    }
733                }
734            } else {
735                AccessibilityNodeInfo root = provider.createAccessibilityNodeInfo(virtualViewId);
736                if (root != null) {
737                    outInfos.add(root);
738                    if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS) != 0) {
739                        prefetchPredecessorsOfVirtualNode(root, view, provider, outInfos);
740                    }
741                    if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS) != 0) {
742                        prefetchSiblingsOfVirtualNode(root, view, provider, outInfos);
743                    }
744                    if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS) != 0) {
745                        prefetchDescendantsOfVirtualNode(root, provider, outInfos);
746                    }
747                }
748            }
749        }
750
751        private void prefetchPredecessorsOfRealNode(View view,
752                List<AccessibilityNodeInfo> outInfos) {
753            ViewParent parent = view.getParentForAccessibility();
754            while (parent instanceof View
755                    && outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
756                View parentView = (View) parent;
757                AccessibilityNodeInfo info = parentView.createAccessibilityNodeInfo();
758                if (info != null) {
759                    outInfos.add(info);
760                }
761                parent = parent.getParentForAccessibility();
762            }
763        }
764
765        private void prefetchSiblingsOfRealNode(View current,
766                List<AccessibilityNodeInfo> outInfos) {
767            ViewParent parent = current.getParentForAccessibility();
768            if (parent instanceof ViewGroup) {
769                ViewGroup parentGroup = (ViewGroup) parent;
770                ArrayList<View> children = mTempViewList;
771                children.clear();
772                try {
773                    parentGroup.addChildrenForAccessibility(children);
774                    final int childCount = children.size();
775                    for (int i = 0; i < childCount; i++) {
776                        if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
777                            return;
778                        }
779                        View child = children.get(i);
780                        if (child.getAccessibilityViewId() != current.getAccessibilityViewId()
781                                &&  isShown(child)) {
782                            AccessibilityNodeInfo info = null;
783                            AccessibilityNodeProvider provider =
784                                child.getAccessibilityNodeProvider();
785                            if (provider == null) {
786                                info = child.createAccessibilityNodeInfo();
787                            } else {
788                                info = provider.createAccessibilityNodeInfo(
789                                        AccessibilityNodeInfo.UNDEFINED);
790                            }
791                            if (info != null) {
792                                outInfos.add(info);
793                            }
794                        }
795                    }
796                } finally {
797                    children.clear();
798                }
799            }
800        }
801
802        private void prefetchDescendantsOfRealNode(View root,
803                List<AccessibilityNodeInfo> outInfos) {
804            if (!(root instanceof ViewGroup)) {
805                return;
806            }
807            ViewGroup rootGroup = (ViewGroup) root;
808            HashMap<View, AccessibilityNodeInfo> addedChildren =
809                new HashMap<View, AccessibilityNodeInfo>();
810            ArrayList<View> children = mTempViewList;
811            children.clear();
812            try {
813                root.addChildrenForAccessibility(children);
814                final int childCount = children.size();
815                for (int i = 0; i < childCount; i++) {
816                    if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
817                        return;
818                    }
819                    View child = children.get(i);
820                    if (isShown(child)) {
821                        AccessibilityNodeProvider provider = child.getAccessibilityNodeProvider();
822                        if (provider == null) {
823                            AccessibilityNodeInfo info = child.createAccessibilityNodeInfo();
824                            if (info != null) {
825                                outInfos.add(info);
826                                addedChildren.put(child, null);
827                            }
828                        } else {
829                            AccessibilityNodeInfo info = provider.createAccessibilityNodeInfo(
830                                   AccessibilityNodeInfo.UNDEFINED);
831                            if (info != null) {
832                                outInfos.add(info);
833                                addedChildren.put(child, info);
834                            }
835                        }
836                    }
837                }
838            } finally {
839                children.clear();
840            }
841            if (outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
842                for (Map.Entry<View, AccessibilityNodeInfo> entry : addedChildren.entrySet()) {
843                    View addedChild = entry.getKey();
844                    AccessibilityNodeInfo virtualRoot = entry.getValue();
845                    if (virtualRoot == null) {
846                        prefetchDescendantsOfRealNode(addedChild, outInfos);
847                    } else {
848                        AccessibilityNodeProvider provider =
849                            addedChild.getAccessibilityNodeProvider();
850                        prefetchDescendantsOfVirtualNode(virtualRoot, provider, outInfos);
851                    }
852                }
853            }
854        }
855
856        private void prefetchPredecessorsOfVirtualNode(AccessibilityNodeInfo root,
857                View providerHost, AccessibilityNodeProvider provider,
858                List<AccessibilityNodeInfo> outInfos) {
859            long parentNodeId = root.getParentNodeId();
860            int accessibilityViewId = AccessibilityNodeInfo.getAccessibilityViewId(parentNodeId);
861            while (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED) {
862                if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
863                    return;
864                }
865                final int virtualDescendantId =
866                    AccessibilityNodeInfo.getVirtualDescendantId(parentNodeId);
867                if (virtualDescendantId != AccessibilityNodeInfo.UNDEFINED
868                        || accessibilityViewId == providerHost.getAccessibilityViewId()) {
869                    AccessibilityNodeInfo parent = provider.createAccessibilityNodeInfo(
870                            virtualDescendantId);
871                    if (parent != null) {
872                        outInfos.add(parent);
873                    }
874                    parentNodeId = parent.getParentNodeId();
875                    accessibilityViewId = AccessibilityNodeInfo.getAccessibilityViewId(
876                            parentNodeId);
877                } else {
878                    prefetchPredecessorsOfRealNode(providerHost, outInfos);
879                    return;
880                }
881            }
882        }
883
884        private void prefetchSiblingsOfVirtualNode(AccessibilityNodeInfo current, View providerHost,
885                AccessibilityNodeProvider provider, List<AccessibilityNodeInfo> outInfos) {
886            final long parentNodeId = current.getParentNodeId();
887            final int parentAccessibilityViewId =
888                AccessibilityNodeInfo.getAccessibilityViewId(parentNodeId);
889            final int parentVirtualDescendantId =
890                AccessibilityNodeInfo.getVirtualDescendantId(parentNodeId);
891            if (parentVirtualDescendantId != AccessibilityNodeInfo.UNDEFINED
892                    || parentAccessibilityViewId == providerHost.getAccessibilityViewId()) {
893                AccessibilityNodeInfo parent =
894                    provider.createAccessibilityNodeInfo(parentVirtualDescendantId);
895                if (parent != null) {
896                    SparseLongArray childNodeIds = parent.getChildNodeIds();
897                    final int childCount = childNodeIds.size();
898                    for (int i = 0; i < childCount; i++) {
899                        if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
900                            return;
901                        }
902                        final long childNodeId = childNodeIds.get(i);
903                        if (childNodeId != current.getSourceNodeId()) {
904                            final int childVirtualDescendantId =
905                                AccessibilityNodeInfo.getVirtualDescendantId(childNodeId);
906                            AccessibilityNodeInfo child = provider.createAccessibilityNodeInfo(
907                                    childVirtualDescendantId);
908                            if (child != null) {
909                                outInfos.add(child);
910                            }
911                        }
912                    }
913                }
914            } else {
915                prefetchSiblingsOfRealNode(providerHost, outInfos);
916            }
917        }
918
919        private void prefetchDescendantsOfVirtualNode(AccessibilityNodeInfo root,
920                AccessibilityNodeProvider provider, List<AccessibilityNodeInfo> outInfos) {
921            SparseLongArray childNodeIds = root.getChildNodeIds();
922            final int initialOutInfosSize = outInfos.size();
923            final int childCount = childNodeIds.size();
924            for (int i = 0; i < childCount; i++) {
925                if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
926                    return;
927                }
928                final long childNodeId = childNodeIds.get(i);
929                AccessibilityNodeInfo child = provider.createAccessibilityNodeInfo(
930                        AccessibilityNodeInfo.getVirtualDescendantId(childNodeId));
931                if (child != null) {
932                    outInfos.add(child);
933                }
934            }
935            if (outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
936                final int addedChildCount = outInfos.size() - initialOutInfosSize;
937                for (int i = 0; i < addedChildCount; i++) {
938                    AccessibilityNodeInfo child = outInfos.get(initialOutInfosSize + i);
939                    prefetchDescendantsOfVirtualNode(child, provider, outInfos);
940                }
941            }
942        }
943    }
944
945    private class PrivateHandler extends Handler {
946        private final static int MSG_PERFORM_ACCESSIBILITY_ACTION = 1;
947        private final static int MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID = 2;
948        private final static int MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_ID = 3;
949        private final static int MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_TEXT = 4;
950        private final static int MSG_FIND_FOCUS = 5;
951        private final static int MSG_FOCUS_SEARCH = 6;
952
953        public PrivateHandler(Looper looper) {
954            super(looper);
955        }
956
957        @Override
958        public String getMessageName(Message message) {
959            final int type = message.what;
960            switch (type) {
961                case MSG_PERFORM_ACCESSIBILITY_ACTION:
962                    return "MSG_PERFORM_ACCESSIBILITY_ACTION";
963                case MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID:
964                    return "MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID";
965                case MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_ID:
966                    return "MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_ID";
967                case MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_TEXT:
968                    return "MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_TEXT";
969                case MSG_FIND_FOCUS:
970                    return "MSG_FIND_FOCUS";
971                case MSG_FOCUS_SEARCH:
972                    return "MSG_FOCUS_SEARCH";
973                default:
974                    throw new IllegalArgumentException("Unknown message type: " + type);
975            }
976        }
977
978        @Override
979        public void handleMessage(Message message) {
980            final int type = message.what;
981            switch (type) {
982                case MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID: {
983                    findAccessibilityNodeInfoByAccessibilityIdUiThread(message);
984                } break;
985                case MSG_PERFORM_ACCESSIBILITY_ACTION: {
986                    perfromAccessibilityActionUiThread(message);
987                } break;
988                case MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_ID: {
989                    findAccessibilityNodeInfoByViewIdUiThread(message);
990                } break;
991                case MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_TEXT: {
992                    findAccessibilityNodeInfosByTextUiThread(message);
993                } break;
994                case MSG_FIND_FOCUS: {
995                    findFocusUiThread(message);
996                } break;
997                case MSG_FOCUS_SEARCH: {
998                    focusSearchUiThread(message);
999                } break;
1000                default:
1001                    throw new IllegalArgumentException("Unknown message type: " + type);
1002            }
1003        }
1004    }
1005}
1006