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