AccessibilityInteractionController.java revision 152e9bb81aa5b2ab4637f4b2dae04b3ce89fa891
1/*
2 * Copyright (C) 2012 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.view;
18
19import static android.view.accessibility.AccessibilityNodeInfo.INCLUDE_NOT_IMPORTANT_VIEWS;
20
21import android.graphics.Point;
22import android.graphics.Rect;
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.SparseLongArray;
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;
37
38import java.util.ArrayList;
39import java.util.HashMap;
40import java.util.List;
41import java.util.Map;
42
43/**
44 * Class for managing accessibility interactions initiated from the system
45 * and targeting the view hierarchy. A *ClientThread method is to be
46 * called from the interaction connection ViewAncestor gives the system to
47 * talk to it and a corresponding *UiThread method that is executed on the
48 * UI thread.
49 */
50final class AccessibilityInteractionController {
51
52    private ArrayList<AccessibilityNodeInfo> mTempAccessibilityNodeInfoList =
53        new ArrayList<AccessibilityNodeInfo>();
54
55    private final Handler mHandler;
56
57    private final ViewRootImpl mViewRootImpl;
58
59    private final AccessibilityNodePrefetcher mPrefetcher;
60
61    private final long mMyLooperThreadId;
62
63    private final int mMyProcessId;
64
65    private final ArrayList<View> mTempArrayList = new ArrayList<View>();
66
67    private final Point mTempPoint = new Point();
68    private final Rect mTempRect = new Rect();
69    private final Rect mTempRect1 = new Rect();
70    private final Rect mTempRect2 = new Rect();
71
72    public AccessibilityInteractionController(ViewRootImpl viewRootImpl) {
73        Looper looper =  viewRootImpl.mHandler.getLooper();
74        mMyLooperThreadId = looper.getThread().getId();
75        mMyProcessId = Process.myPid();
76        mHandler = new PrivateHandler(looper);
77        mViewRootImpl = viewRootImpl;
78        mPrefetcher = new AccessibilityNodePrefetcher();
79    }
80
81    private boolean isShown(View view) {
82        // The first two checks are made also made by isShown() which
83        // however traverses the tree up to the parent to catch that.
84        // Therefore, we do some fail fast check to minimize the up
85        // tree traversal.
86        return (view.mAttachInfo != null
87                && view.mAttachInfo.mWindowVisibility == View.VISIBLE
88                && view.isShown());
89    }
90
91    public void findAccessibilityNodeInfoByAccessibilityIdClientThread(
92            long accessibilityNodeId, int interactionId,
93            IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid,
94            long interrogatingTid, MagnificationSpec spec) {
95        Message message = mHandler.obtainMessage();
96        message.what = PrivateHandler.MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID;
97        message.arg1 = flags;
98
99        SomeArgs args = SomeArgs.obtain();
100        args.argi1 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId);
101        args.argi2 = AccessibilityNodeInfo.getVirtualDescendantId(accessibilityNodeId);
102        args.argi3 = interactionId;
103        args.arg1 = callback;
104        args.arg2 = spec;
105        message.obj = args;
106
107        // If the interrogation is performed by the same thread as the main UI
108        // thread in this process, set the message as a static reference so
109        // after this call completes the same thread but in the interrogating
110        // client can handle the message to generate the result.
111        if (interrogatingPid == mMyProcessId && interrogatingTid == mMyLooperThreadId) {
112            AccessibilityInteractionClient.getInstanceForThread(
113                    interrogatingTid).setSameThreadMessage(message);
114        } else {
115            mHandler.sendMessage(message);
116        }
117    }
118
119    private void findAccessibilityNodeInfoByAccessibilityIdUiThread(Message message) {
120        final int flags = message.arg1;
121
122        SomeArgs args = (SomeArgs) message.obj;
123        final int accessibilityViewId = args.argi1;
124        final int virtualDescendantId = args.argi2;
125        final int interactionId = args.argi3;
126        final IAccessibilityInteractionConnectionCallback callback =
127            (IAccessibilityInteractionConnectionCallback) args.arg1;
128        final MagnificationSpec spec = (MagnificationSpec) args.arg2;
129
130        args.recycle();
131
132        List<AccessibilityNodeInfo> infos = mTempAccessibilityNodeInfoList;
133        infos.clear();
134        try {
135            if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
136                return;
137            }
138            mViewRootImpl.mAttachInfo.mIncludeNotImportantViews =
139                (flags & INCLUDE_NOT_IMPORTANT_VIEWS) != 0;
140            View root = null;
141            if (accessibilityViewId == AccessibilityNodeInfo.UNDEFINED) {
142                root = mViewRootImpl.mView;
143            } else {
144                root = findViewByAccessibilityId(accessibilityViewId);
145            }
146            if (root != null && isShown(root)) {
147                mPrefetcher.prefetchAccessibilityNodeInfos(root, virtualDescendantId, flags, infos);
148            }
149        } finally {
150            try {
151                mViewRootImpl.mAttachInfo.mIncludeNotImportantViews = false;
152                applyAppScaleAndMagnificationSpecIfNeeded(infos, spec);
153                if (spec != null) {
154                    spec.recycle();
155                }
156                callback.setFindAccessibilityNodeInfosResult(infos, interactionId);
157                infos.clear();
158            } catch (RemoteException re) {
159                /* ignore - the other side will time out */
160            }
161        }
162    }
163
164    public void findAccessibilityNodeInfoByViewIdClientThread(long accessibilityNodeId,
165            int viewId, int interactionId, IAccessibilityInteractionConnectionCallback callback,
166            int flags, int interrogatingPid, long interrogatingTid, MagnificationSpec spec) {
167        Message message = mHandler.obtainMessage();
168        message.what = PrivateHandler.MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_ID;
169        message.arg1 = flags;
170        message.arg2 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId);
171
172        SomeArgs args = SomeArgs.obtain();
173        args.argi1 = viewId;
174        args.argi2 = interactionId;
175        args.arg1 = callback;
176        args.arg2 = spec;
177
178        message.obj = args;
179
180        // If the interrogation is performed by the same thread as the main UI
181        // thread in this process, set the message as a static reference so
182        // after this call completes the same thread but in the interrogating
183        // client can handle the message to generate the result.
184        if (interrogatingPid == mMyProcessId && interrogatingTid == mMyLooperThreadId) {
185            AccessibilityInteractionClient.getInstanceForThread(
186                    interrogatingTid).setSameThreadMessage(message);
187        } else {
188            mHandler.sendMessage(message);
189        }
190    }
191
192    private void findAccessibilityNodeInfoByViewIdUiThread(Message message) {
193        final int flags = message.arg1;
194        final int accessibilityViewId = message.arg2;
195
196        SomeArgs args = (SomeArgs) message.obj;
197        final int viewId = args.argi1;
198        final int interactionId = args.argi2;
199        final IAccessibilityInteractionConnectionCallback callback =
200            (IAccessibilityInteractionConnectionCallback) args.arg1;
201        final MagnificationSpec spec = (MagnificationSpec) args.arg2;
202
203        args.recycle();
204
205        AccessibilityNodeInfo info = null;
206        try {
207            if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
208                return;
209            }
210            mViewRootImpl.mAttachInfo.mIncludeNotImportantViews =
211                (flags & INCLUDE_NOT_IMPORTANT_VIEWS) != 0;
212            View root = null;
213            if (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED) {
214                root = findViewByAccessibilityId(accessibilityViewId);
215            } else {
216                root = mViewRootImpl.mView;
217            }
218            if (root != null) {
219                View target = root.findViewById(viewId);
220                if (target != null && isShown(target)) {
221                    info = target.createAccessibilityNodeInfo();
222                }
223            }
224        } finally {
225            try {
226                mViewRootImpl.mAttachInfo.mIncludeNotImportantViews = false;
227                applyAppScaleAndMagnificationSpecIfNeeded(info, spec);
228                if (spec != null) {
229                    spec.recycle();
230                }
231                callback.setFindAccessibilityNodeInfoResult(info, interactionId);
232            } catch (RemoteException re) {
233                /* ignore - the other side will time out */
234            }
235        }
236    }
237
238    public void findAccessibilityNodeInfosByTextClientThread(long accessibilityNodeId,
239            String text, int interactionId, IAccessibilityInteractionConnectionCallback callback,
240            int flags, int interrogatingPid, long interrogatingTid, MagnificationSpec spec) {
241        Message message = mHandler.obtainMessage();
242        message.what = PrivateHandler.MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_TEXT;
243        message.arg1 = flags;
244
245        SomeArgs args = SomeArgs.obtain();
246        args.arg1 = text;
247        args.arg2 = callback;
248        args.arg3 = spec;
249        args.argi1 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId);
250        args.argi2 = AccessibilityNodeInfo.getVirtualDescendantId(accessibilityNodeId);
251        args.argi3 = interactionId;
252        message.obj = args;
253
254        // If the interrogation is performed by the same thread as the main UI
255        // thread in this process, set the message as a static reference so
256        // after this call completes the same thread but in the interrogating
257        // client can handle the message to generate the result.
258        if (interrogatingPid == mMyProcessId && interrogatingTid == mMyLooperThreadId) {
259            AccessibilityInteractionClient.getInstanceForThread(
260                    interrogatingTid).setSameThreadMessage(message);
261        } else {
262            mHandler.sendMessage(message);
263        }
264    }
265
266    private void findAccessibilityNodeInfosByTextUiThread(Message message) {
267        final int flags = message.arg1;
268
269        SomeArgs args = (SomeArgs) message.obj;
270        final String text = (String) args.arg1;
271        final IAccessibilityInteractionConnectionCallback callback =
272            (IAccessibilityInteractionConnectionCallback) args.arg2;
273        final MagnificationSpec spec = (MagnificationSpec) args.arg3;
274        final int accessibilityViewId = args.argi1;
275        final int virtualDescendantId = args.argi2;
276        final int interactionId = args.argi3;
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.mIncludeNotImportantViews =
285                (flags & INCLUDE_NOT_IMPORTANT_VIEWS) != 0;
286            View root = null;
287            if (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED) {
288                root = findViewByAccessibilityId(accessibilityViewId);
289            } else {
290                root = mViewRootImpl.mView;
291            }
292            if (root != null && isShown(root)) {
293                AccessibilityNodeProvider provider = root.getAccessibilityNodeProvider();
294                if (provider != null) {
295                    infos = provider.findAccessibilityNodeInfosByText(text,
296                            virtualDescendantId);
297                } else if (virtualDescendantId == AccessibilityNodeInfo.UNDEFINED) {
298                    ArrayList<View> foundViews = mTempArrayList;
299                    foundViews.clear();
300                    root.findViewsWithText(foundViews, text, View.FIND_VIEWS_WITH_TEXT
301                            | View.FIND_VIEWS_WITH_CONTENT_DESCRIPTION
302                            | View.FIND_VIEWS_WITH_ACCESSIBILITY_NODE_PROVIDERS);
303                    if (!foundViews.isEmpty()) {
304                        infos = mTempAccessibilityNodeInfoList;
305                        infos.clear();
306                        final int viewCount = foundViews.size();
307                        for (int i = 0; i < viewCount; i++) {
308                            View foundView = foundViews.get(i);
309                            if (isShown(foundView)) {
310                                provider = foundView.getAccessibilityNodeProvider();
311                                if (provider != null) {
312                                    List<AccessibilityNodeInfo> infosFromProvider =
313                                        provider.findAccessibilityNodeInfosByText(text,
314                                                AccessibilityNodeInfo.UNDEFINED);
315                                    if (infosFromProvider != null) {
316                                        infos.addAll(infosFromProvider);
317                                    }
318                                } else  {
319                                    infos.add(foundView.createAccessibilityNodeInfo());
320                                }
321                            }
322                        }
323                    }
324                }
325            }
326        } finally {
327            try {
328                mViewRootImpl.mAttachInfo.mIncludeNotImportantViews = false;
329                applyAppScaleAndMagnificationSpecIfNeeded(infos, spec);
330                if (spec != null) {
331                    spec.recycle();
332                }
333                callback.setFindAccessibilityNodeInfosResult(infos, interactionId);
334            } catch (RemoteException re) {
335                /* ignore - the other side will time out */
336            }
337        }
338    }
339
340    public void findFocusClientThread(long accessibilityNodeId, int focusType, int interactionId,
341            IAccessibilityInteractionConnectionCallback callback, int flags, int interogatingPid,
342            long interrogatingTid, MagnificationSpec spec) {
343        Message message = mHandler.obtainMessage();
344        message.what = PrivateHandler.MSG_FIND_FOCUS;
345        message.arg1 = flags;
346        message.arg2 = focusType;
347
348        SomeArgs args = SomeArgs.obtain();
349        args.argi1 = interactionId;
350        args.argi2 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId);
351        args.argi3 = AccessibilityNodeInfo.getVirtualDescendantId(accessibilityNodeId);
352        args.arg1 = callback;
353        args.arg2 = spec;
354
355        message.obj = args;
356
357        // If the interrogation is performed by the same thread as the main UI
358        // thread in this process, set the message as a static reference so
359        // after this call completes the same thread but in the interrogating
360        // client can handle the message to generate the result.
361        if (interogatingPid == mMyProcessId && interrogatingTid == mMyLooperThreadId) {
362            AccessibilityInteractionClient.getInstanceForThread(
363                    interrogatingTid).setSameThreadMessage(message);
364        } else {
365            mHandler.sendMessage(message);
366        }
367    }
368
369    private void findFocusUiThread(Message message) {
370        final int flags = message.arg1;
371        final int focusType = message.arg2;
372
373        SomeArgs args = (SomeArgs) message.obj;
374        final int interactionId = args.argi1;
375        final int accessibilityViewId = args.argi2;
376        final int virtualDescendantId = args.argi3;
377        final IAccessibilityInteractionConnectionCallback callback =
378            (IAccessibilityInteractionConnectionCallback) args.arg1;
379        final MagnificationSpec spec = (MagnificationSpec) args.arg2;
380        args.recycle();
381
382        AccessibilityNodeInfo focused = null;
383        try {
384            if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
385                return;
386            }
387            mViewRootImpl.mAttachInfo.mIncludeNotImportantViews =
388                (flags & INCLUDE_NOT_IMPORTANT_VIEWS) != 0;
389            View root = null;
390            if (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED) {
391                root = findViewByAccessibilityId(accessibilityViewId);
392            } else {
393                root = mViewRootImpl.mView;
394            }
395            if (root != null && isShown(root)) {
396                switch (focusType) {
397                    case AccessibilityNodeInfo.FOCUS_ACCESSIBILITY: {
398                        View host = mViewRootImpl.mAccessibilityFocusedHost;
399                        // If there is no accessibility focus host or it is not a descendant
400                        // of the root from which to start the search, then the search failed.
401                        if (host == null || !ViewRootImpl.isViewDescendantOf(host, root)) {
402                            break;
403                        }
404                        // If the host has a provider ask this provider to search for the
405                        // focus instead fetching all provider nodes to do the search here.
406                        AccessibilityNodeProvider provider = host.getAccessibilityNodeProvider();
407                        if (provider != null) {
408                            if (mViewRootImpl.mAccessibilityFocusedVirtualView != null) {
409                                focused = AccessibilityNodeInfo.obtain(
410                                        mViewRootImpl.mAccessibilityFocusedVirtualView);
411                            }
412                        } else if (virtualDescendantId == View.NO_ID) {
413                            focused = host.createAccessibilityNodeInfo();
414                        }
415                    } break;
416                    case AccessibilityNodeInfo.FOCUS_INPUT: {
417                        // Input focus cannot go to virtual views.
418                        View target = root.findFocus();
419                        if (target != null && isShown(target)) {
420                            focused = target.createAccessibilityNodeInfo();
421                        }
422                    } break;
423                    default:
424                        throw new IllegalArgumentException("Unknown focus type: " + focusType);
425                }
426            }
427        } finally {
428            try {
429                mViewRootImpl.mAttachInfo.mIncludeNotImportantViews = false;
430                applyAppScaleAndMagnificationSpecIfNeeded(focused, spec);
431                if (spec != null) {
432                    spec.recycle();
433                }
434                callback.setFindAccessibilityNodeInfoResult(focused, interactionId);
435            } catch (RemoteException re) {
436                /* ignore - the other side will time out */
437            }
438        }
439    }
440
441    public void focusSearchClientThread(long accessibilityNodeId, int direction, int interactionId,
442            IAccessibilityInteractionConnectionCallback callback, int flags, int interogatingPid,
443            long interrogatingTid, MagnificationSpec spec) {
444        Message message = mHandler.obtainMessage();
445        message.what = PrivateHandler.MSG_FOCUS_SEARCH;
446        message.arg1 = flags;
447        message.arg2 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId);
448
449        SomeArgs args = SomeArgs.obtain();
450        args.argi2 = direction;
451        args.argi3 = interactionId;
452        args.arg1 = callback;
453        args.arg2 = spec;
454
455        message.obj = args;
456
457        // If the interrogation is performed by the same thread as the main UI
458        // thread in this process, set the message as a static reference so
459        // after this call completes the same thread but in the interrogating
460        // client can handle the message to generate the result.
461        if (interogatingPid == mMyProcessId && interrogatingTid == mMyLooperThreadId) {
462            AccessibilityInteractionClient.getInstanceForThread(
463                    interrogatingTid).setSameThreadMessage(message);
464        } else {
465            mHandler.sendMessage(message);
466        }
467    }
468
469    private void focusSearchUiThread(Message message) {
470        final int flags = message.arg1;
471        final int accessibilityViewId = message.arg2;
472
473        SomeArgs args = (SomeArgs) message.obj;
474        final int direction = args.argi2;
475        final int interactionId = args.argi3;
476        final IAccessibilityInteractionConnectionCallback callback =
477            (IAccessibilityInteractionConnectionCallback) args.arg1;
478        final MagnificationSpec spec = (MagnificationSpec) args.arg2;
479
480        args.recycle();
481
482        AccessibilityNodeInfo next = null;
483        try {
484            if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
485                return;
486            }
487            mViewRootImpl.mAttachInfo.mIncludeNotImportantViews =
488                (flags & INCLUDE_NOT_IMPORTANT_VIEWS) != 0;
489            View root = null;
490            if (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED) {
491                root = findViewByAccessibilityId(accessibilityViewId);
492            } else {
493                root = mViewRootImpl.mView;
494            }
495            if (root != null && isShown(root)) {
496                View nextView = root.focusSearch(direction);
497                if (nextView != null) {
498                    next = nextView.createAccessibilityNodeInfo();
499                }
500            }
501        } finally {
502            try {
503                mViewRootImpl.mAttachInfo.mIncludeNotImportantViews = false;
504                applyAppScaleAndMagnificationSpecIfNeeded(next, spec);
505                if (spec != null) {
506                    spec.recycle();
507                }
508                callback.setFindAccessibilityNodeInfoResult(next, interactionId);
509            } catch (RemoteException re) {
510                /* ignore - the other side will time out */
511            }
512        }
513    }
514
515    public void performAccessibilityActionClientThread(long accessibilityNodeId, int action,
516            Bundle arguments, int interactionId,
517            IAccessibilityInteractionConnectionCallback callback, int flags, int interogatingPid,
518            long interrogatingTid) {
519        Message message = mHandler.obtainMessage();
520        message.what = PrivateHandler.MSG_PERFORM_ACCESSIBILITY_ACTION;
521        message.arg1 = flags;
522        message.arg2 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId);
523
524        SomeArgs args = SomeArgs.obtain();
525        args.argi1 = AccessibilityNodeInfo.getVirtualDescendantId(accessibilityNodeId);
526        args.argi2 = action;
527        args.argi3 = interactionId;
528        args.arg1 = callback;
529        args.arg2 = arguments;
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 perfromAccessibilityActionUiThread(Message message) {
546        final int flags = message.arg1;
547        final int accessibilityViewId = message.arg2;
548
549        SomeArgs args = (SomeArgs) message.obj;
550        final int virtualDescendantId = args.argi1;
551        final int action = args.argi2;
552        final int interactionId = args.argi3;
553        final IAccessibilityInteractionConnectionCallback callback =
554            (IAccessibilityInteractionConnectionCallback) args.arg1;
555        Bundle arguments = (Bundle) args.arg2;
556
557        args.recycle();
558
559        boolean succeeded = false;
560        try {
561            if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
562                return;
563            }
564            mViewRootImpl.mAttachInfo.mIncludeNotImportantViews =
565                (flags & INCLUDE_NOT_IMPORTANT_VIEWS) != 0;
566            View target = null;
567            if (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED) {
568                target = findViewByAccessibilityId(accessibilityViewId);
569            } else {
570                target = mViewRootImpl.mView;
571            }
572            if (target != null && isShown(target)) {
573                AccessibilityNodeProvider provider = target.getAccessibilityNodeProvider();
574                if (provider != null) {
575                    succeeded = provider.performAction(virtualDescendantId, action,
576                            arguments);
577                } else if (virtualDescendantId == View.NO_ID) {
578                    succeeded = target.performAccessibilityAction(action, arguments);
579                }
580            }
581        } finally {
582            try {
583                mViewRootImpl.mAttachInfo.mIncludeNotImportantViews = false;
584                callback.setPerformAccessibilityActionResult(succeeded, interactionId);
585            } catch (RemoteException re) {
586                /* ignore - the other side will time out */
587            }
588        }
589    }
590
591    private View findViewByAccessibilityId(int accessibilityId) {
592        View root = mViewRootImpl.mView;
593        if (root == null) {
594            return null;
595        }
596        View foundView = root.findViewByAccessibilityId(accessibilityId);
597        if (foundView != null && !isShown(foundView)) {
598            return null;
599        }
600        return foundView;
601    }
602
603    private void applyAppScaleAndMagnificationSpecIfNeeded(List<AccessibilityNodeInfo> infos,
604            MagnificationSpec spec) {
605        if (infos == null) {
606            return;
607        }
608        final float applicationScale = mViewRootImpl.mAttachInfo.mApplicationScale;
609        if (shouldApplyAppScaleAndMagnificationSpec(applicationScale, spec)) {
610            final int infoCount = infos.size();
611            for (int i = 0; i < infoCount; i++) {
612                AccessibilityNodeInfo info = infos.get(i);
613                applyAppScaleAndMagnificationSpecIfNeeded(info, spec);
614            }
615        }
616    }
617
618    private void applyAppScaleAndMagnificationSpecIfNeeded(AccessibilityNodeInfo info,
619            MagnificationSpec spec) {
620        if (info == null) {
621            return;
622        }
623
624        final float applicationScale = mViewRootImpl.mAttachInfo.mApplicationScale;
625        if (!shouldApplyAppScaleAndMagnificationSpec(applicationScale, spec)) {
626            return;
627        }
628
629        Rect boundsInParent = mTempRect;
630        Rect boundsInScreen = mTempRect1;
631
632        info.getBoundsInParent(boundsInParent);
633        info.getBoundsInScreen(boundsInScreen);
634        if (applicationScale != 1.0f) {
635            boundsInParent.scale(applicationScale);
636            boundsInScreen.scale(applicationScale);
637        }
638        if (spec != null) {
639            boundsInParent.scale(spec.scale);
640            // boundsInParent must not be offset.
641            boundsInScreen.scale(spec.scale);
642            boundsInScreen.offset((int) spec.offsetX, (int) spec.offsetY);
643        }
644        info.setBoundsInParent(boundsInParent);
645        info.setBoundsInScreen(boundsInScreen);
646
647        if (spec != null) {
648            AttachInfo attachInfo = mViewRootImpl.mAttachInfo;
649            if (attachInfo.mDisplay == null) {
650                return;
651            }
652
653            final float scale = attachInfo.mApplicationScale * spec.scale;
654
655            Rect visibleWinFrame = mTempRect1;
656            visibleWinFrame.left = (int) (attachInfo.mWindowLeft * scale + spec.offsetX);
657            visibleWinFrame.top = (int) (attachInfo.mWindowTop * scale + spec.offsetY);
658            visibleWinFrame.right = (int) (visibleWinFrame.left + mViewRootImpl.mWidth * scale);
659            visibleWinFrame.bottom = (int) (visibleWinFrame.top + mViewRootImpl.mHeight * scale);
660
661            attachInfo.mDisplay.getRealSize(mTempPoint);
662            final int displayWidth = mTempPoint.x;
663            final int displayHeight = mTempPoint.y;
664
665            Rect visibleDisplayFrame = mTempRect2;
666            visibleDisplayFrame.set(0, 0, displayWidth, displayHeight);
667
668            visibleWinFrame.intersect(visibleDisplayFrame);
669
670            if (!visibleWinFrame.intersects(boundsInScreen.left, boundsInScreen.top,
671                    boundsInScreen.right, boundsInScreen.bottom)) {
672                info.setVisibleToUser(false);
673            }
674        }
675    }
676
677    private boolean shouldApplyAppScaleAndMagnificationSpec(float appScale,
678            MagnificationSpec spec) {
679        return (appScale != 1.0f || (spec != null && !spec.isNop()));
680    }
681
682    /**
683     * This class encapsulates a prefetching strategy for the accessibility APIs for
684     * querying window content. It is responsible to prefetch a batch of
685     * AccessibilityNodeInfos in addition to the one for a requested node.
686     */
687    private class AccessibilityNodePrefetcher {
688
689        private static final int MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE = 50;
690
691        private final ArrayList<View> mTempViewList = new ArrayList<View>();
692
693        public void prefetchAccessibilityNodeInfos(View view, int virtualViewId, int prefetchFlags,
694                List<AccessibilityNodeInfo> outInfos) {
695            AccessibilityNodeProvider provider = view.getAccessibilityNodeProvider();
696            if (provider == null) {
697                AccessibilityNodeInfo root = view.createAccessibilityNodeInfo();
698                if (root != null) {
699                    outInfos.add(root);
700                    if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS) != 0) {
701                        prefetchPredecessorsOfRealNode(view, outInfos);
702                    }
703                    if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS) != 0) {
704                        prefetchSiblingsOfRealNode(view, outInfos);
705                    }
706                    if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS) != 0) {
707                        prefetchDescendantsOfRealNode(view, outInfos);
708                    }
709                }
710            } else {
711                AccessibilityNodeInfo root = provider.createAccessibilityNodeInfo(virtualViewId);
712                if (root != null) {
713                    outInfos.add(root);
714                    if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS) != 0) {
715                        prefetchPredecessorsOfVirtualNode(root, view, provider, outInfos);
716                    }
717                    if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS) != 0) {
718                        prefetchSiblingsOfVirtualNode(root, view, provider, outInfos);
719                    }
720                    if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS) != 0) {
721                        prefetchDescendantsOfVirtualNode(root, provider, outInfos);
722                    }
723                }
724            }
725        }
726
727        private void prefetchPredecessorsOfRealNode(View view,
728                List<AccessibilityNodeInfo> outInfos) {
729            ViewParent parent = view.getParentForAccessibility();
730            while (parent instanceof View
731                    && outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
732                View parentView = (View) parent;
733                AccessibilityNodeInfo info = parentView.createAccessibilityNodeInfo();
734                if (info != null) {
735                    outInfos.add(info);
736                }
737                parent = parent.getParentForAccessibility();
738            }
739        }
740
741        private void prefetchSiblingsOfRealNode(View current,
742                List<AccessibilityNodeInfo> outInfos) {
743            ViewParent parent = current.getParentForAccessibility();
744            if (parent instanceof ViewGroup) {
745                ViewGroup parentGroup = (ViewGroup) parent;
746                ArrayList<View> children = mTempViewList;
747                children.clear();
748                try {
749                    parentGroup.addChildrenForAccessibility(children);
750                    final int childCount = children.size();
751                    for (int i = 0; i < childCount; i++) {
752                        if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
753                            return;
754                        }
755                        View child = children.get(i);
756                        if (child.getAccessibilityViewId() != current.getAccessibilityViewId()
757                                &&  isShown(child)) {
758                            AccessibilityNodeInfo info = null;
759                            AccessibilityNodeProvider provider =
760                                child.getAccessibilityNodeProvider();
761                            if (provider == null) {
762                                info = child.createAccessibilityNodeInfo();
763                            } else {
764                                info = provider.createAccessibilityNodeInfo(
765                                        AccessibilityNodeInfo.UNDEFINED);
766                            }
767                            if (info != null) {
768                                outInfos.add(info);
769                            }
770                        }
771                    }
772                } finally {
773                    children.clear();
774                }
775            }
776        }
777
778        private void prefetchDescendantsOfRealNode(View root,
779                List<AccessibilityNodeInfo> outInfos) {
780            if (!(root instanceof ViewGroup)) {
781                return;
782            }
783            HashMap<View, AccessibilityNodeInfo> addedChildren =
784                new HashMap<View, AccessibilityNodeInfo>();
785            ArrayList<View> children = mTempViewList;
786            children.clear();
787            try {
788                root.addChildrenForAccessibility(children);
789                final int childCount = children.size();
790                for (int i = 0; i < childCount; i++) {
791                    if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
792                        return;
793                    }
794                    View child = children.get(i);
795                    if (isShown(child)) {
796                        AccessibilityNodeProvider provider = child.getAccessibilityNodeProvider();
797                        if (provider == null) {
798                            AccessibilityNodeInfo info = child.createAccessibilityNodeInfo();
799                            if (info != null) {
800                                outInfos.add(info);
801                                addedChildren.put(child, null);
802                            }
803                        } else {
804                            AccessibilityNodeInfo info = provider.createAccessibilityNodeInfo(
805                                   AccessibilityNodeInfo.UNDEFINED);
806                            if (info != null) {
807                                outInfos.add(info);
808                                addedChildren.put(child, info);
809                            }
810                        }
811                    }
812                }
813            } finally {
814                children.clear();
815            }
816            if (outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
817                for (Map.Entry<View, AccessibilityNodeInfo> entry : addedChildren.entrySet()) {
818                    View addedChild = entry.getKey();
819                    AccessibilityNodeInfo virtualRoot = entry.getValue();
820                    if (virtualRoot == null) {
821                        prefetchDescendantsOfRealNode(addedChild, outInfos);
822                    } else {
823                        AccessibilityNodeProvider provider =
824                            addedChild.getAccessibilityNodeProvider();
825                        prefetchDescendantsOfVirtualNode(virtualRoot, provider, outInfos);
826                    }
827                }
828            }
829        }
830
831        private void prefetchPredecessorsOfVirtualNode(AccessibilityNodeInfo root,
832                View providerHost, AccessibilityNodeProvider provider,
833                List<AccessibilityNodeInfo> outInfos) {
834            long parentNodeId = root.getParentNodeId();
835            int accessibilityViewId = AccessibilityNodeInfo.getAccessibilityViewId(parentNodeId);
836            while (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED) {
837                if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
838                    return;
839                }
840                final int virtualDescendantId =
841                    AccessibilityNodeInfo.getVirtualDescendantId(parentNodeId);
842                if (virtualDescendantId != AccessibilityNodeInfo.UNDEFINED
843                        || accessibilityViewId == providerHost.getAccessibilityViewId()) {
844                    AccessibilityNodeInfo parent = provider.createAccessibilityNodeInfo(
845                            virtualDescendantId);
846                    if (parent != null) {
847                        outInfos.add(parent);
848                    }
849                    parentNodeId = parent.getParentNodeId();
850                    accessibilityViewId = AccessibilityNodeInfo.getAccessibilityViewId(
851                            parentNodeId);
852                } else {
853                    prefetchPredecessorsOfRealNode(providerHost, outInfos);
854                    return;
855                }
856            }
857        }
858
859        private void prefetchSiblingsOfVirtualNode(AccessibilityNodeInfo current, View providerHost,
860                AccessibilityNodeProvider provider, List<AccessibilityNodeInfo> outInfos) {
861            final long parentNodeId = current.getParentNodeId();
862            final int parentAccessibilityViewId =
863                AccessibilityNodeInfo.getAccessibilityViewId(parentNodeId);
864            final int parentVirtualDescendantId =
865                AccessibilityNodeInfo.getVirtualDescendantId(parentNodeId);
866            if (parentVirtualDescendantId != AccessibilityNodeInfo.UNDEFINED
867                    || parentAccessibilityViewId == providerHost.getAccessibilityViewId()) {
868                AccessibilityNodeInfo parent =
869                    provider.createAccessibilityNodeInfo(parentVirtualDescendantId);
870                if (parent != null) {
871                    SparseLongArray childNodeIds = parent.getChildNodeIds();
872                    final int childCount = childNodeIds.size();
873                    for (int i = 0; i < childCount; i++) {
874                        if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
875                            return;
876                        }
877                        final long childNodeId = childNodeIds.get(i);
878                        if (childNodeId != current.getSourceNodeId()) {
879                            final int childVirtualDescendantId =
880                                AccessibilityNodeInfo.getVirtualDescendantId(childNodeId);
881                            AccessibilityNodeInfo child = provider.createAccessibilityNodeInfo(
882                                    childVirtualDescendantId);
883                            if (child != null) {
884                                outInfos.add(child);
885                            }
886                        }
887                    }
888                }
889            } else {
890                prefetchSiblingsOfRealNode(providerHost, outInfos);
891            }
892        }
893
894        private void prefetchDescendantsOfVirtualNode(AccessibilityNodeInfo root,
895                AccessibilityNodeProvider provider, List<AccessibilityNodeInfo> outInfos) {
896            SparseLongArray childNodeIds = root.getChildNodeIds();
897            final int initialOutInfosSize = outInfos.size();
898            final int childCount = childNodeIds.size();
899            for (int i = 0; i < childCount; i++) {
900                if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
901                    return;
902                }
903                final long childNodeId = childNodeIds.get(i);
904                AccessibilityNodeInfo child = provider.createAccessibilityNodeInfo(
905                        AccessibilityNodeInfo.getVirtualDescendantId(childNodeId));
906                if (child != null) {
907                    outInfos.add(child);
908                }
909            }
910            if (outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
911                final int addedChildCount = outInfos.size() - initialOutInfosSize;
912                for (int i = 0; i < addedChildCount; i++) {
913                    AccessibilityNodeInfo child = outInfos.get(initialOutInfosSize + i);
914                    prefetchDescendantsOfVirtualNode(child, provider, outInfos);
915                }
916            }
917        }
918    }
919
920    private class PrivateHandler extends Handler {
921        private final static int MSG_PERFORM_ACCESSIBILITY_ACTION = 1;
922        private final static int MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID = 2;
923        private final static int MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_ID = 3;
924        private final static int MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_TEXT = 4;
925        private final static int MSG_FIND_FOCUS = 5;
926        private final static int MSG_FOCUS_SEARCH = 6;
927
928        public PrivateHandler(Looper looper) {
929            super(looper);
930        }
931
932        @Override
933        public String getMessageName(Message message) {
934            final int type = message.what;
935            switch (type) {
936                case MSG_PERFORM_ACCESSIBILITY_ACTION:
937                    return "MSG_PERFORM_ACCESSIBILITY_ACTION";
938                case MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID:
939                    return "MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID";
940                case MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_ID:
941                    return "MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_ID";
942                case MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_TEXT:
943                    return "MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_TEXT";
944                case MSG_FIND_FOCUS:
945                    return "MSG_FIND_FOCUS";
946                case MSG_FOCUS_SEARCH:
947                    return "MSG_FOCUS_SEARCH";
948                default:
949                    throw new IllegalArgumentException("Unknown message type: " + type);
950            }
951        }
952
953        @Override
954        public void handleMessage(Message message) {
955            final int type = message.what;
956            switch (type) {
957                case MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID: {
958                    findAccessibilityNodeInfoByAccessibilityIdUiThread(message);
959                } break;
960                case MSG_PERFORM_ACCESSIBILITY_ACTION: {
961                    perfromAccessibilityActionUiThread(message);
962                } break;
963                case MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_ID: {
964                    findAccessibilityNodeInfoByViewIdUiThread(message);
965                } break;
966                case MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_TEXT: {
967                    findAccessibilityNodeInfosByTextUiThread(message);
968                } break;
969                case MSG_FIND_FOCUS: {
970                    findFocusUiThread(message);
971                } break;
972                case MSG_FOCUS_SEARCH: {
973                    focusSearchUiThread(message);
974                } break;
975                default:
976                    throw new IllegalArgumentException("Unknown message type: " + type);
977            }
978        }
979    }
980}
981