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