1 /*
2  ** Copyright 2011, 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.accessibility;
18 
19 import static android.accessibilityservice.AccessibilityTrace.FLAGS_ACCESSIBILITY_INTERACTION_CLIENT;
20 import static android.accessibilityservice.AccessibilityTrace.FLAGS_ACCESSIBILITY_INTERACTION_CONNECTION_CALLBACK;
21 
22 import android.accessibilityservice.IAccessibilityServiceConnection;
23 import android.annotation.NonNull;
24 import android.annotation.Nullable;
25 import android.compat.annotation.UnsupportedAppUsage;
26 import android.content.Context;
27 import android.os.Binder;
28 import android.os.Build;
29 import android.os.Bundle;
30 import android.os.IBinder;
31 import android.os.Message;
32 import android.os.Process;
33 import android.os.RemoteException;
34 import android.os.SystemClock;
35 import android.util.Log;
36 import android.util.LongSparseArray;
37 import android.util.SparseArray;
38 import android.util.SparseLongArray;
39 import android.view.Display;
40 import android.view.ViewConfiguration;
41 
42 import com.android.internal.annotations.VisibleForTesting;
43 import com.android.internal.util.ArrayUtils;
44 
45 import java.util.ArrayList;
46 import java.util.Arrays;
47 import java.util.Collections;
48 import java.util.HashSet;
49 import java.util.LinkedList;
50 import java.util.List;
51 import java.util.Queue;
52 import java.util.concurrent.atomic.AtomicInteger;
53 
54 /**
55  * This class is a singleton that performs accessibility interaction
56  * which is it queries remote view hierarchies about snapshots of their
57  * views as well requests from these hierarchies to perform certain
58  * actions on their views.
59  *
60  * Rationale: The content retrieval APIs are synchronous from a client's
61  *     perspective but internally they are asynchronous. The client thread
62  *     calls into the system requesting an action and providing a callback
63  *     to receive the result after which it waits up to a timeout for that
64  *     result. The system enforces security and the delegates the request
65  *     to a given view hierarchy where a message is posted (from a binder
66  *     thread) describing what to be performed by the main UI thread the
67  *     result of which it delivered via the mentioned callback. However,
68  *     the blocked client thread and the main UI thread of the target view
69  *     hierarchy can be the same thread, for example an accessibility service
70  *     and an activity run in the same process, thus they are executed on the
71  *     same main thread. In such a case the retrieval will fail since the UI
72  *     thread that has to process the message describing the work to be done
73  *     is blocked waiting for a result is has to compute! To avoid this scenario
74  *     when making a call the client also passes its process and thread ids so
75  *     the accessed view hierarchy can detect if the client making the request
76  *     is running in its main UI thread. In such a case the view hierarchy,
77  *     specifically the binder thread performing the IPC to it, does not post a
78  *     message to be run on the UI thread but passes it to the singleton
79  *     interaction client through which all interactions occur and the latter is
80  *     responsible to execute the message before starting to wait for the
81  *     asynchronous result delivered via the callback. In this case the expected
82  *     result is already received so no waiting is performed.
83  *
84  * @hide
85  */
86 public final class AccessibilityInteractionClient
87         extends IAccessibilityInteractionConnectionCallback.Stub {
88 
89     public static final int NO_ID = -1;
90 
91     public static final String CALL_STACK = "call_stack";
92     public static final String IGNORE_CALL_STACK = "ignore_call_stack";
93 
94     private static final String LOG_TAG = "AccessibilityInteractionClient";
95 
96     private static final boolean DEBUG = false;
97 
98     private static final boolean CHECK_INTEGRITY = true;
99 
100     private static final long TIMEOUT_INTERACTION_MILLIS = 5000;
101 
102     private static final long DISABLE_PREFETCHING_FOR_SCROLLING_MILLIS =
103             (long) (ViewConfiguration.getSendRecurringAccessibilityEventsInterval() * 1.5);
104 
105     private static final Object sStaticLock = new Object();
106 
107     private static final LongSparseArray<AccessibilityInteractionClient> sClients =
108         new LongSparseArray<>();
109 
110     private static final SparseArray<IAccessibilityServiceConnection> sConnectionCache =
111             new SparseArray<>();
112 
113     /** List of timestamps which indicate the latest time an a11y service receives a scroll event
114         from a window, mapping from windowId -> timestamp. */
115     private static final SparseLongArray sScrollingWindows = new SparseLongArray();
116 
117     private static AccessibilityCache sAccessibilityCache =
118             new AccessibilityCache(new AccessibilityCache.AccessibilityNodeRefresher());
119 
120     private final AtomicInteger mInteractionIdCounter = new AtomicInteger();
121 
122     private final Object mInstanceLock = new Object();
123 
124     private final AccessibilityManager  mAccessibilityManager;
125 
126     private volatile int mInteractionId = -1;
127     private volatile int mCallingUid = Process.INVALID_UID;
128     // call stack for IAccessibilityInteractionConnectionCallback APIs. These callback APIs are
129     // shared by multiple requests APIs in IAccessibilityServiceConnection. To correctly log the
130     // request API which triggers the callback, we log trace entries for callback after the
131     // request API thread waiting for the callback returns. To log the correct callback stack in
132     // the request API thread, we save the callback stack in this member variables.
133     private List<StackTraceElement> mCallStackOfCallback;
134 
135     private AccessibilityNodeInfo mFindAccessibilityNodeInfoResult;
136 
137     private List<AccessibilityNodeInfo> mFindAccessibilityNodeInfosResult;
138 
139     private boolean mPerformAccessibilityActionResult;
140 
141     private Message mSameThreadMessage;
142 
143     private int mInteractionIdWaitingForPrefetchResult = -1;
144     private int mConnectionIdWaitingForPrefetchResult;
145     private String[] mPackageNamesForNextPrefetchResult;
146 
147     /**
148      * @return The client for the current thread.
149      */
150     @UnsupportedAppUsage()
getInstance()151     public static AccessibilityInteractionClient getInstance() {
152         final long threadId = Thread.currentThread().getId();
153         return getInstanceForThread(threadId);
154     }
155 
156     /**
157      * <strong>Note:</strong> We keep one instance per interrogating thread since
158      * the instance contains state which can lead to undesired thread interleavings.
159      * We do not have a thread local variable since other threads should be able to
160      * look up the correct client knowing a thread id. See ViewRootImpl for details.
161      *
162      * @return The client for a given <code>threadId</code>.
163      */
getInstanceForThread(long threadId)164     public static AccessibilityInteractionClient getInstanceForThread(long threadId) {
165         synchronized (sStaticLock) {
166             AccessibilityInteractionClient client = sClients.get(threadId);
167             if (client == null) {
168                 client = new AccessibilityInteractionClient();
169                 sClients.put(threadId, client);
170             }
171             return client;
172         }
173     }
174 
175     /**
176      * @return The client for the current thread.
177      */
getInstance(Context context)178     public static AccessibilityInteractionClient getInstance(Context context) {
179         final long threadId = Thread.currentThread().getId();
180         if (context != null) {
181             return getInstanceForThread(threadId, context);
182         }
183         return getInstanceForThread(threadId);
184     }
185 
186     /**
187      * <strong>Note:</strong> We keep one instance per interrogating thread since
188      * the instance contains state which can lead to undesired thread interleavings.
189      * We do not have a thread local variable since other threads should be able to
190      * look up the correct client knowing a thread id. See ViewRootImpl for details.
191      *
192      * @return The client for a given <code>threadId</code>.
193      */
getInstanceForThread( long threadId, Context context)194     public static AccessibilityInteractionClient getInstanceForThread(
195             long threadId, Context context) {
196         synchronized (sStaticLock) {
197             AccessibilityInteractionClient client = sClients.get(threadId);
198             if (client == null) {
199                 client = new AccessibilityInteractionClient(context);
200                 sClients.put(threadId, client);
201             }
202             return client;
203         }
204     }
205 
206     /**
207      * Gets a cached accessibility service connection.
208      *
209      * @param connectionId The connection id.
210      * @return The cached connection if such.
211      */
getConnection(int connectionId)212     public static IAccessibilityServiceConnection getConnection(int connectionId) {
213         synchronized (sConnectionCache) {
214             return sConnectionCache.get(connectionId);
215         }
216     }
217 
218     /**
219      * Adds a cached accessibility service connection.
220      *
221      * @param connectionId The connection id.
222      * @param connection The connection.
223      */
addConnection(int connectionId, IAccessibilityServiceConnection connection)224     public static void addConnection(int connectionId, IAccessibilityServiceConnection connection) {
225         synchronized (sConnectionCache) {
226             sConnectionCache.put(connectionId, connection);
227         }
228     }
229 
230     /**
231      * Removes a cached accessibility service connection.
232      *
233      * @param connectionId The connection id.
234      */
removeConnection(int connectionId)235     public static void removeConnection(int connectionId) {
236         synchronized (sConnectionCache) {
237             sConnectionCache.remove(connectionId);
238         }
239     }
240 
241     /**
242      * This method is only for testing. Replacing the cache is a generally terrible idea, but
243      * tests need to be able to verify this class's interactions with the cache
244      */
245     @VisibleForTesting
setCache(AccessibilityCache cache)246     public static void setCache(AccessibilityCache cache) {
247         sAccessibilityCache = cache;
248     }
249 
AccessibilityInteractionClient()250     private AccessibilityInteractionClient() {
251         /* reducing constructor visibility */
252         mAccessibilityManager = null;
253     }
254 
AccessibilityInteractionClient(Context context)255     private AccessibilityInteractionClient(Context context) {
256         mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
257     }
258 
259     /**
260      * Sets the message to be processed if the interacted view hierarchy
261      * and the interacting client are running in the same thread.
262      *
263      * @param message The message.
264      */
265     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
setSameThreadMessage(Message message)266     public void setSameThreadMessage(Message message) {
267         synchronized (mInstanceLock) {
268             mSameThreadMessage = message;
269             mInstanceLock.notifyAll();
270         }
271     }
272 
273     /**
274      * Gets the root {@link AccessibilityNodeInfo} in the currently active window.
275      *
276      * @param connectionId The id of a connection for interacting with the system.
277      * @return The root {@link AccessibilityNodeInfo} if found, null otherwise.
278      */
getRootInActiveWindow(int connectionId)279     public AccessibilityNodeInfo getRootInActiveWindow(int connectionId) {
280         return findAccessibilityNodeInfoByAccessibilityId(connectionId,
281                 AccessibilityWindowInfo.ACTIVE_WINDOW_ID, AccessibilityNodeInfo.ROOT_NODE_ID,
282                 false, AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS, null);
283     }
284 
285     /**
286      * Gets the info for a window.
287      *
288      * @param connectionId The id of a connection for interacting with the system.
289      * @param accessibilityWindowId A unique window id. Use
290      *     {@link android.view.accessibility.AccessibilityWindowInfo#ACTIVE_WINDOW_ID}
291      *     to query the currently active window.
292      * @return The {@link AccessibilityWindowInfo}.
293      */
getWindow(int connectionId, int accessibilityWindowId)294     public AccessibilityWindowInfo getWindow(int connectionId, int accessibilityWindowId) {
295         return getWindow(connectionId, accessibilityWindowId, /* bypassCache */ false);
296     }
297 
298     /**
299      * Gets the info for a window.
300      *
301      * @param connectionId The id of a connection for interacting with the system.
302      * @param accessibilityWindowId A unique window id. Use
303      *     {@link android.view.accessibility.AccessibilityWindowInfo#ACTIVE_WINDOW_ID}
304      *     to query the currently active window.
305      * @param bypassCache Whether to bypass the cache.
306      * @return The {@link AccessibilityWindowInfo}.
307      */
getWindow(int connectionId, int accessibilityWindowId, boolean bypassCache)308     public AccessibilityWindowInfo getWindow(int connectionId, int accessibilityWindowId,
309             boolean bypassCache) {
310         try {
311             IAccessibilityServiceConnection connection = getConnection(connectionId);
312             if (connection != null) {
313                 AccessibilityWindowInfo window;
314                 if (!bypassCache) {
315                     window = sAccessibilityCache.getWindow(accessibilityWindowId);
316                     if (window != null) {
317                         if (DEBUG) {
318                             Log.i(LOG_TAG, "Window cache hit");
319                         }
320                         if (shouldTraceClient()) {
321                             logTraceClient(connection, "getWindow cache",
322                                     "connectionId=" + connectionId + ";accessibilityWindowId="
323                                     + accessibilityWindowId + ";bypassCache=false");
324                         }
325                         return window;
326                     }
327                     if (DEBUG) {
328                         Log.i(LOG_TAG, "Window cache miss");
329                     }
330                 }
331 
332                 final long identityToken = Binder.clearCallingIdentity();
333                 try {
334                     window = connection.getWindow(accessibilityWindowId);
335                 } finally {
336                     Binder.restoreCallingIdentity(identityToken);
337                 }
338                 if (shouldTraceClient()) {
339                     logTraceClient(connection, "getWindow", "connectionId=" + connectionId
340                             + ";accessibilityWindowId=" + accessibilityWindowId + ";bypassCache="
341                             + bypassCache);
342                 }
343 
344                 if (window != null) {
345                     if (!bypassCache) {
346                         sAccessibilityCache.addWindow(window);
347                     }
348                     return window;
349                 }
350             } else {
351                 if (DEBUG) {
352                     Log.w(LOG_TAG, "No connection for connection id: " + connectionId);
353                 }
354             }
355         } catch (RemoteException re) {
356             Log.e(LOG_TAG, "Error while calling remote getWindow", re);
357         }
358         return null;
359     }
360 
361     /**
362      * Gets the info for all windows of the default display.
363      *
364      * @param connectionId The id of a connection for interacting with the system.
365      * @return The {@link AccessibilityWindowInfo} list.
366      */
getWindows(int connectionId)367     public List<AccessibilityWindowInfo> getWindows(int connectionId) {
368         final SparseArray<List<AccessibilityWindowInfo>> windows =
369                 getWindowsOnAllDisplays(connectionId);
370         if (windows.size() > 0) {
371             return windows.valueAt(Display.DEFAULT_DISPLAY);
372         }
373         return Collections.emptyList();
374     }
375 
376     /**
377      * Gets the info for all windows of all displays.
378      *
379      * @param connectionId The id of a connection for interacting with the system.
380      * @return The SparseArray of {@link AccessibilityWindowInfo} list.
381      *         The key of SparseArray is display ID.
382      */
getWindowsOnAllDisplays(int connectionId)383     public SparseArray<List<AccessibilityWindowInfo>> getWindowsOnAllDisplays(int connectionId) {
384         try {
385             IAccessibilityServiceConnection connection = getConnection(connectionId);
386             if (connection != null) {
387                 SparseArray<List<AccessibilityWindowInfo>> windows =
388                         sAccessibilityCache.getWindowsOnAllDisplays();
389                 if (windows != null) {
390                     if (DEBUG) {
391                         Log.i(LOG_TAG, "Windows cache hit");
392                     }
393                     if (shouldTraceClient()) {
394                         logTraceClient(
395                                 connection, "getWindows cache", "connectionId=" + connectionId);
396                     }
397                     return windows;
398                 }
399                 if (DEBUG) {
400                     Log.i(LOG_TAG, "Windows cache miss");
401                 }
402                 final long identityToken = Binder.clearCallingIdentity();
403                 try {
404                     windows = connection.getWindows();
405                 } finally {
406                     Binder.restoreCallingIdentity(identityToken);
407                 }
408                 if (shouldTraceClient()) {
409                     logTraceClient(connection, "getWindows", "connectionId=" + connectionId);
410                 }
411                 if (windows != null) {
412                     sAccessibilityCache.setWindowsOnAllDisplays(windows);
413                     return windows;
414                 }
415             } else {
416                 if (DEBUG) {
417                     Log.w(LOG_TAG, "No connection for connection id: " + connectionId);
418                 }
419             }
420         } catch (RemoteException re) {
421             Log.e(LOG_TAG, "Error while calling remote getWindowsOnAllDisplays", re);
422         }
423 
424         final SparseArray<List<AccessibilityWindowInfo>> emptyWindows = new SparseArray<>();
425         return emptyWindows;
426     }
427 
428 
429     /**
430      * Finds an {@link AccessibilityNodeInfo} by accessibility id and given leash token instead of
431      * window id. This method is used to find the leashed node on the embedded view hierarchy.
432      *
433      * @param connectionId The id of a connection for interacting with the system.
434      * @param leashToken The token of the embedded hierarchy.
435      * @param accessibilityNodeId A unique view id or virtual descendant id from
436      *     where to start the search. Use
437      *     {@link android.view.accessibility.AccessibilityNodeInfo#ROOT_NODE_ID}
438      *     to start from the root.
439      * @param bypassCache Whether to bypass the cache while looking for the node.
440      * @param prefetchFlags flags to guide prefetching.
441      * @param arguments Optional action arguments.
442      * @return An {@link AccessibilityNodeInfo} if found, null otherwise.
443      */
findAccessibilityNodeInfoByAccessibilityId( int connectionId, @NonNull IBinder leashToken, long accessibilityNodeId, boolean bypassCache, int prefetchFlags, Bundle arguments)444     public @Nullable AccessibilityNodeInfo findAccessibilityNodeInfoByAccessibilityId(
445             int connectionId, @NonNull IBinder leashToken, long accessibilityNodeId,
446             boolean bypassCache, int prefetchFlags, Bundle arguments) {
447         if (leashToken == null) {
448             return null;
449         }
450         int windowId = -1;
451         try {
452             IAccessibilityServiceConnection connection = getConnection(connectionId);
453             if (connection != null) {
454                 windowId = connection.getWindowIdForLeashToken(leashToken);
455             } else {
456                 if (DEBUG) {
457                     Log.w(LOG_TAG, "No connection for connection id: " + connectionId);
458                 }
459             }
460         } catch (RemoteException re) {
461             Log.e(LOG_TAG, "Error while calling remote getWindowIdForLeashToken", re);
462         }
463         if (windowId == -1) {
464             return null;
465         }
466         return findAccessibilityNodeInfoByAccessibilityId(connectionId, windowId,
467                 accessibilityNodeId, bypassCache, prefetchFlags, arguments);
468     }
469 
470     /**
471      * Finds an {@link AccessibilityNodeInfo} by accessibility id.
472      *
473      * @param connectionId The id of a connection for interacting with the system.
474      * @param accessibilityWindowId A unique window id. Use
475      *     {@link android.view.accessibility.AccessibilityWindowInfo#ACTIVE_WINDOW_ID}
476      *     to query the currently active window.
477      * @param accessibilityNodeId A unique view id or virtual descendant id from
478      *     where to start the search. Use
479      *     {@link android.view.accessibility.AccessibilityNodeInfo#ROOT_NODE_ID}
480      *     to start from the root.
481      * @param bypassCache Whether to bypass the cache while looking for the node.
482      * @param prefetchFlags flags to guide prefetching.
483      * @return An {@link AccessibilityNodeInfo} if found, null otherwise.
484      */
findAccessibilityNodeInfoByAccessibilityId(int connectionId, int accessibilityWindowId, long accessibilityNodeId, boolean bypassCache, int prefetchFlags, Bundle arguments)485     public AccessibilityNodeInfo findAccessibilityNodeInfoByAccessibilityId(int connectionId,
486             int accessibilityWindowId, long accessibilityNodeId, boolean bypassCache,
487             int prefetchFlags, Bundle arguments) {
488         if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS) != 0
489                 && (prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS) == 0) {
490             throw new IllegalArgumentException("FLAG_PREFETCH_SIBLINGS"
491                 + " requires FLAG_PREFETCH_PREDECESSORS");
492         }
493         try {
494             IAccessibilityServiceConnection connection = getConnection(connectionId);
495             if (connection != null) {
496                 if (!bypassCache) {
497                     AccessibilityNodeInfo cachedInfo = sAccessibilityCache.getNode(
498                             accessibilityWindowId, accessibilityNodeId);
499                     if (cachedInfo != null) {
500                         if (DEBUG) {
501                             Log.i(LOG_TAG, "Node cache hit for "
502                                     + idToString(accessibilityWindowId, accessibilityNodeId));
503                         }
504                         if (shouldTraceClient()) {
505                             logTraceClient(connection,
506                                     "findAccessibilityNodeInfoByAccessibilityId cache",
507                                     "connectionId=" + connectionId + ";accessibilityWindowId="
508                                     + accessibilityWindowId + ";accessibilityNodeId="
509                                     + accessibilityNodeId + ";bypassCache=" + bypassCache
510                                     + ";prefetchFlags=" + prefetchFlags + ";arguments="
511                                     + arguments);
512                         }
513                         return cachedInfo;
514                     }
515                     if (DEBUG) {
516                         Log.i(LOG_TAG, "Node cache miss for "
517                                 + idToString(accessibilityWindowId, accessibilityNodeId));
518                     }
519                 } else {
520                     // No need to prefech nodes in bypass cache case.
521                     prefetchFlags &= ~AccessibilityNodeInfo.FLAG_PREFETCH_MASK;
522                 }
523                 // Skip prefetching if window is scrolling.
524                 if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_MASK) != 0
525                         && isWindowScrolling(accessibilityWindowId)) {
526                     prefetchFlags &= ~AccessibilityNodeInfo.FLAG_PREFETCH_MASK;
527                 }
528                 final int interactionId = mInteractionIdCounter.getAndIncrement();
529                 if (shouldTraceClient()) {
530                     logTraceClient(connection, "findAccessibilityNodeInfoByAccessibilityId",
531                             "InteractionId:" + interactionId + "connectionId=" + connectionId
532                             + ";accessibilityWindowId=" + accessibilityWindowId
533                             + ";accessibilityNodeId=" + accessibilityNodeId + ";bypassCache="
534                             + bypassCache + ";prefetchFlags=" + prefetchFlags + ";arguments="
535                             + arguments);
536                 }
537                 final String[] packageNames;
538                 final long identityToken = Binder.clearCallingIdentity();
539                 try {
540                     packageNames = connection.findAccessibilityNodeInfoByAccessibilityId(
541                             accessibilityWindowId, accessibilityNodeId, interactionId, this,
542                             prefetchFlags, Thread.currentThread().getId(), arguments);
543                 } finally {
544                     Binder.restoreCallingIdentity(identityToken);
545                 }
546                 if (packageNames != null) {
547                     AccessibilityNodeInfo info =
548                             getFindAccessibilityNodeInfoResultAndClear(interactionId);
549                     if (shouldTraceCallback()) {
550                         logTraceCallback(connection, "findAccessibilityNodeInfoByAccessibilityId",
551                                 "InteractionId:" + interactionId + ";connectionId="
552                                 + connectionId + ";Result: " + info);
553                     }
554                     if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_MASK) != 0
555                             && info != null) {
556                         setInteractionWaitingForPrefetchResult(interactionId, connectionId,
557                                 packageNames);
558                     }
559                     finalizeAndCacheAccessibilityNodeInfo(info, connectionId,
560                             bypassCache, packageNames);
561                     return info;
562                 }
563             } else {
564                 if (DEBUG) {
565                     Log.w(LOG_TAG, "No connection for connection id: " + connectionId);
566                 }
567             }
568         } catch (RemoteException re) {
569             Log.e(LOG_TAG, "Error while calling remote"
570                     + " findAccessibilityNodeInfoByAccessibilityId", re);
571         }
572         return null;
573     }
574 
setInteractionWaitingForPrefetchResult(int interactionId, int connectionId, String[] packageNames)575     private void setInteractionWaitingForPrefetchResult(int interactionId, int connectionId,
576             String[] packageNames) {
577         synchronized (mInstanceLock) {
578             mInteractionIdWaitingForPrefetchResult = interactionId;
579             mConnectionIdWaitingForPrefetchResult = connectionId;
580             mPackageNamesForNextPrefetchResult = packageNames;
581         }
582     }
583 
idToString(int accessibilityWindowId, long accessibilityNodeId)584     private static String idToString(int accessibilityWindowId, long accessibilityNodeId) {
585         return accessibilityWindowId + "/"
586                 + AccessibilityNodeInfo.idToString(accessibilityNodeId);
587     }
588 
589     /**
590      * Finds an {@link AccessibilityNodeInfo} by View id. The search is performed in
591      * the window whose id is specified and starts from the node whose accessibility
592      * id is specified.
593      *
594      * @param connectionId The id of a connection for interacting with the system.
595      * @param accessibilityWindowId A unique window id. Use
596      *     {@link android.view.accessibility.AccessibilityWindowInfo#ACTIVE_WINDOW_ID}
597      *     to query the currently active window.
598      * @param accessibilityNodeId A unique view id or virtual descendant id from
599      *     where to start the search. Use
600      *     {@link android.view.accessibility.AccessibilityNodeInfo#ROOT_NODE_ID}
601      *     to start from the root.
602      * @param viewId The fully qualified resource name of the view id to find.
603      * @return An list of {@link AccessibilityNodeInfo} if found, empty list otherwise.
604      */
findAccessibilityNodeInfosByViewId(int connectionId, int accessibilityWindowId, long accessibilityNodeId, String viewId)605     public List<AccessibilityNodeInfo> findAccessibilityNodeInfosByViewId(int connectionId,
606             int accessibilityWindowId, long accessibilityNodeId, String viewId) {
607         try {
608             IAccessibilityServiceConnection connection = getConnection(connectionId);
609             if (connection != null) {
610                 final int interactionId = mInteractionIdCounter.getAndIncrement();
611                 final String[] packageNames;
612                 final long identityToken = Binder.clearCallingIdentity();
613                 try {
614                     if (shouldTraceClient()) {
615                         logTraceClient(connection, "findAccessibilityNodeInfosByViewId",
616                                 "InteractionId=" + interactionId + ";connectionId=" + connectionId
617                                 + ";accessibilityWindowId=" + accessibilityWindowId
618                                 + ";accessibilityNodeId=" + accessibilityNodeId + ";viewId="
619                                 + viewId);
620                     }
621 
622                     packageNames = connection.findAccessibilityNodeInfosByViewId(
623                             accessibilityWindowId, accessibilityNodeId, viewId, interactionId, this,
624                             Thread.currentThread().getId());
625                 } finally {
626                     Binder.restoreCallingIdentity(identityToken);
627                 }
628 
629                 if (packageNames != null) {
630                     List<AccessibilityNodeInfo> infos = getFindAccessibilityNodeInfosResultAndClear(
631                             interactionId);
632                     if (shouldTraceCallback()) {
633                         logTraceCallback(connection, "findAccessibilityNodeInfosByViewId",
634                                 "InteractionId=" + interactionId + ";connectionId=" + connectionId
635                                 + ":Result: " + infos);
636                     }
637                     if (infos != null) {
638                         finalizeAndCacheAccessibilityNodeInfos(infos, connectionId,
639                                 false, packageNames);
640                         return infos;
641                     }
642                 }
643             } else {
644                 if (DEBUG) {
645                     Log.w(LOG_TAG, "No connection for connection id: " + connectionId);
646                 }
647             }
648         } catch (RemoteException re) {
649             Log.w(LOG_TAG, "Error while calling remote"
650                     + " findAccessibilityNodeInfoByViewIdInActiveWindow", re);
651         }
652         return Collections.emptyList();
653     }
654 
655     /**
656      * Finds {@link AccessibilityNodeInfo}s by View text. The match is case
657      * insensitive containment. The search is performed in the window whose
658      * id is specified and starts from the node whose accessibility id is
659      * specified.
660      *
661      * @param connectionId The id of a connection for interacting with the system.
662      * @param accessibilityWindowId A unique window id. Use
663      *     {@link android.view.accessibility.AccessibilityWindowInfo#ACTIVE_WINDOW_ID}
664      *     to query the currently active window.
665      * @param accessibilityNodeId A unique view id or virtual descendant id from
666      *     where to start the search. Use
667      *     {@link android.view.accessibility.AccessibilityNodeInfo#ROOT_NODE_ID}
668      *     to start from the root.
669      * @param text The searched text.
670      * @return A list of found {@link AccessibilityNodeInfo}s.
671      */
findAccessibilityNodeInfosByText(int connectionId, int accessibilityWindowId, long accessibilityNodeId, String text)672     public List<AccessibilityNodeInfo> findAccessibilityNodeInfosByText(int connectionId,
673             int accessibilityWindowId, long accessibilityNodeId, String text) {
674         try {
675             IAccessibilityServiceConnection connection = getConnection(connectionId);
676             if (connection != null) {
677                 final int interactionId = mInteractionIdCounter.getAndIncrement();
678                 if (shouldTraceClient()) {
679                     logTraceClient(connection, "findAccessibilityNodeInfosByText",
680                             "InteractionId:" + interactionId + "connectionId=" + connectionId
681                             + ";accessibilityWindowId=" + accessibilityWindowId
682                             + ";accessibilityNodeId=" + accessibilityNodeId + ";text=" + text);
683                 }
684                 final String[] packageNames;
685                 final long identityToken = Binder.clearCallingIdentity();
686                 try {
687                     packageNames = connection.findAccessibilityNodeInfosByText(
688                             accessibilityWindowId, accessibilityNodeId, text, interactionId, this,
689                             Thread.currentThread().getId());
690                 } finally {
691                     Binder.restoreCallingIdentity(identityToken);
692                 }
693 
694                 if (packageNames != null) {
695                     List<AccessibilityNodeInfo> infos = getFindAccessibilityNodeInfosResultAndClear(
696                             interactionId);
697                     if (shouldTraceCallback()) {
698                         logTraceCallback(connection, "findAccessibilityNodeInfosByText",
699                                 "InteractionId=" + interactionId + ";connectionId=" + connectionId
700                                 + ";Result: " + infos);
701                     }
702                     if (infos != null) {
703                         finalizeAndCacheAccessibilityNodeInfos(infos, connectionId,
704                                 false, packageNames);
705                         return infos;
706                     }
707                 }
708             } else {
709                 if (DEBUG) {
710                     Log.w(LOG_TAG, "No connection for connection id: " + connectionId);
711                 }
712             }
713         } catch (RemoteException re) {
714             Log.w(LOG_TAG, "Error while calling remote"
715                     + " findAccessibilityNodeInfosByViewText", re);
716         }
717         return Collections.emptyList();
718     }
719 
720     /**
721      * Finds the {@link android.view.accessibility.AccessibilityNodeInfo} that has the
722      * specified focus type. The search is performed in the window whose id is specified
723      * and starts from the node whose accessibility id is specified.
724      *
725      * @param connectionId The id of a connection for interacting with the system.
726      * @param accessibilityWindowId A unique window id. Use
727      *     {@link android.view.accessibility.AccessibilityWindowInfo#ACTIVE_WINDOW_ID}
728      *     to query the currently active window.
729      * @param accessibilityNodeId A unique view id or virtual descendant id from
730      *     where to start the search. Use
731      *     {@link android.view.accessibility.AccessibilityNodeInfo#ROOT_NODE_ID}
732      *     to start from the root.
733      * @param focusType The focus type.
734      * @return The accessibility focused {@link AccessibilityNodeInfo}.
735      */
findFocus(int connectionId, int accessibilityWindowId, long accessibilityNodeId, int focusType)736     public AccessibilityNodeInfo findFocus(int connectionId, int accessibilityWindowId,
737             long accessibilityNodeId, int focusType) {
738         try {
739             IAccessibilityServiceConnection connection = getConnection(connectionId);
740             if (connection != null) {
741                 final int interactionId = mInteractionIdCounter.getAndIncrement();
742                 if (shouldTraceClient()) {
743                     logTraceClient(connection, "findFocus",
744                             "InteractionId:" + interactionId + "connectionId=" + connectionId
745                             + ";accessibilityWindowId=" + accessibilityWindowId
746                             + ";accessibilityNodeId=" + accessibilityNodeId + ";focusType="
747                             + focusType);
748                 }
749                 final String[] packageNames;
750                 final long identityToken = Binder.clearCallingIdentity();
751                 try {
752                     packageNames = connection.findFocus(accessibilityWindowId,
753                             accessibilityNodeId, focusType, interactionId, this,
754                             Thread.currentThread().getId());
755                 } finally {
756                     Binder.restoreCallingIdentity(identityToken);
757                 }
758 
759                 if (packageNames != null) {
760                     AccessibilityNodeInfo info = getFindAccessibilityNodeInfoResultAndClear(
761                             interactionId);
762                     if (shouldTraceCallback()) {
763                         logTraceCallback(connection, "findFocus", "InteractionId=" + interactionId
764                                 + ";connectionId=" + connectionId + ";Result:" + info);
765                     }
766                     finalizeAndCacheAccessibilityNodeInfo(info, connectionId, false, packageNames);
767                     return info;
768                 }
769             } else {
770                 if (DEBUG) {
771                     Log.w(LOG_TAG, "No connection for connection id: " + connectionId);
772                 }
773             }
774         } catch (RemoteException re) {
775             Log.w(LOG_TAG, "Error while calling remote findFocus", re);
776         }
777         return null;
778     }
779 
780     /**
781      * Finds the accessibility focused {@link android.view.accessibility.AccessibilityNodeInfo}.
782      * The search is performed in the window whose id is specified and starts from the
783      * node whose accessibility id is specified.
784      *
785      * @param connectionId The id of a connection for interacting with the system.
786      * @param accessibilityWindowId A unique window id. Use
787      *     {@link android.view.accessibility.AccessibilityWindowInfo#ACTIVE_WINDOW_ID}
788      *     to query the currently active window.
789      * @param accessibilityNodeId A unique view id or virtual descendant id from
790      *     where to start the search. Use
791      *     {@link android.view.accessibility.AccessibilityNodeInfo#ROOT_NODE_ID}
792      *     to start from the root.
793      * @param direction The direction in which to search for focusable.
794      * @return The accessibility focused {@link AccessibilityNodeInfo}.
795      */
focusSearch(int connectionId, int accessibilityWindowId, long accessibilityNodeId, int direction)796     public AccessibilityNodeInfo focusSearch(int connectionId, int accessibilityWindowId,
797             long accessibilityNodeId, int direction) {
798         try {
799             IAccessibilityServiceConnection connection = getConnection(connectionId);
800             if (connection != null) {
801                 final int interactionId = mInteractionIdCounter.getAndIncrement();
802                 if (shouldTraceClient()) {
803                     logTraceClient(connection, "focusSearch",
804                             "InteractionId:" + interactionId + "connectionId=" + connectionId
805                             + ";accessibilityWindowId=" + accessibilityWindowId
806                             + ";accessibilityNodeId=" + accessibilityNodeId + ";direction="
807                             + direction);
808                 }
809                 final String[] packageNames;
810                 final long identityToken = Binder.clearCallingIdentity();
811                 try {
812                     packageNames = connection.focusSearch(accessibilityWindowId,
813                             accessibilityNodeId, direction, interactionId, this,
814                             Thread.currentThread().getId());
815                 } finally {
816                     Binder.restoreCallingIdentity(identityToken);
817                 }
818 
819                 if (packageNames != null) {
820                     AccessibilityNodeInfo info = getFindAccessibilityNodeInfoResultAndClear(
821                             interactionId);
822                     finalizeAndCacheAccessibilityNodeInfo(info, connectionId, false, packageNames);
823                     if (shouldTraceCallback()) {
824                         logTraceCallback(connection, "focusSearch", "InteractionId=" + interactionId
825                                 + ";connectionId=" + connectionId + ";Result:" + info);
826                     }
827                     return info;
828                 }
829             } else {
830                 if (DEBUG) {
831                     Log.w(LOG_TAG, "No connection for connection id: " + connectionId);
832                 }
833             }
834         } catch (RemoteException re) {
835             Log.w(LOG_TAG, "Error while calling remote accessibilityFocusSearch", re);
836         }
837         return null;
838     }
839 
840     /**
841      * Performs an accessibility action on an {@link AccessibilityNodeInfo}.
842      *
843      * @param connectionId The id of a connection for interacting with the system.
844      * @param accessibilityWindowId A unique window id. Use
845      *     {@link android.view.accessibility.AccessibilityWindowInfo#ACTIVE_WINDOW_ID}
846      *     to query the currently active window.
847      * @param accessibilityNodeId A unique view id or virtual descendant id from
848      *     where to start the search. Use
849      *     {@link android.view.accessibility.AccessibilityNodeInfo#ROOT_NODE_ID}
850      *     to start from the root.
851      * @param action The action to perform.
852      * @param arguments Optional action arguments.
853      * @return Whether the action was performed.
854      */
performAccessibilityAction(int connectionId, int accessibilityWindowId, long accessibilityNodeId, int action, Bundle arguments)855     public boolean performAccessibilityAction(int connectionId, int accessibilityWindowId,
856             long accessibilityNodeId, int action, Bundle arguments) {
857         try {
858             IAccessibilityServiceConnection connection = getConnection(connectionId);
859             if (connection != null) {
860                 final int interactionId = mInteractionIdCounter.getAndIncrement();
861                 if (shouldTraceClient()) {
862                     logTraceClient(connection, "performAccessibilityAction",
863                             "InteractionId:" + interactionId + "connectionId=" + connectionId
864                             + ";accessibilityWindowId=" + accessibilityWindowId
865                             + ";accessibilityNodeId=" + accessibilityNodeId + ";action=" + action
866                             + ";arguments=" + arguments);
867                 }
868                 final boolean success;
869                 final long identityToken = Binder.clearCallingIdentity();
870                 try {
871                     success = connection.performAccessibilityAction(
872                             accessibilityWindowId, accessibilityNodeId, action, arguments,
873                             interactionId, this, Thread.currentThread().getId());
874                 } finally {
875                     Binder.restoreCallingIdentity(identityToken);
876                 }
877 
878                 if (success) {
879                     final boolean result =
880                             getPerformAccessibilityActionResultAndClear(interactionId);
881                     if (shouldTraceCallback()) {
882                         logTraceCallback(connection, "performAccessibilityAction",
883                                 "InteractionId=" + interactionId + ";connectionId=" + connectionId
884                                 + ";Result: " + result);
885                     }
886                     return result;
887                 }
888             } else {
889                 if (DEBUG) {
890                     Log.w(LOG_TAG, "No connection for connection id: " + connectionId);
891                 }
892             }
893         } catch (RemoteException re) {
894             Log.w(LOG_TAG, "Error while calling remote performAccessibilityAction", re);
895         }
896         return false;
897     }
898 
899     /**
900      * Clears the accessibility cache.
901      */
902     @UnsupportedAppUsage()
clearCache()903     public void clearCache() {
904         sAccessibilityCache.clear();
905     }
906 
onAccessibilityEvent(AccessibilityEvent event)907     public void onAccessibilityEvent(AccessibilityEvent event) {
908         switch (event.getEventType()) {
909             case AccessibilityEvent.TYPE_VIEW_SCROLLED:
910                 updateScrollingWindow(event.getWindowId(), SystemClock.uptimeMillis());
911                 break;
912             case AccessibilityEvent.TYPE_WINDOWS_CHANGED:
913                 if (event.getWindowChanges() == AccessibilityEvent.WINDOWS_CHANGE_REMOVED) {
914                     deleteScrollingWindow(event.getWindowId());
915                 }
916                 break;
917             default:
918                 break;
919         }
920         sAccessibilityCache.onAccessibilityEvent(event);
921     }
922 
923     /**
924      * Gets the the result of an async request that returns an {@link AccessibilityNodeInfo}.
925      *
926      * @param interactionId The interaction id to match the result with the request.
927      * @return The result {@link AccessibilityNodeInfo}.
928      */
getFindAccessibilityNodeInfoResultAndClear(int interactionId)929     private AccessibilityNodeInfo getFindAccessibilityNodeInfoResultAndClear(int interactionId) {
930         synchronized (mInstanceLock) {
931             final boolean success = waitForResultTimedLocked(interactionId);
932             AccessibilityNodeInfo result = success ? mFindAccessibilityNodeInfoResult : null;
933             clearResultLocked();
934             return result;
935         }
936     }
937 
938     /**
939      * {@inheritDoc}
940      */
setFindAccessibilityNodeInfoResult(AccessibilityNodeInfo info, int interactionId)941     public void setFindAccessibilityNodeInfoResult(AccessibilityNodeInfo info,
942                 int interactionId) {
943         synchronized (mInstanceLock) {
944             if (interactionId > mInteractionId) {
945                 mFindAccessibilityNodeInfoResult = info;
946                 mInteractionId = interactionId;
947                 mCallingUid = Binder.getCallingUid();
948                 mCallStackOfCallback = new ArrayList<StackTraceElement>(
949                         Arrays.asList(Thread.currentThread().getStackTrace()));
950             }
951             mInstanceLock.notifyAll();
952         }
953     }
954 
955     /**
956      * Gets the the result of an async request that returns {@link AccessibilityNodeInfo}s.
957      *
958      * @param interactionId The interaction id to match the result with the request.
959      * @return The result {@link AccessibilityNodeInfo}s.
960      */
getFindAccessibilityNodeInfosResultAndClear( int interactionId)961     private List<AccessibilityNodeInfo> getFindAccessibilityNodeInfosResultAndClear(
962                 int interactionId) {
963         synchronized (mInstanceLock) {
964             final boolean success = waitForResultTimedLocked(interactionId);
965             final List<AccessibilityNodeInfo> result;
966             if (success) {
967                 result = mFindAccessibilityNodeInfosResult;
968             } else {
969                 result = Collections.emptyList();
970             }
971             clearResultLocked();
972             if (Build.IS_DEBUGGABLE && CHECK_INTEGRITY) {
973                 checkFindAccessibilityNodeInfoResultIntegrity(result);
974             }
975             return result;
976         }
977     }
978 
979     /**
980      * {@inheritDoc}
981      */
setFindAccessibilityNodeInfosResult(List<AccessibilityNodeInfo> infos, int interactionId)982     public void setFindAccessibilityNodeInfosResult(List<AccessibilityNodeInfo> infos,
983                 int interactionId) {
984         synchronized (mInstanceLock) {
985             if (interactionId > mInteractionId) {
986                 if (infos != null) {
987                     // If the call is not an IPC, i.e. it is made from the same process, we need to
988                     // instantiate new result list to avoid passing internal instances to clients.
989                     final boolean isIpcCall = (Binder.getCallingPid() != Process.myPid());
990                     if (!isIpcCall) {
991                         mFindAccessibilityNodeInfosResult = new ArrayList<>(infos);
992                     } else {
993                         mFindAccessibilityNodeInfosResult = infos;
994                     }
995                 } else {
996                     mFindAccessibilityNodeInfosResult = Collections.emptyList();
997                 }
998                 mInteractionId = interactionId;
999                 mCallingUid = Binder.getCallingUid();
1000                 mCallStackOfCallback = new ArrayList<StackTraceElement>(
1001                     Arrays.asList(Thread.currentThread().getStackTrace()));
1002             }
1003             mInstanceLock.notifyAll();
1004         }
1005     }
1006 
1007     /**
1008      * {@inheritDoc}
1009      */
1010     @Override
setPrefetchAccessibilityNodeInfoResult(@onNull List<AccessibilityNodeInfo> infos, int interactionId)1011     public void setPrefetchAccessibilityNodeInfoResult(@NonNull List<AccessibilityNodeInfo> infos,
1012                                                        int interactionId) {
1013         int interactionIdWaitingForPrefetchResultCopy = -1;
1014         int connectionIdWaitingForPrefetchResultCopy = -1;
1015         String[] packageNamesForNextPrefetchResultCopy = null;
1016 
1017         if (infos.isEmpty()) {
1018             return;
1019         }
1020 
1021         synchronized (mInstanceLock) {
1022             if (mInteractionIdWaitingForPrefetchResult == interactionId) {
1023                 interactionIdWaitingForPrefetchResultCopy = mInteractionIdWaitingForPrefetchResult;
1024                 connectionIdWaitingForPrefetchResultCopy =
1025                         mConnectionIdWaitingForPrefetchResult;
1026                 if (mPackageNamesForNextPrefetchResult != null) {
1027                     packageNamesForNextPrefetchResultCopy =
1028                             new String[mPackageNamesForNextPrefetchResult.length];
1029                     for (int i = 0; i < mPackageNamesForNextPrefetchResult.length; i++) {
1030                         packageNamesForNextPrefetchResultCopy[i] =
1031                                 mPackageNamesForNextPrefetchResult[i];
1032                     }
1033                 }
1034             }
1035         }
1036 
1037         if (interactionIdWaitingForPrefetchResultCopy == interactionId) {
1038             finalizeAndCacheAccessibilityNodeInfos(
1039                     infos, connectionIdWaitingForPrefetchResultCopy, false,
1040                     packageNamesForNextPrefetchResultCopy);
1041             if (shouldTraceCallback()) {
1042                 logTrace(getConnection(connectionIdWaitingForPrefetchResultCopy),
1043                         "setPrefetchAccessibilityNodeInfoResult",
1044                         "InteractionId:" + interactionId + ";connectionId="
1045                         + connectionIdWaitingForPrefetchResultCopy + ";Result: " + infos,
1046                         Binder.getCallingUid(),
1047                         Arrays.asList(Thread.currentThread().getStackTrace()),
1048                         new HashSet<String>(Arrays.asList("getStackTrace")),
1049                         FLAGS_ACCESSIBILITY_INTERACTION_CONNECTION_CALLBACK);
1050             }
1051         } else if (DEBUG) {
1052             Log.w(LOG_TAG, "Prefetching for interaction with id " + interactionId + " dropped "
1053                     + infos.size() + " nodes");
1054         }
1055     }
1056 
1057     /**
1058      * Gets the result of a request to perform an accessibility action.
1059      *
1060      * @param interactionId The interaction id to match the result with the request.
1061      * @return Whether the action was performed.
1062      */
getPerformAccessibilityActionResultAndClear(int interactionId)1063     private boolean getPerformAccessibilityActionResultAndClear(int interactionId) {
1064         synchronized (mInstanceLock) {
1065             final boolean success = waitForResultTimedLocked(interactionId);
1066             final boolean result = success ? mPerformAccessibilityActionResult : false;
1067             clearResultLocked();
1068             return result;
1069         }
1070     }
1071 
1072     /**
1073      * {@inheritDoc}
1074      */
setPerformAccessibilityActionResult(boolean succeeded, int interactionId)1075     public void setPerformAccessibilityActionResult(boolean succeeded, int interactionId) {
1076         synchronized (mInstanceLock) {
1077             if (interactionId > mInteractionId) {
1078                 mPerformAccessibilityActionResult = succeeded;
1079                 mInteractionId = interactionId;
1080                 mCallingUid = Binder.getCallingUid();
1081                 mCallStackOfCallback = new ArrayList<StackTraceElement>(
1082                     Arrays.asList(Thread.currentThread().getStackTrace()));
1083             }
1084             mInstanceLock.notifyAll();
1085         }
1086     }
1087 
1088     /**
1089      * Clears the result state.
1090      */
clearResultLocked()1091     private void clearResultLocked() {
1092         mInteractionId = -1;
1093         mFindAccessibilityNodeInfoResult = null;
1094         mFindAccessibilityNodeInfosResult = null;
1095         mPerformAccessibilityActionResult = false;
1096     }
1097 
1098     /**
1099      * Waits up to a given bound for a result of a request and returns it.
1100      *
1101      * @param interactionId The interaction id to match the result with the request.
1102      * @return Whether the result was received.
1103      */
waitForResultTimedLocked(int interactionId)1104     private boolean waitForResultTimedLocked(int interactionId) {
1105         long waitTimeMillis = TIMEOUT_INTERACTION_MILLIS;
1106         final long startTimeMillis = SystemClock.uptimeMillis();
1107         while (true) {
1108             try {
1109                 Message sameProcessMessage = getSameProcessMessageAndClear();
1110                 if (sameProcessMessage != null) {
1111                     sameProcessMessage.getTarget().handleMessage(sameProcessMessage);
1112                 }
1113 
1114                 if (mInteractionId == interactionId) {
1115                     return true;
1116                 }
1117                 if (mInteractionId > interactionId) {
1118                     return false;
1119                 }
1120                 final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis;
1121                 waitTimeMillis = TIMEOUT_INTERACTION_MILLIS - elapsedTimeMillis;
1122                 if (waitTimeMillis <= 0) {
1123                     return false;
1124                 }
1125                 mInstanceLock.wait(waitTimeMillis);
1126             } catch (InterruptedException ie) {
1127                 /* ignore */
1128             }
1129         }
1130     }
1131 
1132     /**
1133      * Finalize an {@link AccessibilityNodeInfo} before passing it to the client.
1134      *
1135      * @param info The info.
1136      * @param connectionId The id of the connection to the system.
1137      * @param bypassCache Whether or not to bypass the cache. The node is added to the cache if
1138      *                    this value is {@code false}
1139      * @param packageNames The valid package names a node can come from.
1140      */
finalizeAndCacheAccessibilityNodeInfo(AccessibilityNodeInfo info, int connectionId, boolean bypassCache, String[] packageNames)1141     private void finalizeAndCacheAccessibilityNodeInfo(AccessibilityNodeInfo info,
1142             int connectionId, boolean bypassCache, String[] packageNames) {
1143         if (info != null) {
1144             info.setConnectionId(connectionId);
1145             // Empty array means any package name is Okay
1146             if (!ArrayUtils.isEmpty(packageNames)) {
1147                 CharSequence packageName = info.getPackageName();
1148                 if (packageName == null
1149                         || !ArrayUtils.contains(packageNames, packageName.toString())) {
1150                     // If the node package not one of the valid ones, pick the top one - this
1151                     // is one of the packages running in the introspected UID.
1152                     info.setPackageName(packageNames[0]);
1153                 }
1154             }
1155             info.setSealed(true);
1156             if (!bypassCache) {
1157                 sAccessibilityCache.add(info);
1158             }
1159         }
1160     }
1161 
1162     /**
1163      * Finalize {@link AccessibilityNodeInfo}s before passing them to the client.
1164      *
1165      * @param infos The {@link AccessibilityNodeInfo}s.
1166      * @param connectionId The id of the connection to the system.
1167      * @param bypassCache Whether or not to bypass the cache. The nodes are added to the cache if
1168      *                    this value is {@code false}
1169      * @param packageNames The valid package names a node can come from.
1170      */
finalizeAndCacheAccessibilityNodeInfos(List<AccessibilityNodeInfo> infos, int connectionId, boolean bypassCache, String[] packageNames)1171     private void finalizeAndCacheAccessibilityNodeInfos(List<AccessibilityNodeInfo> infos,
1172             int connectionId, boolean bypassCache, String[] packageNames) {
1173         if (infos != null) {
1174             final int infosCount = infos.size();
1175             for (int i = 0; i < infosCount; i++) {
1176                 AccessibilityNodeInfo info = infos.get(i);
1177                 finalizeAndCacheAccessibilityNodeInfo(info, connectionId,
1178                         bypassCache, packageNames);
1179             }
1180         }
1181     }
1182 
1183     /**
1184      * Gets the message stored if the interacted and interacting
1185      * threads are the same.
1186      *
1187      * @return The message.
1188      */
getSameProcessMessageAndClear()1189     private Message getSameProcessMessageAndClear() {
1190         synchronized (mInstanceLock) {
1191             Message result = mSameThreadMessage;
1192             mSameThreadMessage = null;
1193             return result;
1194         }
1195     }
1196 
1197     /**
1198      * Checks whether the infos are a fully connected tree with no duplicates.
1199      *
1200      * @param infos The result list to check.
1201      */
checkFindAccessibilityNodeInfoResultIntegrity(List<AccessibilityNodeInfo> infos)1202     private void checkFindAccessibilityNodeInfoResultIntegrity(List<AccessibilityNodeInfo> infos) {
1203         if (infos.size() == 0) {
1204             return;
1205         }
1206         // Find the root node.
1207         AccessibilityNodeInfo root = infos.get(0);
1208         final int infoCount = infos.size();
1209         for (int i = 1; i < infoCount; i++) {
1210             for (int j = i; j < infoCount; j++) {
1211                 AccessibilityNodeInfo candidate = infos.get(j);
1212                 if (root.getParentNodeId() == candidate.getSourceNodeId()) {
1213                     root = candidate;
1214                     break;
1215                 }
1216             }
1217         }
1218         if (root == null) {
1219             Log.e(LOG_TAG, "No root.");
1220         }
1221         // Check for duplicates.
1222         HashSet<AccessibilityNodeInfo> seen = new HashSet<>();
1223         Queue<AccessibilityNodeInfo> fringe = new LinkedList<>();
1224         fringe.add(root);
1225         while (!fringe.isEmpty()) {
1226             AccessibilityNodeInfo current = fringe.poll();
1227             if (!seen.add(current)) {
1228                 Log.e(LOG_TAG, "Duplicate node.");
1229                 return;
1230             }
1231             final int childCount = current.getChildCount();
1232             for (int i = 0; i < childCount; i++) {
1233                 final long childId = current.getChildId(i);
1234                 for (int j = 0; j < infoCount; j++) {
1235                     AccessibilityNodeInfo child = infos.get(j);
1236                     if (child.getSourceNodeId() == childId) {
1237                         fringe.add(child);
1238                     }
1239                 }
1240             }
1241         }
1242         final int disconnectedCount = infos.size() - seen.size();
1243         if (disconnectedCount > 0) {
1244             Log.e(LOG_TAG, disconnectedCount + " Disconnected nodes.");
1245         }
1246     }
1247 
1248     /**
1249      * Update scroll event timestamp of a given window.
1250      *
1251      * @param windowId The window id.
1252      * @param uptimeMillis Device uptime millis.
1253      */
updateScrollingWindow(int windowId, long uptimeMillis)1254     private void updateScrollingWindow(int windowId, long uptimeMillis) {
1255         synchronized (sScrollingWindows) {
1256             sScrollingWindows.put(windowId, uptimeMillis);
1257         }
1258     }
1259 
1260     /**
1261      * Remove a window from the scrolling windows list.
1262      *
1263      * @param windowId The window id.
1264      */
deleteScrollingWindow(int windowId)1265     private void deleteScrollingWindow(int windowId) {
1266         synchronized (sScrollingWindows) {
1267             sScrollingWindows.delete(windowId);
1268         }
1269     }
1270 
1271     /**
1272      * Whether or not the window is scrolling.
1273      *
1274      * @param windowId
1275      * @return true if it's scrolling.
1276      */
isWindowScrolling(int windowId)1277     private boolean isWindowScrolling(int windowId) {
1278         synchronized (sScrollingWindows) {
1279             final long latestScrollingTime = sScrollingWindows.get(windowId);
1280             if (latestScrollingTime == 0) {
1281                 return false;
1282             }
1283             final long currentUptime = SystemClock.uptimeMillis();
1284             if (currentUptime > (latestScrollingTime + DISABLE_PREFETCHING_FOR_SCROLLING_MILLIS)) {
1285                 sScrollingWindows.delete(windowId);
1286                 return false;
1287             }
1288         }
1289         return true;
1290     }
1291 
shouldTraceClient()1292     private boolean shouldTraceClient() {
1293         return (mAccessibilityManager != null)
1294                 && mAccessibilityManager.isA11yInteractionClientTraceEnabled();
1295     }
1296 
shouldTraceCallback()1297     private boolean shouldTraceCallback() {
1298         return (mAccessibilityManager != null)
1299                 && mAccessibilityManager.isA11yInteractionConnectionCBTraceEnabled();
1300     }
1301 
logTrace( IAccessibilityServiceConnection connection, String method, String params, int callingUid, List<StackTraceElement> callStack, HashSet<String> ignoreSet, long logTypes)1302     private void logTrace(
1303             IAccessibilityServiceConnection connection, String method, String params,
1304             int callingUid, List<StackTraceElement> callStack, HashSet<String> ignoreSet,
1305             long logTypes) {
1306         try {
1307             Bundle b = new Bundle();
1308             b.putSerializable(CALL_STACK, new ArrayList<StackTraceElement>(callStack));
1309             if (ignoreSet != null) {
1310                 b.putSerializable(IGNORE_CALL_STACK, ignoreSet);
1311             }
1312             connection.logTrace(SystemClock.elapsedRealtimeNanos(),
1313                     LOG_TAG + "." + method,
1314                     logTypes, params, Process.myPid(), Thread.currentThread().getId(),
1315                     callingUid, b);
1316         } catch (RemoteException e) {
1317             Log.e(LOG_TAG, "Failed to log trace. " + e);
1318         }
1319     }
1320 
logTraceCallback( IAccessibilityServiceConnection connection, String method, String params)1321     private void logTraceCallback(
1322             IAccessibilityServiceConnection connection, String method, String params) {
1323         logTrace(connection, method + " callback", params, mCallingUid, mCallStackOfCallback,
1324                 new HashSet<String>(Arrays.asList("getStackTrace")),
1325                 FLAGS_ACCESSIBILITY_INTERACTION_CONNECTION_CALLBACK);
1326     }
1327 
logTraceClient( IAccessibilityServiceConnection connection, String method, String params)1328     private void logTraceClient(
1329             IAccessibilityServiceConnection connection, String method, String params) {
1330         logTrace(connection, method, params, Binder.getCallingUid(),
1331                 Collections.emptyList(), null, FLAGS_ACCESSIBILITY_INTERACTION_CLIENT);
1332     }
1333 }
1334