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