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