• 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.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
20 import static android.view.accessibility.AccessibilityNodeInfo.ACTION_ARGUMENT_ACCESSIBLE_CLICKABLE_SPAN;
21 import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_REQUESTED_KEY;
22 import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY;
23 
24 import android.graphics.Matrix;
25 import android.graphics.Rect;
26 import android.graphics.RectF;
27 import android.graphics.Region;
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.os.SystemClock;
36 import android.text.style.AccessibilityClickableSpan;
37 import android.text.style.ClickableSpan;
38 import android.util.LongSparseArray;
39 import android.util.Slog;
40 import android.view.accessibility.AccessibilityInteractionClient;
41 import android.view.accessibility.AccessibilityManager;
42 import android.view.accessibility.AccessibilityNodeIdManager;
43 import android.view.accessibility.AccessibilityNodeInfo;
44 import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
45 import android.view.accessibility.AccessibilityNodeProvider;
46 import android.view.accessibility.AccessibilityRequestPreparer;
47 import android.view.accessibility.IAccessibilityInteractionConnectionCallback;
48 
49 import com.android.internal.R;
50 import com.android.internal.annotations.GuardedBy;
51 import com.android.internal.annotations.VisibleForTesting;
52 import com.android.internal.os.SomeArgs;
53 
54 import java.util.ArrayDeque;
55 import java.util.ArrayList;
56 import java.util.HashSet;
57 import java.util.LinkedHashMap;
58 import java.util.LinkedList;
59 import java.util.List;
60 import java.util.Map;
61 import java.util.Queue;
62 import java.util.function.Predicate;
63 
64 /**
65  * Class for managing accessibility interactions initiated from the system
66  * and targeting the view hierarchy. A *ClientThread method is to be
67  * called from the interaction connection ViewAncestor gives the system to
68  * talk to it and a corresponding *UiThread method that is executed on the
69  * UI thread.
70  *
71  * @hide
72  */
73 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
74 public final class AccessibilityInteractionController {
75 
76     private static final String LOG_TAG = "AccessibilityInteractionController";
77 
78     // Debugging flag
79     private static final boolean ENFORCE_NODE_TREE_CONSISTENT = false;
80 
81     // Constants for readability
82     private static final boolean IGNORE_REQUEST_PREPARERS = true;
83     private static final boolean CONSIDER_REQUEST_PREPARERS = false;
84 
85     // If an app holds off accessibility for longer than this, the hold-off is canceled to prevent
86     // accessibility from hanging
87     private static final long REQUEST_PREPARER_TIMEOUT_MS = 500;
88 
89     // Callbacks should have the same configuration of the flags below to allow satisfying a pending
90     // node request on prefetch
91     private static final int FLAGS_AFFECTING_REPORTED_DATA =
92             AccessibilityNodeInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS
93             | AccessibilityNodeInfo.FLAG_REPORT_VIEW_IDS;
94 
95     private final ArrayList<AccessibilityNodeInfo> mTempAccessibilityNodeInfoList =
96         new ArrayList<AccessibilityNodeInfo>();
97 
98     private final Object mLock = new Object();
99 
100     private final PrivateHandler mHandler;
101 
102     private final ViewRootImpl mViewRootImpl;
103 
104     private final AccessibilityNodePrefetcher mPrefetcher;
105 
106     private final long mMyLooperThreadId;
107 
108     private final int mMyProcessId;
109 
110     private final AccessibilityManager mA11yManager;
111 
112     private final ArrayList<View> mTempArrayList = new ArrayList<View>();
113 
114     private final Rect mTempRect = new Rect();
115     private final RectF mTempRectF = new RectF();
116 
117     private AddNodeInfosForViewId mAddNodeInfosForViewId;
118 
119     @GuardedBy("mLock")
120     private ArrayList<Message> mPendingFindNodeByIdMessages;
121 
122     @GuardedBy("mLock")
123     private int mNumActiveRequestPreparers;
124     @GuardedBy("mLock")
125     private List<MessageHolder> mMessagesWaitingForRequestPreparer;
126     @GuardedBy("mLock")
127     private int mActiveRequestPreparerId;
128 
AccessibilityInteractionController(ViewRootImpl viewRootImpl)129     public AccessibilityInteractionController(ViewRootImpl viewRootImpl) {
130         Looper looper = viewRootImpl.mHandler.getLooper();
131         mMyLooperThreadId = looper.getThread().getId();
132         mMyProcessId = Process.myPid();
133         mHandler = new PrivateHandler(looper);
134         mViewRootImpl = viewRootImpl;
135         mPrefetcher = new AccessibilityNodePrefetcher();
136         mA11yManager = mViewRootImpl.mContext.getSystemService(AccessibilityManager.class);
137         mPendingFindNodeByIdMessages = new ArrayList<>();
138     }
139 
scheduleMessage(Message message, int interrogatingPid, long interrogatingTid, boolean ignoreRequestPreparers)140     private void scheduleMessage(Message message, int interrogatingPid, long interrogatingTid,
141             boolean ignoreRequestPreparers) {
142         if (ignoreRequestPreparers
143                 || !holdOffMessageIfNeeded(message, interrogatingPid, interrogatingTid)) {
144             // If the interrogation is performed by the same thread as the main UI
145             // thread in this process, set the message as a static reference so
146             // after this call completes the same thread but in the interrogating
147             // client can handle the message to generate the result.
148             if (interrogatingPid == mMyProcessId && interrogatingTid == mMyLooperThreadId
149                     && mHandler.hasAccessibilityCallback(message)) {
150                 AccessibilityInteractionClient.getInstanceForThread(
151                         interrogatingTid).setSameThreadMessage(message);
152             } else {
153                 // For messages without callback of interrogating client, just handle the
154                 // message immediately if this is UI thread.
155                 if (!mHandler.hasAccessibilityCallback(message)
156                         && Thread.currentThread().getId() == mMyLooperThreadId) {
157                     mHandler.handleMessage(message);
158                 } else {
159                     mHandler.sendMessage(message);
160                 }
161             }
162         }
163     }
164 
isShown(View view)165     private boolean isShown(View view) {
166         return (view != null) && (view.getWindowVisibility() == View.VISIBLE && view.isShown());
167     }
168 
findAccessibilityNodeInfoByAccessibilityIdClientThread( long accessibilityNodeId, Region interactiveRegion, int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid, long interrogatingTid, MagnificationSpec spec, float[] matrixValues, Bundle arguments)169     public void findAccessibilityNodeInfoByAccessibilityIdClientThread(
170             long accessibilityNodeId, Region interactiveRegion, int interactionId,
171             IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid,
172             long interrogatingTid, MagnificationSpec spec, float[] matrixValues,
173             Bundle arguments) {
174         final Message message = mHandler.obtainMessage();
175         message.what = PrivateHandler.MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_ACCESSIBILITY_ID;
176         message.arg1 = flags;
177 
178         final SomeArgs args = SomeArgs.obtain();
179         args.argi1 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId);
180         args.argi2 = AccessibilityNodeInfo.getVirtualDescendantId(accessibilityNodeId);
181         args.argi3 = interactionId;
182         args.arg1 = callback;
183         args.arg2 = spec;
184         args.arg3 = interactiveRegion;
185         args.arg4 = arguments;
186         args.arg5 = matrixValues;
187         message.obj = args;
188 
189         synchronized (mLock) {
190             mPendingFindNodeByIdMessages.add(message);
191             scheduleMessage(message, interrogatingPid, interrogatingTid,
192                     CONSIDER_REQUEST_PREPARERS);
193         }
194     }
195 
196     /**
197      * Check if this message needs to be held off while the app prepares to meet either this
198      * request, or a request ahead of it.
199      *
200      * @param originalMessage The message to be processed
201      * @param callingPid The calling process id
202      * @param callingTid The calling thread id
203      *
204      * @return {@code true} if the message is held off and will be processed later, {@code false} if
205      *         the message should be posted.
206      */
holdOffMessageIfNeeded( Message originalMessage, int callingPid, long callingTid)207     private boolean holdOffMessageIfNeeded(
208             Message originalMessage, int callingPid, long callingTid) {
209         synchronized (mLock) {
210             // If a request is already pending, queue this request for when it's finished
211             if (mNumActiveRequestPreparers != 0) {
212                 queueMessageToHandleOncePrepared(originalMessage, callingPid, callingTid);
213                 return true;
214             }
215 
216             // Currently the only message that can hold things off is findByA11yId with extra data.
217             if (originalMessage.what
218                     != PrivateHandler.MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_ACCESSIBILITY_ID) {
219                 return false;
220             }
221             SomeArgs originalMessageArgs = (SomeArgs) originalMessage.obj;
222             Bundle requestArguments = (Bundle) originalMessageArgs.arg4;
223             if (requestArguments == null) {
224                 return false;
225             }
226 
227             // If nothing it registered for this view, nothing to do
228             int accessibilityViewId = originalMessageArgs.argi1;
229             final List<AccessibilityRequestPreparer> preparers =
230                     mA11yManager.getRequestPreparersForAccessibilityId(accessibilityViewId);
231             if (preparers == null) {
232                 return false;
233             }
234 
235             // If the bundle doesn't request the extra data, nothing to do
236             final String extraDataKey = requestArguments.getString(EXTRA_DATA_REQUESTED_KEY);
237             if (extraDataKey == null) {
238                 return false;
239             }
240 
241             // Send the request to the AccessibilityRequestPreparers on the UI thread
242             mNumActiveRequestPreparers = preparers.size();
243             for (int i = 0; i < preparers.size(); i++) {
244                 final Message requestPreparerMessage = mHandler.obtainMessage(
245                         PrivateHandler.MSG_PREPARE_FOR_EXTRA_DATA_REQUEST);
246                 final SomeArgs requestPreparerArgs = SomeArgs.obtain();
247                 // virtualDescendentId
248                 requestPreparerArgs.argi1 =
249                         (originalMessageArgs.argi2 == AccessibilityNodeInfo.UNDEFINED_ITEM_ID)
250                         ? AccessibilityNodeProvider.HOST_VIEW_ID : originalMessageArgs.argi2;
251                 requestPreparerArgs.arg1 = preparers.get(i);
252                 requestPreparerArgs.arg2 = extraDataKey;
253                 requestPreparerArgs.arg3 = requestArguments;
254                 Message preparationFinishedMessage = mHandler.obtainMessage(
255                         PrivateHandler.MSG_APP_PREPARATION_FINISHED);
256                 preparationFinishedMessage.arg1 = ++mActiveRequestPreparerId;
257                 requestPreparerArgs.arg4 = preparationFinishedMessage;
258 
259                 requestPreparerMessage.obj = requestPreparerArgs;
260                 scheduleMessage(requestPreparerMessage, callingPid, callingTid,
261                         IGNORE_REQUEST_PREPARERS);
262                 mHandler.obtainMessage(PrivateHandler.MSG_APP_PREPARATION_TIMEOUT);
263                 mHandler.sendEmptyMessageDelayed(PrivateHandler.MSG_APP_PREPARATION_TIMEOUT,
264                         REQUEST_PREPARER_TIMEOUT_MS);
265             }
266 
267             // Set the initial request aside
268             queueMessageToHandleOncePrepared(originalMessage, callingPid, callingTid);
269             return true;
270         }
271     }
272 
prepareForExtraDataRequestUiThread(Message message)273     private void prepareForExtraDataRequestUiThread(Message message) {
274         SomeArgs args = (SomeArgs) message.obj;
275         final int virtualDescendantId = args.argi1;
276         final AccessibilityRequestPreparer preparer = (AccessibilityRequestPreparer) args.arg1;
277         final String extraDataKey = (String) args.arg2;
278         final Bundle requestArguments = (Bundle) args.arg3;
279         final Message preparationFinishedMessage = (Message) args.arg4;
280 
281         preparer.onPrepareExtraData(virtualDescendantId, extraDataKey,
282                 requestArguments, preparationFinishedMessage);
283     }
284 
queueMessageToHandleOncePrepared(Message message, int interrogatingPid, long interrogatingTid)285     private void queueMessageToHandleOncePrepared(Message message, int interrogatingPid,
286             long interrogatingTid) {
287         if (mMessagesWaitingForRequestPreparer == null) {
288             mMessagesWaitingForRequestPreparer = new ArrayList<>(1);
289         }
290         MessageHolder messageHolder =
291                 new MessageHolder(message, interrogatingPid, interrogatingTid);
292         mMessagesWaitingForRequestPreparer.add(messageHolder);
293     }
294 
requestPreparerDoneUiThread(Message message)295     private void requestPreparerDoneUiThread(Message message) {
296         synchronized (mLock) {
297             if (message.arg1 != mActiveRequestPreparerId) {
298                 Slog.e(LOG_TAG, "Surprising AccessibilityRequestPreparer callback (likely late)");
299                 return;
300             }
301             mNumActiveRequestPreparers--;
302             if (mNumActiveRequestPreparers <= 0) {
303                 mHandler.removeMessages(PrivateHandler.MSG_APP_PREPARATION_TIMEOUT);
304                 scheduleAllMessagesWaitingForRequestPreparerLocked();
305             }
306         }
307     }
308 
requestPreparerTimeoutUiThread()309     private void requestPreparerTimeoutUiThread() {
310         synchronized (mLock) {
311             Slog.e(LOG_TAG, "AccessibilityRequestPreparer timed out");
312             scheduleAllMessagesWaitingForRequestPreparerLocked();
313         }
314     }
315 
316     @GuardedBy("mLock")
scheduleAllMessagesWaitingForRequestPreparerLocked()317     private void scheduleAllMessagesWaitingForRequestPreparerLocked() {
318         int numMessages = mMessagesWaitingForRequestPreparer.size();
319         for (int i = 0; i < numMessages; i++) {
320             MessageHolder request = mMessagesWaitingForRequestPreparer.get(i);
321             scheduleMessage(request.mMessage, request.mInterrogatingPid,
322                     request.mInterrogatingTid,
323                     (i == 0) /* the app is ready for the first request */);
324         }
325         mMessagesWaitingForRequestPreparer.clear();
326         mNumActiveRequestPreparers = 0; // Just to be safe - should be unnecessary
327         mActiveRequestPreparerId = -1;
328     }
329 
findAccessibilityNodeInfoByAccessibilityIdUiThread(Message message)330     private void findAccessibilityNodeInfoByAccessibilityIdUiThread(Message message) {
331         synchronized (mLock) {
332             mPendingFindNodeByIdMessages.remove(message);
333         }
334         final int flags = message.arg1;
335 
336         SomeArgs args = (SomeArgs) message.obj;
337         final int accessibilityViewId = args.argi1;
338         final int virtualDescendantId = args.argi2;
339         final int interactionId = args.argi3;
340         final IAccessibilityInteractionConnectionCallback callback =
341             (IAccessibilityInteractionConnectionCallback) args.arg1;
342         final MagnificationSpec spec = (MagnificationSpec) args.arg2;
343         final Region interactiveRegion = (Region) args.arg3;
344         final Bundle arguments = (Bundle) args.arg4;
345         final float[] matrixValues = (float[]) args.arg5;
346 
347         args.recycle();
348 
349         View requestedView = null;
350         AccessibilityNodeInfo requestedNode = null;
351         boolean interruptPrefetch =
352                 ((flags & AccessibilityNodeInfo.FLAG_PREFETCH_UNINTERRUPTIBLE) == 0);
353 
354         ArrayList<AccessibilityNodeInfo> infos = mTempAccessibilityNodeInfoList;
355         infos.clear();
356         try {
357             if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
358                 return;
359             }
360             mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags;
361             requestedView = findViewByAccessibilityId(accessibilityViewId);
362             if (requestedView != null && isShown(requestedView)) {
363                 requestedNode = populateAccessibilityNodeInfoForView(
364                         requestedView, arguments, virtualDescendantId);
365                 mPrefetcher.mInterruptPrefetch = interruptPrefetch;
366                 mPrefetcher.mFetchFlags = flags & AccessibilityNodeInfo.FLAG_PREFETCH_MASK;
367 
368                 if (!interruptPrefetch) {
369                     infos.add(requestedNode);
370                     mPrefetcher.prefetchAccessibilityNodeInfos(requestedView,
371                             requestedNode == null ? null : new AccessibilityNodeInfo(requestedNode),
372                             infos);
373                     mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
374                 }
375             }
376         } finally {
377             if (!interruptPrefetch) {
378                 // Return found node and prefetched nodes in one IPC.
379                 updateInfosForViewportAndReturnFindNodeResult(infos, callback, interactionId, spec,
380                         matrixValues, interactiveRegion);
381 
382                 final SatisfiedFindAccessibilityNodeByAccessibilityIdRequest satisfiedRequest =
383                         getSatisfiedRequestInPrefetch(requestedNode == null ? null : requestedNode,
384                                infos, flags);
385                 if (satisfiedRequest != null) {
386                     returnFindNodeResult(satisfiedRequest);
387                 }
388                 return;
389             } else {
390                 // Return found node.
391                 updateInfoForViewportAndReturnFindNodeResult(
392                         requestedNode == null ? null : new AccessibilityNodeInfo(requestedNode),
393                         callback, interactionId, spec, matrixValues, interactiveRegion);
394             }
395         }
396         mPrefetcher.prefetchAccessibilityNodeInfos(requestedView,
397                 requestedNode == null ? null : new AccessibilityNodeInfo(requestedNode), infos);
398         mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
399         updateInfosForViewPort(infos, spec, matrixValues, interactiveRegion);
400         final SatisfiedFindAccessibilityNodeByAccessibilityIdRequest satisfiedRequest =
401                 getSatisfiedRequestInPrefetch(requestedNode == null ? null : requestedNode, infos,
402                         flags);
403 
404         // Return prefetch result separately.
405         returnPrefetchResult(interactionId, infos, callback);
406 
407         if (satisfiedRequest != null) {
408             returnFindNodeResult(satisfiedRequest);
409         }
410     }
411 
populateAccessibilityNodeInfoForView( View view, Bundle arguments, int virtualViewId)412     private AccessibilityNodeInfo populateAccessibilityNodeInfoForView(
413             View view, Bundle arguments, int virtualViewId) {
414         AccessibilityNodeProvider provider = view.getAccessibilityNodeProvider();
415         // Determine if we'll be populating extra data
416         final String extraDataRequested = (arguments == null) ? null
417                 : arguments.getString(EXTRA_DATA_REQUESTED_KEY);
418         AccessibilityNodeInfo root = null;
419         if (provider == null) {
420             root = view.createAccessibilityNodeInfo();
421             if (root != null) {
422                 if (extraDataRequested != null) {
423                     view.addExtraDataToAccessibilityNodeInfo(root, extraDataRequested, arguments);
424                 }
425             }
426         } else {
427             root = provider.createAccessibilityNodeInfo(virtualViewId);
428             if (root != null) {
429                 if (extraDataRequested != null) {
430                     provider.addExtraDataToAccessibilityNodeInfo(
431                             virtualViewId, root, extraDataRequested, arguments);
432                 }
433             }
434         }
435         return root;
436     }
437 
findAccessibilityNodeInfosByViewIdClientThread(long accessibilityNodeId, String viewId, Region interactiveRegion, int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid, long interrogatingTid, MagnificationSpec spec, float[] matrixValues)438     public void findAccessibilityNodeInfosByViewIdClientThread(long accessibilityNodeId,
439             String viewId, Region interactiveRegion, int interactionId,
440             IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid,
441             long interrogatingTid, MagnificationSpec spec, float[] matrixValues) {
442         Message message = mHandler.obtainMessage();
443         message.what = PrivateHandler.MSG_FIND_ACCESSIBILITY_NODE_INFOS_BY_VIEW_ID;
444         message.arg1 = flags;
445         message.arg2 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId);
446 
447         SomeArgs args = SomeArgs.obtain();
448         args.argi1 = interactionId;
449         args.arg1 = callback;
450         args.arg2 = spec;
451         args.arg3 = viewId;
452         args.arg4 = interactiveRegion;
453         args.arg5 = matrixValues;
454         message.obj = args;
455 
456         scheduleMessage(message, interrogatingPid, interrogatingTid, CONSIDER_REQUEST_PREPARERS);
457     }
458 
findAccessibilityNodeInfosByViewIdUiThread(Message message)459     private void findAccessibilityNodeInfosByViewIdUiThread(Message message) {
460         final int flags = message.arg1;
461         final int accessibilityViewId = message.arg2;
462 
463         SomeArgs args = (SomeArgs) message.obj;
464         final int interactionId = args.argi1;
465         final IAccessibilityInteractionConnectionCallback callback =
466             (IAccessibilityInteractionConnectionCallback) args.arg1;
467         final MagnificationSpec spec = (MagnificationSpec) args.arg2;
468         final String viewId = (String) args.arg3;
469         final Region interactiveRegion = (Region) args.arg4;
470         final float[] matrixValues = (float[]) args.arg5;
471         args.recycle();
472 
473         final List<AccessibilityNodeInfo> infos = mTempAccessibilityNodeInfoList;
474         infos.clear();
475         try {
476             if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null
477                     || viewId == null) {
478                 return;
479             }
480             mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags;
481             final View root = findViewByAccessibilityId(accessibilityViewId);
482             if (root != null) {
483                 final int resolvedViewId = root.getContext().getResources()
484                         .getIdentifier(viewId, null, null);
485                 if (resolvedViewId <= 0) {
486                     return;
487                 }
488                 if (mAddNodeInfosForViewId == null) {
489                     mAddNodeInfosForViewId = new AddNodeInfosForViewId();
490                 }
491                 mAddNodeInfosForViewId.init(resolvedViewId, infos);
492                 root.findViewByPredicate(mAddNodeInfosForViewId);
493                 mAddNodeInfosForViewId.reset();
494             }
495         } finally {
496             mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
497             updateInfosForViewportAndReturnFindNodeResult(
498                     infos, callback, interactionId, spec, matrixValues, interactiveRegion);
499         }
500     }
501 
findAccessibilityNodeInfosByTextClientThread(long accessibilityNodeId, String text, Region interactiveRegion, int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid, long interrogatingTid, MagnificationSpec spec, float[] matrixValues)502     public void findAccessibilityNodeInfosByTextClientThread(long accessibilityNodeId,
503             String text, Region interactiveRegion, int interactionId,
504             IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid,
505             long interrogatingTid, MagnificationSpec spec, float[] matrixValues) {
506         Message message = mHandler.obtainMessage();
507         message.what = PrivateHandler.MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_TEXT;
508         message.arg1 = flags;
509 
510         SomeArgs args = SomeArgs.obtain();
511         args.arg1 = text;
512         args.arg2 = callback;
513         args.arg3 = spec;
514         args.argi1 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId);
515         args.argi2 = AccessibilityNodeInfo.getVirtualDescendantId(accessibilityNodeId);
516         args.argi3 = interactionId;
517         args.arg4 = interactiveRegion;
518         args.arg5 = matrixValues;
519         message.obj = args;
520 
521         scheduleMessage(message, interrogatingPid, interrogatingTid, CONSIDER_REQUEST_PREPARERS);
522     }
523 
findAccessibilityNodeInfosByTextUiThread(Message message)524     private void findAccessibilityNodeInfosByTextUiThread(Message message) {
525         final int flags = message.arg1;
526 
527         SomeArgs args = (SomeArgs) message.obj;
528         final String text = (String) args.arg1;
529         final IAccessibilityInteractionConnectionCallback callback =
530             (IAccessibilityInteractionConnectionCallback) args.arg2;
531         final MagnificationSpec spec = (MagnificationSpec) args.arg3;
532         final int accessibilityViewId = args.argi1;
533         final int virtualDescendantId = args.argi2;
534         final int interactionId = args.argi3;
535         final Region interactiveRegion = (Region) args.arg4;
536         final float[] matrixValues = (float[]) args.arg5;
537         args.recycle();
538 
539         List<AccessibilityNodeInfo> infos = null;
540         try {
541             if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
542                 return;
543             }
544             mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags;
545             final View root = findViewByAccessibilityId(accessibilityViewId);
546             if (root != null && isShown(root)) {
547                 AccessibilityNodeProvider provider = root.getAccessibilityNodeProvider();
548                 if (provider != null) {
549                     infos = provider.findAccessibilityNodeInfosByText(text,
550                             virtualDescendantId);
551                 } else if (virtualDescendantId == AccessibilityNodeProvider.HOST_VIEW_ID) {
552                     ArrayList<View> foundViews = mTempArrayList;
553                     foundViews.clear();
554                     root.findViewsWithText(foundViews, text, View.FIND_VIEWS_WITH_TEXT
555                             | View.FIND_VIEWS_WITH_CONTENT_DESCRIPTION
556                             | View.FIND_VIEWS_WITH_ACCESSIBILITY_NODE_PROVIDERS);
557                     if (!foundViews.isEmpty()) {
558                         infos = mTempAccessibilityNodeInfoList;
559                         infos.clear();
560                         final int viewCount = foundViews.size();
561                         for (int i = 0; i < viewCount; i++) {
562                             View foundView = foundViews.get(i);
563                             if (isShown(foundView)) {
564                                 provider = foundView.getAccessibilityNodeProvider();
565                                 if (provider != null) {
566                                     List<AccessibilityNodeInfo> infosFromProvider =
567                                         provider.findAccessibilityNodeInfosByText(text,
568                                                 AccessibilityNodeProvider.HOST_VIEW_ID);
569                                     if (infosFromProvider != null) {
570                                         infos.addAll(infosFromProvider);
571                                     }
572                                 } else  {
573                                     infos.add(foundView.createAccessibilityNodeInfo());
574                                 }
575                             }
576                         }
577                     }
578                 }
579             }
580         } finally {
581             mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
582             updateInfosForViewportAndReturnFindNodeResult(
583                     infos, callback, interactionId, spec, matrixValues, interactiveRegion);
584         }
585     }
586 
findFocusClientThread(long accessibilityNodeId, int focusType, Region interactiveRegion, int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid, long interrogatingTid, MagnificationSpec spec, float[] matrixValues)587     public void findFocusClientThread(long accessibilityNodeId, int focusType,
588             Region interactiveRegion, int interactionId,
589             IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid,
590             long interrogatingTid, MagnificationSpec spec, float[] matrixValues) {
591         Message message = mHandler.obtainMessage();
592         message.what = PrivateHandler.MSG_FIND_FOCUS;
593         message.arg1 = flags;
594         message.arg2 = focusType;
595 
596         SomeArgs args = SomeArgs.obtain();
597         args.argi1 = interactionId;
598         args.argi2 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId);
599         args.argi3 = AccessibilityNodeInfo.getVirtualDescendantId(accessibilityNodeId);
600         args.arg1 = callback;
601         args.arg2 = spec;
602         args.arg3 = interactiveRegion;
603         args.arg4 = matrixValues;
604         message.obj = args;
605 
606         scheduleMessage(message, interrogatingPid, interrogatingTid, CONSIDER_REQUEST_PREPARERS);
607     }
608 
findFocusUiThread(Message message)609     private void findFocusUiThread(Message message) {
610         final int flags = message.arg1;
611         final int focusType = message.arg2;
612 
613         SomeArgs args = (SomeArgs) message.obj;
614         final int interactionId = args.argi1;
615         final int accessibilityViewId = args.argi2;
616         final int virtualDescendantId = args.argi3;
617         final IAccessibilityInteractionConnectionCallback callback =
618             (IAccessibilityInteractionConnectionCallback) args.arg1;
619         final MagnificationSpec spec = (MagnificationSpec) args.arg2;
620         final Region interactiveRegion = (Region) args.arg3;
621         final float[] matrixValues = (float[]) args.arg4;
622         args.recycle();
623 
624         AccessibilityNodeInfo focused = null;
625         try {
626             if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
627                 return;
628             }
629             mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags;
630             final View root = findViewByAccessibilityId(accessibilityViewId);
631             if (root != null && isShown(root)) {
632                 switch (focusType) {
633                     case AccessibilityNodeInfo.FOCUS_ACCESSIBILITY: {
634                         View host = mViewRootImpl.mAccessibilityFocusedHost;
635                         // If there is no accessibility focus host or it is not a descendant
636                         // of the root from which to start the search, then the search failed.
637                         if (host == null || !ViewRootImpl.isViewDescendantOf(host, root)) {
638                             break;
639                         }
640                         // The focused view not shown, we failed.
641                         if (!isShown(host)) {
642                             break;
643                         }
644                         // If the host has a provider ask this provider to search for the
645                         // focus instead fetching all provider nodes to do the search here.
646                         AccessibilityNodeProvider provider = host.getAccessibilityNodeProvider();
647                         if (provider != null) {
648                             final AccessibilityNodeInfo focusNode =
649                                     mViewRootImpl.mAccessibilityFocusedVirtualView;
650                             if (focusNode != null) {
651                                 final int virtualNodeId = AccessibilityNodeInfo
652                                         .getVirtualDescendantId(focusNode.getSourceNodeId());
653                                 focused = provider.createAccessibilityNodeInfo(virtualNodeId);
654                             }
655                         } else if (virtualDescendantId == AccessibilityNodeProvider.HOST_VIEW_ID) {
656                             focused = host.createAccessibilityNodeInfo();
657                         }
658                     } break;
659                     case AccessibilityNodeInfo.FOCUS_INPUT: {
660                         View target = root.findFocus();
661                         if (!isShown(target)) {
662                             break;
663                         }
664                         AccessibilityNodeProvider provider = target.getAccessibilityNodeProvider();
665                         if (provider != null) {
666                             focused = provider.findFocus(focusType);
667                         }
668                         if (focused == null) {
669                             focused = target.createAccessibilityNodeInfo();
670                         }
671                     } break;
672                     default:
673                         throw new IllegalArgumentException("Unknown focus type: " + focusType);
674                 }
675             }
676         } finally {
677             mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
678             updateInfoForViewportAndReturnFindNodeResult(
679                     focused, callback, interactionId, spec, matrixValues, interactiveRegion);
680         }
681     }
682 
focusSearchClientThread(long accessibilityNodeId, int direction, Region interactiveRegion, int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid, long interrogatingTid, MagnificationSpec spec, float[] matrixValues)683     public void focusSearchClientThread(long accessibilityNodeId, int direction,
684             Region interactiveRegion, int interactionId,
685             IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid,
686             long interrogatingTid, MagnificationSpec spec, float[] matrixValues) {
687         Message message = mHandler.obtainMessage();
688         message.what = PrivateHandler.MSG_FOCUS_SEARCH;
689         message.arg1 = flags;
690         message.arg2 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId);
691 
692         SomeArgs args = SomeArgs.obtain();
693         args.argi2 = direction;
694         args.argi3 = interactionId;
695         args.arg1 = callback;
696         args.arg2 = spec;
697         args.arg3 = interactiveRegion;
698         args.arg4 = matrixValues;
699 
700         message.obj = args;
701 
702         scheduleMessage(message, interrogatingPid, interrogatingTid, CONSIDER_REQUEST_PREPARERS);
703     }
704 
focusSearchUiThread(Message message)705     private void focusSearchUiThread(Message message) {
706         final int flags = message.arg1;
707         final int accessibilityViewId = message.arg2;
708 
709         SomeArgs args = (SomeArgs) message.obj;
710         final int direction = args.argi2;
711         final int interactionId = args.argi3;
712         final IAccessibilityInteractionConnectionCallback callback =
713             (IAccessibilityInteractionConnectionCallback) args.arg1;
714         final MagnificationSpec spec = (MagnificationSpec) args.arg2;
715         final Region interactiveRegion = (Region) args.arg3;
716         final float[] matrixValues = (float[]) args.arg4;
717         args.recycle();
718 
719         AccessibilityNodeInfo next = null;
720         try {
721             if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
722                 return;
723             }
724             mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags;
725             final View root = findViewByAccessibilityId(accessibilityViewId);
726             if (root != null && isShown(root)) {
727                 View nextView = root.focusSearch(direction);
728                 if (nextView != null) {
729                     next = nextView.createAccessibilityNodeInfo();
730                 }
731             }
732         } finally {
733             mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
734             updateInfoForViewportAndReturnFindNodeResult(
735                     next, callback, interactionId, spec, matrixValues, interactiveRegion);
736         }
737     }
738 
performAccessibilityActionClientThread(long accessibilityNodeId, int action, Bundle arguments, int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid, long interrogatingTid)739     public void performAccessibilityActionClientThread(long accessibilityNodeId, int action,
740             Bundle arguments, int interactionId,
741             IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid,
742             long interrogatingTid) {
743         Message message = mHandler.obtainMessage();
744         message.what = PrivateHandler.MSG_PERFORM_ACCESSIBILITY_ACTION;
745         message.arg1 = flags;
746         message.arg2 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId);
747 
748         SomeArgs args = SomeArgs.obtain();
749         args.argi1 = AccessibilityNodeInfo.getVirtualDescendantId(accessibilityNodeId);
750         args.argi2 = action;
751         args.argi3 = interactionId;
752         args.arg1 = callback;
753         args.arg2 = arguments;
754 
755         message.obj = args;
756 
757         scheduleMessage(message, interrogatingPid, interrogatingTid, CONSIDER_REQUEST_PREPARERS);
758     }
759 
performAccessibilityActionUiThread(Message message)760     private void performAccessibilityActionUiThread(Message message) {
761         final int flags = message.arg1;
762         final int accessibilityViewId = message.arg2;
763 
764         SomeArgs args = (SomeArgs) message.obj;
765         final int virtualDescendantId = args.argi1;
766         final int action = args.argi2;
767         final int interactionId = args.argi3;
768         final IAccessibilityInteractionConnectionCallback callback =
769             (IAccessibilityInteractionConnectionCallback) args.arg1;
770         Bundle arguments = (Bundle) args.arg2;
771 
772         args.recycle();
773 
774         boolean succeeded = false;
775         try {
776             if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null ||
777                     mViewRootImpl.mStopped || mViewRootImpl.mPausedForTransition) {
778                 return;
779             }
780             mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags;
781             final View target = findViewByAccessibilityId(accessibilityViewId);
782             if (target != null && isShown(target)) {
783                 mA11yManager.notifyPerformingAction(action);
784                 if (action == R.id.accessibilityActionClickOnClickableSpan) {
785                     // Handle this hidden action separately
786                     succeeded = handleClickableSpanActionUiThread(
787                             target, virtualDescendantId, arguments);
788                 } else {
789                     AccessibilityNodeProvider provider = target.getAccessibilityNodeProvider();
790                     if (provider != null) {
791                         succeeded = provider.performAction(virtualDescendantId, action,
792                                 arguments);
793                     } else if (virtualDescendantId == AccessibilityNodeProvider.HOST_VIEW_ID) {
794                         succeeded = target.performAccessibilityAction(action, arguments);
795                     }
796                 }
797                 mA11yManager.notifyPerformingAction(0);
798             }
799         } finally {
800             try {
801                 mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
802                 callback.setPerformAccessibilityActionResult(succeeded, interactionId);
803             } catch (RemoteException re) {
804                 /* ignore - the other side will time out */
805             }
806         }
807     }
808 
809     /**
810      * Finds the accessibility focused node in the root, and clears the accessibility focus.
811      */
clearAccessibilityFocusClientThread()812     public void clearAccessibilityFocusClientThread() {
813         final Message message = mHandler.obtainMessage();
814         message.what = PrivateHandler.MSG_CLEAR_ACCESSIBILITY_FOCUS;
815 
816         // Don't care about pid and tid because there's no interrogating client for this message.
817         scheduleMessage(message, 0, 0, CONSIDER_REQUEST_PREPARERS);
818     }
819 
clearAccessibilityFocusUiThread()820     private void clearAccessibilityFocusUiThread() {
821         if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
822             return;
823         }
824         try {
825             mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags =
826                     AccessibilityNodeInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS;
827             final View root = mViewRootImpl.mView;
828             if (root != null && isShown(root)) {
829                 final View host = mViewRootImpl.mAccessibilityFocusedHost;
830                 // If there is no accessibility focus host or it is not a descendant
831                 // of the root from which to start the search, then the search failed.
832                 if (host == null || !ViewRootImpl.isViewDescendantOf(host, root)) {
833                     return;
834                 }
835                 final AccessibilityNodeProvider provider = host.getAccessibilityNodeProvider();
836                 final AccessibilityNodeInfo focusNode =
837                         mViewRootImpl.mAccessibilityFocusedVirtualView;
838                 if (provider != null && focusNode != null) {
839                     final int virtualNodeId = AccessibilityNodeInfo.getVirtualDescendantId(
840                             focusNode.getSourceNodeId());
841                     provider.performAction(virtualNodeId,
842                             AccessibilityAction.ACTION_CLEAR_ACCESSIBILITY_FOCUS.getId(),
843                             null);
844                 } else {
845                     host.performAccessibilityAction(
846                             AccessibilityAction.ACTION_CLEAR_ACCESSIBILITY_FOCUS.getId(),
847                             null);
848                 }
849             }
850         } finally {
851             mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
852         }
853     }
854 
855     /**
856      * Notify outside touch event to the target window.
857      */
notifyOutsideTouchClientThread()858     public void notifyOutsideTouchClientThread() {
859         final Message message = mHandler.obtainMessage();
860         message.what = PrivateHandler.MSG_NOTIFY_OUTSIDE_TOUCH;
861 
862         // Don't care about pid and tid because there's no interrogating client for this message.
863         scheduleMessage(message, 0, 0, CONSIDER_REQUEST_PREPARERS);
864     }
865 
notifyOutsideTouchUiThread()866     private void notifyOutsideTouchUiThread() {
867         if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null
868                 || mViewRootImpl.mStopped || mViewRootImpl.mPausedForTransition) {
869             return;
870         }
871         final View root = mViewRootImpl.mView;
872         if (root != null && isShown(root)) {
873             // trigger ACTION_OUTSIDE to notify windows
874             final long now = SystemClock.uptimeMillis();
875             final MotionEvent event = MotionEvent.obtain(now, now, MotionEvent.ACTION_OUTSIDE,
876                     0, 0, 0);
877             event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
878             mViewRootImpl.dispatchInputEvent(event);
879         }
880     }
881 
findViewByAccessibilityId(int accessibilityId)882     private View findViewByAccessibilityId(int accessibilityId) {
883         if (accessibilityId == AccessibilityNodeInfo.ROOT_ITEM_ID) {
884             return mViewRootImpl.mView;
885         } else {
886             return AccessibilityNodeIdManager.getInstance().findView(accessibilityId);
887         }
888     }
889 
890     // The boundInScreen includes magnification effect, so we need to normalize it before
891     // determine the visibility.
adjustIsVisibleToUserIfNeeded(AccessibilityNodeInfo info, Region interactiveRegion, MagnificationSpec spec)892     private void adjustIsVisibleToUserIfNeeded(AccessibilityNodeInfo info,
893             Region interactiveRegion, MagnificationSpec spec) {
894         if (interactiveRegion == null || info == null) {
895             return;
896         }
897         Rect boundsInScreen = mTempRect;
898         info.getBoundsInScreen(boundsInScreen);
899         if (spec != null && !spec.isNop()) {
900             boundsInScreen.offset((int) -spec.offsetX, (int) -spec.offsetY);
901             boundsInScreen.scale(1 / spec.scale);
902         }
903 
904         if (interactiveRegion.quickReject(boundsInScreen) && !shouldBypassAdjustIsVisible()) {
905             info.setVisibleToUser(false);
906         }
907     }
908 
shouldBypassAdjustIsVisible()909     private boolean shouldBypassAdjustIsVisible() {
910         final int windowType = mViewRootImpl.mOrigWindowType;
911         if (windowType == TYPE_INPUT_METHOD) {
912             return true;
913         }
914         return false;
915     }
916 
917     /**
918      * Applies the host-window matrix to the embedded node. After this transform, The node bounds
919      *  will be transformed from embedded window coordinates to host-window coordinates.
920      *
921      */
applyHostWindowMatrixIfNeeded(AccessibilityNodeInfo info)922     private void applyHostWindowMatrixIfNeeded(AccessibilityNodeInfo info) {
923         if (info == null || shouldBypassApplyWindowMatrix()) {
924             return;
925         }
926         final Rect boundsInScreen = mTempRect;
927         final RectF transformedBounds = mTempRectF;
928         final Matrix windowMatrix = mViewRootImpl.mAttachInfo.mWindowMatrixInEmbeddedHierarchy;
929 
930         info.getBoundsInScreen(boundsInScreen);
931         transformedBounds.set(boundsInScreen);
932         windowMatrix.mapRect(transformedBounds);
933         boundsInScreen.set((int) transformedBounds.left, (int) transformedBounds.top,
934                 (int) transformedBounds.right, (int) transformedBounds.bottom);
935         info.setBoundsInScreen(boundsInScreen);
936     }
937 
shouldBypassApplyWindowMatrix()938     private boolean shouldBypassApplyWindowMatrix() {
939         final Matrix windowMatrix = mViewRootImpl.mAttachInfo.mWindowMatrixInEmbeddedHierarchy;
940         return windowMatrix == null || windowMatrix.isIdentity();
941     }
942 
associateLeashedParentIfNeeded(AccessibilityNodeInfo info)943     private void associateLeashedParentIfNeeded(AccessibilityNodeInfo info) {
944         if (info == null || shouldBypassAssociateLeashedParent()) {
945             return;
946         }
947         // The node id of root node in embedded maybe not be ROOT_NODE_ID so we compare the id
948         // with root view.
949         if (mViewRootImpl.mView.getAccessibilityViewId()
950                 != AccessibilityNodeInfo.getAccessibilityViewId(info.getSourceNodeId())) {
951             return;
952         }
953         info.setLeashedParent(mViewRootImpl.mAttachInfo.mLeashedParentToken,
954                 mViewRootImpl.mAttachInfo.mLeashedParentAccessibilityViewId);
955     }
956 
shouldBypassAssociateLeashedParent()957     private boolean shouldBypassAssociateLeashedParent() {
958         return (mViewRootImpl.mAttachInfo.mLeashedParentToken == null
959                 && mViewRootImpl.mAttachInfo.mLeashedParentAccessibilityViewId == View.NO_ID);
960     }
961 
applyAppScaleAndMagnificationSpecIfNeeded(AccessibilityNodeInfo info, MagnificationSpec spec)962     private void applyAppScaleAndMagnificationSpecIfNeeded(AccessibilityNodeInfo info,
963             MagnificationSpec spec) {
964         if (info == null) {
965             return;
966         }
967 
968         final float applicationScale = mViewRootImpl.mAttachInfo.mApplicationScale;
969         if (!shouldApplyAppScaleAndMagnificationSpec(applicationScale, spec)) {
970             return;
971         }
972         Rect boundsInParent = mTempRect;
973 
974         info.getBoundsInParent(boundsInParent);
975         if (applicationScale != 1.0f) {
976             boundsInParent.scale(applicationScale);
977         }
978         if (spec != null) {
979             boundsInParent.scale(spec.scale);
980             // boundsInParent must not be offset.
981         }
982         info.setBoundsInParent(boundsInParent);
983     }
984 
shouldApplyAppScaleAndMagnificationSpec(float appScale, MagnificationSpec spec)985     private boolean shouldApplyAppScaleAndMagnificationSpec(float appScale,
986             MagnificationSpec spec) {
987         return (appScale != 1.0f || (spec != null && !spec.isNop()));
988     }
989 
updateInfosForViewPort(List<AccessibilityNodeInfo> infos, MagnificationSpec spec, float[] matrixValues, Region interactiveRegion)990     private void updateInfosForViewPort(List<AccessibilityNodeInfo> infos, MagnificationSpec spec,
991             float[] matrixValues, Region interactiveRegion) {
992         for (int i = 0; i < infos.size(); i++) {
993             updateInfoForViewPort(infos.get(i), spec, matrixValues, interactiveRegion);
994         }
995     }
996 
updateInfoForViewPort(AccessibilityNodeInfo info, MagnificationSpec spec, float[] matrixValues, Region interactiveRegion)997     private void updateInfoForViewPort(AccessibilityNodeInfo info, MagnificationSpec spec,
998             float[] matrixValues, Region interactiveRegion) {
999         associateLeashedParentIfNeeded(info);
1000 
1001         applyHostWindowMatrixIfNeeded(info);
1002         // Transform view bounds from window coordinates to screen coordinates.
1003         transformBoundsWithScreenMatrix(info, matrixValues);
1004         adjustIsVisibleToUserIfNeeded(info, interactiveRegion, spec);
1005         applyAppScaleAndMagnificationSpecIfNeeded(info, spec);
1006     }
1007 
1008 
1009     /**
1010      * Transforms the regions from local screen coordinate to global screen coordinate with the
1011      * given transform matrix used in on-screen coordinate.
1012      *
1013      * @param info the AccessibilityNodeInfo that has the region in application screen coordinate
1014      * @param matrixValues the matrix to be applied
1015      */
transformBoundsWithScreenMatrix(AccessibilityNodeInfo info, float[] matrixValues)1016     private void transformBoundsWithScreenMatrix(AccessibilityNodeInfo info,
1017             float[] matrixValues) {
1018         if (info == null || matrixValues == null) {
1019             return;
1020         }
1021         final Rect boundInScreen = mTempRect;
1022         final RectF transformedBounds = mTempRectF;
1023 
1024         info.getBoundsInScreen(boundInScreen);
1025         transformedBounds.set(boundInScreen);
1026 
1027         final Matrix transformMatrix = new Matrix();
1028         transformMatrix.setValues(matrixValues);
1029         final float applicationScale = mViewRootImpl.mAttachInfo.mApplicationScale;
1030         if (applicationScale != 1f) {
1031             transformMatrix.preScale(applicationScale, applicationScale);
1032         }
1033         // Transform the bounds from application screen coordinates to global window coordinates.
1034         // For the embedded node, the bounds we get is already in window coordinates, so we don't
1035         // need to do it.
1036         if (mViewRootImpl.mAttachInfo.mWindowMatrixInEmbeddedHierarchy == null) {
1037             transformMatrix.preTranslate(-mViewRootImpl.mAttachInfo.mWindowLeft,
1038                     -mViewRootImpl.mAttachInfo.mWindowTop);
1039         }
1040 
1041         if (transformMatrix.isIdentity()) {
1042             return;
1043         }
1044         transformMatrix.mapRect(transformedBounds);
1045         // Offset 0.5f to round after casting.
1046         transformedBounds.offset(0.5f, 0.5f);
1047         boundInScreen.set((int) (transformedBounds.left), (int) transformedBounds.top,
1048                 (int) transformedBounds.right, (int) transformedBounds.bottom);
1049         info.setBoundsInScreen(boundInScreen);
1050         // Scale text locations if they are present
1051         if (info.hasExtras()) {
1052             final Bundle extras = info.getExtras();
1053             final RectF[] textLocations =
1054                     extras.getParcelableArray(EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY, RectF.class);
1055             if (textLocations != null) {
1056                 for (int i = 0; i < textLocations.length; i++) {
1057                     // Unchecked cast - an app that puts other objects in this bundle with this
1058                     // key will crash.
1059                     final RectF textLocation = textLocations[i];
1060                     if (textLocation != null) {
1061                         transformMatrix.mapRect(textLocation);
1062                     }
1063                 }
1064             }
1065         }
1066     }
1067 
updateInfosForViewportAndReturnFindNodeResult(List<AccessibilityNodeInfo> infos, IAccessibilityInteractionConnectionCallback callback, int interactionId, MagnificationSpec spec, float[] matrixValues, Region interactiveRegion)1068     private void updateInfosForViewportAndReturnFindNodeResult(List<AccessibilityNodeInfo> infos,
1069             IAccessibilityInteractionConnectionCallback callback, int interactionId,
1070             MagnificationSpec spec, float[] matrixValues, Region interactiveRegion) {
1071         if (infos != null) {
1072             updateInfosForViewPort(infos, spec, matrixValues, interactiveRegion);
1073         }
1074         returnFindNodesResult(infos, callback, interactionId);
1075     }
1076 
returnFindNodeResult(AccessibilityNodeInfo info, IAccessibilityInteractionConnectionCallback callback, int interactionId)1077     private void returnFindNodeResult(AccessibilityNodeInfo info,
1078                                       IAccessibilityInteractionConnectionCallback callback,
1079                                       int interactionId) {
1080         try {
1081             callback.setFindAccessibilityNodeInfoResult(info, interactionId);
1082         } catch (RemoteException re) {
1083             /* ignore - the other side will time out */
1084         }
1085     }
1086 
returnFindNodeResult(SatisfiedFindAccessibilityNodeByAccessibilityIdRequest satisfiedRequest)1087     private void returnFindNodeResult(SatisfiedFindAccessibilityNodeByAccessibilityIdRequest
1088             satisfiedRequest) {
1089         try {
1090             final AccessibilityNodeInfo info = satisfiedRequest.mSatisfiedRequestNode;
1091             final IAccessibilityInteractionConnectionCallback callback =
1092                     satisfiedRequest.mSatisfiedRequestCallback;
1093             final int interactionId = satisfiedRequest.mSatisfiedRequestInteractionId;
1094             callback.setFindAccessibilityNodeInfoResult(info, interactionId);
1095         } catch (RemoteException re) {
1096             /* ignore - the other side will time out */
1097         }
1098     }
1099 
returnFindNodesResult(List<AccessibilityNodeInfo> infos, IAccessibilityInteractionConnectionCallback callback, int interactionId)1100     private void returnFindNodesResult(List<AccessibilityNodeInfo> infos,
1101             IAccessibilityInteractionConnectionCallback callback, int interactionId) {
1102         try {
1103             callback.setFindAccessibilityNodeInfosResult(infos, interactionId);
1104             if (infos != null) {
1105                 infos.clear();
1106             }
1107         } catch (RemoteException re) {
1108             /* ignore - the other side will time out */
1109         }
1110     }
1111 
getSatisfiedRequestInPrefetch( AccessibilityNodeInfo requestedNode, List<AccessibilityNodeInfo> infos, int flags)1112     private SatisfiedFindAccessibilityNodeByAccessibilityIdRequest getSatisfiedRequestInPrefetch(
1113             AccessibilityNodeInfo requestedNode, List<AccessibilityNodeInfo> infos, int flags) {
1114         SatisfiedFindAccessibilityNodeByAccessibilityIdRequest satisfiedRequest = null;
1115         synchronized (mLock) {
1116             for (int i = 0; i < mPendingFindNodeByIdMessages.size(); i++) {
1117                 final Message pendingMessage = mPendingFindNodeByIdMessages.get(i);
1118                 final int pendingFlags = pendingMessage.arg1;
1119                 if ((pendingFlags & FLAGS_AFFECTING_REPORTED_DATA)
1120                         != (flags & FLAGS_AFFECTING_REPORTED_DATA)) {
1121                     continue;
1122                 }
1123                 SomeArgs args = (SomeArgs) pendingMessage.obj;
1124                 final int accessibilityViewId = args.argi1;
1125                 final int virtualDescendantId = args.argi2;
1126 
1127                 final AccessibilityNodeInfo satisfiedRequestNode = nodeWithIdFromList(requestedNode,
1128                         infos, AccessibilityNodeInfo.makeNodeId(
1129                                 accessibilityViewId, virtualDescendantId));
1130 
1131                 if (satisfiedRequestNode != null) {
1132                     mHandler.removeMessages(
1133                             PrivateHandler.MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_ACCESSIBILITY_ID,
1134                             pendingMessage.obj);
1135                     final IAccessibilityInteractionConnectionCallback satisfiedRequestCallback =
1136                             (IAccessibilityInteractionConnectionCallback) args.arg1;
1137                     final int satisfiedRequestInteractionId = args.argi3;
1138                     satisfiedRequest = new SatisfiedFindAccessibilityNodeByAccessibilityIdRequest(
1139                                     satisfiedRequestNode, satisfiedRequestCallback,
1140                                     satisfiedRequestInteractionId);
1141                     args.recycle();
1142                     break;
1143                 }
1144             }
1145             mPendingFindNodeByIdMessages.clear();
1146             // Remove node from prefetched infos.
1147             if (satisfiedRequest != null && satisfiedRequest.mSatisfiedRequestNode
1148                     != requestedNode) {
1149                 infos.remove(satisfiedRequest.mSatisfiedRequestNode);
1150             }
1151             return satisfiedRequest;
1152         }
1153     }
1154 
nodeWithIdFromList(AccessibilityNodeInfo requestedNode, List<AccessibilityNodeInfo> infos, long nodeId)1155     private AccessibilityNodeInfo nodeWithIdFromList(AccessibilityNodeInfo requestedNode,
1156             List<AccessibilityNodeInfo> infos, long nodeId) {
1157         if (requestedNode != null && requestedNode.getSourceNodeId() == nodeId) {
1158             return requestedNode;
1159         }
1160         for (int j = 0; j < infos.size(); j++) {
1161             AccessibilityNodeInfo info = infos.get(j);
1162             if (info.getSourceNodeId() == nodeId) {
1163                 return info;
1164             }
1165         }
1166         return null;
1167     }
1168 
returnPrefetchResult(int interactionId, List<AccessibilityNodeInfo> infos, IAccessibilityInteractionConnectionCallback callback)1169     private void returnPrefetchResult(int interactionId, List<AccessibilityNodeInfo> infos,
1170                                       IAccessibilityInteractionConnectionCallback callback) {
1171         if (infos.size() > 0) {
1172             try {
1173                 callback.setPrefetchAccessibilityNodeInfoResult(infos, interactionId);
1174             } catch (RemoteException re) {
1175                 /* ignore - other side isn't too bothered if this doesn't arrive */
1176             }
1177         }
1178     }
1179 
updateInfoForViewportAndReturnFindNodeResult(AccessibilityNodeInfo info, IAccessibilityInteractionConnectionCallback callback, int interactionId, MagnificationSpec spec, float[] matrixValues, Region interactiveRegion)1180     private void updateInfoForViewportAndReturnFindNodeResult(AccessibilityNodeInfo info,
1181             IAccessibilityInteractionConnectionCallback callback, int interactionId,
1182             MagnificationSpec spec, float[] matrixValues, Region interactiveRegion) {
1183         updateInfoForViewPort(info, spec, matrixValues, interactiveRegion);
1184         returnFindNodeResult(info, callback, interactionId);
1185     }
1186 
handleClickableSpanActionUiThread( View view, int virtualDescendantId, Bundle arguments)1187     private boolean handleClickableSpanActionUiThread(
1188             View view, int virtualDescendantId, Bundle arguments) {
1189         Parcelable span = arguments.getParcelable(ACTION_ARGUMENT_ACCESSIBLE_CLICKABLE_SPAN);
1190         if (!(span instanceof AccessibilityClickableSpan)) {
1191             return false;
1192         }
1193 
1194         // Find the original ClickableSpan if it's still on the screen
1195         AccessibilityNodeInfo infoWithSpan = null;
1196         AccessibilityNodeProvider provider = view.getAccessibilityNodeProvider();
1197         if (provider != null) {
1198             infoWithSpan = provider.createAccessibilityNodeInfo(virtualDescendantId);
1199         } else if (virtualDescendantId == AccessibilityNodeProvider.HOST_VIEW_ID) {
1200             infoWithSpan = view.createAccessibilityNodeInfo();
1201         }
1202         if (infoWithSpan == null) {
1203             return false;
1204         }
1205 
1206         // Click on the corresponding span
1207         ClickableSpan clickableSpan = ((AccessibilityClickableSpan) span).findClickableSpan(
1208                 infoWithSpan.getOriginalText());
1209         if (clickableSpan != null) {
1210             clickableSpan.onClick(view);
1211             return true;
1212         }
1213         return false;
1214     }
1215 
1216     /**
1217      * This class encapsulates a prefetching strategy for the accessibility APIs for
1218      * querying window content. It is responsible to prefetch a batch of
1219      * AccessibilityNodeInfos in addition to the one for a requested node.
1220      */
1221     private class AccessibilityNodePrefetcher {
1222 
1223         private final ArrayList<View> mTempViewList = new ArrayList<View>();
1224         private boolean mInterruptPrefetch;
1225         private int mFetchFlags;
1226 
prefetchAccessibilityNodeInfos(View view, AccessibilityNodeInfo root, List<AccessibilityNodeInfo> outInfos)1227         public void prefetchAccessibilityNodeInfos(View view, AccessibilityNodeInfo root,
1228                 List<AccessibilityNodeInfo> outInfos) {
1229             if (root == null) {
1230                 return;
1231             }
1232             AccessibilityNodeProvider provider = view.getAccessibilityNodeProvider();
1233             final boolean prefetchPredecessors =
1234                     isFlagSet(AccessibilityNodeInfo.FLAG_PREFETCH_ANCESTORS);
1235             if (provider == null) {
1236                 if (prefetchPredecessors) {
1237                     prefetchPredecessorsOfRealNode(view, outInfos);
1238                 }
1239                 if (isFlagSet(AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS)) {
1240                     prefetchSiblingsOfRealNode(view, outInfos, prefetchPredecessors);
1241                 }
1242                 if (isFlagSet(AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS_HYBRID)) {
1243                     prefetchDescendantsOfRealNode(view, outInfos);
1244                 }
1245             } else {
1246                 if (prefetchPredecessors) {
1247                     prefetchPredecessorsOfVirtualNode(root, view, provider, outInfos);
1248                 }
1249                 if (isFlagSet(AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS)) {
1250                     prefetchSiblingsOfVirtualNode(root, view, provider, outInfos,
1251                             prefetchPredecessors);
1252                 }
1253                 if (isFlagSet(AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS_HYBRID)) {
1254                     prefetchDescendantsOfVirtualNode(root, provider, outInfos);
1255                 }
1256             }
1257             if (isFlagSet(AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS_DEPTH_FIRST)
1258                     || isFlagSet(AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS_BREADTH_FIRST)) {
1259                 if (shouldStopPrefetching(outInfos)) {
1260                     return;
1261                 }
1262                 PrefetchDeque<DequeNode> deque = new PrefetchDeque<>(
1263                         mFetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS_MASK,
1264                         outInfos);
1265                 addChildrenOfRoot(view, root, provider, deque);
1266                 deque.performTraversalAndPrefetch();
1267             }
1268             if (ENFORCE_NODE_TREE_CONSISTENT) {
1269                 enforceNodeTreeConsistent(root, outInfos);
1270             }
1271         }
1272 
addChildrenOfRoot(View root, AccessibilityNodeInfo rootInfo, AccessibilityNodeProvider rootProvider, PrefetchDeque deque)1273         private void addChildrenOfRoot(View root, AccessibilityNodeInfo rootInfo,
1274                 AccessibilityNodeProvider rootProvider, PrefetchDeque deque) {
1275             DequeNode rootDequeNode;
1276             if (rootProvider == null) {
1277                 rootDequeNode = new ViewNode(root);
1278             } else {
1279                 rootDequeNode = new VirtualNode(
1280                         AccessibilityNodeProvider.HOST_VIEW_ID, rootProvider);
1281             }
1282             rootDequeNode.addChildren(rootInfo, deque);
1283         }
1284 
isFlagSet(@ccessibilityNodeInfo.PrefetchingStrategy int strategy)1285         private boolean isFlagSet(@AccessibilityNodeInfo.PrefetchingStrategy int strategy) {
1286             return (mFetchFlags & strategy) != 0;
1287         }
1288 
shouldStopPrefetching(List prefetchedInfos)1289         public boolean shouldStopPrefetching(List prefetchedInfos) {
1290             return ((mHandler.hasUserInteractiveMessagesWaiting() && mInterruptPrefetch)
1291                     || prefetchedInfos.size()
1292                     >= AccessibilityNodeInfo.MAX_NUMBER_OF_PREFETCHED_NODES);
1293         }
1294 
enforceNodeTreeConsistent( AccessibilityNodeInfo root, List<AccessibilityNodeInfo> nodes)1295         private void enforceNodeTreeConsistent(
1296                 AccessibilityNodeInfo root, List<AccessibilityNodeInfo> nodes) {
1297             LongSparseArray<AccessibilityNodeInfo> nodeMap =
1298                     new LongSparseArray<AccessibilityNodeInfo>();
1299             final int nodeCount = nodes.size();
1300             for (int i = 0; i < nodeCount; i++) {
1301                 AccessibilityNodeInfo node = nodes.get(i);
1302                 nodeMap.put(node.getSourceNodeId(), node);
1303             }
1304 
1305             // If the nodes are a tree it does not matter from
1306             // which node we start to search for the root.
1307             AccessibilityNodeInfo parent = root;
1308             while (parent != null) {
1309                 root = parent;
1310                 parent = nodeMap.get(parent.getParentNodeId());
1311             }
1312 
1313             // Traverse the tree and do some checks.
1314             AccessibilityNodeInfo accessFocus = null;
1315             AccessibilityNodeInfo inputFocus = null;
1316             HashSet<AccessibilityNodeInfo> seen = new HashSet<AccessibilityNodeInfo>();
1317             Queue<AccessibilityNodeInfo> fringe = new LinkedList<AccessibilityNodeInfo>();
1318             fringe.add(root);
1319 
1320             while (!fringe.isEmpty()) {
1321                 AccessibilityNodeInfo current = fringe.poll();
1322 
1323                 // Check for duplicates
1324                 if (!seen.add(current)) {
1325                     throw new IllegalStateException("Duplicate node: "
1326                             + current + " in window:"
1327                             + mViewRootImpl.mAttachInfo.mAccessibilityWindowId);
1328                 }
1329 
1330                 // Check for one accessibility focus.
1331                 if (current.isAccessibilityFocused()) {
1332                     if (accessFocus != null) {
1333                         throw new IllegalStateException("Duplicate accessibility focus:"
1334                                 + current
1335                                 + " in window:" + mViewRootImpl.mAttachInfo.mAccessibilityWindowId);
1336                     } else {
1337                         accessFocus = current;
1338                     }
1339                 }
1340 
1341                 // Check for one input focus.
1342                 if (current.isFocused()) {
1343                     if (inputFocus != null) {
1344                         throw new IllegalStateException("Duplicate input focus: "
1345                             + current + " in window:"
1346                             + mViewRootImpl.mAttachInfo.mAccessibilityWindowId);
1347                     } else {
1348                         inputFocus = current;
1349                     }
1350                 }
1351 
1352                 final int childCount = current.getChildCount();
1353                 for (int j = 0; j < childCount; j++) {
1354                     final long childId = current.getChildId(j);
1355                     final AccessibilityNodeInfo child = nodeMap.get(childId);
1356                     if (child != null) {
1357                         fringe.add(child);
1358                     }
1359                 }
1360             }
1361 
1362             // Check for disconnected nodes.
1363             for (int j = nodeMap.size() - 1; j >= 0; j--) {
1364                 AccessibilityNodeInfo info = nodeMap.valueAt(j);
1365                 if (!seen.contains(info)) {
1366                     throw new IllegalStateException("Disconnected node: " + info);
1367                 }
1368             }
1369         }
1370 
prefetchPredecessorsOfRealNode(View view, List<AccessibilityNodeInfo> outInfos)1371         private void prefetchPredecessorsOfRealNode(View view,
1372                 List<AccessibilityNodeInfo> outInfos) {
1373             if (shouldStopPrefetching(outInfos)) {
1374                 return;
1375             }
1376             ViewParent parent = view.getParentForAccessibility();
1377             while (parent instanceof View && !shouldStopPrefetching(outInfos)) {
1378                 View parentView = (View) parent;
1379                 AccessibilityNodeInfo info = parentView.createAccessibilityNodeInfo();
1380                 if (info != null) {
1381                     outInfos.add(info);
1382                 }
1383                 parent = parent.getParentForAccessibility();
1384             }
1385         }
1386 
prefetchSiblingsOfRealNode(View current, List<AccessibilityNodeInfo> outInfos, boolean predecessorsPrefetched)1387         private void prefetchSiblingsOfRealNode(View current,
1388                 List<AccessibilityNodeInfo> outInfos, boolean predecessorsPrefetched) {
1389             if (shouldStopPrefetching(outInfos)) {
1390                 return;
1391             }
1392             ViewParent parent = current.getParentForAccessibility();
1393             if (parent instanceof ViewGroup) {
1394                 ViewGroup parentGroup = (ViewGroup) parent;
1395                 ArrayList<View> children = mTempViewList;
1396                 children.clear();
1397                 try {
1398                     if (!predecessorsPrefetched) {
1399                         AccessibilityNodeInfo parentInfo =
1400                                 ((ViewGroup) parent).createAccessibilityNodeInfo();
1401                         if (parentInfo != null) {
1402                             outInfos.add(parentInfo);
1403                         }
1404                     }
1405                     parentGroup.addChildrenForAccessibility(children);
1406                     final int childCount = children.size();
1407                     for (int i = 0; i < childCount; i++) {
1408                         if (shouldStopPrefetching(outInfos)) {
1409                             return;
1410                         }
1411                         View child = children.get(i);
1412                         if (child.getAccessibilityViewId() != current.getAccessibilityViewId()
1413                                 && isShown(child)) {
1414                             AccessibilityNodeInfo info = null;
1415                             AccessibilityNodeProvider provider =
1416                                     child.getAccessibilityNodeProvider();
1417                             if (provider == null) {
1418                                 info = child.createAccessibilityNodeInfo();
1419                             } else {
1420                                 info = provider.createAccessibilityNodeInfo(
1421                                         AccessibilityNodeProvider.HOST_VIEW_ID);
1422                             }
1423                             if (info != null) {
1424                                 outInfos.add(info);
1425                             }
1426                         }
1427                     }
1428                 } finally {
1429                     children.clear();
1430                 }
1431             }
1432         }
1433 
prefetchDescendantsOfRealNode(View root, List<AccessibilityNodeInfo> outInfos)1434         private void prefetchDescendantsOfRealNode(View root,
1435                 List<AccessibilityNodeInfo> outInfos) {
1436             if (shouldStopPrefetching(outInfos) || !(root instanceof ViewGroup)) {
1437                 return;
1438             }
1439             LinkedHashMap<View, AccessibilityNodeInfo> addedChildren =
1440                     new LinkedHashMap<View, AccessibilityNodeInfo>();
1441             ArrayList<View> children = mTempViewList;
1442             children.clear();
1443             try {
1444                 root.addChildrenForAccessibility(children);
1445                 final int childCount = children.size();
1446                 for (int i = 0; i < childCount; i++) {
1447                     if (shouldStopPrefetching(outInfos)) {
1448                         return;
1449                     }
1450                     View child = children.get(i);
1451                     if (isShown(child)) {
1452                         AccessibilityNodeProvider provider = child.getAccessibilityNodeProvider();
1453                         if (provider == null) {
1454                             AccessibilityNodeInfo info = child.createAccessibilityNodeInfo();
1455                             if (info != null) {
1456                                 outInfos.add(info);
1457                                 addedChildren.put(child, null);
1458                             }
1459                         } else {
1460                             AccessibilityNodeInfo info = provider.createAccessibilityNodeInfo(
1461                                    AccessibilityNodeProvider.HOST_VIEW_ID);
1462                             if (info != null) {
1463                                 outInfos.add(info);
1464                                 addedChildren.put(child, info);
1465                             }
1466                         }
1467                     }
1468                 }
1469             } finally {
1470                 children.clear();
1471             }
1472             if (!shouldStopPrefetching(outInfos)) {
1473                 for (Map.Entry<View, AccessibilityNodeInfo> entry : addedChildren.entrySet()) {
1474                     View addedChild = entry.getKey();
1475                     AccessibilityNodeInfo virtualRoot = entry.getValue();
1476                     if (virtualRoot == null) {
1477                         prefetchDescendantsOfRealNode(addedChild, outInfos);
1478                     } else {
1479                         AccessibilityNodeProvider provider =
1480                             addedChild.getAccessibilityNodeProvider();
1481                         prefetchDescendantsOfVirtualNode(virtualRoot, provider, outInfos);
1482                     }
1483                 }
1484             }
1485         }
1486 
prefetchPredecessorsOfVirtualNode(AccessibilityNodeInfo root, View providerHost, AccessibilityNodeProvider provider, List<AccessibilityNodeInfo> outInfos)1487         private void prefetchPredecessorsOfVirtualNode(AccessibilityNodeInfo root,
1488                 View providerHost, AccessibilityNodeProvider provider,
1489                 List<AccessibilityNodeInfo> outInfos) {
1490             final int initialResultSize = outInfos.size();
1491             long parentNodeId = root.getParentNodeId();
1492             int accessibilityViewId = AccessibilityNodeInfo.getAccessibilityViewId(parentNodeId);
1493             while (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) {
1494                 if (shouldStopPrefetching(outInfos)) {
1495                     return;
1496                 }
1497                 final int virtualDescendantId =
1498                     AccessibilityNodeInfo.getVirtualDescendantId(parentNodeId);
1499                 if (virtualDescendantId != AccessibilityNodeProvider.HOST_VIEW_ID
1500                         || accessibilityViewId == providerHost.getAccessibilityViewId()) {
1501                     final AccessibilityNodeInfo parent;
1502                     parent = provider.createAccessibilityNodeInfo(virtualDescendantId);
1503                     if (parent == null) {
1504                         // Going up the parent relation we found a null predecessor,
1505                         // so remove these disconnected nodes form the result.
1506                         final int currentResultSize = outInfos.size();
1507                         for (int i = currentResultSize - 1; i >= initialResultSize; i--) {
1508                             outInfos.remove(i);
1509                         }
1510                         // Couldn't obtain the parent, which means we have a
1511                         // disconnected sub-tree. Abort prefetch immediately.
1512                         return;
1513                     }
1514                     outInfos.add(parent);
1515                     parentNodeId = parent.getParentNodeId();
1516                     accessibilityViewId = AccessibilityNodeInfo.getAccessibilityViewId(
1517                             parentNodeId);
1518                 } else {
1519                     prefetchPredecessorsOfRealNode(providerHost, outInfos);
1520                     return;
1521                 }
1522             }
1523         }
1524 
prefetchSiblingsOfVirtualNode(AccessibilityNodeInfo current, View providerHost, AccessibilityNodeProvider provider, List<AccessibilityNodeInfo> outInfos, boolean predecessorsPrefetched)1525         private void prefetchSiblingsOfVirtualNode(AccessibilityNodeInfo current, View providerHost,
1526                 AccessibilityNodeProvider provider, List<AccessibilityNodeInfo> outInfos,
1527                 boolean predecessorsPrefetched) {
1528             final long parentNodeId = current.getParentNodeId();
1529             final int parentAccessibilityViewId =
1530                     AccessibilityNodeInfo.getAccessibilityViewId(parentNodeId);
1531             final int parentVirtualDescendantId =
1532                     AccessibilityNodeInfo.getVirtualDescendantId(parentNodeId);
1533             if (parentVirtualDescendantId != AccessibilityNodeProvider.HOST_VIEW_ID
1534                     || parentAccessibilityViewId == providerHost.getAccessibilityViewId()) {
1535                 final AccessibilityNodeInfo parent =
1536                         provider.createAccessibilityNodeInfo(parentVirtualDescendantId);
1537                 if (parent != null) {
1538                     if (!predecessorsPrefetched) {
1539                         outInfos.add(parent);
1540                     }
1541                     final int childCount = parent.getChildCount();
1542                     for (int i = 0; i < childCount; i++) {
1543                         if (shouldStopPrefetching(outInfos)) {
1544                             return;
1545                         }
1546                         final long childNodeId = parent.getChildId(i);
1547                         if (childNodeId != current.getSourceNodeId()) {
1548                             final int childVirtualDescendantId =
1549                                 AccessibilityNodeInfo.getVirtualDescendantId(childNodeId);
1550                             AccessibilityNodeInfo child = provider.createAccessibilityNodeInfo(
1551                                     childVirtualDescendantId);
1552                             if (child != null) {
1553                                 outInfos.add(child);
1554                             }
1555                         }
1556                     }
1557                 }
1558             } else {
1559                 prefetchSiblingsOfRealNode(providerHost, outInfos, predecessorsPrefetched);
1560             }
1561         }
1562 
prefetchDescendantsOfVirtualNode(AccessibilityNodeInfo root, AccessibilityNodeProvider provider, List<AccessibilityNodeInfo> outInfos)1563         private void prefetchDescendantsOfVirtualNode(AccessibilityNodeInfo root,
1564                 AccessibilityNodeProvider provider, List<AccessibilityNodeInfo> outInfos) {
1565             final int initialOutInfosSize = outInfos.size();
1566             final int childCount = root.getChildCount();
1567             for (int i = 0; i < childCount; i++) {
1568                 if (shouldStopPrefetching(outInfos)) {
1569                     return;
1570                 }
1571                 final long childNodeId = root.getChildId(i);
1572                 AccessibilityNodeInfo child = provider.createAccessibilityNodeInfo(
1573                         AccessibilityNodeInfo.getVirtualDescendantId(childNodeId));
1574                 if (child != null) {
1575                     outInfos.add(child);
1576                 }
1577             }
1578             if (!shouldStopPrefetching(outInfos)) {
1579                 final int addedChildCount = outInfos.size() - initialOutInfosSize;
1580                 for (int i = 0; i < addedChildCount; i++) {
1581                     AccessibilityNodeInfo child = outInfos.get(initialOutInfosSize + i);
1582                     prefetchDescendantsOfVirtualNode(child, provider, outInfos);
1583                 }
1584             }
1585         }
1586     }
1587 
1588     private class PrivateHandler extends Handler {
1589         private static final int MSG_PERFORM_ACCESSIBILITY_ACTION = 1;
1590         private static final int MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_ACCESSIBILITY_ID = 2;
1591         private static final int MSG_FIND_ACCESSIBILITY_NODE_INFOS_BY_VIEW_ID = 3;
1592         private static final int MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_TEXT = 4;
1593         private static final int MSG_FIND_FOCUS = 5;
1594         private static final int MSG_FOCUS_SEARCH = 6;
1595         private static final int MSG_PREPARE_FOR_EXTRA_DATA_REQUEST = 7;
1596         private static final int MSG_APP_PREPARATION_FINISHED = 8;
1597         private static final int MSG_APP_PREPARATION_TIMEOUT = 9;
1598 
1599         // Uses FIRST_NO_ACCESSIBILITY_CALLBACK_MSG for messages that don't need to call back
1600         // results to interrogating client.
1601         private static final int FIRST_NO_ACCESSIBILITY_CALLBACK_MSG = 100;
1602         private static final int MSG_CLEAR_ACCESSIBILITY_FOCUS =
1603                 FIRST_NO_ACCESSIBILITY_CALLBACK_MSG + 1;
1604         private static final int MSG_NOTIFY_OUTSIDE_TOUCH =
1605                 FIRST_NO_ACCESSIBILITY_CALLBACK_MSG + 2;
1606 
PrivateHandler(Looper looper)1607         public PrivateHandler(Looper looper) {
1608             super(looper);
1609         }
1610 
1611         @Override
getMessageName(Message message)1612         public String getMessageName(Message message) {
1613             final int type = message.what;
1614             switch (type) {
1615                 case MSG_PERFORM_ACCESSIBILITY_ACTION:
1616                     return "MSG_PERFORM_ACCESSIBILITY_ACTION";
1617                 case MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_ACCESSIBILITY_ID:
1618                     return "MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_ACCESSIBILITY_ID";
1619                 case MSG_FIND_ACCESSIBILITY_NODE_INFOS_BY_VIEW_ID:
1620                     return "MSG_FIND_ACCESSIBILITY_NODE_INFOS_BY_VIEW_ID";
1621                 case MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_TEXT:
1622                     return "MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_TEXT";
1623                 case MSG_FIND_FOCUS:
1624                     return "MSG_FIND_FOCUS";
1625                 case MSG_FOCUS_SEARCH:
1626                     return "MSG_FOCUS_SEARCH";
1627                 case MSG_PREPARE_FOR_EXTRA_DATA_REQUEST:
1628                     return "MSG_PREPARE_FOR_EXTRA_DATA_REQUEST";
1629                 case MSG_APP_PREPARATION_FINISHED:
1630                     return "MSG_APP_PREPARATION_FINISHED";
1631                 case MSG_APP_PREPARATION_TIMEOUT:
1632                     return "MSG_APP_PREPARATION_TIMEOUT";
1633                 case MSG_CLEAR_ACCESSIBILITY_FOCUS:
1634                     return "MSG_CLEAR_ACCESSIBILITY_FOCUS";
1635                 case MSG_NOTIFY_OUTSIDE_TOUCH:
1636                     return "MSG_NOTIFY_OUTSIDE_TOUCH";
1637                 default:
1638                     throw new IllegalArgumentException("Unknown message type: " + type);
1639             }
1640         }
1641 
1642         @Override
handleMessage(Message message)1643         public void handleMessage(Message message) {
1644             final int type = message.what;
1645             switch (type) {
1646                 case MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_ACCESSIBILITY_ID: {
1647                     findAccessibilityNodeInfoByAccessibilityIdUiThread(message);
1648                 } break;
1649                 case MSG_PERFORM_ACCESSIBILITY_ACTION: {
1650                     performAccessibilityActionUiThread(message);
1651                 } break;
1652                 case MSG_FIND_ACCESSIBILITY_NODE_INFOS_BY_VIEW_ID: {
1653                     findAccessibilityNodeInfosByViewIdUiThread(message);
1654                 } break;
1655                 case MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_TEXT: {
1656                     findAccessibilityNodeInfosByTextUiThread(message);
1657                 } break;
1658                 case MSG_FIND_FOCUS: {
1659                     findFocusUiThread(message);
1660                 } break;
1661                 case MSG_FOCUS_SEARCH: {
1662                     focusSearchUiThread(message);
1663                 } break;
1664                 case MSG_PREPARE_FOR_EXTRA_DATA_REQUEST: {
1665                     prepareForExtraDataRequestUiThread(message);
1666                 } break;
1667                 case MSG_APP_PREPARATION_FINISHED: {
1668                     requestPreparerDoneUiThread(message);
1669                 } break;
1670                 case MSG_APP_PREPARATION_TIMEOUT: {
1671                     requestPreparerTimeoutUiThread();
1672                 } break;
1673                 case MSG_CLEAR_ACCESSIBILITY_FOCUS: {
1674                     clearAccessibilityFocusUiThread();
1675                 } break;
1676                 case MSG_NOTIFY_OUTSIDE_TOUCH: {
1677                     notifyOutsideTouchUiThread();
1678                 } break;
1679                 default:
1680                     throw new IllegalArgumentException("Unknown message type: " + type);
1681             }
1682         }
1683 
hasAccessibilityCallback(Message message)1684         boolean hasAccessibilityCallback(Message message) {
1685             return message.what < FIRST_NO_ACCESSIBILITY_CALLBACK_MSG ? true : false;
1686         }
1687 
hasUserInteractiveMessagesWaiting()1688         boolean hasUserInteractiveMessagesWaiting() {
1689             return hasMessagesOrCallbacks();
1690         }
1691     }
1692 
1693     private final class AddNodeInfosForViewId implements Predicate<View> {
1694         private int mViewId = View.NO_ID;
1695         private List<AccessibilityNodeInfo> mInfos;
1696 
init(int viewId, List<AccessibilityNodeInfo> infos)1697         public void init(int viewId, List<AccessibilityNodeInfo> infos) {
1698             mViewId = viewId;
1699             mInfos = infos;
1700         }
1701 
reset()1702         public void reset() {
1703             mViewId = View.NO_ID;
1704             mInfos = null;
1705         }
1706 
1707         @Override
test(View view)1708         public boolean test(View view) {
1709             if (view.getId() == mViewId && isShown(view)) {
1710                 mInfos.add(view.createAccessibilityNodeInfo());
1711             }
1712             return false;
1713         }
1714     }
1715 
1716     private static final class MessageHolder {
1717         final Message mMessage;
1718         final int mInterrogatingPid;
1719         final long mInterrogatingTid;
1720 
MessageHolder(Message message, int interrogatingPid, long interrogatingTid)1721         MessageHolder(Message message, int interrogatingPid, long interrogatingTid) {
1722             mMessage = message;
1723             mInterrogatingPid = interrogatingPid;
1724             mInterrogatingTid = interrogatingTid;
1725         }
1726     }
1727 
1728     private static class SatisfiedFindAccessibilityNodeByAccessibilityIdRequest {
1729         final AccessibilityNodeInfo mSatisfiedRequestNode;
1730         final IAccessibilityInteractionConnectionCallback mSatisfiedRequestCallback;
1731         final int mSatisfiedRequestInteractionId;
1732 
SatisfiedFindAccessibilityNodeByAccessibilityIdRequest( AccessibilityNodeInfo satisfiedRequestNode, IAccessibilityInteractionConnectionCallback satisfiedRequestCallback, int satisfiedRequestInteractionId)1733         SatisfiedFindAccessibilityNodeByAccessibilityIdRequest(
1734                 AccessibilityNodeInfo satisfiedRequestNode,
1735                 IAccessibilityInteractionConnectionCallback satisfiedRequestCallback,
1736                 int satisfiedRequestInteractionId) {
1737             mSatisfiedRequestNode = satisfiedRequestNode;
1738             mSatisfiedRequestCallback = satisfiedRequestCallback;
1739             mSatisfiedRequestInteractionId = satisfiedRequestInteractionId;
1740         }
1741     }
1742 
1743     private class PrefetchDeque<E extends DequeNode>
1744             extends ArrayDeque<E> {
1745         int mStrategy;
1746         List<AccessibilityNodeInfo> mPrefetchOutput;
1747 
PrefetchDeque(int strategy, List<AccessibilityNodeInfo> output)1748         PrefetchDeque(int strategy, List<AccessibilityNodeInfo> output) {
1749             mStrategy = strategy;
1750             mPrefetchOutput = output;
1751         }
1752 
1753         /** Performs depth-first or breadth-first traversal.
1754          *
1755          * For depth-first search, we iterate through the children in backwards order and push them
1756          * to the stack before taking from the head. For breadth-first search, we iterate through
1757          * the children in order and push them to the stack before taking from the tail.
1758          *
1759          * Depth-first search:  0 has children 0, 1, 2, 4. 1 has children 5 and 6.
1760          * Head         Tail
1761          * 1  2  3  4 ->  pop: 1 -> 5  6  2  3  4
1762          *
1763          * Breadth-first search
1764          * Head         Tail
1765          * 4  3  2  1 -> remove last: 1 -> 6  5  3  2
1766          *
1767          **/
performTraversalAndPrefetch()1768         void performTraversalAndPrefetch() {
1769             try {
1770                 while (!isEmpty()) {
1771                     E child = getNext();
1772                     AccessibilityNodeInfo childInfo = child.getA11yNodeInfo();
1773                     if (childInfo != null) {
1774                         mPrefetchOutput.add(childInfo);
1775                     }
1776                     if (mPrefetcher.shouldStopPrefetching(mPrefetchOutput)) {
1777                         return;
1778                     }
1779                     // Add children to deque.
1780                     child.addChildren(childInfo, this);
1781                 }
1782             } finally {
1783                 clear();
1784             }
1785         }
1786 
getNext()1787         E getNext() {
1788             if (isStack()) {
1789                 return pop();
1790             }
1791             return removeLast();
1792         }
1793 
isStack()1794         boolean isStack() {
1795             return (mStrategy & AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS_DEPTH_FIRST) != 0;
1796         }
1797     }
1798 
1799     interface DequeNode {
getA11yNodeInfo()1800         AccessibilityNodeInfo getA11yNodeInfo();
addChildren(AccessibilityNodeInfo virtualRoot, PrefetchDeque deque)1801         void addChildren(AccessibilityNodeInfo virtualRoot, PrefetchDeque deque);
1802     }
1803 
1804     private class ViewNode implements DequeNode {
1805         View mView;
1806         private final ArrayList<View> mTempViewList = new ArrayList<>();
1807 
ViewNode(View view)1808         ViewNode(View view) {
1809             mView = view;
1810         }
1811 
1812         @Override
getA11yNodeInfo()1813         public AccessibilityNodeInfo getA11yNodeInfo() {
1814             if (mView == null) {
1815                 return null;
1816             }
1817             return mView.createAccessibilityNodeInfo();
1818         }
1819 
1820         @Override
addChildren(AccessibilityNodeInfo virtualRoot, PrefetchDeque deque)1821         public void addChildren(AccessibilityNodeInfo virtualRoot, PrefetchDeque deque) {
1822             if (mView == null) {
1823                 return;
1824             }
1825             if (!(mView instanceof ViewGroup)) {
1826                 return;
1827             }
1828             ArrayList<View> children = mTempViewList;
1829             children.clear();
1830             try {
1831                 mView.addChildrenForAccessibility(children);
1832                 final int childCount = children.size();
1833 
1834                 if (deque.isStack()) {
1835                     for (int i = childCount - 1; i >= 0; i--) {
1836                         addChild(deque, children.get(i));
1837                     }
1838                 } else {
1839                     for (int i = 0; i < childCount; i++) {
1840                         addChild(deque, children.get(i));
1841                     }
1842                 }
1843             } finally {
1844                 children.clear();
1845             }
1846         }
1847 
addChild(ArrayDeque deque, View child)1848         private void addChild(ArrayDeque deque, View child) {
1849             if (isShown(child)) {
1850                 AccessibilityNodeProvider provider = child.getAccessibilityNodeProvider();
1851                 if (provider == null) {
1852                     deque.push(new ViewNode(child));
1853                 } else {
1854                     deque.push(new VirtualNode(AccessibilityNodeProvider.HOST_VIEW_ID,
1855                             provider));
1856                 }
1857             }
1858         }
1859     }
1860 
1861     private class VirtualNode implements DequeNode {
1862         long mInfoId;
1863         AccessibilityNodeProvider mProvider;
1864 
VirtualNode(long id, AccessibilityNodeProvider provider)1865         VirtualNode(long id, AccessibilityNodeProvider provider) {
1866             mInfoId = id;
1867             mProvider = provider;
1868         }
1869         @Override
getA11yNodeInfo()1870         public AccessibilityNodeInfo getA11yNodeInfo() {
1871             if (mProvider == null) {
1872                 return null;
1873             }
1874             return mProvider.createAccessibilityNodeInfo(
1875                     AccessibilityNodeInfo.getVirtualDescendantId(mInfoId));
1876         }
1877 
1878         @Override
addChildren(AccessibilityNodeInfo virtualRoot, PrefetchDeque deque)1879         public void addChildren(AccessibilityNodeInfo virtualRoot, PrefetchDeque deque) {
1880             if (virtualRoot == null) {
1881                 return;
1882             }
1883             final int childCount = virtualRoot.getChildCount();
1884             if (deque.isStack()) {
1885                 for (int i = childCount - 1; i >= 0; i--) {
1886                     final long childNodeId = virtualRoot.getChildId(i);
1887                     deque.push(new VirtualNode(childNodeId, mProvider));
1888                 }
1889             } else {
1890                 for (int i = 0; i < childCount; i++) {
1891                     final long childNodeId = virtualRoot.getChildId(i);
1892                     deque.push(new VirtualNode(childNodeId, mProvider));
1893                 }
1894             }
1895         }
1896     }
1897 }
1898