AccessibilityInteractionController.java revision ed2fdc6ef560c76ba310a2c4e5bb63f01d08af40
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 android.graphics.Point;
20import android.graphics.Rect;
21import android.graphics.Region;
22import android.os.Bundle;
23import android.os.Handler;
24import android.os.Looper;
25import android.os.Message;
26import android.os.Process;
27import android.os.RemoteException;
28import android.util.LongSparseArray;
29import android.view.View.AttachInfo;
30import android.view.accessibility.AccessibilityInteractionClient;
31import android.view.accessibility.AccessibilityNodeInfo;
32import android.view.accessibility.AccessibilityNodeProvider;
33import android.view.accessibility.IAccessibilityInteractionConnectionCallback;
34
35import com.android.internal.os.SomeArgs;
36import com.android.internal.util.Predicate;
37
38import java.util.ArrayList;
39import java.util.HashMap;
40import java.util.HashSet;
41import java.util.LinkedList;
42import java.util.List;
43import java.util.Map;
44import java.util.Queue;
45
46/**
47 * Class for managing accessibility interactions initiated from the system
48 * and targeting the view hierarchy. A *ClientThread method is to be
49 * called from the interaction connection ViewAncestor gives the system to
50 * talk to it and a corresponding *UiThread method that is executed on the
51 * UI thread.
52 */
53final class AccessibilityInteractionController {
54
55    private static final boolean ENFORCE_NODE_TREE_CONSISTENT = false;
56
57    private final ArrayList<AccessibilityNodeInfo> mTempAccessibilityNodeInfoList =
58        new ArrayList<AccessibilityNodeInfo>();
59
60    private final Handler mHandler;
61
62    private final ViewRootImpl mViewRootImpl;
63
64    private final AccessibilityNodePrefetcher mPrefetcher;
65
66    private final long mMyLooperThreadId;
67
68    private final int mMyProcessId;
69
70    private final ArrayList<View> mTempArrayList = new ArrayList<View>();
71
72    private final Point mTempPoint = new Point();
73    private final Rect mTempRect = new Rect();
74    private final Rect mTempRect1 = new Rect();
75    private final Rect mTempRect2 = new Rect();
76
77    private AddNodeInfosForViewId mAddNodeInfosForViewId;
78
79    public AccessibilityInteractionController(ViewRootImpl viewRootImpl) {
80        Looper looper =  viewRootImpl.mHandler.getLooper();
81        mMyLooperThreadId = looper.getThread().getId();
82        mMyProcessId = Process.myPid();
83        mHandler = new PrivateHandler(looper);
84        mViewRootImpl = viewRootImpl;
85        mPrefetcher = new AccessibilityNodePrefetcher();
86    }
87
88    private boolean isShown(View view) {
89        // The first two checks are made also made by isShown() which
90        // however traverses the tree up to the parent to catch that.
91        // Therefore, we do some fail fast check to minimize the up
92        // tree traversal.
93        return (view.mAttachInfo != null
94                && view.mAttachInfo.mWindowVisibility == View.VISIBLE
95                && view.isShown());
96    }
97
98    public void findAccessibilityNodeInfoByAccessibilityIdClientThread(
99            long accessibilityNodeId, Region interactiveRegion, int interactionId,
100            IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid,
101            long interrogatingTid, MagnificationSpec spec) {
102        Message message = mHandler.obtainMessage();
103        message.what = PrivateHandler.MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_ACCESSIBILITY_ID;
104        message.arg1 = flags;
105
106        SomeArgs args = SomeArgs.obtain();
107        args.argi1 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId);
108        args.argi2 = AccessibilityNodeInfo.getVirtualDescendantId(accessibilityNodeId);
109        args.argi3 = interactionId;
110        args.arg1 = callback;
111        args.arg2 = spec;
112        args.arg3 = interactiveRegion;
113        message.obj = args;
114
115        // If the interrogation is performed by the same thread as the main UI
116        // thread in this process, set the message as a static reference so
117        // after this call completes the same thread but in the interrogating
118        // client can handle the message to generate the result.
119        if (interrogatingPid == mMyProcessId && interrogatingTid == mMyLooperThreadId) {
120            AccessibilityInteractionClient.getInstanceForThread(
121                    interrogatingTid).setSameThreadMessage(message);
122        } else {
123            mHandler.sendMessage(message);
124        }
125    }
126
127    private void findAccessibilityNodeInfoByAccessibilityIdUiThread(Message message) {
128        final int flags = message.arg1;
129
130        SomeArgs args = (SomeArgs) message.obj;
131        final int accessibilityViewId = args.argi1;
132        final int virtualDescendantId = args.argi2;
133        final int interactionId = args.argi3;
134        final IAccessibilityInteractionConnectionCallback callback =
135            (IAccessibilityInteractionConnectionCallback) args.arg1;
136        final MagnificationSpec spec = (MagnificationSpec) args.arg2;
137        final Region interactiveRegion = (Region) args.arg3;
138
139        args.recycle();
140
141        List<AccessibilityNodeInfo> infos = mTempAccessibilityNodeInfoList;
142        infos.clear();
143        try {
144            if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
145                return;
146            }
147            mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags;
148            View root = null;
149            if (accessibilityViewId == AccessibilityNodeInfo.UNDEFINED_ITEM_ID) {
150                root = mViewRootImpl.mView;
151            } else {
152                root = findViewByAccessibilityId(accessibilityViewId);
153            }
154            if (root != null && isShown(root)) {
155                mPrefetcher.prefetchAccessibilityNodeInfos(root, virtualDescendantId, flags, infos);
156            }
157        } finally {
158            try {
159                mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
160                applyAppScaleAndMagnificationSpecIfNeeded(infos, spec);
161                if (spec != null) {
162                    spec.recycle();
163                }
164                adjustIsVisibleToUserIfNeeded(infos, interactiveRegion);
165                callback.setFindAccessibilityNodeInfosResult(infos, interactionId);
166                infos.clear();
167            } catch (RemoteException re) {
168                /* ignore - the other side will time out */
169            }
170        }
171    }
172
173    public void findAccessibilityNodeInfosByViewIdClientThread(long accessibilityNodeId,
174            String viewId, Region interactiveRegion, int interactionId,
175            IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid,
176            long interrogatingTid, MagnificationSpec spec) {
177        Message message = mHandler.obtainMessage();
178        message.what = PrivateHandler.MSG_FIND_ACCESSIBILITY_NODE_INFOS_BY_VIEW_ID;
179        message.arg1 = flags;
180        message.arg2 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId);
181
182        SomeArgs args = SomeArgs.obtain();
183        args.argi1 = interactionId;
184        args.arg1 = callback;
185        args.arg2 = spec;
186        args.arg3 = viewId;
187        args.arg4 = interactiveRegion;
188
189        message.obj = args;
190
191        // If the interrogation is performed by the same thread as the main UI
192        // thread in this process, set the message as a static reference so
193        // after this call completes the same thread but in the interrogating
194        // client can handle the message to generate the result.
195        if (interrogatingPid == mMyProcessId && interrogatingTid == mMyLooperThreadId) {
196            AccessibilityInteractionClient.getInstanceForThread(
197                    interrogatingTid).setSameThreadMessage(message);
198        } else {
199            mHandler.sendMessage(message);
200        }
201    }
202
203    private void findAccessibilityNodeInfosByViewIdUiThread(Message message) {
204        final int flags = message.arg1;
205        final int accessibilityViewId = message.arg2;
206
207        SomeArgs args = (SomeArgs) message.obj;
208        final int interactionId = args.argi1;
209        final IAccessibilityInteractionConnectionCallback callback =
210            (IAccessibilityInteractionConnectionCallback) args.arg1;
211        final MagnificationSpec spec = (MagnificationSpec) args.arg2;
212        final String viewId = (String) args.arg3;
213        final Region interactiveRegion = (Region) args.arg4;
214
215        args.recycle();
216
217        final List<AccessibilityNodeInfo> infos = mTempAccessibilityNodeInfoList;
218        infos.clear();
219        try {
220            if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
221                return;
222            }
223            mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags;
224            View root = null;
225            if (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) {
226                root = findViewByAccessibilityId(accessibilityViewId);
227            } else {
228                root = mViewRootImpl.mView;
229            }
230            if (root != null) {
231                final int resolvedViewId = root.getContext().getResources()
232                        .getIdentifier(viewId, null, null);
233                if (resolvedViewId <= 0) {
234                    return;
235                }
236                if (mAddNodeInfosForViewId == null) {
237                    mAddNodeInfosForViewId = new AddNodeInfosForViewId();
238                }
239                mAddNodeInfosForViewId.init(resolvedViewId, infos);
240                root.findViewByPredicate(mAddNodeInfosForViewId);
241                mAddNodeInfosForViewId.reset();
242            }
243        } finally {
244            try {
245                mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
246                applyAppScaleAndMagnificationSpecIfNeeded(infos, spec);
247                if (spec != null) {
248                    spec.recycle();
249                }
250                adjustIsVisibleToUserIfNeeded(infos, interactiveRegion);
251                callback.setFindAccessibilityNodeInfosResult(infos, interactionId);
252            } catch (RemoteException re) {
253                /* ignore - the other side will time out */
254            }
255        }
256    }
257
258    public void findAccessibilityNodeInfosByTextClientThread(long accessibilityNodeId,
259            String text, Region interactiveRegion, int interactionId,
260            IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid,
261            long interrogatingTid, MagnificationSpec spec) {
262        Message message = mHandler.obtainMessage();
263        message.what = PrivateHandler.MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_TEXT;
264        message.arg1 = flags;
265
266        SomeArgs args = SomeArgs.obtain();
267        args.arg1 = text;
268        args.arg2 = callback;
269        args.arg3 = spec;
270        args.argi1 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId);
271        args.argi2 = AccessibilityNodeInfo.getVirtualDescendantId(accessibilityNodeId);
272        args.argi3 = interactionId;
273        args.arg4 = interactiveRegion;
274        message.obj = args;
275
276        // If the interrogation is performed by the same thread as the main UI
277        // thread in this process, set the message as a static reference so
278        // after this call completes the same thread but in the interrogating
279        // client can handle the message to generate the result.
280        if (interrogatingPid == mMyProcessId && interrogatingTid == mMyLooperThreadId) {
281            AccessibilityInteractionClient.getInstanceForThread(
282                    interrogatingTid).setSameThreadMessage(message);
283        } else {
284            mHandler.sendMessage(message);
285        }
286    }
287
288    private void findAccessibilityNodeInfosByTextUiThread(Message message) {
289        final int flags = message.arg1;
290
291        SomeArgs args = (SomeArgs) message.obj;
292        final String text = (String) args.arg1;
293        final IAccessibilityInteractionConnectionCallback callback =
294            (IAccessibilityInteractionConnectionCallback) args.arg2;
295        final MagnificationSpec spec = (MagnificationSpec) args.arg3;
296        final int accessibilityViewId = args.argi1;
297        final int virtualDescendantId = args.argi2;
298        final int interactionId = args.argi3;
299        final Region interactiveRegion = (Region) args.arg4;
300        args.recycle();
301
302        List<AccessibilityNodeInfo> infos = null;
303        try {
304            if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
305                return;
306            }
307            mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags;
308            View root = null;
309            if (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) {
310                root = findViewByAccessibilityId(accessibilityViewId);
311            } else {
312                root = mViewRootImpl.mView;
313            }
314            if (root != null && isShown(root)) {
315                AccessibilityNodeProvider provider = root.getAccessibilityNodeProvider();
316                if (provider != null) {
317                    if (virtualDescendantId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) {
318                        infos = provider.findAccessibilityNodeInfosByText(text,
319                                virtualDescendantId);
320                    } else {
321                        infos = provider.findAccessibilityNodeInfosByText(text,
322                                AccessibilityNodeProvider.HOST_VIEW_ID);
323                    }
324                } else if (virtualDescendantId == AccessibilityNodeInfo.UNDEFINED_ITEM_ID) {
325                    ArrayList<View> foundViews = mTempArrayList;
326                    foundViews.clear();
327                    root.findViewsWithText(foundViews, text, View.FIND_VIEWS_WITH_TEXT
328                            | View.FIND_VIEWS_WITH_CONTENT_DESCRIPTION
329                            | View.FIND_VIEWS_WITH_ACCESSIBILITY_NODE_PROVIDERS);
330                    if (!foundViews.isEmpty()) {
331                        infos = mTempAccessibilityNodeInfoList;
332                        infos.clear();
333                        final int viewCount = foundViews.size();
334                        for (int i = 0; i < viewCount; i++) {
335                            View foundView = foundViews.get(i);
336                            if (isShown(foundView)) {
337                                provider = foundView.getAccessibilityNodeProvider();
338                                if (provider != null) {
339                                    List<AccessibilityNodeInfo> infosFromProvider =
340                                        provider.findAccessibilityNodeInfosByText(text,
341                                                AccessibilityNodeProvider.HOST_VIEW_ID);
342                                    if (infosFromProvider != null) {
343                                        infos.addAll(infosFromProvider);
344                                    }
345                                } else  {
346                                    infos.add(foundView.createAccessibilityNodeInfo());
347                                }
348                            }
349                        }
350                    }
351                }
352            }
353        } finally {
354            try {
355                mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
356                applyAppScaleAndMagnificationSpecIfNeeded(infos, spec);
357                if (spec != null) {
358                    spec.recycle();
359                }
360                adjustIsVisibleToUserIfNeeded(infos, interactiveRegion);
361                callback.setFindAccessibilityNodeInfosResult(infos, interactionId);
362            } catch (RemoteException re) {
363                /* ignore - the other side will time out */
364            }
365        }
366    }
367
368    public void findFocusClientThread(long accessibilityNodeId, int focusType,
369            Region interactiveRegion, int interactionId,
370            IAccessibilityInteractionConnectionCallback callback, int flags, int interogatingPid,
371            long interrogatingTid, MagnificationSpec spec) {
372        Message message = mHandler.obtainMessage();
373        message.what = PrivateHandler.MSG_FIND_FOCUS;
374        message.arg1 = flags;
375        message.arg2 = focusType;
376
377        SomeArgs args = SomeArgs.obtain();
378        args.argi1 = interactionId;
379        args.argi2 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId);
380        args.argi3 = AccessibilityNodeInfo.getVirtualDescendantId(accessibilityNodeId);
381        args.arg1 = callback;
382        args.arg2 = spec;
383        args.arg3 = interactiveRegion;
384
385        message.obj = args;
386
387        // If the interrogation is performed by the same thread as the main UI
388        // thread in this process, set the message as a static reference so
389        // after this call completes the same thread but in the interrogating
390        // client can handle the message to generate the result.
391        if (interogatingPid == mMyProcessId && interrogatingTid == mMyLooperThreadId) {
392            AccessibilityInteractionClient.getInstanceForThread(
393                    interrogatingTid).setSameThreadMessage(message);
394        } else {
395            mHandler.sendMessage(message);
396        }
397    }
398
399    private void findFocusUiThread(Message message) {
400        final int flags = message.arg1;
401        final int focusType = message.arg2;
402
403        SomeArgs args = (SomeArgs) message.obj;
404        final int interactionId = args.argi1;
405        final int accessibilityViewId = args.argi2;
406        final int virtualDescendantId = args.argi3;
407        final IAccessibilityInteractionConnectionCallback callback =
408            (IAccessibilityInteractionConnectionCallback) args.arg1;
409        final MagnificationSpec spec = (MagnificationSpec) args.arg2;
410        final Region interactiveRegion = (Region) args.arg3;
411        args.recycle();
412
413        AccessibilityNodeInfo focused = null;
414        try {
415            if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
416                return;
417            }
418            mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags;
419            View root = null;
420            if (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) {
421                root = findViewByAccessibilityId(accessibilityViewId);
422            } else {
423                root = mViewRootImpl.mView;
424            }
425            if (root != null && isShown(root)) {
426                switch (focusType) {
427                    case AccessibilityNodeInfo.FOCUS_ACCESSIBILITY: {
428                        View host = mViewRootImpl.mAccessibilityFocusedHost;
429                        // If there is no accessibility focus host or it is not a descendant
430                        // of the root from which to start the search, then the search failed.
431                        if (host == null || !ViewRootImpl.isViewDescendantOf(host, root)) {
432                            break;
433                        }
434                        // The focused view not shown, we failed.
435                        if (!isShown(host)) {
436                            break;
437                        }
438                        // If the host has a provider ask this provider to search for the
439                        // focus instead fetching all provider nodes to do the search here.
440                        AccessibilityNodeProvider provider = host.getAccessibilityNodeProvider();
441                        if (provider != null) {
442                            if (mViewRootImpl.mAccessibilityFocusedVirtualView != null) {
443                                focused = AccessibilityNodeInfo.obtain(
444                                        mViewRootImpl.mAccessibilityFocusedVirtualView);
445                            }
446                        } else if (virtualDescendantId == AccessibilityNodeInfo.UNDEFINED_ITEM_ID) {
447                            focused = host.createAccessibilityNodeInfo();
448                        }
449                    } break;
450                    case AccessibilityNodeInfo.FOCUS_INPUT: {
451                        View target = root.findFocus();
452                        if (target == null || !isShown(target)) {
453                            break;
454                        }
455                        AccessibilityNodeProvider provider = target.getAccessibilityNodeProvider();
456                        if (provider != null) {
457                            focused = provider.findFocus(focusType);
458                        }
459                        if (focused == null) {
460                            focused = target.createAccessibilityNodeInfo();
461                        }
462                    } break;
463                    default:
464                        throw new IllegalArgumentException("Unknown focus type: " + focusType);
465                }
466            }
467        } finally {
468            try {
469                mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
470                applyAppScaleAndMagnificationSpecIfNeeded(focused, spec);
471                if (spec != null) {
472                    spec.recycle();
473                }
474                adjustIsVisibleToUserIfNeeded(focused, interactiveRegion);
475                callback.setFindAccessibilityNodeInfoResult(focused, interactionId);
476            } catch (RemoteException re) {
477                /* ignore - the other side will time out */
478            }
479        }
480    }
481
482    public void focusSearchClientThread(long accessibilityNodeId, int direction,
483            Region interactiveRegion, int interactionId,
484            IAccessibilityInteractionConnectionCallback callback, int flags, int interogatingPid,
485            long interrogatingTid, MagnificationSpec spec) {
486        Message message = mHandler.obtainMessage();
487        message.what = PrivateHandler.MSG_FOCUS_SEARCH;
488        message.arg1 = flags;
489        message.arg2 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId);
490
491        SomeArgs args = SomeArgs.obtain();
492        args.argi2 = direction;
493        args.argi3 = interactionId;
494        args.arg1 = callback;
495        args.arg2 = spec;
496        args.arg3 = interactiveRegion;
497
498        message.obj = args;
499
500        // If the interrogation is performed by the same thread as the main UI
501        // thread in this process, set the message as a static reference so
502        // after this call completes the same thread but in the interrogating
503        // client can handle the message to generate the result.
504        if (interogatingPid == mMyProcessId && interrogatingTid == mMyLooperThreadId) {
505            AccessibilityInteractionClient.getInstanceForThread(
506                    interrogatingTid).setSameThreadMessage(message);
507        } else {
508            mHandler.sendMessage(message);
509        }
510    }
511
512    private void focusSearchUiThread(Message message) {
513        final int flags = message.arg1;
514        final int accessibilityViewId = message.arg2;
515
516        SomeArgs args = (SomeArgs) message.obj;
517        final int direction = args.argi2;
518        final int interactionId = args.argi3;
519        final IAccessibilityInteractionConnectionCallback callback =
520            (IAccessibilityInteractionConnectionCallback) args.arg1;
521        final MagnificationSpec spec = (MagnificationSpec) args.arg2;
522        final Region interactiveRegion = (Region) args.arg3;
523
524        args.recycle();
525
526        AccessibilityNodeInfo next = null;
527        try {
528            if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
529                return;
530            }
531            mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags;
532            View root = null;
533            if (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) {
534                root = findViewByAccessibilityId(accessibilityViewId);
535            } else {
536                root = mViewRootImpl.mView;
537            }
538            if (root != null && isShown(root)) {
539                View nextView = root.focusSearch(direction);
540                if (nextView != null) {
541                    next = nextView.createAccessibilityNodeInfo();
542                }
543            }
544        } finally {
545            try {
546                mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
547                applyAppScaleAndMagnificationSpecIfNeeded(next, spec);
548                if (spec != null) {
549                    spec.recycle();
550                }
551                adjustIsVisibleToUserIfNeeded(next, interactiveRegion);
552                callback.setFindAccessibilityNodeInfoResult(next, interactionId);
553            } catch (RemoteException re) {
554                /* ignore - the other side will time out */
555            }
556        }
557    }
558
559    public void performAccessibilityActionClientThread(long accessibilityNodeId, int action,
560            Bundle arguments, int interactionId,
561            IAccessibilityInteractionConnectionCallback callback, int flags, int interogatingPid,
562            long interrogatingTid) {
563        Message message = mHandler.obtainMessage();
564        message.what = PrivateHandler.MSG_PERFORM_ACCESSIBILITY_ACTION;
565        message.arg1 = flags;
566        message.arg2 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId);
567
568        SomeArgs args = SomeArgs.obtain();
569        args.argi1 = AccessibilityNodeInfo.getVirtualDescendantId(accessibilityNodeId);
570        args.argi2 = action;
571        args.argi3 = interactionId;
572        args.arg1 = callback;
573        args.arg2 = arguments;
574
575        message.obj = args;
576
577        // If the interrogation is performed by the same thread as the main UI
578        // thread in this process, set the message as a static reference so
579        // after this call completes the same thread but in the interrogating
580        // client can handle the message to generate the result.
581        if (interogatingPid == mMyProcessId && interrogatingTid == mMyLooperThreadId) {
582            AccessibilityInteractionClient.getInstanceForThread(
583                    interrogatingTid).setSameThreadMessage(message);
584        } else {
585            mHandler.sendMessage(message);
586        }
587    }
588
589    private void perfromAccessibilityActionUiThread(Message message) {
590        final int flags = message.arg1;
591        final int accessibilityViewId = message.arg2;
592
593        SomeArgs args = (SomeArgs) message.obj;
594        final int virtualDescendantId = args.argi1;
595        final int action = args.argi2;
596        final int interactionId = args.argi3;
597        final IAccessibilityInteractionConnectionCallback callback =
598            (IAccessibilityInteractionConnectionCallback) args.arg1;
599        Bundle arguments = (Bundle) args.arg2;
600
601        args.recycle();
602
603        boolean succeeded = false;
604        try {
605            if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
606                return;
607            }
608            mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags;
609            View target = null;
610            if (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) {
611                target = findViewByAccessibilityId(accessibilityViewId);
612            } else {
613                target = mViewRootImpl.mView;
614            }
615            if (target != null && isShown(target)) {
616                AccessibilityNodeProvider provider = target.getAccessibilityNodeProvider();
617                if (provider != null) {
618                    if (virtualDescendantId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) {
619                        succeeded = provider.performAction(virtualDescendantId, action,
620                                arguments);
621                    } else {
622                        succeeded = provider.performAction(AccessibilityNodeProvider.HOST_VIEW_ID,
623                                action, arguments);
624                    }
625                } else if (virtualDescendantId == AccessibilityNodeInfo.UNDEFINED_ITEM_ID) {
626                    succeeded = target.performAccessibilityAction(action, arguments);
627                }
628            }
629        } finally {
630            try {
631                mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
632                callback.setPerformAccessibilityActionResult(succeeded, interactionId);
633            } catch (RemoteException re) {
634                /* ignore - the other side will time out */
635            }
636        }
637    }
638
639    public void computeClickPointInScreenClientThread(long accessibilityNodeId,
640            Region interactiveRegion, int interactionId,
641            IAccessibilityInteractionConnectionCallback callback, int interrogatingPid,
642            long interrogatingTid, MagnificationSpec spec) {
643        Message message = mHandler.obtainMessage();
644        message.what = PrivateHandler.MSG_COMPUTE_CLICK_POINT_IN_SCREEN;
645
646        SomeArgs args = SomeArgs.obtain();
647        args.argi1 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId);
648        args.argi2 = AccessibilityNodeInfo.getVirtualDescendantId(accessibilityNodeId);
649        args.argi3 = interactionId;
650        args.arg1 = callback;
651        args.arg2 = spec;
652        args.arg3 = interactiveRegion;
653
654        message.obj = args;
655
656        // If the interrogation is performed by the same thread as the main UI
657        // thread in this process, set the message as a static reference so
658        // after this call completes the same thread but in the interrogating
659        // client can handle the message to generate the result.
660        if (interrogatingPid == mMyProcessId && interrogatingTid == mMyLooperThreadId) {
661            AccessibilityInteractionClient.getInstanceForThread(
662                    interrogatingTid).setSameThreadMessage(message);
663        } else {
664            mHandler.sendMessage(message);
665        }
666    }
667
668    private void computeClickPointInScreenUiThread(Message message) {
669        SomeArgs args = (SomeArgs) message.obj;
670        final int accessibilityViewId = args.argi1;
671        final int virtualDescendantId = args.argi2;
672        final int interactionId = args.argi3;
673        final IAccessibilityInteractionConnectionCallback callback =
674                (IAccessibilityInteractionConnectionCallback) args.arg1;
675        final MagnificationSpec spec = (MagnificationSpec) args.arg2;
676        final Region interactiveRegion = (Region) args.arg3;
677        args.recycle();
678
679        boolean succeeded = false;
680        Point point = mTempPoint;
681        try {
682            if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
683                return;
684            }
685            View target = null;
686            if (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) {
687                target = findViewByAccessibilityId(accessibilityViewId);
688            } else {
689                target = mViewRootImpl.mView;
690            }
691            if (target != null && isShown(target)) {
692                AccessibilityNodeProvider provider = target.getAccessibilityNodeProvider();
693                if (provider != null) {
694                    // For virtual views just use the center of the bounds in screen.
695                    AccessibilityNodeInfo node = null;
696                    if (virtualDescendantId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) {
697                        node = provider.createAccessibilityNodeInfo(virtualDescendantId);
698                    } else {
699                        node = provider.createAccessibilityNodeInfo(
700                                AccessibilityNodeProvider.HOST_VIEW_ID);
701                    }
702                    if (node != null) {
703                        succeeded = true;
704                        Rect boundsInScreen = mTempRect;
705                        node.getBoundsInScreen(boundsInScreen);
706                        point.set(boundsInScreen.centerX(), boundsInScreen.centerY());
707                    }
708                } else if (virtualDescendantId == AccessibilityNodeInfo.UNDEFINED_ITEM_ID) {
709                    // For a real view, ask the view to compute the click point.
710                    succeeded = target.computeClickPointInScreenForAccessibility(
711                            interactiveRegion, point);
712                }
713            }
714        } finally {
715            try {
716                Point result = null;
717                if (succeeded) {
718                    applyAppScaleAndMagnificationSpecIfNeeded(point, spec);
719                    result = point;
720                }
721                callback.setComputeClickPointInScreenActionResult(result, interactionId);
722            } catch (RemoteException re) {
723                /* ignore - the other side will time out */
724            }
725        }
726    }
727
728    private View findViewByAccessibilityId(int accessibilityId) {
729        View root = mViewRootImpl.mView;
730        if (root == null) {
731            return null;
732        }
733        View foundView = root.findViewByAccessibilityId(accessibilityId);
734        if (foundView != null && !isShown(foundView)) {
735            return null;
736        }
737        return foundView;
738    }
739
740    private void applyAppScaleAndMagnificationSpecIfNeeded(List<AccessibilityNodeInfo> infos,
741            MagnificationSpec spec) {
742        if (infos == null) {
743            return;
744        }
745        final float applicationScale = mViewRootImpl.mAttachInfo.mApplicationScale;
746        if (shouldApplyAppScaleAndMagnificationSpec(applicationScale, spec)) {
747            final int infoCount = infos.size();
748            for (int i = 0; i < infoCount; i++) {
749                AccessibilityNodeInfo info = infos.get(i);
750                applyAppScaleAndMagnificationSpecIfNeeded(info, spec);
751            }
752        }
753    }
754
755    private void adjustIsVisibleToUserIfNeeded(List<AccessibilityNodeInfo> infos,
756            Region interactiveRegion) {
757        if (interactiveRegion == null || infos == null) {
758            return;
759        }
760        final int infoCount = infos.size();
761        for (int i = 0; i < infoCount; i++) {
762            AccessibilityNodeInfo info = infos.get(i);
763            adjustIsVisibleToUserIfNeeded(info, interactiveRegion);
764        }
765    }
766
767    private void adjustIsVisibleToUserIfNeeded(AccessibilityNodeInfo info,
768            Region interactiveRegion) {
769        if (interactiveRegion == null || info == null) {
770            return;
771        }
772        Rect boundsInScreen = mTempRect;
773        info.getBoundsInScreen(boundsInScreen);
774        if (interactiveRegion.quickReject(boundsInScreen)) {
775            info.setVisibleToUser(false);
776        }
777    }
778
779    private void applyAppScaleAndMagnificationSpecIfNeeded(Point point,
780            MagnificationSpec spec) {
781        final float applicationScale = mViewRootImpl.mAttachInfo.mApplicationScale;
782        if (!shouldApplyAppScaleAndMagnificationSpec(applicationScale, spec)) {
783            return;
784        }
785
786        if (applicationScale != 1.0f) {
787            point.x *= applicationScale;
788            point.y *= applicationScale;
789        }
790
791        if (spec != null) {
792            point.x *= spec.scale;
793            point.y *= spec.scale;
794            point.x += (int) spec.offsetX;
795            point.y += (int) spec.offsetY;
796        }
797    }
798
799    private void applyAppScaleAndMagnificationSpecIfNeeded(AccessibilityNodeInfo info,
800            MagnificationSpec spec) {
801        if (info == null) {
802            return;
803        }
804
805        final float applicationScale = mViewRootImpl.mAttachInfo.mApplicationScale;
806        if (!shouldApplyAppScaleAndMagnificationSpec(applicationScale, spec)) {
807            return;
808        }
809
810        Rect boundsInParent = mTempRect;
811        Rect boundsInScreen = mTempRect1;
812
813        info.getBoundsInParent(boundsInParent);
814        info.getBoundsInScreen(boundsInScreen);
815        if (applicationScale != 1.0f) {
816            boundsInParent.scale(applicationScale);
817            boundsInScreen.scale(applicationScale);
818        }
819        if (spec != null) {
820            boundsInParent.scale(spec.scale);
821            // boundsInParent must not be offset.
822            boundsInScreen.scale(spec.scale);
823            boundsInScreen.offset((int) spec.offsetX, (int) spec.offsetY);
824        }
825        info.setBoundsInParent(boundsInParent);
826        info.setBoundsInScreen(boundsInScreen);
827
828        if (spec != null) {
829            AttachInfo attachInfo = mViewRootImpl.mAttachInfo;
830            if (attachInfo.mDisplay == null) {
831                return;
832            }
833
834            final float scale = attachInfo.mApplicationScale * spec.scale;
835
836            Rect visibleWinFrame = mTempRect1;
837            visibleWinFrame.left = (int) (attachInfo.mWindowLeft * scale + spec.offsetX);
838            visibleWinFrame.top = (int) (attachInfo.mWindowTop * scale + spec.offsetY);
839            visibleWinFrame.right = (int) (visibleWinFrame.left + mViewRootImpl.mWidth * scale);
840            visibleWinFrame.bottom = (int) (visibleWinFrame.top + mViewRootImpl.mHeight * scale);
841
842            attachInfo.mDisplay.getRealSize(mTempPoint);
843            final int displayWidth = mTempPoint.x;
844            final int displayHeight = mTempPoint.y;
845
846            Rect visibleDisplayFrame = mTempRect2;
847            visibleDisplayFrame.set(0, 0, displayWidth, displayHeight);
848
849            visibleWinFrame.intersect(visibleDisplayFrame);
850
851            if (!visibleWinFrame.intersects(boundsInScreen.left, boundsInScreen.top,
852                    boundsInScreen.right, boundsInScreen.bottom)) {
853                info.setVisibleToUser(false);
854            }
855        }
856    }
857
858    private boolean shouldApplyAppScaleAndMagnificationSpec(float appScale,
859            MagnificationSpec spec) {
860        return (appScale != 1.0f || (spec != null && !spec.isNop()));
861    }
862
863    /**
864     * This class encapsulates a prefetching strategy for the accessibility APIs for
865     * querying window content. It is responsible to prefetch a batch of
866     * AccessibilityNodeInfos in addition to the one for a requested node.
867     */
868    private class AccessibilityNodePrefetcher {
869
870        private static final int MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE = 50;
871
872        private final ArrayList<View> mTempViewList = new ArrayList<View>();
873
874        public void prefetchAccessibilityNodeInfos(View view, int virtualViewId, int fetchFlags,
875                List<AccessibilityNodeInfo> outInfos) {
876            AccessibilityNodeProvider provider = view.getAccessibilityNodeProvider();
877            if (provider == null) {
878                AccessibilityNodeInfo root = view.createAccessibilityNodeInfo();
879                if (root != null) {
880                    outInfos.add(root);
881                    if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS) != 0) {
882                        prefetchPredecessorsOfRealNode(view, outInfos);
883                    }
884                    if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS) != 0) {
885                        prefetchSiblingsOfRealNode(view, outInfos);
886                    }
887                    if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS) != 0) {
888                        prefetchDescendantsOfRealNode(view, outInfos);
889                    }
890                }
891            } else {
892                final AccessibilityNodeInfo root;
893                if (virtualViewId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) {
894                    root = provider.createAccessibilityNodeInfo(virtualViewId);
895                } else {
896                    root = provider.createAccessibilityNodeInfo(
897                            AccessibilityNodeProvider.HOST_VIEW_ID);
898                }
899                if (root != null) {
900                    outInfos.add(root);
901                    if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS) != 0) {
902                        prefetchPredecessorsOfVirtualNode(root, view, provider, outInfos);
903                    }
904                    if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS) != 0) {
905                        prefetchSiblingsOfVirtualNode(root, view, provider, outInfos);
906                    }
907                    if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS) != 0) {
908                        prefetchDescendantsOfVirtualNode(root, provider, outInfos);
909                    }
910                }
911            }
912            if (ENFORCE_NODE_TREE_CONSISTENT) {
913                enforceNodeTreeConsistent(outInfos);
914            }
915        }
916
917        private void enforceNodeTreeConsistent(List<AccessibilityNodeInfo> nodes) {
918            LongSparseArray<AccessibilityNodeInfo> nodeMap =
919                    new LongSparseArray<AccessibilityNodeInfo>();
920            final int nodeCount = nodes.size();
921            for (int i = 0; i < nodeCount; i++) {
922                AccessibilityNodeInfo node = nodes.get(i);
923                nodeMap.put(node.getSourceNodeId(), node);
924            }
925
926            // If the nodes are a tree it does not matter from
927            // which node we start to search for the root.
928            AccessibilityNodeInfo root = nodeMap.valueAt(0);
929            AccessibilityNodeInfo parent = root;
930            while (parent != null) {
931                root = parent;
932                parent = nodeMap.get(parent.getParentNodeId());
933            }
934
935            // Traverse the tree and do some checks.
936            AccessibilityNodeInfo accessFocus = null;
937            AccessibilityNodeInfo inputFocus = null;
938            HashSet<AccessibilityNodeInfo> seen = new HashSet<AccessibilityNodeInfo>();
939            Queue<AccessibilityNodeInfo> fringe = new LinkedList<AccessibilityNodeInfo>();
940            fringe.add(root);
941
942            while (!fringe.isEmpty()) {
943                AccessibilityNodeInfo current = fringe.poll();
944
945                // Check for duplicates
946                if (!seen.add(current)) {
947                    throw new IllegalStateException("Duplicate node: "
948                            + current + " in window:"
949                            + mViewRootImpl.mAttachInfo.mAccessibilityWindowId);
950                }
951
952                // Check for one accessibility focus.
953                if (current.isAccessibilityFocused()) {
954                    if (accessFocus != null) {
955                        throw new IllegalStateException("Duplicate accessibility focus:"
956                                + current
957                                + " in window:" + mViewRootImpl.mAttachInfo.mAccessibilityWindowId);
958                    } else {
959                        accessFocus = current;
960                    }
961                }
962
963                // Check for one input focus.
964                if (current.isFocused()) {
965                    if (inputFocus != null) {
966                        throw new IllegalStateException("Duplicate input focus: "
967                            + current + " in window:"
968                            + mViewRootImpl.mAttachInfo.mAccessibilityWindowId);
969                    } else {
970                        inputFocus = current;
971                    }
972                }
973
974                final int childCount = current.getChildCount();
975                for (int j = 0; j < childCount; j++) {
976                    final long childId = current.getChildId(j);
977                    final AccessibilityNodeInfo child = nodeMap.get(childId);
978                    if (child != null) {
979                        fringe.add(child);
980                    }
981                }
982            }
983
984            // Check for disconnected nodes.
985            for (int j = nodeMap.size() - 1; j >= 0; j--) {
986                AccessibilityNodeInfo info = nodeMap.valueAt(j);
987                if (!seen.contains(info)) {
988                    throw new IllegalStateException("Disconnected node: " + info);
989                }
990            }
991        }
992
993        private void prefetchPredecessorsOfRealNode(View view,
994                List<AccessibilityNodeInfo> outInfos) {
995            ViewParent parent = view.getParentForAccessibility();
996            while (parent instanceof View
997                    && outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
998                View parentView = (View) parent;
999                AccessibilityNodeInfo info = parentView.createAccessibilityNodeInfo();
1000                if (info != null) {
1001                    outInfos.add(info);
1002                }
1003                parent = parent.getParentForAccessibility();
1004            }
1005        }
1006
1007        private void prefetchSiblingsOfRealNode(View current,
1008                List<AccessibilityNodeInfo> outInfos) {
1009            ViewParent parent = current.getParentForAccessibility();
1010            if (parent instanceof ViewGroup) {
1011                ViewGroup parentGroup = (ViewGroup) parent;
1012                ArrayList<View> children = mTempViewList;
1013                children.clear();
1014                try {
1015                    parentGroup.addChildrenForAccessibility(children);
1016                    final int childCount = children.size();
1017                    for (int i = 0; i < childCount; i++) {
1018                        if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
1019                            return;
1020                        }
1021                        View child = children.get(i);
1022                        if (child.getAccessibilityViewId() != current.getAccessibilityViewId()
1023                                &&  isShown(child)) {
1024                            AccessibilityNodeInfo info = null;
1025                            AccessibilityNodeProvider provider =
1026                                child.getAccessibilityNodeProvider();
1027                            if (provider == null) {
1028                                info = child.createAccessibilityNodeInfo();
1029                            } else {
1030                                info = provider.createAccessibilityNodeInfo(
1031                                        AccessibilityNodeProvider.HOST_VIEW_ID);
1032                            }
1033                            if (info != null) {
1034                                outInfos.add(info);
1035                            }
1036                        }
1037                    }
1038                } finally {
1039                    children.clear();
1040                }
1041            }
1042        }
1043
1044        private void prefetchDescendantsOfRealNode(View root,
1045                List<AccessibilityNodeInfo> outInfos) {
1046            if (!(root instanceof ViewGroup)) {
1047                return;
1048            }
1049            HashMap<View, AccessibilityNodeInfo> addedChildren =
1050                new HashMap<View, AccessibilityNodeInfo>();
1051            ArrayList<View> children = mTempViewList;
1052            children.clear();
1053            try {
1054                root.addChildrenForAccessibility(children);
1055                final int childCount = children.size();
1056                for (int i = 0; i < childCount; i++) {
1057                    if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
1058                        return;
1059                    }
1060                    View child = children.get(i);
1061                    if (isShown(child)) {
1062                        AccessibilityNodeProvider provider = child.getAccessibilityNodeProvider();
1063                        if (provider == null) {
1064                            AccessibilityNodeInfo info = child.createAccessibilityNodeInfo();
1065                            if (info != null) {
1066                                outInfos.add(info);
1067                                addedChildren.put(child, null);
1068                            }
1069                        } else {
1070                            AccessibilityNodeInfo info = provider.createAccessibilityNodeInfo(
1071                                   AccessibilityNodeProvider.HOST_VIEW_ID);
1072                            if (info != null) {
1073                                outInfos.add(info);
1074                                addedChildren.put(child, info);
1075                            }
1076                        }
1077                    }
1078                }
1079            } finally {
1080                children.clear();
1081            }
1082            if (outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
1083                for (Map.Entry<View, AccessibilityNodeInfo> entry : addedChildren.entrySet()) {
1084                    View addedChild = entry.getKey();
1085                    AccessibilityNodeInfo virtualRoot = entry.getValue();
1086                    if (virtualRoot == null) {
1087                        prefetchDescendantsOfRealNode(addedChild, outInfos);
1088                    } else {
1089                        AccessibilityNodeProvider provider =
1090                            addedChild.getAccessibilityNodeProvider();
1091                        prefetchDescendantsOfVirtualNode(virtualRoot, provider, outInfos);
1092                    }
1093                }
1094            }
1095        }
1096
1097        private void prefetchPredecessorsOfVirtualNode(AccessibilityNodeInfo root,
1098                View providerHost, AccessibilityNodeProvider provider,
1099                List<AccessibilityNodeInfo> outInfos) {
1100            long parentNodeId = root.getParentNodeId();
1101            int accessibilityViewId = AccessibilityNodeInfo.getAccessibilityViewId(parentNodeId);
1102            while (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) {
1103                if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
1104                    return;
1105                }
1106                final int virtualDescendantId =
1107                    AccessibilityNodeInfo.getVirtualDescendantId(parentNodeId);
1108                if (virtualDescendantId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID
1109                        || accessibilityViewId == providerHost.getAccessibilityViewId()) {
1110                    final AccessibilityNodeInfo parent;
1111                    if (virtualDescendantId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) {
1112                        parent = provider.createAccessibilityNodeInfo(
1113                                virtualDescendantId);
1114                    } else {
1115                        parent= provider.createAccessibilityNodeInfo(
1116                                AccessibilityNodeProvider.HOST_VIEW_ID);
1117                    }
1118                    if (parent != null) {
1119                        outInfos.add(parent);
1120                    }
1121                    parentNodeId = parent.getParentNodeId();
1122                    accessibilityViewId = AccessibilityNodeInfo.getAccessibilityViewId(
1123                            parentNodeId);
1124                } else {
1125                    prefetchPredecessorsOfRealNode(providerHost, outInfos);
1126                    return;
1127                }
1128            }
1129        }
1130
1131        private void prefetchSiblingsOfVirtualNode(AccessibilityNodeInfo current, View providerHost,
1132                AccessibilityNodeProvider provider, List<AccessibilityNodeInfo> outInfos) {
1133            final long parentNodeId = current.getParentNodeId();
1134            final int parentAccessibilityViewId =
1135                AccessibilityNodeInfo.getAccessibilityViewId(parentNodeId);
1136            final int parentVirtualDescendantId =
1137                AccessibilityNodeInfo.getVirtualDescendantId(parentNodeId);
1138            if (parentVirtualDescendantId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID
1139                    || parentAccessibilityViewId == providerHost.getAccessibilityViewId()) {
1140                final AccessibilityNodeInfo parent;
1141                if (parentVirtualDescendantId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) {
1142                    parent = provider.createAccessibilityNodeInfo(parentVirtualDescendantId);
1143                } else {
1144                    parent = provider.createAccessibilityNodeInfo(
1145                            AccessibilityNodeProvider.HOST_VIEW_ID);
1146                }
1147                if (parent != null) {
1148                    final int childCount = parent.getChildCount();
1149                    for (int i = 0; i < childCount; i++) {
1150                        if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
1151                            return;
1152                        }
1153                        final long childNodeId = parent.getChildId(i);
1154                        if (childNodeId != current.getSourceNodeId()) {
1155                            final int childVirtualDescendantId =
1156                                AccessibilityNodeInfo.getVirtualDescendantId(childNodeId);
1157                            AccessibilityNodeInfo child = provider.createAccessibilityNodeInfo(
1158                                    childVirtualDescendantId);
1159                            if (child != null) {
1160                                outInfos.add(child);
1161                            }
1162                        }
1163                    }
1164                }
1165            } else {
1166                prefetchSiblingsOfRealNode(providerHost, outInfos);
1167            }
1168        }
1169
1170        private void prefetchDescendantsOfVirtualNode(AccessibilityNodeInfo root,
1171                AccessibilityNodeProvider provider, List<AccessibilityNodeInfo> outInfos) {
1172            final int initialOutInfosSize = outInfos.size();
1173            final int childCount = root.getChildCount();
1174            for (int i = 0; i < childCount; i++) {
1175                if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
1176                    return;
1177                }
1178                final long childNodeId = root.getChildId(i);
1179                AccessibilityNodeInfo child = provider.createAccessibilityNodeInfo(
1180                        AccessibilityNodeInfo.getVirtualDescendantId(childNodeId));
1181                if (child != null) {
1182                    outInfos.add(child);
1183                }
1184            }
1185            if (outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
1186                final int addedChildCount = outInfos.size() - initialOutInfosSize;
1187                for (int i = 0; i < addedChildCount; i++) {
1188                    AccessibilityNodeInfo child = outInfos.get(initialOutInfosSize + i);
1189                    prefetchDescendantsOfVirtualNode(child, provider, outInfos);
1190                }
1191            }
1192        }
1193    }
1194
1195    private class PrivateHandler extends Handler {
1196        private final static int MSG_PERFORM_ACCESSIBILITY_ACTION = 1;
1197        private final static int MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_ACCESSIBILITY_ID = 2;
1198        private final static int MSG_FIND_ACCESSIBILITY_NODE_INFOS_BY_VIEW_ID = 3;
1199        private final static int MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_TEXT = 4;
1200        private final static int MSG_FIND_FOCUS = 5;
1201        private final static int MSG_FOCUS_SEARCH = 6;
1202        private final static int MSG_COMPUTE_CLICK_POINT_IN_SCREEN = 7;
1203
1204        public PrivateHandler(Looper looper) {
1205            super(looper);
1206        }
1207
1208        @Override
1209        public String getMessageName(Message message) {
1210            final int type = message.what;
1211            switch (type) {
1212                case MSG_PERFORM_ACCESSIBILITY_ACTION:
1213                    return "MSG_PERFORM_ACCESSIBILITY_ACTION";
1214                case MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_ACCESSIBILITY_ID:
1215                    return "MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_ACCESSIBILITY_ID";
1216                case MSG_FIND_ACCESSIBILITY_NODE_INFOS_BY_VIEW_ID:
1217                    return "MSG_FIND_ACCESSIBILITY_NODE_INFOS_BY_VIEW_ID";
1218                case MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_TEXT:
1219                    return "MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_TEXT";
1220                case MSG_FIND_FOCUS:
1221                    return "MSG_FIND_FOCUS";
1222                case MSG_FOCUS_SEARCH:
1223                    return "MSG_FOCUS_SEARCH";
1224                case MSG_COMPUTE_CLICK_POINT_IN_SCREEN:
1225                    return "MSG_COMPUTE_CLICK_POINT_IN_SCREEN";
1226                default:
1227                    throw new IllegalArgumentException("Unknown message type: " + type);
1228            }
1229        }
1230
1231        @Override
1232        public void handleMessage(Message message) {
1233            final int type = message.what;
1234            switch (type) {
1235                case MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_ACCESSIBILITY_ID: {
1236                    findAccessibilityNodeInfoByAccessibilityIdUiThread(message);
1237                } break;
1238                case MSG_PERFORM_ACCESSIBILITY_ACTION: {
1239                    perfromAccessibilityActionUiThread(message);
1240                } break;
1241                case MSG_FIND_ACCESSIBILITY_NODE_INFOS_BY_VIEW_ID: {
1242                    findAccessibilityNodeInfosByViewIdUiThread(message);
1243                } break;
1244                case MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_TEXT: {
1245                    findAccessibilityNodeInfosByTextUiThread(message);
1246                } break;
1247                case MSG_FIND_FOCUS: {
1248                    findFocusUiThread(message);
1249                } break;
1250                case MSG_FOCUS_SEARCH: {
1251                    focusSearchUiThread(message);
1252                } break;
1253                case MSG_COMPUTE_CLICK_POINT_IN_SCREEN: {
1254                    computeClickPointInScreenUiThread(message);
1255                } break;
1256                default:
1257                    throw new IllegalArgumentException("Unknown message type: " + type);
1258            }
1259        }
1260    }
1261
1262    private final class AddNodeInfosForViewId implements Predicate<View> {
1263        private int mViewId = View.NO_ID;
1264        private List<AccessibilityNodeInfo> mInfos;
1265
1266        public void init(int viewId, List<AccessibilityNodeInfo> infos) {
1267            mViewId = viewId;
1268            mInfos = infos;
1269        }
1270
1271        public void reset() {
1272            mViewId = View.NO_ID;
1273            mInfos = null;
1274        }
1275
1276        @Override
1277        public boolean apply(View view) {
1278            if (view.getId() == mViewId && isShown(view)) {
1279                mInfos.add(view.createAccessibilityNodeInfo());
1280            }
1281            return false;
1282        }
1283    }
1284}
1285