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 
adjustBoundsInScreenIfNeeded(List<AccessibilityNodeInfo> infos)833     private void adjustBoundsInScreenIfNeeded(List<AccessibilityNodeInfo> infos) {
834         if (infos == null || shouldBypassAdjustBoundsInScreen()) {
835             return;
836         }
837         final int infoCount = infos.size();
838         for (int i = 0; i < infoCount; i++) {
839             final AccessibilityNodeInfo info = infos.get(i);
840             adjustBoundsInScreenIfNeeded(info);
841         }
842     }
843 
adjustBoundsInScreenIfNeeded(AccessibilityNodeInfo info)844     private void adjustBoundsInScreenIfNeeded(AccessibilityNodeInfo info) {
845         if (info == null || shouldBypassAdjustBoundsInScreen()) {
846             return;
847         }
848         final Rect boundsInScreen = mTempRect;
849         info.getBoundsInScreen(boundsInScreen);
850         boundsInScreen.offset(mViewRootImpl.mAttachInfo.mLocationInParentDisplay.x,
851                 mViewRootImpl.mAttachInfo.mLocationInParentDisplay.y);
852         info.setBoundsInScreen(boundsInScreen);
853     }
854 
shouldBypassAdjustBoundsInScreen()855     private boolean shouldBypassAdjustBoundsInScreen() {
856         return mViewRootImpl.mAttachInfo.mLocationInParentDisplay.equals(0, 0);
857     }
858 
applyAppScaleAndMagnificationSpecIfNeeded(AccessibilityNodeInfo info, MagnificationSpec spec)859     private void applyAppScaleAndMagnificationSpecIfNeeded(AccessibilityNodeInfo info,
860             MagnificationSpec spec) {
861         if (info == null) {
862             return;
863         }
864 
865         final float applicationScale = mViewRootImpl.mAttachInfo.mApplicationScale;
866         if (!shouldApplyAppScaleAndMagnificationSpec(applicationScale, spec)) {
867             return;
868         }
869 
870         Rect boundsInParent = mTempRect;
871         Rect boundsInScreen = mTempRect1;
872 
873         info.getBoundsInParent(boundsInParent);
874         info.getBoundsInScreen(boundsInScreen);
875         if (applicationScale != 1.0f) {
876             boundsInParent.scale(applicationScale);
877             boundsInScreen.scale(applicationScale);
878         }
879         if (spec != null) {
880             boundsInParent.scale(spec.scale);
881             // boundsInParent must not be offset.
882             boundsInScreen.scale(spec.scale);
883             boundsInScreen.offset((int) spec.offsetX, (int) spec.offsetY);
884         }
885         info.setBoundsInParent(boundsInParent);
886         info.setBoundsInScreen(boundsInScreen);
887 
888         // Scale text locations if they are present
889         if (info.hasExtras()) {
890             Bundle extras = info.getExtras();
891             Parcelable[] textLocations =
892                     extras.getParcelableArray(EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY);
893             if (textLocations != null) {
894                 for (int i = 0; i < textLocations.length; i++) {
895                     // Unchecked cast - an app that puts other objects in this bundle with this
896                     // key will crash.
897                     RectF textLocation = ((RectF) textLocations[i]);
898                     textLocation.scale(applicationScale);
899                     if (spec != null) {
900                         textLocation.scale(spec.scale);
901                         textLocation.offset(spec.offsetX, spec.offsetY);
902                     }
903                 }
904             }
905         }
906 
907         if (spec != null) {
908             AttachInfo attachInfo = mViewRootImpl.mAttachInfo;
909             if (attachInfo.mDisplay == null) {
910                 return;
911             }
912 
913             final float scale = attachInfo.mApplicationScale * spec.scale;
914 
915             Rect visibleWinFrame = mTempRect1;
916             visibleWinFrame.left = (int) (attachInfo.mWindowLeft * scale + spec.offsetX);
917             visibleWinFrame.top = (int) (attachInfo.mWindowTop * scale + spec.offsetY);
918             visibleWinFrame.right = (int) (visibleWinFrame.left + mViewRootImpl.mWidth * scale);
919             visibleWinFrame.bottom = (int) (visibleWinFrame.top + mViewRootImpl.mHeight * scale);
920 
921             attachInfo.mDisplay.getRealSize(mTempPoint);
922             final int displayWidth = mTempPoint.x;
923             final int displayHeight = mTempPoint.y;
924 
925             Rect visibleDisplayFrame = mTempRect2;
926             visibleDisplayFrame.set(0, 0, displayWidth, displayHeight);
927 
928             if (!visibleWinFrame.intersect(visibleDisplayFrame)) {
929                 // If there's no intersection with display, set visibleWinFrame empty.
930                 visibleDisplayFrame.setEmpty();
931             }
932 
933             if (!visibleWinFrame.intersects(boundsInScreen.left, boundsInScreen.top,
934                     boundsInScreen.right, boundsInScreen.bottom)) {
935                 info.setVisibleToUser(false);
936             }
937         }
938     }
939 
shouldApplyAppScaleAndMagnificationSpec(float appScale, MagnificationSpec spec)940     private boolean shouldApplyAppScaleAndMagnificationSpec(float appScale,
941             MagnificationSpec spec) {
942         return (appScale != 1.0f || (spec != null && !spec.isNop()));
943     }
944 
updateInfosForViewportAndReturnFindNodeResult(List<AccessibilityNodeInfo> infos, IAccessibilityInteractionConnectionCallback callback, int interactionId, MagnificationSpec spec, Region interactiveRegion)945     private void updateInfosForViewportAndReturnFindNodeResult(List<AccessibilityNodeInfo> infos,
946             IAccessibilityInteractionConnectionCallback callback, int interactionId,
947             MagnificationSpec spec, Region interactiveRegion) {
948         try {
949             mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
950             adjustBoundsInScreenIfNeeded(infos);
951             applyAppScaleAndMagnificationSpecIfNeeded(infos, spec);
952             adjustIsVisibleToUserIfNeeded(infos, interactiveRegion);
953             callback.setFindAccessibilityNodeInfosResult(infos, interactionId);
954             if (infos != null) {
955                 infos.clear();
956             }
957         } catch (RemoteException re) {
958             /* ignore - the other side will time out */
959         } finally {
960             recycleMagnificationSpecAndRegionIfNeeded(spec, interactiveRegion);
961         }
962     }
963 
updateInfoForViewportAndReturnFindNodeResult(AccessibilityNodeInfo info, IAccessibilityInteractionConnectionCallback callback, int interactionId, MagnificationSpec spec, Region interactiveRegion)964     private void updateInfoForViewportAndReturnFindNodeResult(AccessibilityNodeInfo info,
965             IAccessibilityInteractionConnectionCallback callback, int interactionId,
966             MagnificationSpec spec, Region interactiveRegion) {
967         try {
968             mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
969             adjustBoundsInScreenIfNeeded(info);
970             applyAppScaleAndMagnificationSpecIfNeeded(info, spec);
971             adjustIsVisibleToUserIfNeeded(info, interactiveRegion);
972             callback.setFindAccessibilityNodeInfoResult(info, interactionId);
973         } catch (RemoteException re) {
974                 /* ignore - the other side will time out */
975         } finally {
976             recycleMagnificationSpecAndRegionIfNeeded(spec, interactiveRegion);
977         }
978     }
979 
recycleMagnificationSpecAndRegionIfNeeded(MagnificationSpec spec, Region region)980     private void recycleMagnificationSpecAndRegionIfNeeded(MagnificationSpec spec, Region region) {
981         if (android.os.Process.myPid() != Binder.getCallingPid()) {
982             // Specs are cached in the system process and obtained from a pool when read from
983             // a parcel, so only recycle the spec if called from another process.
984             if (spec != null) {
985                 spec.recycle();
986             }
987         } else {
988             // Regions are obtained in the system process and instantiated when read from
989             // a parcel, so only recycle the region if caled from the same process.
990             if (region != null) {
991                 region.recycle();
992             }
993         }
994     }
995 
handleClickableSpanActionUiThread( View view, int virtualDescendantId, Bundle arguments)996     private boolean handleClickableSpanActionUiThread(
997             View view, int virtualDescendantId, Bundle arguments) {
998         Parcelable span = arguments.getParcelable(ACTION_ARGUMENT_ACCESSIBLE_CLICKABLE_SPAN);
999         if (!(span instanceof AccessibilityClickableSpan)) {
1000             return false;
1001         }
1002 
1003         // Find the original ClickableSpan if it's still on the screen
1004         AccessibilityNodeInfo infoWithSpan = null;
1005         AccessibilityNodeProvider provider = view.getAccessibilityNodeProvider();
1006         if (provider != null) {
1007             infoWithSpan = provider.createAccessibilityNodeInfo(virtualDescendantId);
1008         } else if (virtualDescendantId == AccessibilityNodeProvider.HOST_VIEW_ID) {
1009             infoWithSpan = view.createAccessibilityNodeInfo();
1010         }
1011         if (infoWithSpan == null) {
1012             return false;
1013         }
1014 
1015         // Click on the corresponding span
1016         ClickableSpan clickableSpan = ((AccessibilityClickableSpan) span).findClickableSpan(
1017                 infoWithSpan.getOriginalText());
1018         if (clickableSpan != null) {
1019             clickableSpan.onClick(view);
1020             return true;
1021         }
1022         return false;
1023     }
1024 
1025     /**
1026      * This class encapsulates a prefetching strategy for the accessibility APIs for
1027      * querying window content. It is responsible to prefetch a batch of
1028      * AccessibilityNodeInfos in addition to the one for a requested node.
1029      */
1030     private class AccessibilityNodePrefetcher {
1031 
1032         private static final int MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE = 50;
1033 
1034         private final ArrayList<View> mTempViewList = new ArrayList<View>();
1035 
prefetchAccessibilityNodeInfos(View view, int virtualViewId, int fetchFlags, List<AccessibilityNodeInfo> outInfos, Bundle arguments)1036         public void prefetchAccessibilityNodeInfos(View view, int virtualViewId, int fetchFlags,
1037                 List<AccessibilityNodeInfo> outInfos, Bundle arguments) {
1038             AccessibilityNodeProvider provider = view.getAccessibilityNodeProvider();
1039             // Determine if we'll be populating extra data
1040             final String extraDataRequested = (arguments == null) ? null
1041                     : arguments.getString(EXTRA_DATA_REQUESTED_KEY);
1042             if (provider == null) {
1043                 AccessibilityNodeInfo root = view.createAccessibilityNodeInfo();
1044                 if (root != null) {
1045                     if (extraDataRequested != null) {
1046                         view.addExtraDataToAccessibilityNodeInfo(
1047                                 root, extraDataRequested, arguments);
1048                     }
1049                     outInfos.add(root);
1050                     if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS) != 0) {
1051                         prefetchPredecessorsOfRealNode(view, outInfos);
1052                     }
1053                     if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS) != 0) {
1054                         prefetchSiblingsOfRealNode(view, outInfos);
1055                     }
1056                     if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS) != 0) {
1057                         prefetchDescendantsOfRealNode(view, outInfos);
1058                     }
1059                 }
1060             } else {
1061                 final AccessibilityNodeInfo root =
1062                         provider.createAccessibilityNodeInfo(virtualViewId);
1063                 if (root != null) {
1064                     if (extraDataRequested != null) {
1065                         provider.addExtraDataToAccessibilityNodeInfo(
1066                                 virtualViewId, root, extraDataRequested, arguments);
1067                     }
1068                     outInfos.add(root);
1069                     if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS) != 0) {
1070                         prefetchPredecessorsOfVirtualNode(root, view, provider, outInfos);
1071                     }
1072                     if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS) != 0) {
1073                         prefetchSiblingsOfVirtualNode(root, view, provider, outInfos);
1074                     }
1075                     if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS) != 0) {
1076                         prefetchDescendantsOfVirtualNode(root, provider, outInfos);
1077                     }
1078                 }
1079             }
1080             if (ENFORCE_NODE_TREE_CONSISTENT) {
1081                 enforceNodeTreeConsistent(outInfos);
1082             }
1083         }
1084 
enforceNodeTreeConsistent(List<AccessibilityNodeInfo> nodes)1085         private void enforceNodeTreeConsistent(List<AccessibilityNodeInfo> nodes) {
1086             LongSparseArray<AccessibilityNodeInfo> nodeMap =
1087                     new LongSparseArray<AccessibilityNodeInfo>();
1088             final int nodeCount = nodes.size();
1089             for (int i = 0; i < nodeCount; i++) {
1090                 AccessibilityNodeInfo node = nodes.get(i);
1091                 nodeMap.put(node.getSourceNodeId(), node);
1092             }
1093 
1094             // If the nodes are a tree it does not matter from
1095             // which node we start to search for the root.
1096             AccessibilityNodeInfo root = nodeMap.valueAt(0);
1097             AccessibilityNodeInfo parent = root;
1098             while (parent != null) {
1099                 root = parent;
1100                 parent = nodeMap.get(parent.getParentNodeId());
1101             }
1102 
1103             // Traverse the tree and do some checks.
1104             AccessibilityNodeInfo accessFocus = null;
1105             AccessibilityNodeInfo inputFocus = null;
1106             HashSet<AccessibilityNodeInfo> seen = new HashSet<AccessibilityNodeInfo>();
1107             Queue<AccessibilityNodeInfo> fringe = new LinkedList<AccessibilityNodeInfo>();
1108             fringe.add(root);
1109 
1110             while (!fringe.isEmpty()) {
1111                 AccessibilityNodeInfo current = fringe.poll();
1112 
1113                 // Check for duplicates
1114                 if (!seen.add(current)) {
1115                     throw new IllegalStateException("Duplicate node: "
1116                             + current + " in window:"
1117                             + mViewRootImpl.mAttachInfo.mAccessibilityWindowId);
1118                 }
1119 
1120                 // Check for one accessibility focus.
1121                 if (current.isAccessibilityFocused()) {
1122                     if (accessFocus != null) {
1123                         throw new IllegalStateException("Duplicate accessibility focus:"
1124                                 + current
1125                                 + " in window:" + mViewRootImpl.mAttachInfo.mAccessibilityWindowId);
1126                     } else {
1127                         accessFocus = current;
1128                     }
1129                 }
1130 
1131                 // Check for one input focus.
1132                 if (current.isFocused()) {
1133                     if (inputFocus != null) {
1134                         throw new IllegalStateException("Duplicate input focus: "
1135                             + current + " in window:"
1136                             + mViewRootImpl.mAttachInfo.mAccessibilityWindowId);
1137                     } else {
1138                         inputFocus = current;
1139                     }
1140                 }
1141 
1142                 final int childCount = current.getChildCount();
1143                 for (int j = 0; j < childCount; j++) {
1144                     final long childId = current.getChildId(j);
1145                     final AccessibilityNodeInfo child = nodeMap.get(childId);
1146                     if (child != null) {
1147                         fringe.add(child);
1148                     }
1149                 }
1150             }
1151 
1152             // Check for disconnected nodes.
1153             for (int j = nodeMap.size() - 1; j >= 0; j--) {
1154                 AccessibilityNodeInfo info = nodeMap.valueAt(j);
1155                 if (!seen.contains(info)) {
1156                     throw new IllegalStateException("Disconnected node: " + info);
1157                 }
1158             }
1159         }
1160 
prefetchPredecessorsOfRealNode(View view, List<AccessibilityNodeInfo> outInfos)1161         private void prefetchPredecessorsOfRealNode(View view,
1162                 List<AccessibilityNodeInfo> outInfos) {
1163             ViewParent parent = view.getParentForAccessibility();
1164             while (parent instanceof View
1165                     && outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
1166                 View parentView = (View) parent;
1167                 AccessibilityNodeInfo info = parentView.createAccessibilityNodeInfo();
1168                 if (info != null) {
1169                     outInfos.add(info);
1170                 }
1171                 parent = parent.getParentForAccessibility();
1172             }
1173         }
1174 
prefetchSiblingsOfRealNode(View current, List<AccessibilityNodeInfo> outInfos)1175         private void prefetchSiblingsOfRealNode(View current,
1176                 List<AccessibilityNodeInfo> outInfos) {
1177             ViewParent parent = current.getParentForAccessibility();
1178             if (parent instanceof ViewGroup) {
1179                 ViewGroup parentGroup = (ViewGroup) parent;
1180                 ArrayList<View> children = mTempViewList;
1181                 children.clear();
1182                 try {
1183                     parentGroup.addChildrenForAccessibility(children);
1184                     final int childCount = children.size();
1185                     for (int i = 0; i < childCount; i++) {
1186                         if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
1187                             return;
1188                         }
1189                         View child = children.get(i);
1190                         if (child.getAccessibilityViewId() != current.getAccessibilityViewId()
1191                                 && isShown(child)) {
1192                             AccessibilityNodeInfo info = null;
1193                             AccessibilityNodeProvider provider =
1194                                 child.getAccessibilityNodeProvider();
1195                             if (provider == null) {
1196                                 info = child.createAccessibilityNodeInfo();
1197                             } else {
1198                                 info = provider.createAccessibilityNodeInfo(
1199                                         AccessibilityNodeProvider.HOST_VIEW_ID);
1200                             }
1201                             if (info != null) {
1202                                 outInfos.add(info);
1203                             }
1204                         }
1205                     }
1206                 } finally {
1207                     children.clear();
1208                 }
1209             }
1210         }
1211 
prefetchDescendantsOfRealNode(View root, List<AccessibilityNodeInfo> outInfos)1212         private void prefetchDescendantsOfRealNode(View root,
1213                 List<AccessibilityNodeInfo> outInfos) {
1214             if (!(root instanceof ViewGroup)) {
1215                 return;
1216             }
1217             HashMap<View, AccessibilityNodeInfo> addedChildren =
1218                 new HashMap<View, AccessibilityNodeInfo>();
1219             ArrayList<View> children = mTempViewList;
1220             children.clear();
1221             try {
1222                 root.addChildrenForAccessibility(children);
1223                 final int childCount = children.size();
1224                 for (int i = 0; i < childCount; i++) {
1225                     if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
1226                         return;
1227                     }
1228                     View child = children.get(i);
1229                     if (isShown(child)) {
1230                         AccessibilityNodeProvider provider = child.getAccessibilityNodeProvider();
1231                         if (provider == null) {
1232                             AccessibilityNodeInfo info = child.createAccessibilityNodeInfo();
1233                             if (info != null) {
1234                                 outInfos.add(info);
1235                                 addedChildren.put(child, null);
1236                             }
1237                         } else {
1238                             AccessibilityNodeInfo info = provider.createAccessibilityNodeInfo(
1239                                    AccessibilityNodeProvider.HOST_VIEW_ID);
1240                             if (info != null) {
1241                                 outInfos.add(info);
1242                                 addedChildren.put(child, info);
1243                             }
1244                         }
1245                     }
1246                 }
1247             } finally {
1248                 children.clear();
1249             }
1250             if (outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
1251                 for (Map.Entry<View, AccessibilityNodeInfo> entry : addedChildren.entrySet()) {
1252                     View addedChild = entry.getKey();
1253                     AccessibilityNodeInfo virtualRoot = entry.getValue();
1254                     if (virtualRoot == null) {
1255                         prefetchDescendantsOfRealNode(addedChild, outInfos);
1256                     } else {
1257                         AccessibilityNodeProvider provider =
1258                             addedChild.getAccessibilityNodeProvider();
1259                         prefetchDescendantsOfVirtualNode(virtualRoot, provider, outInfos);
1260                     }
1261                 }
1262             }
1263         }
1264 
prefetchPredecessorsOfVirtualNode(AccessibilityNodeInfo root, View providerHost, AccessibilityNodeProvider provider, List<AccessibilityNodeInfo> outInfos)1265         private void prefetchPredecessorsOfVirtualNode(AccessibilityNodeInfo root,
1266                 View providerHost, AccessibilityNodeProvider provider,
1267                 List<AccessibilityNodeInfo> outInfos) {
1268             final int initialResultSize = outInfos.size();
1269             long parentNodeId = root.getParentNodeId();
1270             int accessibilityViewId = AccessibilityNodeInfo.getAccessibilityViewId(parentNodeId);
1271             while (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) {
1272                 if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
1273                     return;
1274                 }
1275                 final int virtualDescendantId =
1276                     AccessibilityNodeInfo.getVirtualDescendantId(parentNodeId);
1277                 if (virtualDescendantId != AccessibilityNodeProvider.HOST_VIEW_ID
1278                         || accessibilityViewId == providerHost.getAccessibilityViewId()) {
1279                     final AccessibilityNodeInfo parent;
1280                     parent = provider.createAccessibilityNodeInfo(virtualDescendantId);
1281                     if (parent == null) {
1282                         // Going up the parent relation we found a null predecessor,
1283                         // so remove these disconnected nodes form the result.
1284                         final int currentResultSize = outInfos.size();
1285                         for (int i = currentResultSize - 1; i >= initialResultSize; i--) {
1286                             outInfos.remove(i);
1287                         }
1288                         // Couldn't obtain the parent, which means we have a
1289                         // disconnected sub-tree. Abort prefetch immediately.
1290                         return;
1291                     }
1292                     outInfos.add(parent);
1293                     parentNodeId = parent.getParentNodeId();
1294                     accessibilityViewId = AccessibilityNodeInfo.getAccessibilityViewId(
1295                             parentNodeId);
1296                 } else {
1297                     prefetchPredecessorsOfRealNode(providerHost, outInfos);
1298                     return;
1299                 }
1300             }
1301         }
1302 
prefetchSiblingsOfVirtualNode(AccessibilityNodeInfo current, View providerHost, AccessibilityNodeProvider provider, List<AccessibilityNodeInfo> outInfos)1303         private void prefetchSiblingsOfVirtualNode(AccessibilityNodeInfo current, View providerHost,
1304                 AccessibilityNodeProvider provider, List<AccessibilityNodeInfo> outInfos) {
1305             final long parentNodeId = current.getParentNodeId();
1306             final int parentAccessibilityViewId =
1307                 AccessibilityNodeInfo.getAccessibilityViewId(parentNodeId);
1308             final int parentVirtualDescendantId =
1309                 AccessibilityNodeInfo.getVirtualDescendantId(parentNodeId);
1310             if (parentVirtualDescendantId != AccessibilityNodeProvider.HOST_VIEW_ID
1311                     || parentAccessibilityViewId == providerHost.getAccessibilityViewId()) {
1312                 final AccessibilityNodeInfo parent =
1313                         provider.createAccessibilityNodeInfo(parentVirtualDescendantId);
1314                 if (parent != null) {
1315                     final int childCount = parent.getChildCount();
1316                     for (int i = 0; i < childCount; i++) {
1317                         if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
1318                             return;
1319                         }
1320                         final long childNodeId = parent.getChildId(i);
1321                         if (childNodeId != current.getSourceNodeId()) {
1322                             final int childVirtualDescendantId =
1323                                 AccessibilityNodeInfo.getVirtualDescendantId(childNodeId);
1324                             AccessibilityNodeInfo child = provider.createAccessibilityNodeInfo(
1325                                     childVirtualDescendantId);
1326                             if (child != null) {
1327                                 outInfos.add(child);
1328                             }
1329                         }
1330                     }
1331                 }
1332             } else {
1333                 prefetchSiblingsOfRealNode(providerHost, outInfos);
1334             }
1335         }
1336 
prefetchDescendantsOfVirtualNode(AccessibilityNodeInfo root, AccessibilityNodeProvider provider, List<AccessibilityNodeInfo> outInfos)1337         private void prefetchDescendantsOfVirtualNode(AccessibilityNodeInfo root,
1338                 AccessibilityNodeProvider provider, List<AccessibilityNodeInfo> outInfos) {
1339             final int initialOutInfosSize = outInfos.size();
1340             final int childCount = root.getChildCount();
1341             for (int i = 0; i < childCount; i++) {
1342                 if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
1343                     return;
1344                 }
1345                 final long childNodeId = root.getChildId(i);
1346                 AccessibilityNodeInfo child = provider.createAccessibilityNodeInfo(
1347                         AccessibilityNodeInfo.getVirtualDescendantId(childNodeId));
1348                 if (child != null) {
1349                     outInfos.add(child);
1350                 }
1351             }
1352             if (outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
1353                 final int addedChildCount = outInfos.size() - initialOutInfosSize;
1354                 for (int i = 0; i < addedChildCount; i++) {
1355                     AccessibilityNodeInfo child = outInfos.get(initialOutInfosSize + i);
1356                     prefetchDescendantsOfVirtualNode(child, provider, outInfos);
1357                 }
1358             }
1359         }
1360     }
1361 
1362     private class PrivateHandler extends Handler {
1363         private static final int MSG_PERFORM_ACCESSIBILITY_ACTION = 1;
1364         private static final int MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_ACCESSIBILITY_ID = 2;
1365         private static final int MSG_FIND_ACCESSIBILITY_NODE_INFOS_BY_VIEW_ID = 3;
1366         private static final int MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_TEXT = 4;
1367         private static final int MSG_FIND_FOCUS = 5;
1368         private static final int MSG_FOCUS_SEARCH = 6;
1369         private static final int MSG_PREPARE_FOR_EXTRA_DATA_REQUEST = 7;
1370         private static final int MSG_APP_PREPARATION_FINISHED = 8;
1371         private static final int MSG_APP_PREPARATION_TIMEOUT = 9;
1372 
1373         // Uses FIRST_NO_ACCESSIBILITY_CALLBACK_MSG for messages that don't need to call back
1374         // results to interrogating client.
1375         private static final int FIRST_NO_ACCESSIBILITY_CALLBACK_MSG = 100;
1376         private static final int MSG_CLEAR_ACCESSIBILITY_FOCUS =
1377                 FIRST_NO_ACCESSIBILITY_CALLBACK_MSG + 1;
1378         private static final int MSG_NOTIFY_OUTSIDE_TOUCH =
1379                 FIRST_NO_ACCESSIBILITY_CALLBACK_MSG + 2;
1380 
PrivateHandler(Looper looper)1381         public PrivateHandler(Looper looper) {
1382             super(looper);
1383         }
1384 
1385         @Override
getMessageName(Message message)1386         public String getMessageName(Message message) {
1387             final int type = message.what;
1388             switch (type) {
1389                 case MSG_PERFORM_ACCESSIBILITY_ACTION:
1390                     return "MSG_PERFORM_ACCESSIBILITY_ACTION";
1391                 case MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_ACCESSIBILITY_ID:
1392                     return "MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_ACCESSIBILITY_ID";
1393                 case MSG_FIND_ACCESSIBILITY_NODE_INFOS_BY_VIEW_ID:
1394                     return "MSG_FIND_ACCESSIBILITY_NODE_INFOS_BY_VIEW_ID";
1395                 case MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_TEXT:
1396                     return "MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_TEXT";
1397                 case MSG_FIND_FOCUS:
1398                     return "MSG_FIND_FOCUS";
1399                 case MSG_FOCUS_SEARCH:
1400                     return "MSG_FOCUS_SEARCH";
1401                 case MSG_PREPARE_FOR_EXTRA_DATA_REQUEST:
1402                     return "MSG_PREPARE_FOR_EXTRA_DATA_REQUEST";
1403                 case MSG_APP_PREPARATION_FINISHED:
1404                     return "MSG_APP_PREPARATION_FINISHED";
1405                 case MSG_APP_PREPARATION_TIMEOUT:
1406                     return "MSG_APP_PREPARATION_TIMEOUT";
1407                 case MSG_CLEAR_ACCESSIBILITY_FOCUS:
1408                     return "MSG_CLEAR_ACCESSIBILITY_FOCUS";
1409                 case MSG_NOTIFY_OUTSIDE_TOUCH:
1410                     return "MSG_NOTIFY_OUTSIDE_TOUCH";
1411                 default:
1412                     throw new IllegalArgumentException("Unknown message type: " + type);
1413             }
1414         }
1415 
1416         @Override
handleMessage(Message message)1417         public void handleMessage(Message message) {
1418             final int type = message.what;
1419             switch (type) {
1420                 case MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_ACCESSIBILITY_ID: {
1421                     findAccessibilityNodeInfoByAccessibilityIdUiThread(message);
1422                 } break;
1423                 case MSG_PERFORM_ACCESSIBILITY_ACTION: {
1424                     performAccessibilityActionUiThread(message);
1425                 } break;
1426                 case MSG_FIND_ACCESSIBILITY_NODE_INFOS_BY_VIEW_ID: {
1427                     findAccessibilityNodeInfosByViewIdUiThread(message);
1428                 } break;
1429                 case MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_TEXT: {
1430                     findAccessibilityNodeInfosByTextUiThread(message);
1431                 } break;
1432                 case MSG_FIND_FOCUS: {
1433                     findFocusUiThread(message);
1434                 } break;
1435                 case MSG_FOCUS_SEARCH: {
1436                     focusSearchUiThread(message);
1437                 } break;
1438                 case MSG_PREPARE_FOR_EXTRA_DATA_REQUEST: {
1439                     prepareForExtraDataRequestUiThread(message);
1440                 } break;
1441                 case MSG_APP_PREPARATION_FINISHED: {
1442                     requestPreparerDoneUiThread(message);
1443                 } break;
1444                 case MSG_APP_PREPARATION_TIMEOUT: {
1445                     requestPreparerTimeoutUiThread();
1446                 } break;
1447                 case MSG_CLEAR_ACCESSIBILITY_FOCUS: {
1448                     clearAccessibilityFocusUiThread();
1449                 } break;
1450                 case MSG_NOTIFY_OUTSIDE_TOUCH: {
1451                     notifyOutsideTouchUiThread();
1452                 } break;
1453                 default:
1454                     throw new IllegalArgumentException("Unknown message type: " + type);
1455             }
1456         }
1457 
hasAccessibilityCallback(Message message)1458         boolean hasAccessibilityCallback(Message message) {
1459             return message.what < FIRST_NO_ACCESSIBILITY_CALLBACK_MSG ? true : false;
1460         }
1461     }
1462 
1463     private final class AddNodeInfosForViewId implements Predicate<View> {
1464         private int mViewId = View.NO_ID;
1465         private List<AccessibilityNodeInfo> mInfos;
1466 
init(int viewId, List<AccessibilityNodeInfo> infos)1467         public void init(int viewId, List<AccessibilityNodeInfo> infos) {
1468             mViewId = viewId;
1469             mInfos = infos;
1470         }
1471 
reset()1472         public void reset() {
1473             mViewId = View.NO_ID;
1474             mInfos = null;
1475         }
1476 
1477         @Override
test(View view)1478         public boolean test(View view) {
1479             if (view.getId() == mViewId && isShown(view)) {
1480                 mInfos.add(view.createAccessibilityNodeInfo());
1481             }
1482             return false;
1483         }
1484     }
1485 
1486     private static final class MessageHolder {
1487         final Message mMessage;
1488         final int mInterrogatingPid;
1489         final long mInterrogatingTid;
1490 
MessageHolder(Message message, int interrogatingPid, long interrogatingTid)1491         MessageHolder(Message message, int interrogatingPid, long interrogatingTid) {
1492             mMessage = message;
1493             mInterrogatingPid = interrogatingPid;
1494             mInterrogatingTid = interrogatingTid;
1495         }
1496     }
1497 }
1498