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