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