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