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