AccessibilityInteractionController.java revision ded133c446fa9d0d23e6bde19a66fb2ce3980491
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    private View findViewByAccessibilityId(int accessibilityId) {
640        View root = mViewRootImpl.mView;
641        if (root == null) {
642            return null;
643        }
644        View foundView = root.findViewByAccessibilityId(accessibilityId);
645        if (foundView != null && !isShown(foundView)) {
646            return null;
647        }
648        return foundView;
649    }
650
651    private void applyAppScaleAndMagnificationSpecIfNeeded(List<AccessibilityNodeInfo> infos,
652            MagnificationSpec spec) {
653        if (infos == null) {
654            return;
655        }
656        final float applicationScale = mViewRootImpl.mAttachInfo.mApplicationScale;
657        if (shouldApplyAppScaleAndMagnificationSpec(applicationScale, spec)) {
658            final int infoCount = infos.size();
659            for (int i = 0; i < infoCount; i++) {
660                AccessibilityNodeInfo info = infos.get(i);
661                applyAppScaleAndMagnificationSpecIfNeeded(info, spec);
662            }
663        }
664    }
665
666    private void adjustIsVisibleToUserIfNeeded(List<AccessibilityNodeInfo> infos,
667            Region interactiveRegion) {
668        if (interactiveRegion == null || infos == null) {
669            return;
670        }
671        final int infoCount = infos.size();
672        for (int i = 0; i < infoCount; i++) {
673            AccessibilityNodeInfo info = infos.get(i);
674            adjustIsVisibleToUserIfNeeded(info, interactiveRegion);
675        }
676    }
677
678    private void adjustIsVisibleToUserIfNeeded(AccessibilityNodeInfo info,
679            Region interactiveRegion) {
680        if (interactiveRegion == null || info == null) {
681            return;
682        }
683        Rect boundsInScreen = mTempRect;
684        info.getBoundsInScreen(boundsInScreen);
685        if (interactiveRegion.quickReject(boundsInScreen)) {
686            info.setVisibleToUser(false);
687        }
688    }
689
690    private void applyAppScaleAndMagnificationSpecIfNeeded(Point point,
691            MagnificationSpec spec) {
692        final float applicationScale = mViewRootImpl.mAttachInfo.mApplicationScale;
693        if (!shouldApplyAppScaleAndMagnificationSpec(applicationScale, spec)) {
694            return;
695        }
696
697        if (applicationScale != 1.0f) {
698            point.x *= applicationScale;
699            point.y *= applicationScale;
700        }
701
702        if (spec != null) {
703            point.x *= spec.scale;
704            point.y *= spec.scale;
705            point.x += (int) spec.offsetX;
706            point.y += (int) spec.offsetY;
707        }
708    }
709
710    private void applyAppScaleAndMagnificationSpecIfNeeded(AccessibilityNodeInfo info,
711            MagnificationSpec spec) {
712        if (info == null) {
713            return;
714        }
715
716        final float applicationScale = mViewRootImpl.mAttachInfo.mApplicationScale;
717        if (!shouldApplyAppScaleAndMagnificationSpec(applicationScale, spec)) {
718            return;
719        }
720
721        Rect boundsInParent = mTempRect;
722        Rect boundsInScreen = mTempRect1;
723
724        info.getBoundsInParent(boundsInParent);
725        info.getBoundsInScreen(boundsInScreen);
726        if (applicationScale != 1.0f) {
727            boundsInParent.scale(applicationScale);
728            boundsInScreen.scale(applicationScale);
729        }
730        if (spec != null) {
731            boundsInParent.scale(spec.scale);
732            // boundsInParent must not be offset.
733            boundsInScreen.scale(spec.scale);
734            boundsInScreen.offset((int) spec.offsetX, (int) spec.offsetY);
735        }
736        info.setBoundsInParent(boundsInParent);
737        info.setBoundsInScreen(boundsInScreen);
738
739        if (spec != null) {
740            AttachInfo attachInfo = mViewRootImpl.mAttachInfo;
741            if (attachInfo.mDisplay == null) {
742                return;
743            }
744
745            final float scale = attachInfo.mApplicationScale * spec.scale;
746
747            Rect visibleWinFrame = mTempRect1;
748            visibleWinFrame.left = (int) (attachInfo.mWindowLeft * scale + spec.offsetX);
749            visibleWinFrame.top = (int) (attachInfo.mWindowTop * scale + spec.offsetY);
750            visibleWinFrame.right = (int) (visibleWinFrame.left + mViewRootImpl.mWidth * scale);
751            visibleWinFrame.bottom = (int) (visibleWinFrame.top + mViewRootImpl.mHeight * scale);
752
753            attachInfo.mDisplay.getRealSize(mTempPoint);
754            final int displayWidth = mTempPoint.x;
755            final int displayHeight = mTempPoint.y;
756
757            Rect visibleDisplayFrame = mTempRect2;
758            visibleDisplayFrame.set(0, 0, displayWidth, displayHeight);
759
760            visibleWinFrame.intersect(visibleDisplayFrame);
761
762            if (!visibleWinFrame.intersects(boundsInScreen.left, boundsInScreen.top,
763                    boundsInScreen.right, boundsInScreen.bottom)) {
764                info.setVisibleToUser(false);
765            }
766        }
767    }
768
769    private boolean shouldApplyAppScaleAndMagnificationSpec(float appScale,
770            MagnificationSpec spec) {
771        return (appScale != 1.0f || (spec != null && !spec.isNop()));
772    }
773
774    /**
775     * This class encapsulates a prefetching strategy for the accessibility APIs for
776     * querying window content. It is responsible to prefetch a batch of
777     * AccessibilityNodeInfos in addition to the one for a requested node.
778     */
779    private class AccessibilityNodePrefetcher {
780
781        private static final int MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE = 50;
782
783        private final ArrayList<View> mTempViewList = new ArrayList<View>();
784
785        public void prefetchAccessibilityNodeInfos(View view, int virtualViewId, int fetchFlags,
786                List<AccessibilityNodeInfo> outInfos) {
787            AccessibilityNodeProvider provider = view.getAccessibilityNodeProvider();
788            if (provider == null) {
789                AccessibilityNodeInfo root = view.createAccessibilityNodeInfo();
790                if (root != null) {
791                    outInfos.add(root);
792                    if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS) != 0) {
793                        prefetchPredecessorsOfRealNode(view, outInfos);
794                    }
795                    if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS) != 0) {
796                        prefetchSiblingsOfRealNode(view, outInfos);
797                    }
798                    if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS) != 0) {
799                        prefetchDescendantsOfRealNode(view, outInfos);
800                    }
801                }
802            } else {
803                final AccessibilityNodeInfo root;
804                if (virtualViewId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) {
805                    root = provider.createAccessibilityNodeInfo(virtualViewId);
806                } else {
807                    root = provider.createAccessibilityNodeInfo(
808                            AccessibilityNodeProvider.HOST_VIEW_ID);
809                }
810                if (root != null) {
811                    outInfos.add(root);
812                    if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS) != 0) {
813                        prefetchPredecessorsOfVirtualNode(root, view, provider, outInfos);
814                    }
815                    if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS) != 0) {
816                        prefetchSiblingsOfVirtualNode(root, view, provider, outInfos);
817                    }
818                    if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS) != 0) {
819                        prefetchDescendantsOfVirtualNode(root, provider, outInfos);
820                    }
821                }
822            }
823            if (ENFORCE_NODE_TREE_CONSISTENT) {
824                enforceNodeTreeConsistent(outInfos);
825            }
826        }
827
828        private void enforceNodeTreeConsistent(List<AccessibilityNodeInfo> nodes) {
829            LongSparseArray<AccessibilityNodeInfo> nodeMap =
830                    new LongSparseArray<AccessibilityNodeInfo>();
831            final int nodeCount = nodes.size();
832            for (int i = 0; i < nodeCount; i++) {
833                AccessibilityNodeInfo node = nodes.get(i);
834                nodeMap.put(node.getSourceNodeId(), node);
835            }
836
837            // If the nodes are a tree it does not matter from
838            // which node we start to search for the root.
839            AccessibilityNodeInfo root = nodeMap.valueAt(0);
840            AccessibilityNodeInfo parent = root;
841            while (parent != null) {
842                root = parent;
843                parent = nodeMap.get(parent.getParentNodeId());
844            }
845
846            // Traverse the tree and do some checks.
847            AccessibilityNodeInfo accessFocus = null;
848            AccessibilityNodeInfo inputFocus = null;
849            HashSet<AccessibilityNodeInfo> seen = new HashSet<AccessibilityNodeInfo>();
850            Queue<AccessibilityNodeInfo> fringe = new LinkedList<AccessibilityNodeInfo>();
851            fringe.add(root);
852
853            while (!fringe.isEmpty()) {
854                AccessibilityNodeInfo current = fringe.poll();
855
856                // Check for duplicates
857                if (!seen.add(current)) {
858                    throw new IllegalStateException("Duplicate node: "
859                            + current + " in window:"
860                            + mViewRootImpl.mAttachInfo.mAccessibilityWindowId);
861                }
862
863                // Check for one accessibility focus.
864                if (current.isAccessibilityFocused()) {
865                    if (accessFocus != null) {
866                        throw new IllegalStateException("Duplicate accessibility focus:"
867                                + current
868                                + " in window:" + mViewRootImpl.mAttachInfo.mAccessibilityWindowId);
869                    } else {
870                        accessFocus = current;
871                    }
872                }
873
874                // Check for one input focus.
875                if (current.isFocused()) {
876                    if (inputFocus != null) {
877                        throw new IllegalStateException("Duplicate input focus: "
878                            + current + " in window:"
879                            + mViewRootImpl.mAttachInfo.mAccessibilityWindowId);
880                    } else {
881                        inputFocus = current;
882                    }
883                }
884
885                final int childCount = current.getChildCount();
886                for (int j = 0; j < childCount; j++) {
887                    final long childId = current.getChildId(j);
888                    final AccessibilityNodeInfo child = nodeMap.get(childId);
889                    if (child != null) {
890                        fringe.add(child);
891                    }
892                }
893            }
894
895            // Check for disconnected nodes.
896            for (int j = nodeMap.size() - 1; j >= 0; j--) {
897                AccessibilityNodeInfo info = nodeMap.valueAt(j);
898                if (!seen.contains(info)) {
899                    throw new IllegalStateException("Disconnected node: " + info);
900                }
901            }
902        }
903
904        private void prefetchPredecessorsOfRealNode(View view,
905                List<AccessibilityNodeInfo> outInfos) {
906            ViewParent parent = view.getParentForAccessibility();
907            while (parent instanceof View
908                    && outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
909                View parentView = (View) parent;
910                AccessibilityNodeInfo info = parentView.createAccessibilityNodeInfo();
911                if (info != null) {
912                    outInfos.add(info);
913                }
914                parent = parent.getParentForAccessibility();
915            }
916        }
917
918        private void prefetchSiblingsOfRealNode(View current,
919                List<AccessibilityNodeInfo> outInfos) {
920            ViewParent parent = current.getParentForAccessibility();
921            if (parent instanceof ViewGroup) {
922                ViewGroup parentGroup = (ViewGroup) parent;
923                ArrayList<View> children = mTempViewList;
924                children.clear();
925                try {
926                    parentGroup.addChildrenForAccessibility(children);
927                    final int childCount = children.size();
928                    for (int i = 0; i < childCount; i++) {
929                        if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
930                            return;
931                        }
932                        View child = children.get(i);
933                        if (child.getAccessibilityViewId() != current.getAccessibilityViewId()
934                                &&  isShown(child)) {
935                            AccessibilityNodeInfo info = null;
936                            AccessibilityNodeProvider provider =
937                                child.getAccessibilityNodeProvider();
938                            if (provider == null) {
939                                info = child.createAccessibilityNodeInfo();
940                            } else {
941                                info = provider.createAccessibilityNodeInfo(
942                                        AccessibilityNodeProvider.HOST_VIEW_ID);
943                            }
944                            if (info != null) {
945                                outInfos.add(info);
946                            }
947                        }
948                    }
949                } finally {
950                    children.clear();
951                }
952            }
953        }
954
955        private void prefetchDescendantsOfRealNode(View root,
956                List<AccessibilityNodeInfo> outInfos) {
957            if (!(root instanceof ViewGroup)) {
958                return;
959            }
960            HashMap<View, AccessibilityNodeInfo> addedChildren =
961                new HashMap<View, AccessibilityNodeInfo>();
962            ArrayList<View> children = mTempViewList;
963            children.clear();
964            try {
965                root.addChildrenForAccessibility(children);
966                final int childCount = children.size();
967                for (int i = 0; i < childCount; i++) {
968                    if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
969                        return;
970                    }
971                    View child = children.get(i);
972                    if (isShown(child)) {
973                        AccessibilityNodeProvider provider = child.getAccessibilityNodeProvider();
974                        if (provider == null) {
975                            AccessibilityNodeInfo info = child.createAccessibilityNodeInfo();
976                            if (info != null) {
977                                outInfos.add(info);
978                                addedChildren.put(child, null);
979                            }
980                        } else {
981                            AccessibilityNodeInfo info = provider.createAccessibilityNodeInfo(
982                                   AccessibilityNodeProvider.HOST_VIEW_ID);
983                            if (info != null) {
984                                outInfos.add(info);
985                                addedChildren.put(child, info);
986                            }
987                        }
988                    }
989                }
990            } finally {
991                children.clear();
992            }
993            if (outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
994                for (Map.Entry<View, AccessibilityNodeInfo> entry : addedChildren.entrySet()) {
995                    View addedChild = entry.getKey();
996                    AccessibilityNodeInfo virtualRoot = entry.getValue();
997                    if (virtualRoot == null) {
998                        prefetchDescendantsOfRealNode(addedChild, outInfos);
999                    } else {
1000                        AccessibilityNodeProvider provider =
1001                            addedChild.getAccessibilityNodeProvider();
1002                        prefetchDescendantsOfVirtualNode(virtualRoot, provider, outInfos);
1003                    }
1004                }
1005            }
1006        }
1007
1008        private void prefetchPredecessorsOfVirtualNode(AccessibilityNodeInfo root,
1009                View providerHost, AccessibilityNodeProvider provider,
1010                List<AccessibilityNodeInfo> outInfos) {
1011            long parentNodeId = root.getParentNodeId();
1012            int accessibilityViewId = AccessibilityNodeInfo.getAccessibilityViewId(parentNodeId);
1013            while (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) {
1014                if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
1015                    return;
1016                }
1017                final int virtualDescendantId =
1018                    AccessibilityNodeInfo.getVirtualDescendantId(parentNodeId);
1019                if (virtualDescendantId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID
1020                        || accessibilityViewId == providerHost.getAccessibilityViewId()) {
1021                    final AccessibilityNodeInfo parent;
1022                    if (virtualDescendantId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) {
1023                        parent = provider.createAccessibilityNodeInfo(virtualDescendantId);
1024                    } else {
1025                        parent = provider.createAccessibilityNodeInfo(
1026                                AccessibilityNodeProvider.HOST_VIEW_ID);
1027                    }
1028                    if (parent == null) {
1029                        // Couldn't obtain the parent, which means we have a
1030                        // disconnected sub-tree. Abort prefetch immediately.
1031                        return;
1032                    }
1033                    outInfos.add(parent);
1034                    parentNodeId = parent.getParentNodeId();
1035                    accessibilityViewId = AccessibilityNodeInfo.getAccessibilityViewId(
1036                            parentNodeId);
1037                } else {
1038                    prefetchPredecessorsOfRealNode(providerHost, outInfos);
1039                    return;
1040                }
1041            }
1042        }
1043
1044        private void prefetchSiblingsOfVirtualNode(AccessibilityNodeInfo current, View providerHost,
1045                AccessibilityNodeProvider provider, List<AccessibilityNodeInfo> outInfos) {
1046            final long parentNodeId = current.getParentNodeId();
1047            final int parentAccessibilityViewId =
1048                AccessibilityNodeInfo.getAccessibilityViewId(parentNodeId);
1049            final int parentVirtualDescendantId =
1050                AccessibilityNodeInfo.getVirtualDescendantId(parentNodeId);
1051            if (parentVirtualDescendantId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID
1052                    || parentAccessibilityViewId == providerHost.getAccessibilityViewId()) {
1053                final AccessibilityNodeInfo parent;
1054                if (parentVirtualDescendantId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) {
1055                    parent = provider.createAccessibilityNodeInfo(parentVirtualDescendantId);
1056                } else {
1057                    parent = provider.createAccessibilityNodeInfo(
1058                            AccessibilityNodeProvider.HOST_VIEW_ID);
1059                }
1060                if (parent != null) {
1061                    final int childCount = parent.getChildCount();
1062                    for (int i = 0; i < childCount; i++) {
1063                        if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
1064                            return;
1065                        }
1066                        final long childNodeId = parent.getChildId(i);
1067                        if (childNodeId != current.getSourceNodeId()) {
1068                            final int childVirtualDescendantId =
1069                                AccessibilityNodeInfo.getVirtualDescendantId(childNodeId);
1070                            AccessibilityNodeInfo child = provider.createAccessibilityNodeInfo(
1071                                    childVirtualDescendantId);
1072                            if (child != null) {
1073                                outInfos.add(child);
1074                            }
1075                        }
1076                    }
1077                }
1078            } else {
1079                prefetchSiblingsOfRealNode(providerHost, outInfos);
1080            }
1081        }
1082
1083        private void prefetchDescendantsOfVirtualNode(AccessibilityNodeInfo root,
1084                AccessibilityNodeProvider provider, List<AccessibilityNodeInfo> outInfos) {
1085            final int initialOutInfosSize = outInfos.size();
1086            final int childCount = root.getChildCount();
1087            for (int i = 0; i < childCount; i++) {
1088                if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
1089                    return;
1090                }
1091                final long childNodeId = root.getChildId(i);
1092                AccessibilityNodeInfo child = provider.createAccessibilityNodeInfo(
1093                        AccessibilityNodeInfo.getVirtualDescendantId(childNodeId));
1094                if (child != null) {
1095                    outInfos.add(child);
1096                }
1097            }
1098            if (outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
1099                final int addedChildCount = outInfos.size() - initialOutInfosSize;
1100                for (int i = 0; i < addedChildCount; i++) {
1101                    AccessibilityNodeInfo child = outInfos.get(initialOutInfosSize + i);
1102                    prefetchDescendantsOfVirtualNode(child, provider, outInfos);
1103                }
1104            }
1105        }
1106    }
1107
1108    private class PrivateHandler extends Handler {
1109        private final static int MSG_PERFORM_ACCESSIBILITY_ACTION = 1;
1110        private final static int MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_ACCESSIBILITY_ID = 2;
1111        private final static int MSG_FIND_ACCESSIBILITY_NODE_INFOS_BY_VIEW_ID = 3;
1112        private final static int MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_TEXT = 4;
1113        private final static int MSG_FIND_FOCUS = 5;
1114        private final static int MSG_FOCUS_SEARCH = 6;
1115
1116        public PrivateHandler(Looper looper) {
1117            super(looper);
1118        }
1119
1120        @Override
1121        public String getMessageName(Message message) {
1122            final int type = message.what;
1123            switch (type) {
1124                case MSG_PERFORM_ACCESSIBILITY_ACTION:
1125                    return "MSG_PERFORM_ACCESSIBILITY_ACTION";
1126                case MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_ACCESSIBILITY_ID:
1127                    return "MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_ACCESSIBILITY_ID";
1128                case MSG_FIND_ACCESSIBILITY_NODE_INFOS_BY_VIEW_ID:
1129                    return "MSG_FIND_ACCESSIBILITY_NODE_INFOS_BY_VIEW_ID";
1130                case MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_TEXT:
1131                    return "MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_TEXT";
1132                case MSG_FIND_FOCUS:
1133                    return "MSG_FIND_FOCUS";
1134                case MSG_FOCUS_SEARCH:
1135                    return "MSG_FOCUS_SEARCH";
1136                default:
1137                    throw new IllegalArgumentException("Unknown message type: " + type);
1138            }
1139        }
1140
1141        @Override
1142        public void handleMessage(Message message) {
1143            final int type = message.what;
1144            switch (type) {
1145                case MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_ACCESSIBILITY_ID: {
1146                    findAccessibilityNodeInfoByAccessibilityIdUiThread(message);
1147                } break;
1148                case MSG_PERFORM_ACCESSIBILITY_ACTION: {
1149                    perfromAccessibilityActionUiThread(message);
1150                } break;
1151                case MSG_FIND_ACCESSIBILITY_NODE_INFOS_BY_VIEW_ID: {
1152                    findAccessibilityNodeInfosByViewIdUiThread(message);
1153                } break;
1154                case MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_TEXT: {
1155                    findAccessibilityNodeInfosByTextUiThread(message);
1156                } break;
1157                case MSG_FIND_FOCUS: {
1158                    findFocusUiThread(message);
1159                } break;
1160                case MSG_FOCUS_SEARCH: {
1161                    focusSearchUiThread(message);
1162                } break;
1163                default:
1164                    throw new IllegalArgumentException("Unknown message type: " + type);
1165            }
1166        }
1167    }
1168
1169    private final class AddNodeInfosForViewId implements Predicate<View> {
1170        private int mViewId = View.NO_ID;
1171        private List<AccessibilityNodeInfo> mInfos;
1172
1173        public void init(int viewId, List<AccessibilityNodeInfo> infos) {
1174            mViewId = viewId;
1175            mInfos = infos;
1176        }
1177
1178        public void reset() {
1179            mViewId = View.NO_ID;
1180            mInfos = null;
1181        }
1182
1183        @Override
1184        public boolean apply(View view) {
1185            if (view.getId() == mViewId && isShown(view)) {
1186                mInfos.add(view.createAccessibilityNodeInfo());
1187            }
1188            return false;
1189        }
1190    }
1191}
1192