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