AccessibilityInteractionClient.java revision aa780c110922148a6a4ba06734bb2b0bb8c98f93
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 17package android.view.accessibility; 18 19import android.accessibilityservice.IAccessibilityServiceConnection; 20import android.graphics.Rect; 21import android.os.Binder; 22import android.os.Bundle; 23import android.os.Message; 24import android.os.Process; 25import android.os.RemoteException; 26import android.os.SystemClock; 27import android.util.Log; 28import android.util.LongSparseArray; 29import android.util.SparseArray; 30 31import java.util.ArrayList; 32import java.util.Collections; 33import java.util.List; 34import java.util.concurrent.atomic.AtomicInteger; 35 36/** 37 * This class is a singleton that performs accessibility interaction 38 * which is it queries remote view hierarchies about snapshots of their 39 * views as well requests from these hierarchies to perform certain 40 * actions on their views. 41 * 42 * Rationale: The content retrieval APIs are synchronous from a client's 43 * perspective but internally they are asynchronous. The client thread 44 * calls into the system requesting an action and providing a callback 45 * to receive the result after which it waits up to a timeout for that 46 * result. The system enforces security and the delegates the request 47 * to a given view hierarchy where a message is posted (from a binder 48 * thread) describing what to be performed by the main UI thread the 49 * result of which it delivered via the mentioned callback. However, 50 * the blocked client thread and the main UI thread of the target view 51 * hierarchy can be the same thread, for example an accessibility service 52 * and an activity run in the same process, thus they are executed on the 53 * same main thread. In such a case the retrieval will fail since the UI 54 * thread that has to process the message describing the work to be done 55 * is blocked waiting for a result is has to compute! To avoid this scenario 56 * when making a call the client also passes its process and thread ids so 57 * the accessed view hierarchy can detect if the client making the request 58 * is running in its main UI thread. In such a case the view hierarchy, 59 * specifically the binder thread performing the IPC to it, does not post a 60 * message to be run on the UI thread but passes it to the singleton 61 * interaction client through which all interactions occur and the latter is 62 * responsible to execute the message before starting to wait for the 63 * asynchronous result delivered via the callback. In this case the expected 64 * result is already received so no waiting is performed. 65 * 66 * @hide 67 */ 68public final class AccessibilityInteractionClient 69 extends IAccessibilityInteractionConnectionCallback.Stub { 70 71 public static final int NO_ID = -1; 72 73 private static final String LOG_TAG = "AccessibilityInteractionClient"; 74 75 private static final boolean DEBUG = false; 76 77 private static final long TIMEOUT_INTERACTION_MILLIS = 5000; 78 79 private static final Object sStaticLock = new Object(); 80 81 private static final LongSparseArray<AccessibilityInteractionClient> sClients = 82 new LongSparseArray<AccessibilityInteractionClient>(); 83 84 private final AtomicInteger mInteractionIdCounter = new AtomicInteger(); 85 86 private final Object mInstanceLock = new Object(); 87 88 private volatile int mInteractionId = -1; 89 90 private AccessibilityNodeInfo mFindAccessibilityNodeInfoResult; 91 92 private List<AccessibilityNodeInfo> mFindAccessibilityNodeInfosResult; 93 94 private boolean mPerformAccessibilityActionResult; 95 96 private Message mSameThreadMessage; 97 98 private final Rect mTempBounds = new Rect(); 99 100 // The connection cache is shared between all interrogating threads. 101 private static final SparseArray<IAccessibilityServiceConnection> sConnectionCache = 102 new SparseArray<IAccessibilityServiceConnection>(); 103 104 // The connection cache is shared between all interrogating threads since 105 // at any given time there is only one window allowing querying. 106 private static final AccessibilityNodeInfoCache sAccessibilityNodeInfoCache = 107 new AccessibilityNodeInfoCache(); 108 109 /** 110 * @return The client for the current thread. 111 */ 112 public static AccessibilityInteractionClient getInstance() { 113 final long threadId = Thread.currentThread().getId(); 114 return getInstanceForThread(threadId); 115 } 116 117 /** 118 * <strong>Note:</strong> We keep one instance per interrogating thread since 119 * the instance contains state which can lead to undesired thread interleavings. 120 * We do not have a thread local variable since other threads should be able to 121 * look up the correct client knowing a thread id. See ViewRootImpl for details. 122 * 123 * @return The client for a given <code>threadId</code>. 124 */ 125 public static AccessibilityInteractionClient getInstanceForThread(long threadId) { 126 synchronized (sStaticLock) { 127 AccessibilityInteractionClient client = sClients.get(threadId); 128 if (client == null) { 129 client = new AccessibilityInteractionClient(); 130 sClients.put(threadId, client); 131 } 132 return client; 133 } 134 } 135 136 private AccessibilityInteractionClient() { 137 /* reducing constructor visibility */ 138 } 139 140 /** 141 * Sets the message to be processed if the interacted view hierarchy 142 * and the interacting client are running in the same thread. 143 * 144 * @param message The message. 145 */ 146 public void setSameThreadMessage(Message message) { 147 synchronized (mInstanceLock) { 148 mSameThreadMessage = message; 149 mInstanceLock.notifyAll(); 150 } 151 } 152 153 /** 154 * Gets the root {@link AccessibilityNodeInfo} in the currently active window. 155 * 156 * @param connectionId The id of a connection for interacting with the system. 157 * @return The root {@link AccessibilityNodeInfo} if found, null otherwise. 158 */ 159 public AccessibilityNodeInfo getRootInActiveWindow(int connectionId) { 160 return findAccessibilityNodeInfoByAccessibilityId(connectionId, 161 AccessibilityNodeInfo.ACTIVE_WINDOW_ID, AccessibilityNodeInfo.ROOT_NODE_ID, 162 AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS); 163 } 164 165 /** 166 * Finds an {@link AccessibilityNodeInfo} by accessibility id. 167 * 168 * @param connectionId The id of a connection for interacting with the system. 169 * @param accessibilityWindowId A unique window id. Use 170 * {@link android.view.accessibility.AccessibilityNodeInfo#ACTIVE_WINDOW_ID} 171 * to query the currently active window. 172 * @param accessibilityNodeId A unique view id or virtual descendant id from 173 * where to start the search. Use 174 * {@link android.view.accessibility.AccessibilityNodeInfo#ROOT_NODE_ID} 175 * to start from the root. 176 * @param prefetchFlags flags to guide prefetching. 177 * @return An {@link AccessibilityNodeInfo} if found, null otherwise. 178 */ 179 public AccessibilityNodeInfo findAccessibilityNodeInfoByAccessibilityId(int connectionId, 180 int accessibilityWindowId, long accessibilityNodeId, int prefetchFlags) { 181 try { 182 IAccessibilityServiceConnection connection = getConnection(connectionId); 183 if (connection != null) { 184 AccessibilityNodeInfo cachedInfo = sAccessibilityNodeInfoCache.get( 185 accessibilityNodeId); 186 if (cachedInfo != null) { 187 return cachedInfo; 188 } 189 final int interactionId = mInteractionIdCounter.getAndIncrement(); 190 final float windowScale = connection.findAccessibilityNodeInfoByAccessibilityId( 191 accessibilityWindowId, accessibilityNodeId, interactionId, this, 192 prefetchFlags, Thread.currentThread().getId()); 193 // If the scale is zero the call has failed. 194 if (windowScale > 0) { 195 List<AccessibilityNodeInfo> infos = getFindAccessibilityNodeInfosResultAndClear( 196 interactionId); 197 finalizeAndCacheAccessibilityNodeInfos(infos, connectionId, windowScale); 198 if (infos != null && !infos.isEmpty()) { 199 return infos.get(0); 200 } 201 } 202 } else { 203 if (DEBUG) { 204 Log.w(LOG_TAG, "No connection for connection id: " + connectionId); 205 } 206 } 207 } catch (RemoteException re) { 208 if (DEBUG) { 209 Log.w(LOG_TAG, "Error while calling remote" 210 + " findAccessibilityNodeInfoByAccessibilityId", re); 211 } 212 } 213 return null; 214 } 215 216 /** 217 * Finds an {@link AccessibilityNodeInfo} by View id. The search is performed in 218 * the window whose id is specified and starts from the node whose accessibility 219 * id is specified. 220 * 221 * @param connectionId The id of a connection for interacting with the system. 222 * @param accessibilityWindowId A unique window id. Use 223 * {@link android.view.accessibility.AccessibilityNodeInfo#ACTIVE_WINDOW_ID} 224 * to query the currently active window. 225 * @param accessibilityNodeId A unique view id or virtual descendant id from 226 * where to start the search. Use 227 * {@link android.view.accessibility.AccessibilityNodeInfo#ROOT_NODE_ID} 228 * to start from the root. 229 * @param viewId The id of the view. 230 * @return An {@link AccessibilityNodeInfo} if found, null otherwise. 231 */ 232 public AccessibilityNodeInfo findAccessibilityNodeInfoByViewId(int connectionId, 233 int accessibilityWindowId, long accessibilityNodeId, int viewId) { 234 try { 235 IAccessibilityServiceConnection connection = getConnection(connectionId); 236 if (connection != null) { 237 final int interactionId = mInteractionIdCounter.getAndIncrement(); 238 final float windowScale = 239 connection.findAccessibilityNodeInfoByViewId(accessibilityWindowId, 240 accessibilityNodeId, viewId, interactionId, this, 241 Thread.currentThread().getId()); 242 // If the scale is zero the call has failed. 243 if (windowScale > 0) { 244 AccessibilityNodeInfo info = getFindAccessibilityNodeInfoResultAndClear( 245 interactionId); 246 finalizeAndCacheAccessibilityNodeInfo(info, connectionId, windowScale); 247 return info; 248 } 249 } else { 250 if (DEBUG) { 251 Log.w(LOG_TAG, "No connection for connection id: " + connectionId); 252 } 253 } 254 } catch (RemoteException re) { 255 if (DEBUG) { 256 Log.w(LOG_TAG, "Error while calling remote" 257 + " findAccessibilityNodeInfoByViewIdInActiveWindow", re); 258 } 259 } 260 return null; 261 } 262 263 /** 264 * Finds {@link AccessibilityNodeInfo}s by View text. The match is case 265 * insensitive containment. The search is performed in the window whose 266 * id is specified and starts from the node whose accessibility id is 267 * specified. 268 * 269 * @param connectionId The id of a connection for interacting with the system. 270 * @param accessibilityWindowId A unique window id. Use 271 * {@link android.view.accessibility.AccessibilityNodeInfo#ACTIVE_WINDOW_ID} 272 * to query the currently active window. 273 * @param accessibilityNodeId A unique view id or virtual descendant id from 274 * where to start the search. Use 275 * {@link android.view.accessibility.AccessibilityNodeInfo#ROOT_NODE_ID} 276 * to start from the root. 277 * @param text The searched text. 278 * @return A list of found {@link AccessibilityNodeInfo}s. 279 */ 280 public List<AccessibilityNodeInfo> findAccessibilityNodeInfosByText(int connectionId, 281 int accessibilityWindowId, long accessibilityNodeId, String text) { 282 try { 283 IAccessibilityServiceConnection connection = getConnection(connectionId); 284 if (connection != null) { 285 final int interactionId = mInteractionIdCounter.getAndIncrement(); 286 final float windowScale = connection.findAccessibilityNodeInfosByText( 287 accessibilityWindowId, accessibilityNodeId, text, interactionId, this, 288 Thread.currentThread().getId()); 289 // If the scale is zero the call has failed. 290 if (windowScale > 0) { 291 List<AccessibilityNodeInfo> infos = getFindAccessibilityNodeInfosResultAndClear( 292 interactionId); 293 finalizeAndCacheAccessibilityNodeInfos(infos, connectionId, windowScale); 294 return infos; 295 } 296 } else { 297 if (DEBUG) { 298 Log.w(LOG_TAG, "No connection for connection id: " + connectionId); 299 } 300 } 301 } catch (RemoteException re) { 302 if (DEBUG) { 303 Log.w(LOG_TAG, "Error while calling remote" 304 + " findAccessibilityNodeInfosByViewText", re); 305 } 306 } 307 return Collections.emptyList(); 308 } 309 310 /** 311 * Finds the {@link android.view.accessibility.AccessibilityNodeInfo} that has the 312 * specified focus type. The search is performed in the window whose id is specified 313 * and starts from the node whose accessibility id is specified. 314 * 315 * @param connectionId The id of a connection for interacting with the system. 316 * @param accessibilityWindowId A unique window id. Use 317 * {@link android.view.accessibility.AccessibilityNodeInfo#ACTIVE_WINDOW_ID} 318 * to query the currently active window. 319 * @param accessibilityNodeId A unique view id or virtual descendant id from 320 * where to start the search. Use 321 * {@link android.view.accessibility.AccessibilityNodeInfo#ROOT_NODE_ID} 322 * to start from the root. 323 * @param focusType The focus type. 324 * @return The accessibility focused {@link AccessibilityNodeInfo}. 325 */ 326 public AccessibilityNodeInfo findFocus(int connectionId, int accessibilityWindowId, 327 long accessibilityNodeId, int focusType) { 328 try { 329 IAccessibilityServiceConnection connection = getConnection(connectionId); 330 if (connection != null) { 331 final int interactionId = mInteractionIdCounter.getAndIncrement(); 332 final float windowScale = connection.findFocus(accessibilityWindowId, 333 accessibilityNodeId, focusType, interactionId, this, 334 Thread.currentThread().getId()); 335 // If the scale is zero the call has failed. 336 if (windowScale > 0) { 337 AccessibilityNodeInfo info = getFindAccessibilityNodeInfoResultAndClear( 338 interactionId); 339 finalizeAndCacheAccessibilityNodeInfo(info, connectionId, windowScale); 340 return info; 341 } 342 } else { 343 if (DEBUG) { 344 Log.w(LOG_TAG, "No connection for connection id: " + connectionId); 345 } 346 } 347 } catch (RemoteException re) { 348 if (DEBUG) { 349 Log.w(LOG_TAG, "Error while calling remote findAccessibilityFocus", re); 350 } 351 } 352 return null; 353 } 354 355 /** 356 * Finds the accessibility focused {@link android.view.accessibility.AccessibilityNodeInfo}. 357 * The search is performed in the window whose id is specified and starts from the 358 * node whose accessibility id is specified. 359 * 360 * @param connectionId The id of a connection for interacting with the system. 361 * @param accessibilityWindowId A unique window id. Use 362 * {@link android.view.accessibility.AccessibilityNodeInfo#ACTIVE_WINDOW_ID} 363 * to query the currently active window. 364 * @param accessibilityNodeId A unique view id or virtual descendant id from 365 * where to start the search. Use 366 * {@link android.view.accessibility.AccessibilityNodeInfo#ROOT_NODE_ID} 367 * to start from the root. 368 * @param direction The direction in which to search for focusable. 369 * @return The accessibility focused {@link AccessibilityNodeInfo}. 370 */ 371 public AccessibilityNodeInfo focusSearch(int connectionId, int accessibilityWindowId, 372 long accessibilityNodeId, int direction) { 373 try { 374 IAccessibilityServiceConnection connection = getConnection(connectionId); 375 if (connection != null) { 376 final int interactionId = mInteractionIdCounter.getAndIncrement(); 377 final float windowScale = connection.focusSearch(accessibilityWindowId, 378 accessibilityNodeId, direction, interactionId, this, 379 Thread.currentThread().getId()); 380 // If the scale is zero the call has failed. 381 if (windowScale > 0) { 382 AccessibilityNodeInfo info = getFindAccessibilityNodeInfoResultAndClear( 383 interactionId); 384 finalizeAndCacheAccessibilityNodeInfo(info, connectionId, windowScale); 385 return info; 386 } 387 } else { 388 if (DEBUG) { 389 Log.w(LOG_TAG, "No connection for connection id: " + connectionId); 390 } 391 } 392 } catch (RemoteException re) { 393 if (DEBUG) { 394 Log.w(LOG_TAG, "Error while calling remote accessibilityFocusSearch", re); 395 } 396 } 397 return null; 398 } 399 400 /** 401 * Performs an accessibility action on an {@link AccessibilityNodeInfo}. 402 * 403 * @param connectionId The id of a connection for interacting with the system. 404 * @param accessibilityWindowId A unique window id. Use 405 * {@link android.view.accessibility.AccessibilityNodeInfo#ACTIVE_WINDOW_ID} 406 * to query the currently active window. 407 * @param accessibilityNodeId A unique view id or virtual descendant id from 408 * where to start the search. Use 409 * {@link android.view.accessibility.AccessibilityNodeInfo#ROOT_NODE_ID} 410 * to start from the root. 411 * @param action The action to perform. 412 * @param arguments Optional action arguments. 413 * @return Whether the action was performed. 414 */ 415 public boolean performAccessibilityAction(int connectionId, int accessibilityWindowId, 416 long accessibilityNodeId, int action, Bundle arguments) { 417 try { 418 IAccessibilityServiceConnection connection = getConnection(connectionId); 419 if (connection != null) { 420 final int interactionId = mInteractionIdCounter.getAndIncrement(); 421 final boolean success = connection.performAccessibilityAction( 422 accessibilityWindowId, accessibilityNodeId, action, arguments, 423 interactionId, this, Thread.currentThread().getId()); 424 if (success) { 425 return getPerformAccessibilityActionResultAndClear(interactionId); 426 } 427 } else { 428 if (DEBUG) { 429 Log.w(LOG_TAG, "No connection for connection id: " + connectionId); 430 } 431 } 432 } catch (RemoteException re) { 433 if (DEBUG) { 434 Log.w(LOG_TAG, "Error while calling remote performAccessibilityAction", re); 435 } 436 } 437 return false; 438 } 439 440 public void clearCache() { 441 sAccessibilityNodeInfoCache.clear(); 442 } 443 444 public void removeCachedNode(long accessibilityNodeId) { 445 sAccessibilityNodeInfoCache.remove(accessibilityNodeId); 446 } 447 448 public void onAccessibilityEvent(AccessibilityEvent event) { 449 sAccessibilityNodeInfoCache.onAccessibilityEvent(event); 450 } 451 452 /** 453 * Gets the the result of an async request that returns an {@link AccessibilityNodeInfo}. 454 * 455 * @param interactionId The interaction id to match the result with the request. 456 * @return The result {@link AccessibilityNodeInfo}. 457 */ 458 private AccessibilityNodeInfo getFindAccessibilityNodeInfoResultAndClear(int interactionId) { 459 synchronized (mInstanceLock) { 460 final boolean success = waitForResultTimedLocked(interactionId); 461 AccessibilityNodeInfo result = success ? mFindAccessibilityNodeInfoResult : null; 462 clearResultLocked(); 463 return result; 464 } 465 } 466 467 /** 468 * {@inheritDoc} 469 */ 470 public void setFindAccessibilityNodeInfoResult(AccessibilityNodeInfo info, 471 int interactionId) { 472 synchronized (mInstanceLock) { 473 if (interactionId > mInteractionId) { 474 mFindAccessibilityNodeInfoResult = info; 475 mInteractionId = interactionId; 476 } 477 mInstanceLock.notifyAll(); 478 } 479 } 480 481 /** 482 * Gets the the result of an async request that returns {@link AccessibilityNodeInfo}s. 483 * 484 * @param interactionId The interaction id to match the result with the request. 485 * @return The result {@link AccessibilityNodeInfo}s. 486 */ 487 private List<AccessibilityNodeInfo> getFindAccessibilityNodeInfosResultAndClear( 488 int interactionId) { 489 synchronized (mInstanceLock) { 490 final boolean success = waitForResultTimedLocked(interactionId); 491 List<AccessibilityNodeInfo> result = null; 492 if (success) { 493 result = mFindAccessibilityNodeInfosResult; 494 } else { 495 result = Collections.emptyList(); 496 } 497 clearResultLocked(); 498 return result; 499 } 500 } 501 502 /** 503 * {@inheritDoc} 504 */ 505 public void setFindAccessibilityNodeInfosResult(List<AccessibilityNodeInfo> infos, 506 int interactionId) { 507 synchronized (mInstanceLock) { 508 if (interactionId > mInteractionId) { 509 if (infos != null) { 510 // If the call is not an IPC, i.e. it is made from the same process, we need to 511 // instantiate new result list to avoid passing internal instances to clients. 512 final boolean isIpcCall = (Binder.getCallingPid() != Process.myPid()); 513 if (!isIpcCall) { 514 mFindAccessibilityNodeInfosResult = 515 new ArrayList<AccessibilityNodeInfo>(infos); 516 } else { 517 mFindAccessibilityNodeInfosResult = infos; 518 } 519 } else { 520 mFindAccessibilityNodeInfosResult = Collections.emptyList(); 521 } 522 mInteractionId = interactionId; 523 } 524 mInstanceLock.notifyAll(); 525 } 526 } 527 528 /** 529 * Gets the result of a request to perform an accessibility action. 530 * 531 * @param interactionId The interaction id to match the result with the request. 532 * @return Whether the action was performed. 533 */ 534 private boolean getPerformAccessibilityActionResultAndClear(int interactionId) { 535 synchronized (mInstanceLock) { 536 final boolean success = waitForResultTimedLocked(interactionId); 537 final boolean result = success ? mPerformAccessibilityActionResult : false; 538 clearResultLocked(); 539 return result; 540 } 541 } 542 543 /** 544 * {@inheritDoc} 545 */ 546 public void setPerformAccessibilityActionResult(boolean succeeded, int interactionId) { 547 synchronized (mInstanceLock) { 548 if (interactionId > mInteractionId) { 549 mPerformAccessibilityActionResult = succeeded; 550 mInteractionId = interactionId; 551 } 552 mInstanceLock.notifyAll(); 553 } 554 } 555 556 /** 557 * Clears the result state. 558 */ 559 private void clearResultLocked() { 560 mInteractionId = -1; 561 mFindAccessibilityNodeInfoResult = null; 562 mFindAccessibilityNodeInfosResult = null; 563 mPerformAccessibilityActionResult = false; 564 } 565 566 /** 567 * Waits up to a given bound for a result of a request and returns it. 568 * 569 * @param interactionId The interaction id to match the result with the request. 570 * @return Whether the result was received. 571 */ 572 private boolean waitForResultTimedLocked(int interactionId) { 573 long waitTimeMillis = TIMEOUT_INTERACTION_MILLIS; 574 final long startTimeMillis = SystemClock.uptimeMillis(); 575 while (true) { 576 try { 577 Message sameProcessMessage = getSameProcessMessageAndClear(); 578 if (sameProcessMessage != null) { 579 sameProcessMessage.getTarget().handleMessage(sameProcessMessage); 580 } 581 582 if (mInteractionId == interactionId) { 583 return true; 584 } 585 if (mInteractionId > interactionId) { 586 return false; 587 } 588 final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis; 589 waitTimeMillis = TIMEOUT_INTERACTION_MILLIS - elapsedTimeMillis; 590 if (waitTimeMillis <= 0) { 591 return false; 592 } 593 mInstanceLock.wait(waitTimeMillis); 594 } catch (InterruptedException ie) { 595 /* ignore */ 596 } 597 } 598 } 599 600 /** 601 * Applies compatibility scale to the info bounds if it is not equal to one. 602 * 603 * @param info The info whose bounds to scale. 604 * @param scale The scale to apply. 605 */ 606 private void applyCompatibilityScaleIfNeeded(AccessibilityNodeInfo info, float scale) { 607 if (scale == 1.0f) { 608 return; 609 } 610 Rect bounds = mTempBounds; 611 info.getBoundsInParent(bounds); 612 bounds.scale(scale); 613 info.setBoundsInParent(bounds); 614 615 info.getBoundsInScreen(bounds); 616 bounds.scale(scale); 617 info.setBoundsInScreen(bounds); 618 } 619 620 /** 621 * Finalize an {@link AccessibilityNodeInfo} before passing it to the client. 622 * 623 * @param info The info. 624 * @param connectionId The id of the connection to the system. 625 * @param windowScale The source window compatibility scale. 626 */ 627 private void finalizeAndCacheAccessibilityNodeInfo(AccessibilityNodeInfo info, int connectionId, 628 float windowScale) { 629 if (info != null) { 630 applyCompatibilityScaleIfNeeded(info, windowScale); 631 info.setConnectionId(connectionId); 632 info.setSealed(true); 633 sAccessibilityNodeInfoCache.put(info.getSourceNodeId(), info); 634 } 635 } 636 637 /** 638 * Finalize {@link AccessibilityNodeInfo}s before passing them to the client. 639 * 640 * @param infos The {@link AccessibilityNodeInfo}s. 641 * @param connectionId The id of the connection to the system. 642 * @param windowScale The source window compatibility scale. 643 */ 644 private void finalizeAndCacheAccessibilityNodeInfos(List<AccessibilityNodeInfo> infos, 645 int connectionId, float windowScale) { 646 if (infos != null) { 647 final int infosCount = infos.size(); 648 for (int i = 0; i < infosCount; i++) { 649 AccessibilityNodeInfo info = infos.get(i); 650 finalizeAndCacheAccessibilityNodeInfo(info, connectionId, windowScale); 651 } 652 } 653 } 654 655 /** 656 * Gets the message stored if the interacted and interacting 657 * threads are the same. 658 * 659 * @return The message. 660 */ 661 private Message getSameProcessMessageAndClear() { 662 synchronized (mInstanceLock) { 663 Message result = mSameThreadMessage; 664 mSameThreadMessage = null; 665 return result; 666 } 667 } 668 669 /** 670 * Gets a cached accessibility service connection. 671 * 672 * @param connectionId The connection id. 673 * @return The cached connection if such. 674 */ 675 public IAccessibilityServiceConnection getConnection(int connectionId) { 676 synchronized (sConnectionCache) { 677 return sConnectionCache.get(connectionId); 678 } 679 } 680 681 /** 682 * Adds a cached accessibility service connection. 683 * 684 * @param connectionId The connection id. 685 * @param connection The connection. 686 */ 687 public void addConnection(int connectionId, IAccessibilityServiceConnection connection) { 688 synchronized (sConnectionCache) { 689 sConnectionCache.put(connectionId, connection); 690 } 691 } 692 693 /** 694 * Removes a cached accessibility service connection. 695 * 696 * @param connectionId The connection id. 697 */ 698 public void removeConnection(int connectionId) { 699 synchronized (sConnectionCache) { 700 sConnectionCache.remove(connectionId); 701 } 702 } 703} 704