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