• 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 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 
computeClickPointInScreenClientThread(long accessibilityNodeId, Region interactiveRegion, int interactionId, IAccessibilityInteractionConnectionCallback callback, int interrogatingPid, long interrogatingTid, MagnificationSpec spec)639     public void computeClickPointInScreenClientThread(long accessibilityNodeId,
640             Region interactiveRegion, int interactionId,
641             IAccessibilityInteractionConnectionCallback callback, int interrogatingPid,
642             long interrogatingTid, MagnificationSpec spec) {
643         Message message = mHandler.obtainMessage();
644         message.what = PrivateHandler.MSG_COMPUTE_CLICK_POINT_IN_SCREEN;
645 
646         SomeArgs args = SomeArgs.obtain();
647         args.argi1 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId);
648         args.argi2 = AccessibilityNodeInfo.getVirtualDescendantId(accessibilityNodeId);
649         args.argi3 = interactionId;
650         args.arg1 = callback;
651         args.arg2 = spec;
652         args.arg3 = interactiveRegion;
653 
654         message.obj = args;
655 
656         // If the interrogation is performed by the same thread as the main UI
657         // thread in this process, set the message as a static reference so
658         // after this call completes the same thread but in the interrogating
659         // client can handle the message to generate the result.
660         if (interrogatingPid == mMyProcessId && interrogatingTid == mMyLooperThreadId) {
661             AccessibilityInteractionClient.getInstanceForThread(
662                     interrogatingTid).setSameThreadMessage(message);
663         } else {
664             mHandler.sendMessage(message);
665         }
666     }
667 
computeClickPointInScreenUiThread(Message message)668     private void computeClickPointInScreenUiThread(Message message) {
669         SomeArgs args = (SomeArgs) message.obj;
670         final int accessibilityViewId = args.argi1;
671         final int virtualDescendantId = args.argi2;
672         final int interactionId = args.argi3;
673         final IAccessibilityInteractionConnectionCallback callback =
674                 (IAccessibilityInteractionConnectionCallback) args.arg1;
675         final MagnificationSpec spec = (MagnificationSpec) args.arg2;
676         final Region interactiveRegion = (Region) args.arg3;
677         args.recycle();
678 
679         boolean succeeded = false;
680         Point point = mTempPoint;
681         try {
682             if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
683                 return;
684             }
685             View target = null;
686             if (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) {
687                 target = findViewByAccessibilityId(accessibilityViewId);
688             } else {
689                 target = mViewRootImpl.mView;
690             }
691             if (target != null && isShown(target)) {
692                 AccessibilityNodeProvider provider = target.getAccessibilityNodeProvider();
693                 if (provider != null) {
694                     // For virtual views just use the center of the bounds in screen.
695                     AccessibilityNodeInfo node = null;
696                     if (virtualDescendantId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) {
697                         node = provider.createAccessibilityNodeInfo(virtualDescendantId);
698                     } else {
699                         node = provider.createAccessibilityNodeInfo(
700                                 AccessibilityNodeProvider.HOST_VIEW_ID);
701                     }
702                     if (node != null) {
703                         succeeded = true;
704                         Rect boundsInScreen = mTempRect;
705                         node.getBoundsInScreen(boundsInScreen);
706                         point.set(boundsInScreen.centerX(), boundsInScreen.centerY());
707                     }
708                 } else if (virtualDescendantId == AccessibilityNodeInfo.UNDEFINED_ITEM_ID) {
709                     // For a real view, ask the view to compute the click point.
710                     succeeded = target.computeClickPointInScreenForAccessibility(
711                             interactiveRegion, point);
712                 }
713             }
714         } finally {
715             try {
716                 Point result = null;
717                 if (succeeded) {
718                     applyAppScaleAndMagnificationSpecIfNeeded(point, spec);
719                     result = point;
720                 }
721                 callback.setComputeClickPointInScreenActionResult(result, interactionId);
722             } catch (RemoteException re) {
723                 /* ignore - the other side will time out */
724             }
725         }
726     }
727 
findViewByAccessibilityId(int accessibilityId)728     private View findViewByAccessibilityId(int accessibilityId) {
729         View root = mViewRootImpl.mView;
730         if (root == null) {
731             return null;
732         }
733         View foundView = root.findViewByAccessibilityId(accessibilityId);
734         if (foundView != null && !isShown(foundView)) {
735             return null;
736         }
737         return foundView;
738     }
739 
applyAppScaleAndMagnificationSpecIfNeeded(List<AccessibilityNodeInfo> infos, MagnificationSpec spec)740     private void applyAppScaleAndMagnificationSpecIfNeeded(List<AccessibilityNodeInfo> infos,
741             MagnificationSpec spec) {
742         if (infos == null) {
743             return;
744         }
745         final float applicationScale = mViewRootImpl.mAttachInfo.mApplicationScale;
746         if (shouldApplyAppScaleAndMagnificationSpec(applicationScale, spec)) {
747             final int infoCount = infos.size();
748             for (int i = 0; i < infoCount; i++) {
749                 AccessibilityNodeInfo info = infos.get(i);
750                 applyAppScaleAndMagnificationSpecIfNeeded(info, spec);
751             }
752         }
753     }
754 
adjustIsVisibleToUserIfNeeded(List<AccessibilityNodeInfo> infos, Region interactiveRegion)755     private void adjustIsVisibleToUserIfNeeded(List<AccessibilityNodeInfo> infos,
756             Region interactiveRegion) {
757         if (interactiveRegion == null || infos == null) {
758             return;
759         }
760         final int infoCount = infos.size();
761         for (int i = 0; i < infoCount; i++) {
762             AccessibilityNodeInfo info = infos.get(i);
763             adjustIsVisibleToUserIfNeeded(info, interactiveRegion);
764         }
765     }
766 
adjustIsVisibleToUserIfNeeded(AccessibilityNodeInfo info, Region interactiveRegion)767     private void adjustIsVisibleToUserIfNeeded(AccessibilityNodeInfo info,
768             Region interactiveRegion) {
769         if (interactiveRegion == null || info == null) {
770             return;
771         }
772         Rect boundsInScreen = mTempRect;
773         info.getBoundsInScreen(boundsInScreen);
774         if (interactiveRegion.quickReject(boundsInScreen)) {
775             info.setVisibleToUser(false);
776         }
777     }
778 
applyAppScaleAndMagnificationSpecIfNeeded(Point point, MagnificationSpec spec)779     private void applyAppScaleAndMagnificationSpecIfNeeded(Point point,
780             MagnificationSpec spec) {
781         final float applicationScale = mViewRootImpl.mAttachInfo.mApplicationScale;
782         if (!shouldApplyAppScaleAndMagnificationSpec(applicationScale, spec)) {
783             return;
784         }
785 
786         if (applicationScale != 1.0f) {
787             point.x *= applicationScale;
788             point.y *= applicationScale;
789         }
790 
791         if (spec != null) {
792             point.x *= spec.scale;
793             point.y *= spec.scale;
794             point.x += (int) spec.offsetX;
795             point.y += (int) spec.offsetY;
796         }
797     }
798 
applyAppScaleAndMagnificationSpecIfNeeded(AccessibilityNodeInfo info, MagnificationSpec spec)799     private void applyAppScaleAndMagnificationSpecIfNeeded(AccessibilityNodeInfo info,
800             MagnificationSpec spec) {
801         if (info == null) {
802             return;
803         }
804 
805         final float applicationScale = mViewRootImpl.mAttachInfo.mApplicationScale;
806         if (!shouldApplyAppScaleAndMagnificationSpec(applicationScale, spec)) {
807             return;
808         }
809 
810         Rect boundsInParent = mTempRect;
811         Rect boundsInScreen = mTempRect1;
812 
813         info.getBoundsInParent(boundsInParent);
814         info.getBoundsInScreen(boundsInScreen);
815         if (applicationScale != 1.0f) {
816             boundsInParent.scale(applicationScale);
817             boundsInScreen.scale(applicationScale);
818         }
819         if (spec != null) {
820             boundsInParent.scale(spec.scale);
821             // boundsInParent must not be offset.
822             boundsInScreen.scale(spec.scale);
823             boundsInScreen.offset((int) spec.offsetX, (int) spec.offsetY);
824         }
825         info.setBoundsInParent(boundsInParent);
826         info.setBoundsInScreen(boundsInScreen);
827 
828         if (spec != null) {
829             AttachInfo attachInfo = mViewRootImpl.mAttachInfo;
830             if (attachInfo.mDisplay == null) {
831                 return;
832             }
833 
834             final float scale = attachInfo.mApplicationScale * spec.scale;
835 
836             Rect visibleWinFrame = mTempRect1;
837             visibleWinFrame.left = (int) (attachInfo.mWindowLeft * scale + spec.offsetX);
838             visibleWinFrame.top = (int) (attachInfo.mWindowTop * scale + spec.offsetY);
839             visibleWinFrame.right = (int) (visibleWinFrame.left + mViewRootImpl.mWidth * scale);
840             visibleWinFrame.bottom = (int) (visibleWinFrame.top + mViewRootImpl.mHeight * scale);
841 
842             attachInfo.mDisplay.getRealSize(mTempPoint);
843             final int displayWidth = mTempPoint.x;
844             final int displayHeight = mTempPoint.y;
845 
846             Rect visibleDisplayFrame = mTempRect2;
847             visibleDisplayFrame.set(0, 0, displayWidth, displayHeight);
848 
849             visibleWinFrame.intersect(visibleDisplayFrame);
850 
851             if (!visibleWinFrame.intersects(boundsInScreen.left, boundsInScreen.top,
852                     boundsInScreen.right, boundsInScreen.bottom)) {
853                 info.setVisibleToUser(false);
854             }
855         }
856     }
857 
shouldApplyAppScaleAndMagnificationSpec(float appScale, MagnificationSpec spec)858     private boolean shouldApplyAppScaleAndMagnificationSpec(float appScale,
859             MagnificationSpec spec) {
860         return (appScale != 1.0f || (spec != null && !spec.isNop()));
861     }
862 
863     /**
864      * This class encapsulates a prefetching strategy for the accessibility APIs for
865      * querying window content. It is responsible to prefetch a batch of
866      * AccessibilityNodeInfos in addition to the one for a requested node.
867      */
868     private class AccessibilityNodePrefetcher {
869 
870         private static final int MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE = 50;
871 
872         private final ArrayList<View> mTempViewList = new ArrayList<View>();
873 
prefetchAccessibilityNodeInfos(View view, int virtualViewId, int fetchFlags, List<AccessibilityNodeInfo> outInfos)874         public void prefetchAccessibilityNodeInfos(View view, int virtualViewId, int fetchFlags,
875                 List<AccessibilityNodeInfo> outInfos) {
876             AccessibilityNodeProvider provider = view.getAccessibilityNodeProvider();
877             if (provider == null) {
878                 AccessibilityNodeInfo root = view.createAccessibilityNodeInfo();
879                 if (root != null) {
880                     outInfos.add(root);
881                     if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS) != 0) {
882                         prefetchPredecessorsOfRealNode(view, outInfos);
883                     }
884                     if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS) != 0) {
885                         prefetchSiblingsOfRealNode(view, outInfos);
886                     }
887                     if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS) != 0) {
888                         prefetchDescendantsOfRealNode(view, outInfos);
889                     }
890                 }
891             } else {
892                 final AccessibilityNodeInfo root;
893                 if (virtualViewId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) {
894                     root = provider.createAccessibilityNodeInfo(virtualViewId);
895                 } else {
896                     root = provider.createAccessibilityNodeInfo(
897                             AccessibilityNodeProvider.HOST_VIEW_ID);
898                 }
899                 if (root != null) {
900                     outInfos.add(root);
901                     if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS) != 0) {
902                         prefetchPredecessorsOfVirtualNode(root, view, provider, outInfos);
903                     }
904                     if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS) != 0) {
905                         prefetchSiblingsOfVirtualNode(root, view, provider, outInfos);
906                     }
907                     if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS) != 0) {
908                         prefetchDescendantsOfVirtualNode(root, provider, outInfos);
909                     }
910                 }
911             }
912             if (ENFORCE_NODE_TREE_CONSISTENT) {
913                 enforceNodeTreeConsistent(outInfos);
914             }
915         }
916 
enforceNodeTreeConsistent(List<AccessibilityNodeInfo> nodes)917         private void enforceNodeTreeConsistent(List<AccessibilityNodeInfo> nodes) {
918             LongSparseArray<AccessibilityNodeInfo> nodeMap =
919                     new LongSparseArray<AccessibilityNodeInfo>();
920             final int nodeCount = nodes.size();
921             for (int i = 0; i < nodeCount; i++) {
922                 AccessibilityNodeInfo node = nodes.get(i);
923                 nodeMap.put(node.getSourceNodeId(), node);
924             }
925 
926             // If the nodes are a tree it does not matter from
927             // which node we start to search for the root.
928             AccessibilityNodeInfo root = nodeMap.valueAt(0);
929             AccessibilityNodeInfo parent = root;
930             while (parent != null) {
931                 root = parent;
932                 parent = nodeMap.get(parent.getParentNodeId());
933             }
934 
935             // Traverse the tree and do some checks.
936             AccessibilityNodeInfo accessFocus = null;
937             AccessibilityNodeInfo inputFocus = null;
938             HashSet<AccessibilityNodeInfo> seen = new HashSet<AccessibilityNodeInfo>();
939             Queue<AccessibilityNodeInfo> fringe = new LinkedList<AccessibilityNodeInfo>();
940             fringe.add(root);
941 
942             while (!fringe.isEmpty()) {
943                 AccessibilityNodeInfo current = fringe.poll();
944 
945                 // Check for duplicates
946                 if (!seen.add(current)) {
947                     throw new IllegalStateException("Duplicate node: "
948                             + current + " in window:"
949                             + mViewRootImpl.mAttachInfo.mAccessibilityWindowId);
950                 }
951 
952                 // Check for one accessibility focus.
953                 if (current.isAccessibilityFocused()) {
954                     if (accessFocus != null) {
955                         throw new IllegalStateException("Duplicate accessibility focus:"
956                                 + current
957                                 + " in window:" + mViewRootImpl.mAttachInfo.mAccessibilityWindowId);
958                     } else {
959                         accessFocus = current;
960                     }
961                 }
962 
963                 // Check for one input focus.
964                 if (current.isFocused()) {
965                     if (inputFocus != null) {
966                         throw new IllegalStateException("Duplicate input focus: "
967                             + current + " in window:"
968                             + mViewRootImpl.mAttachInfo.mAccessibilityWindowId);
969                     } else {
970                         inputFocus = current;
971                     }
972                 }
973 
974                 final int childCount = current.getChildCount();
975                 for (int j = 0; j < childCount; j++) {
976                     final long childId = current.getChildId(j);
977                     final AccessibilityNodeInfo child = nodeMap.get(childId);
978                     if (child != null) {
979                         fringe.add(child);
980                     }
981                 }
982             }
983 
984             // Check for disconnected nodes.
985             for (int j = nodeMap.size() - 1; j >= 0; j--) {
986                 AccessibilityNodeInfo info = nodeMap.valueAt(j);
987                 if (!seen.contains(info)) {
988                     throw new IllegalStateException("Disconnected node: " + info);
989                 }
990             }
991         }
992 
prefetchPredecessorsOfRealNode(View view, List<AccessibilityNodeInfo> outInfos)993         private void prefetchPredecessorsOfRealNode(View view,
994                 List<AccessibilityNodeInfo> outInfos) {
995             ViewParent parent = view.getParentForAccessibility();
996             while (parent instanceof View
997                     && outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
998                 View parentView = (View) parent;
999                 AccessibilityNodeInfo info = parentView.createAccessibilityNodeInfo();
1000                 if (info != null) {
1001                     outInfos.add(info);
1002                 }
1003                 parent = parent.getParentForAccessibility();
1004             }
1005         }
1006 
prefetchSiblingsOfRealNode(View current, List<AccessibilityNodeInfo> outInfos)1007         private void prefetchSiblingsOfRealNode(View current,
1008                 List<AccessibilityNodeInfo> outInfos) {
1009             ViewParent parent = current.getParentForAccessibility();
1010             if (parent instanceof ViewGroup) {
1011                 ViewGroup parentGroup = (ViewGroup) parent;
1012                 ArrayList<View> children = mTempViewList;
1013                 children.clear();
1014                 try {
1015                     parentGroup.addChildrenForAccessibility(children);
1016                     final int childCount = children.size();
1017                     for (int i = 0; i < childCount; i++) {
1018                         if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
1019                             return;
1020                         }
1021                         View child = children.get(i);
1022                         if (child.getAccessibilityViewId() != current.getAccessibilityViewId()
1023                                 &&  isShown(child)) {
1024                             AccessibilityNodeInfo info = null;
1025                             AccessibilityNodeProvider provider =
1026                                 child.getAccessibilityNodeProvider();
1027                             if (provider == null) {
1028                                 info = child.createAccessibilityNodeInfo();
1029                             } else {
1030                                 info = provider.createAccessibilityNodeInfo(
1031                                         AccessibilityNodeProvider.HOST_VIEW_ID);
1032                             }
1033                             if (info != null) {
1034                                 outInfos.add(info);
1035                             }
1036                         }
1037                     }
1038                 } finally {
1039                     children.clear();
1040                 }
1041             }
1042         }
1043 
prefetchDescendantsOfRealNode(View root, List<AccessibilityNodeInfo> outInfos)1044         private void prefetchDescendantsOfRealNode(View root,
1045                 List<AccessibilityNodeInfo> outInfos) {
1046             if (!(root instanceof ViewGroup)) {
1047                 return;
1048             }
1049             HashMap<View, AccessibilityNodeInfo> addedChildren =
1050                 new HashMap<View, AccessibilityNodeInfo>();
1051             ArrayList<View> children = mTempViewList;
1052             children.clear();
1053             try {
1054                 root.addChildrenForAccessibility(children);
1055                 final int childCount = children.size();
1056                 for (int i = 0; i < childCount; i++) {
1057                     if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
1058                         return;
1059                     }
1060                     View child = children.get(i);
1061                     if (isShown(child)) {
1062                         AccessibilityNodeProvider provider = child.getAccessibilityNodeProvider();
1063                         if (provider == null) {
1064                             AccessibilityNodeInfo info = child.createAccessibilityNodeInfo();
1065                             if (info != null) {
1066                                 outInfos.add(info);
1067                                 addedChildren.put(child, null);
1068                             }
1069                         } else {
1070                             AccessibilityNodeInfo info = provider.createAccessibilityNodeInfo(
1071                                    AccessibilityNodeProvider.HOST_VIEW_ID);
1072                             if (info != null) {
1073                                 outInfos.add(info);
1074                                 addedChildren.put(child, info);
1075                             }
1076                         }
1077                     }
1078                 }
1079             } finally {
1080                 children.clear();
1081             }
1082             if (outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
1083                 for (Map.Entry<View, AccessibilityNodeInfo> entry : addedChildren.entrySet()) {
1084                     View addedChild = entry.getKey();
1085                     AccessibilityNodeInfo virtualRoot = entry.getValue();
1086                     if (virtualRoot == null) {
1087                         prefetchDescendantsOfRealNode(addedChild, outInfos);
1088                     } else {
1089                         AccessibilityNodeProvider provider =
1090                             addedChild.getAccessibilityNodeProvider();
1091                         prefetchDescendantsOfVirtualNode(virtualRoot, provider, outInfos);
1092                     }
1093                 }
1094             }
1095         }
1096 
prefetchPredecessorsOfVirtualNode(AccessibilityNodeInfo root, View providerHost, AccessibilityNodeProvider provider, List<AccessibilityNodeInfo> outInfos)1097         private void prefetchPredecessorsOfVirtualNode(AccessibilityNodeInfo root,
1098                 View providerHost, AccessibilityNodeProvider provider,
1099                 List<AccessibilityNodeInfo> outInfos) {
1100             long parentNodeId = root.getParentNodeId();
1101             int accessibilityViewId = AccessibilityNodeInfo.getAccessibilityViewId(parentNodeId);
1102             while (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) {
1103                 if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
1104                     return;
1105                 }
1106                 final int virtualDescendantId =
1107                     AccessibilityNodeInfo.getVirtualDescendantId(parentNodeId);
1108                 if (virtualDescendantId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID
1109                         || accessibilityViewId == providerHost.getAccessibilityViewId()) {
1110                     final AccessibilityNodeInfo parent;
1111                     if (virtualDescendantId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) {
1112                         parent = provider.createAccessibilityNodeInfo(
1113                                 virtualDescendantId);
1114                     } else {
1115                         parent= provider.createAccessibilityNodeInfo(
1116                                 AccessibilityNodeProvider.HOST_VIEW_ID);
1117                     }
1118                     if (parent != null) {
1119                         outInfos.add(parent);
1120                     }
1121                     parentNodeId = parent.getParentNodeId();
1122                     accessibilityViewId = AccessibilityNodeInfo.getAccessibilityViewId(
1123                             parentNodeId);
1124                 } else {
1125                     prefetchPredecessorsOfRealNode(providerHost, outInfos);
1126                     return;
1127                 }
1128             }
1129         }
1130 
prefetchSiblingsOfVirtualNode(AccessibilityNodeInfo current, View providerHost, AccessibilityNodeProvider provider, List<AccessibilityNodeInfo> outInfos)1131         private void prefetchSiblingsOfVirtualNode(AccessibilityNodeInfo current, View providerHost,
1132                 AccessibilityNodeProvider provider, List<AccessibilityNodeInfo> outInfos) {
1133             final long parentNodeId = current.getParentNodeId();
1134             final int parentAccessibilityViewId =
1135                 AccessibilityNodeInfo.getAccessibilityViewId(parentNodeId);
1136             final int parentVirtualDescendantId =
1137                 AccessibilityNodeInfo.getVirtualDescendantId(parentNodeId);
1138             if (parentVirtualDescendantId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID
1139                     || parentAccessibilityViewId == providerHost.getAccessibilityViewId()) {
1140                 final AccessibilityNodeInfo parent;
1141                 if (parentVirtualDescendantId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) {
1142                     parent = provider.createAccessibilityNodeInfo(parentVirtualDescendantId);
1143                 } else {
1144                     parent = provider.createAccessibilityNodeInfo(
1145                             AccessibilityNodeProvider.HOST_VIEW_ID);
1146                 }
1147                 if (parent != null) {
1148                     final int childCount = parent.getChildCount();
1149                     for (int i = 0; i < childCount; i++) {
1150                         if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
1151                             return;
1152                         }
1153                         final long childNodeId = parent.getChildId(i);
1154                         if (childNodeId != current.getSourceNodeId()) {
1155                             final int childVirtualDescendantId =
1156                                 AccessibilityNodeInfo.getVirtualDescendantId(childNodeId);
1157                             AccessibilityNodeInfo child = provider.createAccessibilityNodeInfo(
1158                                     childVirtualDescendantId);
1159                             if (child != null) {
1160                                 outInfos.add(child);
1161                             }
1162                         }
1163                     }
1164                 }
1165             } else {
1166                 prefetchSiblingsOfRealNode(providerHost, outInfos);
1167             }
1168         }
1169 
prefetchDescendantsOfVirtualNode(AccessibilityNodeInfo root, AccessibilityNodeProvider provider, List<AccessibilityNodeInfo> outInfos)1170         private void prefetchDescendantsOfVirtualNode(AccessibilityNodeInfo root,
1171                 AccessibilityNodeProvider provider, List<AccessibilityNodeInfo> outInfos) {
1172             final int initialOutInfosSize = outInfos.size();
1173             final int childCount = root.getChildCount();
1174             for (int i = 0; i < childCount; i++) {
1175                 if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
1176                     return;
1177                 }
1178                 final long childNodeId = root.getChildId(i);
1179                 AccessibilityNodeInfo child = provider.createAccessibilityNodeInfo(
1180                         AccessibilityNodeInfo.getVirtualDescendantId(childNodeId));
1181                 if (child != null) {
1182                     outInfos.add(child);
1183                 }
1184             }
1185             if (outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
1186                 final int addedChildCount = outInfos.size() - initialOutInfosSize;
1187                 for (int i = 0; i < addedChildCount; i++) {
1188                     AccessibilityNodeInfo child = outInfos.get(initialOutInfosSize + i);
1189                     prefetchDescendantsOfVirtualNode(child, provider, outInfos);
1190                 }
1191             }
1192         }
1193     }
1194 
1195     private class PrivateHandler extends Handler {
1196         private final static int MSG_PERFORM_ACCESSIBILITY_ACTION = 1;
1197         private final static int MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_ACCESSIBILITY_ID = 2;
1198         private final static int MSG_FIND_ACCESSIBILITY_NODE_INFOS_BY_VIEW_ID = 3;
1199         private final static int MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_TEXT = 4;
1200         private final static int MSG_FIND_FOCUS = 5;
1201         private final static int MSG_FOCUS_SEARCH = 6;
1202         private final static int MSG_COMPUTE_CLICK_POINT_IN_SCREEN = 7;
1203 
PrivateHandler(Looper looper)1204         public PrivateHandler(Looper looper) {
1205             super(looper);
1206         }
1207 
1208         @Override
getMessageName(Message message)1209         public String getMessageName(Message message) {
1210             final int type = message.what;
1211             switch (type) {
1212                 case MSG_PERFORM_ACCESSIBILITY_ACTION:
1213                     return "MSG_PERFORM_ACCESSIBILITY_ACTION";
1214                 case MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_ACCESSIBILITY_ID:
1215                     return "MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_ACCESSIBILITY_ID";
1216                 case MSG_FIND_ACCESSIBILITY_NODE_INFOS_BY_VIEW_ID:
1217                     return "MSG_FIND_ACCESSIBILITY_NODE_INFOS_BY_VIEW_ID";
1218                 case MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_TEXT:
1219                     return "MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_TEXT";
1220                 case MSG_FIND_FOCUS:
1221                     return "MSG_FIND_FOCUS";
1222                 case MSG_FOCUS_SEARCH:
1223                     return "MSG_FOCUS_SEARCH";
1224                 case MSG_COMPUTE_CLICK_POINT_IN_SCREEN:
1225                     return "MSG_COMPUTE_CLICK_POINT_IN_SCREEN";
1226                 default:
1227                     throw new IllegalArgumentException("Unknown message type: " + type);
1228             }
1229         }
1230 
1231         @Override
handleMessage(Message message)1232         public void handleMessage(Message message) {
1233             final int type = message.what;
1234             switch (type) {
1235                 case MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_ACCESSIBILITY_ID: {
1236                     findAccessibilityNodeInfoByAccessibilityIdUiThread(message);
1237                 } break;
1238                 case MSG_PERFORM_ACCESSIBILITY_ACTION: {
1239                     perfromAccessibilityActionUiThread(message);
1240                 } break;
1241                 case MSG_FIND_ACCESSIBILITY_NODE_INFOS_BY_VIEW_ID: {
1242                     findAccessibilityNodeInfosByViewIdUiThread(message);
1243                 } break;
1244                 case MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_TEXT: {
1245                     findAccessibilityNodeInfosByTextUiThread(message);
1246                 } break;
1247                 case MSG_FIND_FOCUS: {
1248                     findFocusUiThread(message);
1249                 } break;
1250                 case MSG_FOCUS_SEARCH: {
1251                     focusSearchUiThread(message);
1252                 } break;
1253                 case MSG_COMPUTE_CLICK_POINT_IN_SCREEN: {
1254                     computeClickPointInScreenUiThread(message);
1255                 } break;
1256                 default:
1257                     throw new IllegalArgumentException("Unknown message type: " + type);
1258             }
1259         }
1260     }
1261 
1262     private final class AddNodeInfosForViewId implements Predicate<View> {
1263         private int mViewId = View.NO_ID;
1264         private List<AccessibilityNodeInfo> mInfos;
1265 
init(int viewId, List<AccessibilityNodeInfo> infos)1266         public void init(int viewId, List<AccessibilityNodeInfo> infos) {
1267             mViewId = viewId;
1268             mInfos = infos;
1269         }
1270 
reset()1271         public void reset() {
1272             mViewId = View.NO_ID;
1273             mInfos = null;
1274         }
1275 
1276         @Override
apply(View view)1277         public boolean apply(View view) {
1278             if (view.getId() == mViewId && isShown(view)) {
1279                 mInfos.add(view.createAccessibilityNodeInfo());
1280             }
1281             return false;
1282         }
1283     }
1284 }
1285