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