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