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