AccessibilityInteractionController.java revision b3830f6737bb17185e2e1c95f4dcde9ce82ac7e4
1/* 2 * Copyright (C) 2012 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; 18 19import static android.view.accessibility.AccessibilityNodeInfo.INCLUDE_NOT_IMPORTANT_VIEWS; 20 21import android.os.Handler; 22import android.os.Looper; 23import android.os.Message; 24import android.os.Process; 25import android.os.RemoteException; 26import android.util.Pool; 27import android.util.Poolable; 28import android.util.PoolableManager; 29import android.util.Pools; 30import android.util.SparseLongArray; 31import android.view.ViewGroup.ChildListForAccessibility; 32import android.view.accessibility.AccessibilityInteractionClient; 33import android.view.accessibility.AccessibilityNodeInfo; 34import android.view.accessibility.AccessibilityNodeProvider; 35import android.view.accessibility.IAccessibilityInteractionConnectionCallback; 36 37import java.util.ArrayList; 38import java.util.HashMap; 39import java.util.List; 40import java.util.Map; 41 42/** 43 * Class for managing accessibility interactions initiated from the system 44 * and targeting the view hierarchy. A *ClientThread method is to be 45 * called from the interaction connection ViewAncestor gives the system to 46 * talk to it and a corresponding *UiThread method that is executed on the 47 * UI thread. 48 */ 49final class AccessibilityInteractionController { 50 private static final int POOL_SIZE = 5; 51 52 private ArrayList<AccessibilityNodeInfo> mTempAccessibilityNodeInfoList = 53 new ArrayList<AccessibilityNodeInfo>(); 54 55 private final Handler mHandler = new PrivateHandler(); 56 57 private final ViewRootImpl mViewRootImpl; 58 59 private final AccessibilityNodePrefetcher mPrefetcher; 60 61 public AccessibilityInteractionController(ViewRootImpl viewRootImpl) { 62 mViewRootImpl = viewRootImpl; 63 mPrefetcher = new AccessibilityNodePrefetcher(); 64 } 65 66 // Reusable poolable arguments for interacting with the view hierarchy 67 // to fit more arguments than Message and to avoid sharing objects between 68 // two messages since several threads can send messages concurrently. 69 private final Pool<SomeArgs> mPool = Pools.synchronizedPool(Pools.finitePool( 70 new PoolableManager<SomeArgs>() { 71 public SomeArgs newInstance() { 72 return new SomeArgs(); 73 } 74 75 public void onAcquired(SomeArgs info) { 76 /* do nothing */ 77 } 78 79 public void onReleased(SomeArgs info) { 80 info.clear(); 81 } 82 }, POOL_SIZE) 83 ); 84 85 private class SomeArgs implements Poolable<SomeArgs> { 86 private SomeArgs mNext; 87 private boolean mIsPooled; 88 89 public Object arg1; 90 public Object arg2; 91 public int argi1; 92 public int argi2; 93 public int argi3; 94 95 public SomeArgs getNextPoolable() { 96 return mNext; 97 } 98 99 public boolean isPooled() { 100 return mIsPooled; 101 } 102 103 public void setNextPoolable(SomeArgs args) { 104 mNext = args; 105 } 106 107 public void setPooled(boolean isPooled) { 108 mIsPooled = isPooled; 109 } 110 111 private void clear() { 112 arg1 = null; 113 arg2 = null; 114 argi1 = 0; 115 argi2 = 0; 116 argi3 = 0; 117 } 118 } 119 120 public void findAccessibilityNodeInfoByAccessibilityIdClientThread( 121 long accessibilityNodeId, int interactionId, 122 IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid, 123 long interrogatingTid) { 124 Message message = mHandler.obtainMessage(); 125 message.what = PrivateHandler.MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID; 126 message.arg1 = flags; 127 SomeArgs args = mPool.acquire(); 128 args.argi1 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId); 129 args.argi2 = AccessibilityNodeInfo.getVirtualDescendantId(accessibilityNodeId); 130 args.argi3 = interactionId; 131 args.arg1 = callback; 132 message.obj = args; 133 // If the interrogation is performed by the same thread as the main UI 134 // thread in this process, set the message as a static reference so 135 // after this call completes the same thread but in the interrogating 136 // client can handle the message to generate the result. 137 if (interrogatingPid == Process.myPid() 138 && interrogatingTid == Looper.getMainLooper().getThread().getId()) { 139 AccessibilityInteractionClient.getInstanceForThread( 140 interrogatingTid).setSameThreadMessage(message); 141 } else { 142 mHandler.sendMessage(message); 143 } 144 } 145 146 private void findAccessibilityNodeInfoByAccessibilityIdUiThread(Message message) { 147 final int flags = message.arg1; 148 SomeArgs args = (SomeArgs) message.obj; 149 final int accessibilityViewId = args.argi1; 150 final int virtualDescendantId = args.argi2; 151 final int interactionId = args.argi3; 152 final IAccessibilityInteractionConnectionCallback callback = 153 (IAccessibilityInteractionConnectionCallback) args.arg1; 154 mPool.release(args); 155 List<AccessibilityNodeInfo> infos = mTempAccessibilityNodeInfoList; 156 infos.clear(); 157 try { 158 if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) { 159 return; 160 } 161 mViewRootImpl.mAttachInfo.mIncludeNotImportantViews = 162 (flags & INCLUDE_NOT_IMPORTANT_VIEWS) != 0; 163 View root = null; 164 if (accessibilityViewId == AccessibilityNodeInfo.UNDEFINED) { 165 root = mViewRootImpl.mView; 166 } else { 167 root = findViewByAccessibilityId(accessibilityViewId); 168 } 169 if (root != null && isDisplayedOnScreen(root)) { 170 mPrefetcher.prefetchAccessibilityNodeInfos(root, virtualDescendantId, flags, infos); 171 } 172 } finally { 173 try { 174 mViewRootImpl.mAttachInfo.mIncludeNotImportantViews = false; 175 callback.setFindAccessibilityNodeInfosResult(infos, interactionId); 176 infos.clear(); 177 } catch (RemoteException re) { 178 /* ignore - the other side will time out */ 179 } 180 } 181 } 182 183 public void findAccessibilityNodeInfoByViewIdClientThread(long accessibilityNodeId, 184 int viewId, int interactionId, IAccessibilityInteractionConnectionCallback callback, 185 int flags, int interrogatingPid, long interrogatingTid) { 186 Message message = mHandler.obtainMessage(); 187 message.what = PrivateHandler.MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_ID; 188 message.arg1 = flags; 189 message.arg2 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId); 190 SomeArgs args = mPool.acquire(); 191 args.argi1 = viewId; 192 args.argi2 = interactionId; 193 args.arg1 = callback; 194 message.obj = args; 195 // If the interrogation is performed by the same thread as the main UI 196 // thread in this process, set the message as a static reference so 197 // after this call completes the same thread but in the interrogating 198 // client can handle the message to generate the result. 199 if (interrogatingPid == Process.myPid() 200 && interrogatingTid == Looper.getMainLooper().getThread().getId()) { 201 AccessibilityInteractionClient.getInstanceForThread( 202 interrogatingTid).setSameThreadMessage(message); 203 } else { 204 mHandler.sendMessage(message); 205 } 206 } 207 208 private void findAccessibilityNodeInfoByViewIdUiThread(Message message) { 209 final int flags = message.arg1; 210 final int accessibilityViewId = message.arg2; 211 SomeArgs args = (SomeArgs) message.obj; 212 final int viewId = args.argi1; 213 final int interactionId = args.argi2; 214 final IAccessibilityInteractionConnectionCallback callback = 215 (IAccessibilityInteractionConnectionCallback) args.arg1; 216 mPool.release(args); 217 AccessibilityNodeInfo info = null; 218 try { 219 if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) { 220 return; 221 } 222 mViewRootImpl.mAttachInfo.mIncludeNotImportantViews = 223 (flags & INCLUDE_NOT_IMPORTANT_VIEWS) != 0; 224 View root = null; 225 if (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED) { 226 root = findViewByAccessibilityId(accessibilityViewId); 227 } else { 228 root = mViewRootImpl.mView; 229 } 230 if (root != null) { 231 View target = root.findViewById(viewId); 232 if (target != null && isDisplayedOnScreen(target)) { 233 info = target.createAccessibilityNodeInfo(); 234 } 235 } 236 } finally { 237 try { 238 mViewRootImpl.mAttachInfo.mIncludeNotImportantViews = false; 239 callback.setFindAccessibilityNodeInfoResult(info, interactionId); 240 } catch (RemoteException re) { 241 /* ignore - the other side will time out */ 242 } 243 } 244 } 245 246 public void findAccessibilityNodeInfosByTextClientThread(long accessibilityNodeId, 247 String text, int interactionId, IAccessibilityInteractionConnectionCallback callback, 248 int flags, int interrogatingPid, long interrogatingTid) { 249 Message message = mHandler.obtainMessage(); 250 message.what = PrivateHandler.MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_TEXT; 251 message.arg1 = flags; 252 SomeArgs args = mPool.acquire(); 253 args.arg1 = text; 254 args.argi1 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId); 255 args.argi2 = AccessibilityNodeInfo.getVirtualDescendantId(accessibilityNodeId); 256 args.argi3 = interactionId; 257 args.arg2 = callback; 258 message.obj = args; 259 // If the interrogation is performed by the same thread as the main UI 260 // thread in this process, set the message as a static reference so 261 // after this call completes the same thread but in the interrogating 262 // client can handle the message to generate the result. 263 if (interrogatingPid == Process.myPid() 264 && interrogatingTid == Looper.getMainLooper().getThread().getId()) { 265 AccessibilityInteractionClient.getInstanceForThread( 266 interrogatingTid).setSameThreadMessage(message); 267 } else { 268 mHandler.sendMessage(message); 269 } 270 } 271 272 private void findAccessibilityNodeInfosByTextUiThread(Message message) { 273 final int flags = message.arg1; 274 SomeArgs args = (SomeArgs) message.obj; 275 final String text = (String) args.arg1; 276 final int accessibilityViewId = args.argi1; 277 final int virtualDescendantId = args.argi2; 278 final int interactionId = args.argi3; 279 final IAccessibilityInteractionConnectionCallback callback = 280 (IAccessibilityInteractionConnectionCallback) args.arg2; 281 mPool.release(args); 282 List<AccessibilityNodeInfo> infos = null; 283 try { 284 if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) { 285 return; 286 } 287 mViewRootImpl.mAttachInfo.mIncludeNotImportantViews = 288 (flags & INCLUDE_NOT_IMPORTANT_VIEWS) != 0; 289 View root = null; 290 if (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED) { 291 root = findViewByAccessibilityId(accessibilityViewId); 292 } else { 293 root = mViewRootImpl.mView; 294 } 295 if (root != null && isDisplayedOnScreen(root)) { 296 AccessibilityNodeProvider provider = root.getAccessibilityNodeProvider(); 297 if (provider != null) { 298 infos = provider.findAccessibilityNodeInfosByText(text, 299 virtualDescendantId); 300 } else if (virtualDescendantId == AccessibilityNodeInfo.UNDEFINED) { 301 ArrayList<View> foundViews = mViewRootImpl.mAttachInfo.mTempArrayList; 302 foundViews.clear(); 303 root.findViewsWithText(foundViews, text, View.FIND_VIEWS_WITH_TEXT 304 | View.FIND_VIEWS_WITH_CONTENT_DESCRIPTION 305 | View.FIND_VIEWS_WITH_ACCESSIBILITY_NODE_PROVIDERS); 306 if (!foundViews.isEmpty()) { 307 infos = mTempAccessibilityNodeInfoList; 308 infos.clear(); 309 final int viewCount = foundViews.size(); 310 for (int i = 0; i < viewCount; i++) { 311 View foundView = foundViews.get(i); 312 if (isDisplayedOnScreen(foundView)) { 313 provider = foundView.getAccessibilityNodeProvider(); 314 if (provider != null) { 315 List<AccessibilityNodeInfo> infosFromProvider = 316 provider.findAccessibilityNodeInfosByText(text, 317 virtualDescendantId); 318 if (infosFromProvider != null) { 319 infos.addAll(infosFromProvider); 320 } 321 } else { 322 infos.add(foundView.createAccessibilityNodeInfo()); 323 } 324 } 325 } 326 } 327 } 328 } 329 } finally { 330 try { 331 mViewRootImpl.mAttachInfo.mIncludeNotImportantViews = false; 332 callback.setFindAccessibilityNodeInfosResult(infos, interactionId); 333 } catch (RemoteException re) { 334 /* ignore - the other side will time out */ 335 } 336 } 337 } 338 339 public void findFocusClientThread(long accessibilityNodeId, int interactionId, int focusType, 340 IAccessibilityInteractionConnectionCallback callback, int flags, int interogatingPid, 341 long interrogatingTid) { 342 Message message = mHandler.obtainMessage(); 343 message.what = PrivateHandler.MSG_FIND_FOCUS; 344 message.arg1 = flags; 345 message.arg2 = focusType; 346 SomeArgs args = mPool.acquire(); 347 args.argi1 = interactionId; 348 args.argi2 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId); 349 args.argi3 = AccessibilityNodeInfo.getVirtualDescendantId(accessibilityNodeId); 350 args.arg1 = callback; 351 message.obj = args; 352 // If the interrogation is performed by the same thread as the main UI 353 // thread in this process, set the message as a static reference so 354 // after this call completes the same thread but in the interrogating 355 // client can handle the message to generate the result. 356 if (interogatingPid == Process.myPid() 357 && interrogatingTid == Looper.getMainLooper().getThread().getId()) { 358 AccessibilityInteractionClient.getInstanceForThread( 359 interrogatingTid).setSameThreadMessage(message); 360 } else { 361 mHandler.sendMessage(message); 362 } 363 } 364 365 private void findFocusUiThread(Message message) { 366 final int flags = message.arg1; 367 final int focusType = message.arg2; 368 SomeArgs args = (SomeArgs) message.obj; 369 final int interactionId = args.argi1; 370 final int accessibilityViewId = args.argi2; 371 final int virtualDescendantId = args.argi3; 372 final IAccessibilityInteractionConnectionCallback callback = 373 (IAccessibilityInteractionConnectionCallback) args.arg1; 374 mPool.release(args); 375 AccessibilityNodeInfo focused = null; 376 try { 377 if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) { 378 return; 379 } 380 mViewRootImpl.mAttachInfo.mIncludeNotImportantViews = 381 (flags & INCLUDE_NOT_IMPORTANT_VIEWS) != 0; 382 View root = null; 383 if (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED) { 384 root = findViewByAccessibilityId(accessibilityViewId); 385 } else { 386 root = mViewRootImpl.mView; 387 } 388 if (root != null && isDisplayedOnScreen(root)) { 389 switch (focusType) { 390 case AccessibilityNodeInfo.FOCUS_ACCESSIBILITY: { 391 View host = mViewRootImpl.mAccessibilityFocusedHost; 392 // If there is no accessibility focus host or it is not a descendant 393 // of the root from which to start the search, then the search failed. 394 if (host == null || !ViewRootImpl.isViewDescendantOf(host, root)) { 395 break; 396 } 397 // If the host has a provider ask this provider to search for the 398 // focus instead fetching all provider nodes to do the search here. 399 AccessibilityNodeProvider provider = host.getAccessibilityNodeProvider(); 400 if (provider != null) { 401 focused = provider.findAccessibilitiyFocus(virtualDescendantId); 402 } else if (virtualDescendantId == View.NO_ID) { 403 focused = host.createAccessibilityNodeInfo(); 404 } 405 } break; 406 case AccessibilityNodeInfo.FOCUS_INPUT: { 407 // Input focus cannot go to virtual views. 408 View target = root.findFocus(); 409 if (target != null && isDisplayedOnScreen(target)) { 410 focused = target.createAccessibilityNodeInfo(); 411 } 412 } break; 413 default: 414 throw new IllegalArgumentException("Unknown focus type: " + focusType); 415 } 416 } 417 } finally { 418 try { 419 mViewRootImpl.mAttachInfo.mIncludeNotImportantViews = false; 420 callback.setFindAccessibilityNodeInfoResult(focused, interactionId); 421 } catch (RemoteException re) { 422 /* ignore - the other side will time out */ 423 } 424 } 425 } 426 427 public void focusSearchClientThread(long accessibilityNodeId, int interactionId, int direction, 428 IAccessibilityInteractionConnectionCallback callback, int flags, int interogatingPid, 429 long interrogatingTid) { 430 Message message = mHandler.obtainMessage(); 431 message.what = PrivateHandler.MSG_FOCUS_SEARCH; 432 message.arg1 = flags; 433 message.arg2 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId); 434 SomeArgs args = mPool.acquire(); 435 args.argi1 = AccessibilityNodeInfo.getVirtualDescendantId(accessibilityNodeId); 436 args.argi2 = direction; 437 args.argi3 = interactionId; 438 args.arg1 = callback; 439 message.obj = args; 440 // If the interrogation is performed by the same thread as the main UI 441 // thread in this process, set the message as a static reference so 442 // after this call completes the same thread but in the interrogating 443 // client can handle the message to generate the result. 444 if (interogatingPid == Process.myPid() 445 && interrogatingTid == Looper.getMainLooper().getThread().getId()) { 446 AccessibilityInteractionClient.getInstanceForThread( 447 interrogatingTid).setSameThreadMessage(message); 448 } else { 449 mHandler.sendMessage(message); 450 } 451 } 452 453 private void focusSearchUiThread(Message message) { 454 final int flags = message.arg1; 455 final int accessibilityViewId = message.arg2; 456 SomeArgs args = (SomeArgs) message.obj; 457 final int virtualDescendantId = args.argi1; 458 final int direction = args.argi2; 459 final int interactionId = args.argi3; 460 final IAccessibilityInteractionConnectionCallback callback = 461 (IAccessibilityInteractionConnectionCallback) args.arg1; 462 mPool.release(args); 463 AccessibilityNodeInfo next = null; 464 try { 465 if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) { 466 return; 467 } 468 mViewRootImpl.mAttachInfo.mIncludeNotImportantViews = 469 (flags & INCLUDE_NOT_IMPORTANT_VIEWS) != 0; 470 View root = null; 471 if (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED) { 472 root = findViewByAccessibilityId(accessibilityViewId); 473 } else { 474 root = mViewRootImpl.mView; 475 } 476 if (root != null && isDisplayedOnScreen(root)) { 477 if ((direction & View.FOCUS_ACCESSIBILITY) == View.FOCUS_ACCESSIBILITY) { 478 AccessibilityNodeProvider provider = root.getAccessibilityNodeProvider(); 479 if (provider != null) { 480 next = provider.accessibilityFocusSearch(direction, 481 virtualDescendantId); 482 } else if (virtualDescendantId == View.NO_ID) { 483 View nextView = root.focusSearch(direction); 484 if (nextView != null) { 485 // If the focus search reached a node with a provider 486 // we delegate to the provider to find the next one. 487 provider = nextView.getAccessibilityNodeProvider(); 488 if (provider != null) { 489 next = provider.accessibilityFocusSearch(direction, 490 virtualDescendantId); 491 } else { 492 next = nextView.createAccessibilityNodeInfo(); 493 } 494 } 495 } 496 } else { 497 View nextView = root.focusSearch(direction); 498 if (nextView != null) { 499 next = nextView.createAccessibilityNodeInfo(); 500 } 501 } 502 } 503 } finally { 504 try { 505 mViewRootImpl.mAttachInfo.mIncludeNotImportantViews = false; 506 callback.setFindAccessibilityNodeInfoResult(next, interactionId); 507 } catch (RemoteException re) { 508 /* ignore - the other side will time out */ 509 } 510 } 511 } 512 513 public void performAccessibilityActionClientThread(long accessibilityNodeId, int action, 514 int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags, 515 int interogatingPid, long interrogatingTid) { 516 Message message = mHandler.obtainMessage(); 517 message.what = PrivateHandler.MSG_PERFORM_ACCESSIBILITY_ACTION; 518 message.arg1 = flags; 519 message.arg2 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId); 520 SomeArgs args = mPool.acquire(); 521 args.argi1 = AccessibilityNodeInfo.getVirtualDescendantId(accessibilityNodeId); 522 args.argi2 = action; 523 args.argi3 = interactionId; 524 args.arg1 = callback; 525 message.obj = args; 526 // If the interrogation is performed by the same thread as the main UI 527 // thread in this process, set the message as a static reference so 528 // after this call completes the same thread but in the interrogating 529 // client can handle the message to generate the result. 530 if (interogatingPid == Process.myPid() 531 && interrogatingTid == Looper.getMainLooper().getThread().getId()) { 532 AccessibilityInteractionClient.getInstanceForThread( 533 interrogatingTid).setSameThreadMessage(message); 534 } else { 535 mHandler.sendMessage(message); 536 } 537 } 538 539 private void perfromAccessibilityActionUiThread(Message message) { 540 final int flags = message.arg1; 541 final int accessibilityViewId = message.arg2; 542 SomeArgs args = (SomeArgs) message.obj; 543 final int virtualDescendantId = args.argi1; 544 final int action = args.argi2; 545 final int interactionId = args.argi3; 546 final IAccessibilityInteractionConnectionCallback callback = 547 (IAccessibilityInteractionConnectionCallback) args.arg1; 548 mPool.release(args); 549 boolean succeeded = false; 550 try { 551 if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) { 552 return; 553 } 554 mViewRootImpl.mAttachInfo.mIncludeNotImportantViews = 555 (flags & INCLUDE_NOT_IMPORTANT_VIEWS) != 0; 556 View target = null; 557 if (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED) { 558 target = findViewByAccessibilityId(accessibilityViewId); 559 } else { 560 target = mViewRootImpl.mView; 561 } 562 if (target != null && isDisplayedOnScreen(target)) { 563 AccessibilityNodeProvider provider = target.getAccessibilityNodeProvider(); 564 if (provider != null) { 565 succeeded = provider.performAccessibilityAction(action, virtualDescendantId); 566 } else if (virtualDescendantId == View.NO_ID) { 567 succeeded = target.performAccessibilityAction(action); 568 } 569 } 570 } finally { 571 try { 572 mViewRootImpl.mAttachInfo.mIncludeNotImportantViews = false; 573 callback.setPerformAccessibilityActionResult(succeeded, interactionId); 574 } catch (RemoteException re) { 575 /* ignore - the other side will time out */ 576 } 577 } 578 } 579 580 private View findViewByAccessibilityId(int accessibilityId) { 581 View root = mViewRootImpl.mView; 582 if (root == null) { 583 return null; 584 } 585 View foundView = root.findViewByAccessibilityId(accessibilityId); 586 if (foundView != null && !isDisplayedOnScreen(foundView)) { 587 return null; 588 } 589 return foundView; 590 } 591 592 /** 593 * Computes whether a view is visible on the screen. 594 * 595 * @param view The view to check. 596 * @return Whether the view is visible on the screen. 597 */ 598 private boolean isDisplayedOnScreen(View view) { 599 // The first two checks are made also made by isShown() which 600 // however traverses the tree up to the parent to catch that. 601 // Therefore, we do some fail fast check to minimize the up 602 // tree traversal. 603 return (view.mAttachInfo != null 604 && view.mAttachInfo.mWindowVisibility == View.VISIBLE 605 && view.getAlpha() > 0 606 && view.isShown() 607 && view.getGlobalVisibleRect(mViewRootImpl.mTempRect)); 608 } 609 610 /** 611 * This class encapsulates a prefetching strategy for the accessibility APIs for 612 * querying window content. It is responsible to prefetch a batch of 613 * AccessibilityNodeInfos in addition to the one for a requested node. 614 */ 615 private class AccessibilityNodePrefetcher { 616 617 private static final int MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE = 50; 618 619 public void prefetchAccessibilityNodeInfos(View view, int virtualViewId, int prefetchFlags, 620 List<AccessibilityNodeInfo> outInfos) { 621 AccessibilityNodeProvider provider = view.getAccessibilityNodeProvider(); 622 if (provider == null) { 623 AccessibilityNodeInfo root = view.createAccessibilityNodeInfo(); 624 if (root != null) { 625 outInfos.add(root); 626 if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS) != 0) { 627 prefetchPredecessorsOfRealNode(view, outInfos); 628 } 629 if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS) != 0) { 630 prefetchSiblingsOfRealNode(view, outInfos); 631 } 632 if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS) != 0) { 633 prefetchDescendantsOfRealNode(view, outInfos); 634 } 635 } 636 } else { 637 AccessibilityNodeInfo root = provider.createAccessibilityNodeInfo(virtualViewId); 638 if (root != null) { 639 outInfos.add(root); 640 if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS) != 0) { 641 prefetchPredecessorsOfVirtualNode(root, view, provider, outInfos); 642 } 643 if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS) != 0) { 644 prefetchSiblingsOfVirtualNode(root, view, provider, outInfos); 645 } 646 if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS) != 0) { 647 prefetchDescendantsOfVirtualNode(root, provider, outInfos); 648 } 649 } 650 } 651 } 652 653 private void prefetchPredecessorsOfRealNode(View view, 654 List<AccessibilityNodeInfo> outInfos) { 655 ViewParent parent = view.getParentForAccessibility(); 656 while (parent instanceof View 657 && outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) { 658 View parentView = (View) parent; 659 final long parentNodeId = AccessibilityNodeInfo.makeNodeId( 660 parentView.getAccessibilityViewId(), AccessibilityNodeInfo.UNDEFINED); 661 AccessibilityNodeInfo info = parentView.createAccessibilityNodeInfo(); 662 if (info != null) { 663 outInfos.add(info); 664 } 665 parent = parent.getParentForAccessibility(); 666 } 667 } 668 669 private void prefetchSiblingsOfRealNode(View current, 670 List<AccessibilityNodeInfo> outInfos) { 671 ViewParent parent = current.getParentForAccessibility(); 672 if (parent instanceof ViewGroup) { 673 ViewGroup parentGroup = (ViewGroup) parent; 674 ChildListForAccessibility children = ChildListForAccessibility.obtain(parentGroup, 675 false); 676 final int childCount = children.getChildCount(); 677 for (int i = 0; i < childCount; i++) { 678 if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) { 679 children.recycle(); 680 return; 681 } 682 View child = children.getChildAt(i); 683 if (child.getAccessibilityViewId() != current.getAccessibilityViewId() 684 && isDisplayedOnScreen(child)) { 685 AccessibilityNodeInfo info = null; 686 AccessibilityNodeProvider provider = child.getAccessibilityNodeProvider(); 687 if (provider == null) { 688 info = child.createAccessibilityNodeInfo(); 689 } else { 690 info = provider.createAccessibilityNodeInfo( 691 AccessibilityNodeInfo.UNDEFINED); 692 } 693 if (info != null) { 694 outInfos.add(info); 695 } 696 } 697 } 698 children.recycle(); 699 } 700 } 701 702 private void prefetchDescendantsOfRealNode(View root, 703 List<AccessibilityNodeInfo> outInfos) { 704 if (!(root instanceof ViewGroup)) { 705 return; 706 } 707 ViewGroup rootGroup = (ViewGroup) root; 708 HashMap<View, AccessibilityNodeInfo> addedChildren = 709 new HashMap<View, AccessibilityNodeInfo>(); 710 ChildListForAccessibility children = ChildListForAccessibility.obtain(rootGroup, false); 711 final int childCount = children.getChildCount(); 712 for (int i = 0; i < childCount; i++) { 713 if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) { 714 children.recycle(); 715 return; 716 } 717 View child = children.getChildAt(i); 718 if ( isDisplayedOnScreen(child)) { 719 AccessibilityNodeProvider provider = child.getAccessibilityNodeProvider(); 720 if (provider == null) { 721 AccessibilityNodeInfo info = child.createAccessibilityNodeInfo(); 722 if (info != null) { 723 outInfos.add(info); 724 addedChildren.put(child, null); 725 } 726 } else { 727 AccessibilityNodeInfo info = provider.createAccessibilityNodeInfo( 728 AccessibilityNodeInfo.UNDEFINED); 729 if (info != null) { 730 outInfos.add(info); 731 addedChildren.put(child, info); 732 } 733 } 734 } 735 } 736 children.recycle(); 737 if (outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) { 738 for (Map.Entry<View, AccessibilityNodeInfo> entry : addedChildren.entrySet()) { 739 View addedChild = entry.getKey(); 740 AccessibilityNodeInfo virtualRoot = entry.getValue(); 741 if (virtualRoot == null) { 742 prefetchDescendantsOfRealNode(addedChild, outInfos); 743 } else { 744 AccessibilityNodeProvider provider = 745 addedChild.getAccessibilityNodeProvider(); 746 prefetchDescendantsOfVirtualNode(virtualRoot, provider, outInfos); 747 } 748 } 749 } 750 } 751 752 private void prefetchPredecessorsOfVirtualNode(AccessibilityNodeInfo root, 753 View providerHost, AccessibilityNodeProvider provider, 754 List<AccessibilityNodeInfo> outInfos) { 755 long parentNodeId = root.getParentNodeId(); 756 int accessibilityViewId = AccessibilityNodeInfo.getAccessibilityViewId(parentNodeId); 757 while (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED) { 758 if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) { 759 return; 760 } 761 final int virtualDescendantId = 762 AccessibilityNodeInfo.getVirtualDescendantId(parentNodeId); 763 if (virtualDescendantId != AccessibilityNodeInfo.UNDEFINED 764 || accessibilityViewId == providerHost.getAccessibilityViewId()) { 765 AccessibilityNodeInfo parent = provider.createAccessibilityNodeInfo( 766 virtualDescendantId); 767 if (parent != null) { 768 outInfos.add(parent); 769 } 770 parentNodeId = parent.getParentNodeId(); 771 accessibilityViewId = AccessibilityNodeInfo.getAccessibilityViewId( 772 parentNodeId); 773 } else { 774 prefetchPredecessorsOfRealNode(providerHost, outInfos); 775 return; 776 } 777 } 778 } 779 780 private void prefetchSiblingsOfVirtualNode(AccessibilityNodeInfo current, View providerHost, 781 AccessibilityNodeProvider provider, List<AccessibilityNodeInfo> outInfos) { 782 final long parentNodeId = current.getParentNodeId(); 783 final int parentAccessibilityViewId = 784 AccessibilityNodeInfo.getAccessibilityViewId(parentNodeId); 785 final int parentVirtualDescendantId = 786 AccessibilityNodeInfo.getVirtualDescendantId(parentNodeId); 787 if (parentVirtualDescendantId != AccessibilityNodeInfo.UNDEFINED 788 || parentAccessibilityViewId == providerHost.getAccessibilityViewId()) { 789 AccessibilityNodeInfo parent = 790 provider.createAccessibilityNodeInfo(parentVirtualDescendantId); 791 if (parent != null) { 792 SparseLongArray childNodeIds = parent.getChildNodeIds(); 793 final int childCount = childNodeIds.size(); 794 for (int i = 0; i < childCount; i++) { 795 if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) { 796 return; 797 } 798 final long childNodeId = childNodeIds.get(i); 799 if (childNodeId != current.getSourceNodeId()) { 800 final int childVirtualDescendantId = 801 AccessibilityNodeInfo.getVirtualDescendantId(childNodeId); 802 AccessibilityNodeInfo child = provider.createAccessibilityNodeInfo( 803 childVirtualDescendantId); 804 if (child != null) { 805 outInfos.add(child); 806 } 807 } 808 } 809 } 810 } else { 811 prefetchSiblingsOfRealNode(providerHost, outInfos); 812 } 813 } 814 815 private void prefetchDescendantsOfVirtualNode(AccessibilityNodeInfo root, 816 AccessibilityNodeProvider provider, List<AccessibilityNodeInfo> outInfos) { 817 SparseLongArray childNodeIds = root.getChildNodeIds(); 818 final int initialOutInfosSize = outInfos.size(); 819 final int childCount = childNodeIds.size(); 820 for (int i = 0; i < childCount; i++) { 821 if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) { 822 return; 823 } 824 final long childNodeId = childNodeIds.get(i); 825 AccessibilityNodeInfo child = provider.createAccessibilityNodeInfo( 826 AccessibilityNodeInfo.getVirtualDescendantId(childNodeId)); 827 if (child != null) { 828 outInfos.add(child); 829 } 830 } 831 if (outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) { 832 final int addedChildCount = outInfos.size() - initialOutInfosSize; 833 for (int i = 0; i < addedChildCount; i++) { 834 AccessibilityNodeInfo child = outInfos.get(initialOutInfosSize + i); 835 prefetchDescendantsOfVirtualNode(child, provider, outInfos); 836 } 837 } 838 } 839 } 840 841 private class PrivateHandler extends Handler { 842 private final static int MSG_PERFORM_ACCESSIBILITY_ACTION = 1; 843 private final static int MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID = 2; 844 private final static int MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_ID = 3; 845 private final static int MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_TEXT = 4; 846 private final static int MSG_FIND_FOCUS = 5; 847 private final static int MSG_FOCUS_SEARCH = 6; 848 849 public PrivateHandler() { 850 super(Looper.getMainLooper()); 851 } 852 853 @Override 854 public String getMessageName(Message message) { 855 final int type = message.what; 856 switch (type) { 857 case MSG_PERFORM_ACCESSIBILITY_ACTION: 858 return "MSG_PERFORM_ACCESSIBILITY_ACTION"; 859 case MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID: 860 return "MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID"; 861 case MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_ID: 862 return "MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_ID"; 863 case MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_TEXT: 864 return "MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_TEXT"; 865 case MSG_FIND_FOCUS: 866 return "MSG_FIND_FOCUS"; 867 case MSG_FOCUS_SEARCH: 868 return "MSG_FOCUS_SEARCH"; 869 default: 870 throw new IllegalArgumentException("Unknown message type: " + type); 871 } 872 } 873 874 @Override 875 public void handleMessage(Message message) { 876 final int type = message.what; 877 switch (type) { 878 case MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID: { 879 findAccessibilityNodeInfoByAccessibilityIdUiThread(message); 880 } break; 881 case MSG_PERFORM_ACCESSIBILITY_ACTION: { 882 perfromAccessibilityActionUiThread(message); 883 } break; 884 case MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_ID: { 885 findAccessibilityNodeInfoByViewIdUiThread(message); 886 } break; 887 case MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_TEXT: { 888 findAccessibilityNodeInfosByTextUiThread(message); 889 } break; 890 case MSG_FIND_FOCUS: { 891 findFocusUiThread(message); 892 } break; 893 case MSG_FOCUS_SEARCH: { 894 focusSearchUiThread(message); 895 } break; 896 default: 897 throw new IllegalArgumentException("Unknown message type: " + type); 898 } 899 } 900 } 901} 902