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