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