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