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