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.ACTION_ARGUMENT_ACCESSIBLE_CLICKABLE_SPAN; 20import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY; 21 22import android.graphics.Point; 23import android.graphics.Rect; 24import android.graphics.RectF; 25import android.graphics.Region; 26import android.os.Binder; 27import android.os.Bundle; 28import android.os.Handler; 29import android.os.Looper; 30import android.os.Message; 31import android.os.Parcelable; 32import android.os.Process; 33import android.os.RemoteException; 34import android.text.style.AccessibilityClickableSpan; 35import android.text.style.ClickableSpan; 36import android.util.LongSparseArray; 37import android.view.View.AttachInfo; 38import android.view.accessibility.AccessibilityInteractionClient; 39import android.view.accessibility.AccessibilityNodeInfo; 40import android.view.accessibility.AccessibilityNodeProvider; 41import android.view.accessibility.IAccessibilityInteractionConnectionCallback; 42 43import com.android.internal.R; 44import com.android.internal.os.SomeArgs; 45 46import java.util.ArrayList; 47import java.util.HashMap; 48import java.util.HashSet; 49import java.util.LinkedList; 50import java.util.List; 51import java.util.Map; 52import java.util.Queue; 53import java.util.function.Predicate; 54 55/** 56 * Class for managing accessibility interactions initiated from the system 57 * and targeting the view hierarchy. A *ClientThread method is to be 58 * called from the interaction connection ViewAncestor gives the system to 59 * talk to it and a corresponding *UiThread method that is executed on the 60 * UI thread. 61 */ 62final class AccessibilityInteractionController { 63 64 private static final boolean ENFORCE_NODE_TREE_CONSISTENT = false; 65 66 private final ArrayList<AccessibilityNodeInfo> mTempAccessibilityNodeInfoList = 67 new ArrayList<AccessibilityNodeInfo>(); 68 69 private final Handler mHandler; 70 71 private final ViewRootImpl mViewRootImpl; 72 73 private final AccessibilityNodePrefetcher mPrefetcher; 74 75 private final long mMyLooperThreadId; 76 77 private final int mMyProcessId; 78 79 private final ArrayList<View> mTempArrayList = new ArrayList<View>(); 80 81 private final Point mTempPoint = new Point(); 82 private final Rect mTempRect = new Rect(); 83 private final Rect mTempRect1 = new Rect(); 84 private final Rect mTempRect2 = new Rect(); 85 86 private AddNodeInfosForViewId mAddNodeInfosForViewId; 87 88 public AccessibilityInteractionController(ViewRootImpl viewRootImpl) { 89 Looper looper = viewRootImpl.mHandler.getLooper(); 90 mMyLooperThreadId = looper.getThread().getId(); 91 mMyProcessId = Process.myPid(); 92 mHandler = new PrivateHandler(looper); 93 mViewRootImpl = viewRootImpl; 94 mPrefetcher = new AccessibilityNodePrefetcher(); 95 } 96 97 private void scheduleMessage(Message message, int interrogatingPid, long interrogatingTid) { 98 // If the interrogation is performed by the same thread as the main UI 99 // thread in this process, set the message as a static reference so 100 // after this call completes the same thread but in the interrogating 101 // client can handle the message to generate the result. 102 if (interrogatingPid == mMyProcessId && interrogatingTid == mMyLooperThreadId) { 103 AccessibilityInteractionClient.getInstanceForThread( 104 interrogatingTid).setSameThreadMessage(message); 105 } else { 106 mHandler.sendMessage(message); 107 } 108 } 109 110 private boolean isShown(View view) { 111 // The first two checks are made also made by isShown() which 112 // however traverses the tree up to the parent to catch that. 113 // Therefore, we do some fail fast check to minimize the up 114 // tree traversal. 115 return (view.mAttachInfo != null 116 && view.mAttachInfo.mWindowVisibility == View.VISIBLE 117 && view.isShown()); 118 } 119 120 public void findAccessibilityNodeInfoByAccessibilityIdClientThread( 121 long accessibilityNodeId, Region interactiveRegion, int interactionId, 122 IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid, 123 long interrogatingTid, MagnificationSpec spec, Bundle arguments) { 124 Message message = mHandler.obtainMessage(); 125 message.what = PrivateHandler.MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_ACCESSIBILITY_ID; 126 message.arg1 = flags; 127 128 SomeArgs args = SomeArgs.obtain(); 129 args.argi1 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId); 130 args.argi2 = AccessibilityNodeInfo.getVirtualDescendantId(accessibilityNodeId); 131 args.argi3 = interactionId; 132 args.arg1 = callback; 133 args.arg2 = spec; 134 args.arg3 = interactiveRegion; 135 args.arg4 = arguments; 136 message.obj = args; 137 138 scheduleMessage(message, interrogatingPid, interrogatingTid); 139 } 140 141 private void findAccessibilityNodeInfoByAccessibilityIdUiThread(Message message) { 142 final int flags = message.arg1; 143 144 SomeArgs args = (SomeArgs) message.obj; 145 final int accessibilityViewId = args.argi1; 146 final int virtualDescendantId = args.argi2; 147 final int interactionId = args.argi3; 148 final IAccessibilityInteractionConnectionCallback callback = 149 (IAccessibilityInteractionConnectionCallback) args.arg1; 150 final MagnificationSpec spec = (MagnificationSpec) args.arg2; 151 final Region interactiveRegion = (Region) args.arg3; 152 final Bundle arguments = (Bundle) args.arg4; 153 154 args.recycle(); 155 156 List<AccessibilityNodeInfo> infos = mTempAccessibilityNodeInfoList; 157 infos.clear(); 158 try { 159 if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) { 160 return; 161 } 162 mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags; 163 View root = null; 164 if (accessibilityViewId == AccessibilityNodeInfo.ROOT_ITEM_ID) { 165 root = mViewRootImpl.mView; 166 } else { 167 root = findViewByAccessibilityId(accessibilityViewId); 168 } 169 if (root != null && isShown(root)) { 170 mPrefetcher.prefetchAccessibilityNodeInfos( 171 root, virtualDescendantId, flags, infos, arguments); 172 } 173 } finally { 174 updateInfosForViewportAndReturnFindNodeResult( 175 infos, callback, interactionId, spec, interactiveRegion); 176 } 177 } 178 179 public void findAccessibilityNodeInfosByViewIdClientThread(long accessibilityNodeId, 180 String viewId, Region interactiveRegion, int interactionId, 181 IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid, 182 long interrogatingTid, MagnificationSpec spec) { 183 Message message = mHandler.obtainMessage(); 184 message.what = PrivateHandler.MSG_FIND_ACCESSIBILITY_NODE_INFOS_BY_VIEW_ID; 185 message.arg1 = flags; 186 message.arg2 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId); 187 188 SomeArgs args = SomeArgs.obtain(); 189 args.argi1 = interactionId; 190 args.arg1 = callback; 191 args.arg2 = spec; 192 args.arg3 = viewId; 193 args.arg4 = interactiveRegion; 194 message.obj = args; 195 196 scheduleMessage(message, interrogatingPid, interrogatingTid); 197 } 198 199 private void findAccessibilityNodeInfosByViewIdUiThread(Message message) { 200 final int flags = message.arg1; 201 final int accessibilityViewId = message.arg2; 202 203 SomeArgs args = (SomeArgs) message.obj; 204 final int interactionId = args.argi1; 205 final IAccessibilityInteractionConnectionCallback callback = 206 (IAccessibilityInteractionConnectionCallback) args.arg1; 207 final MagnificationSpec spec = (MagnificationSpec) args.arg2; 208 final String viewId = (String) args.arg3; 209 final Region interactiveRegion = (Region) args.arg4; 210 args.recycle(); 211 212 final List<AccessibilityNodeInfo> infos = mTempAccessibilityNodeInfoList; 213 infos.clear(); 214 try { 215 if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) { 216 return; 217 } 218 mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags; 219 View root = null; 220 if (accessibilityViewId != AccessibilityNodeInfo.ROOT_ITEM_ID) { 221 root = findViewByAccessibilityId(accessibilityViewId); 222 } else { 223 root = mViewRootImpl.mView; 224 } 225 if (root != null) { 226 final int resolvedViewId = root.getContext().getResources() 227 .getIdentifier(viewId, null, null); 228 if (resolvedViewId <= 0) { 229 return; 230 } 231 if (mAddNodeInfosForViewId == null) { 232 mAddNodeInfosForViewId = new AddNodeInfosForViewId(); 233 } 234 mAddNodeInfosForViewId.init(resolvedViewId, infos); 235 root.findViewByPredicate(mAddNodeInfosForViewId); 236 mAddNodeInfosForViewId.reset(); 237 } 238 } finally { 239 updateInfosForViewportAndReturnFindNodeResult( 240 infos, callback, interactionId, spec, interactiveRegion); 241 } 242 } 243 244 public void findAccessibilityNodeInfosByTextClientThread(long accessibilityNodeId, 245 String text, Region interactiveRegion, int interactionId, 246 IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid, 247 long interrogatingTid, MagnificationSpec spec) { 248 Message message = mHandler.obtainMessage(); 249 message.what = PrivateHandler.MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_TEXT; 250 message.arg1 = flags; 251 252 SomeArgs args = SomeArgs.obtain(); 253 args.arg1 = text; 254 args.arg2 = callback; 255 args.arg3 = spec; 256 args.argi1 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId); 257 args.argi2 = AccessibilityNodeInfo.getVirtualDescendantId(accessibilityNodeId); 258 args.argi3 = interactionId; 259 args.arg4 = interactiveRegion; 260 message.obj = args; 261 262 scheduleMessage(message, interrogatingPid, interrogatingTid); 263 } 264 265 private void findAccessibilityNodeInfosByTextUiThread(Message message) { 266 final int flags = message.arg1; 267 268 SomeArgs args = (SomeArgs) message.obj; 269 final String text = (String) args.arg1; 270 final IAccessibilityInteractionConnectionCallback callback = 271 (IAccessibilityInteractionConnectionCallback) args.arg2; 272 final MagnificationSpec spec = (MagnificationSpec) args.arg3; 273 final int accessibilityViewId = args.argi1; 274 final int virtualDescendantId = args.argi2; 275 final int interactionId = args.argi3; 276 final Region interactiveRegion = (Region) args.arg4; 277 args.recycle(); 278 279 List<AccessibilityNodeInfo> infos = null; 280 try { 281 if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) { 282 return; 283 } 284 mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags; 285 View root = null; 286 if (accessibilityViewId != AccessibilityNodeInfo.ROOT_ITEM_ID) { 287 root = findViewByAccessibilityId(accessibilityViewId); 288 } else { 289 root = mViewRootImpl.mView; 290 } 291 if (root != null && isShown(root)) { 292 AccessibilityNodeProvider provider = root.getAccessibilityNodeProvider(); 293 if (provider != null) { 294 infos = provider.findAccessibilityNodeInfosByText(text, 295 virtualDescendantId); 296 } else if (virtualDescendantId == AccessibilityNodeProvider.HOST_VIEW_ID) { 297 ArrayList<View> foundViews = mTempArrayList; 298 foundViews.clear(); 299 root.findViewsWithText(foundViews, text, View.FIND_VIEWS_WITH_TEXT 300 | View.FIND_VIEWS_WITH_CONTENT_DESCRIPTION 301 | View.FIND_VIEWS_WITH_ACCESSIBILITY_NODE_PROVIDERS); 302 if (!foundViews.isEmpty()) { 303 infos = mTempAccessibilityNodeInfoList; 304 infos.clear(); 305 final int viewCount = foundViews.size(); 306 for (int i = 0; i < viewCount; i++) { 307 View foundView = foundViews.get(i); 308 if (isShown(foundView)) { 309 provider = foundView.getAccessibilityNodeProvider(); 310 if (provider != null) { 311 List<AccessibilityNodeInfo> infosFromProvider = 312 provider.findAccessibilityNodeInfosByText(text, 313 AccessibilityNodeProvider.HOST_VIEW_ID); 314 if (infosFromProvider != null) { 315 infos.addAll(infosFromProvider); 316 } 317 } else { 318 infos.add(foundView.createAccessibilityNodeInfo()); 319 } 320 } 321 } 322 } 323 } 324 } 325 } finally { 326 updateInfosForViewportAndReturnFindNodeResult( 327 infos, callback, interactionId, spec, interactiveRegion); 328 } 329 } 330 331 public void findFocusClientThread(long accessibilityNodeId, int focusType, 332 Region interactiveRegion, int interactionId, 333 IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid, 334 long interrogatingTid, MagnificationSpec spec) { 335 Message message = mHandler.obtainMessage(); 336 message.what = PrivateHandler.MSG_FIND_FOCUS; 337 message.arg1 = flags; 338 message.arg2 = focusType; 339 340 SomeArgs args = SomeArgs.obtain(); 341 args.argi1 = interactionId; 342 args.argi2 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId); 343 args.argi3 = AccessibilityNodeInfo.getVirtualDescendantId(accessibilityNodeId); 344 args.arg1 = callback; 345 args.arg2 = spec; 346 args.arg3 = interactiveRegion; 347 348 message.obj = args; 349 350 scheduleMessage(message, interrogatingPid, interrogatingTid); 351 } 352 353 private void findFocusUiThread(Message message) { 354 final int flags = message.arg1; 355 final int focusType = message.arg2; 356 357 SomeArgs args = (SomeArgs) message.obj; 358 final int interactionId = args.argi1; 359 final int accessibilityViewId = args.argi2; 360 final int virtualDescendantId = args.argi3; 361 final IAccessibilityInteractionConnectionCallback callback = 362 (IAccessibilityInteractionConnectionCallback) args.arg1; 363 final MagnificationSpec spec = (MagnificationSpec) args.arg2; 364 final Region interactiveRegion = (Region) args.arg3; 365 args.recycle(); 366 367 AccessibilityNodeInfo focused = null; 368 try { 369 if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) { 370 return; 371 } 372 mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags; 373 View root = null; 374 if (accessibilityViewId != AccessibilityNodeInfo.ROOT_ITEM_ID) { 375 root = findViewByAccessibilityId(accessibilityViewId); 376 } else { 377 root = mViewRootImpl.mView; 378 } 379 if (root != null && isShown(root)) { 380 switch (focusType) { 381 case AccessibilityNodeInfo.FOCUS_ACCESSIBILITY: { 382 View host = mViewRootImpl.mAccessibilityFocusedHost; 383 // If there is no accessibility focus host or it is not a descendant 384 // of the root from which to start the search, then the search failed. 385 if (host == null || !ViewRootImpl.isViewDescendantOf(host, root)) { 386 break; 387 } 388 // The focused view not shown, we failed. 389 if (!isShown(host)) { 390 break; 391 } 392 // If the host has a provider ask this provider to search for the 393 // focus instead fetching all provider nodes to do the search here. 394 AccessibilityNodeProvider provider = host.getAccessibilityNodeProvider(); 395 if (provider != null) { 396 if (mViewRootImpl.mAccessibilityFocusedVirtualView != null) { 397 focused = AccessibilityNodeInfo.obtain( 398 mViewRootImpl.mAccessibilityFocusedVirtualView); 399 } 400 } else if (virtualDescendantId == AccessibilityNodeProvider.HOST_VIEW_ID) { 401 focused = host.createAccessibilityNodeInfo(); 402 } 403 } break; 404 case AccessibilityNodeInfo.FOCUS_INPUT: { 405 View target = root.findFocus(); 406 if (target == null || !isShown(target)) { 407 break; 408 } 409 AccessibilityNodeProvider provider = target.getAccessibilityNodeProvider(); 410 if (provider != null) { 411 focused = provider.findFocus(focusType); 412 } 413 if (focused == null) { 414 focused = target.createAccessibilityNodeInfo(); 415 } 416 } break; 417 default: 418 throw new IllegalArgumentException("Unknown focus type: " + focusType); 419 } 420 } 421 } finally { 422 updateInfoForViewportAndReturnFindNodeResult( 423 focused, callback, interactionId, spec, interactiveRegion); 424 } 425 } 426 427 public void focusSearchClientThread(long accessibilityNodeId, int direction, 428 Region interactiveRegion, int interactionId, 429 IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid, 430 long interrogatingTid, MagnificationSpec spec) { 431 Message message = mHandler.obtainMessage(); 432 message.what = PrivateHandler.MSG_FOCUS_SEARCH; 433 message.arg1 = flags; 434 message.arg2 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId); 435 436 SomeArgs args = SomeArgs.obtain(); 437 args.argi2 = direction; 438 args.argi3 = interactionId; 439 args.arg1 = callback; 440 args.arg2 = spec; 441 args.arg3 = interactiveRegion; 442 443 message.obj = args; 444 445 scheduleMessage(message, interrogatingPid, interrogatingTid); 446 } 447 448 private void focusSearchUiThread(Message message) { 449 final int flags = message.arg1; 450 final int accessibilityViewId = message.arg2; 451 452 SomeArgs args = (SomeArgs) message.obj; 453 final int direction = args.argi2; 454 final int interactionId = args.argi3; 455 final IAccessibilityInteractionConnectionCallback callback = 456 (IAccessibilityInteractionConnectionCallback) args.arg1; 457 final MagnificationSpec spec = (MagnificationSpec) args.arg2; 458 final Region interactiveRegion = (Region) args.arg3; 459 460 args.recycle(); 461 462 AccessibilityNodeInfo next = null; 463 try { 464 if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) { 465 return; 466 } 467 mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags; 468 View root = null; 469 if (accessibilityViewId != AccessibilityNodeInfo.ROOT_ITEM_ID) { 470 root = findViewByAccessibilityId(accessibilityViewId); 471 } else { 472 root = mViewRootImpl.mView; 473 } 474 if (root != null && isShown(root)) { 475 View nextView = root.focusSearch(direction); 476 if (nextView != null) { 477 next = nextView.createAccessibilityNodeInfo(); 478 } 479 } 480 } finally { 481 updateInfoForViewportAndReturnFindNodeResult( 482 next, callback, interactionId, spec, interactiveRegion); 483 } 484 } 485 486 public void performAccessibilityActionClientThread(long accessibilityNodeId, int action, 487 Bundle arguments, int interactionId, 488 IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid, 489 long interrogatingTid) { 490 Message message = mHandler.obtainMessage(); 491 message.what = PrivateHandler.MSG_PERFORM_ACCESSIBILITY_ACTION; 492 message.arg1 = flags; 493 message.arg2 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId); 494 495 SomeArgs args = SomeArgs.obtain(); 496 args.argi1 = AccessibilityNodeInfo.getVirtualDescendantId(accessibilityNodeId); 497 args.argi2 = action; 498 args.argi3 = interactionId; 499 args.arg1 = callback; 500 args.arg2 = arguments; 501 502 message.obj = args; 503 504 scheduleMessage(message, interrogatingPid, interrogatingTid); 505 } 506 507 private void performAccessibilityActionUiThread(Message message) { 508 final int flags = message.arg1; 509 final int accessibilityViewId = message.arg2; 510 511 SomeArgs args = (SomeArgs) message.obj; 512 final int virtualDescendantId = args.argi1; 513 final int action = args.argi2; 514 final int interactionId = args.argi3; 515 final IAccessibilityInteractionConnectionCallback callback = 516 (IAccessibilityInteractionConnectionCallback) args.arg1; 517 Bundle arguments = (Bundle) args.arg2; 518 519 args.recycle(); 520 521 boolean succeeded = false; 522 try { 523 if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null || 524 mViewRootImpl.mStopped || mViewRootImpl.mPausedForTransition) { 525 return; 526 } 527 mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags; 528 View target = null; 529 if (accessibilityViewId != AccessibilityNodeInfo.ROOT_ITEM_ID) { 530 target = findViewByAccessibilityId(accessibilityViewId); 531 } else { 532 target = mViewRootImpl.mView; 533 } 534 if (target != null && isShown(target)) { 535 if (action == R.id.accessibilityActionClickOnClickableSpan) { 536 // Handle this hidden action separately 537 succeeded = handleClickableSpanActionUiThread( 538 target, virtualDescendantId, arguments); 539 } else { 540 AccessibilityNodeProvider provider = target.getAccessibilityNodeProvider(); 541 if (provider != null) { 542 succeeded = provider.performAction(virtualDescendantId, action, 543 arguments); 544 } else if (virtualDescendantId == AccessibilityNodeProvider.HOST_VIEW_ID) { 545 succeeded = target.performAccessibilityAction(action, arguments); 546 } 547 } 548 } 549 } finally { 550 try { 551 mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0; 552 callback.setPerformAccessibilityActionResult(succeeded, interactionId); 553 } catch (RemoteException re) { 554 /* ignore - the other side will time out */ 555 } 556 } 557 } 558 559 private View findViewByAccessibilityId(int accessibilityId) { 560 View root = mViewRootImpl.mView; 561 if (root == null) { 562 return null; 563 } 564 View foundView = root.findViewByAccessibilityId(accessibilityId); 565 if (foundView != null && !isShown(foundView)) { 566 return null; 567 } 568 return foundView; 569 } 570 571 private void applyAppScaleAndMagnificationSpecIfNeeded(List<AccessibilityNodeInfo> infos, 572 MagnificationSpec spec) { 573 if (infos == null) { 574 return; 575 } 576 final float applicationScale = mViewRootImpl.mAttachInfo.mApplicationScale; 577 if (shouldApplyAppScaleAndMagnificationSpec(applicationScale, spec)) { 578 final int infoCount = infos.size(); 579 for (int i = 0; i < infoCount; i++) { 580 AccessibilityNodeInfo info = infos.get(i); 581 applyAppScaleAndMagnificationSpecIfNeeded(info, spec); 582 } 583 } 584 } 585 586 private void adjustIsVisibleToUserIfNeeded(List<AccessibilityNodeInfo> infos, 587 Region interactiveRegion) { 588 if (interactiveRegion == null || infos == null) { 589 return; 590 } 591 final int infoCount = infos.size(); 592 for (int i = 0; i < infoCount; i++) { 593 AccessibilityNodeInfo info = infos.get(i); 594 adjustIsVisibleToUserIfNeeded(info, interactiveRegion); 595 } 596 } 597 598 private void adjustIsVisibleToUserIfNeeded(AccessibilityNodeInfo info, 599 Region interactiveRegion) { 600 if (interactiveRegion == null || info == null) { 601 return; 602 } 603 Rect boundsInScreen = mTempRect; 604 info.getBoundsInScreen(boundsInScreen); 605 if (interactiveRegion.quickReject(boundsInScreen)) { 606 info.setVisibleToUser(false); 607 } 608 } 609 610 private void applyAppScaleAndMagnificationSpecIfNeeded(AccessibilityNodeInfo info, 611 MagnificationSpec spec) { 612 if (info == null) { 613 return; 614 } 615 616 final float applicationScale = mViewRootImpl.mAttachInfo.mApplicationScale; 617 if (!shouldApplyAppScaleAndMagnificationSpec(applicationScale, spec)) { 618 return; 619 } 620 621 Rect boundsInParent = mTempRect; 622 Rect boundsInScreen = mTempRect1; 623 624 info.getBoundsInParent(boundsInParent); 625 info.getBoundsInScreen(boundsInScreen); 626 if (applicationScale != 1.0f) { 627 boundsInParent.scale(applicationScale); 628 boundsInScreen.scale(applicationScale); 629 } 630 if (spec != null) { 631 boundsInParent.scale(spec.scale); 632 // boundsInParent must not be offset. 633 boundsInScreen.scale(spec.scale); 634 boundsInScreen.offset((int) spec.offsetX, (int) spec.offsetY); 635 } 636 info.setBoundsInParent(boundsInParent); 637 info.setBoundsInScreen(boundsInScreen); 638 639 // Scale text locations if they are present 640 if (info.hasExtras()) { 641 Bundle extras = info.getExtras(); 642 Parcelable[] textLocations = 643 extras.getParcelableArray(EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY); 644 if (textLocations != null) { 645 for (int i = 0; i < textLocations.length; i++) { 646 // Unchecked cast - an app that puts other objects in this bundle with this 647 // key will crash. 648 RectF textLocation = ((RectF) textLocations[i]); 649 textLocation.scale(applicationScale); 650 if (spec != null) { 651 textLocation.scale(spec.scale); 652 textLocation.offset(spec.offsetX, spec.offsetY); 653 } 654 } 655 } 656 } 657 658 if (spec != null) { 659 AttachInfo attachInfo = mViewRootImpl.mAttachInfo; 660 if (attachInfo.mDisplay == null) { 661 return; 662 } 663 664 final float scale = attachInfo.mApplicationScale * spec.scale; 665 666 Rect visibleWinFrame = mTempRect1; 667 visibleWinFrame.left = (int) (attachInfo.mWindowLeft * scale + spec.offsetX); 668 visibleWinFrame.top = (int) (attachInfo.mWindowTop * scale + spec.offsetY); 669 visibleWinFrame.right = (int) (visibleWinFrame.left + mViewRootImpl.mWidth * scale); 670 visibleWinFrame.bottom = (int) (visibleWinFrame.top + mViewRootImpl.mHeight * scale); 671 672 attachInfo.mDisplay.getRealSize(mTempPoint); 673 final int displayWidth = mTempPoint.x; 674 final int displayHeight = mTempPoint.y; 675 676 Rect visibleDisplayFrame = mTempRect2; 677 visibleDisplayFrame.set(0, 0, displayWidth, displayHeight); 678 679 if (!visibleWinFrame.intersect(visibleDisplayFrame)) { 680 // If there's no intersection with display, set visibleWinFrame empty. 681 visibleDisplayFrame.setEmpty(); 682 } 683 684 if (!visibleWinFrame.intersects(boundsInScreen.left, boundsInScreen.top, 685 boundsInScreen.right, boundsInScreen.bottom)) { 686 info.setVisibleToUser(false); 687 } 688 } 689 } 690 691 private boolean shouldApplyAppScaleAndMagnificationSpec(float appScale, 692 MagnificationSpec spec) { 693 return (appScale != 1.0f || (spec != null && !spec.isNop())); 694 } 695 696 private void updateInfosForViewportAndReturnFindNodeResult(List<AccessibilityNodeInfo> infos, 697 IAccessibilityInteractionConnectionCallback callback, int interactionId, 698 MagnificationSpec spec, Region interactiveRegion) { 699 try { 700 mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0; 701 applyAppScaleAndMagnificationSpecIfNeeded(infos, spec); 702 adjustIsVisibleToUserIfNeeded(infos, interactiveRegion); 703 callback.setFindAccessibilityNodeInfosResult(infos, interactionId); 704 if (infos != null) { 705 infos.clear(); 706 } 707 } catch (RemoteException re) { 708 /* ignore - the other side will time out */ 709 } finally { 710 recycleMagnificationSpecAndRegionIfNeeded(spec, interactiveRegion); 711 } 712 } 713 714 private void updateInfoForViewportAndReturnFindNodeResult(AccessibilityNodeInfo info, 715 IAccessibilityInteractionConnectionCallback callback, int interactionId, 716 MagnificationSpec spec, Region interactiveRegion) { 717 try { 718 mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0; 719 applyAppScaleAndMagnificationSpecIfNeeded(info, spec); 720 adjustIsVisibleToUserIfNeeded(info, interactiveRegion); 721 callback.setFindAccessibilityNodeInfoResult(info, interactionId); 722 } catch (RemoteException re) { 723 /* ignore - the other side will time out */ 724 } finally { 725 recycleMagnificationSpecAndRegionIfNeeded(spec, interactiveRegion); 726 } 727 } 728 729 private void recycleMagnificationSpecAndRegionIfNeeded(MagnificationSpec spec, Region region) { 730 if (android.os.Process.myPid() != Binder.getCallingPid()) { 731 // Specs are cached in the system process and obtained from a pool when read from 732 // a parcel, so only recycle the spec if called from another process. 733 if (spec != null) { 734 spec.recycle(); 735 } 736 } else { 737 // Regions are obtained in the system process and instantiated when read from 738 // a parcel, so only recycle the region if caled from the same process. 739 if (region != null) { 740 region.recycle(); 741 } 742 } 743 } 744 745 private boolean handleClickableSpanActionUiThread( 746 View view, int virtualDescendantId, Bundle arguments) { 747 Parcelable span = arguments.getParcelable(ACTION_ARGUMENT_ACCESSIBLE_CLICKABLE_SPAN); 748 if (!(span instanceof AccessibilityClickableSpan)) { 749 return false; 750 } 751 752 // Find the original ClickableSpan if it's still on the screen 753 AccessibilityNodeInfo infoWithSpan = null; 754 AccessibilityNodeProvider provider = view.getAccessibilityNodeProvider(); 755 if (provider != null) { 756 infoWithSpan = provider.createAccessibilityNodeInfo(virtualDescendantId); 757 } else if (virtualDescendantId == AccessibilityNodeProvider.HOST_VIEW_ID) { 758 infoWithSpan = view.createAccessibilityNodeInfo(); 759 } 760 if (infoWithSpan == null) { 761 return false; 762 } 763 764 // Click on the corresponding span 765 ClickableSpan clickableSpan = ((AccessibilityClickableSpan) span).findClickableSpan( 766 infoWithSpan.getOriginalText()); 767 if (clickableSpan != null) { 768 clickableSpan.onClick(view); 769 return true; 770 } 771 return false; 772 } 773 774 /** 775 * This class encapsulates a prefetching strategy for the accessibility APIs for 776 * querying window content. It is responsible to prefetch a batch of 777 * AccessibilityNodeInfos in addition to the one for a requested node. 778 */ 779 private class AccessibilityNodePrefetcher { 780 781 private static final int MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE = 50; 782 783 private final ArrayList<View> mTempViewList = new ArrayList<View>(); 784 785 public void prefetchAccessibilityNodeInfos(View view, int virtualViewId, int fetchFlags, 786 List<AccessibilityNodeInfo> outInfos, Bundle arguments) { 787 AccessibilityNodeProvider provider = view.getAccessibilityNodeProvider(); 788 // Determine if we'll be populating extra data 789 final String extraDataRequested = (arguments == null) ? null 790 : arguments.getString(AccessibilityNodeInfo.EXTRA_DATA_REQUESTED_KEY); 791 if (provider == null) { 792 AccessibilityNodeInfo root = view.createAccessibilityNodeInfo(); 793 if (root != null) { 794 if (extraDataRequested != null) { 795 view.addExtraDataToAccessibilityNodeInfo( 796 root, extraDataRequested, arguments); 797 } 798 outInfos.add(root); 799 if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS) != 0) { 800 prefetchPredecessorsOfRealNode(view, outInfos); 801 } 802 if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS) != 0) { 803 prefetchSiblingsOfRealNode(view, outInfos); 804 } 805 if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS) != 0) { 806 prefetchDescendantsOfRealNode(view, outInfos); 807 } 808 } 809 } else { 810 final AccessibilityNodeInfo root = 811 provider.createAccessibilityNodeInfo(virtualViewId); 812 if (root != null) { 813 if (extraDataRequested != null) { 814 provider.addExtraDataToAccessibilityNodeInfo( 815 virtualViewId, root, extraDataRequested, arguments); 816 } 817 outInfos.add(root); 818 if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS) != 0) { 819 prefetchPredecessorsOfVirtualNode(root, view, provider, outInfos); 820 } 821 if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS) != 0) { 822 prefetchSiblingsOfVirtualNode(root, view, provider, outInfos); 823 } 824 if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS) != 0) { 825 prefetchDescendantsOfVirtualNode(root, provider, outInfos); 826 } 827 } 828 } 829 if (ENFORCE_NODE_TREE_CONSISTENT) { 830 enforceNodeTreeConsistent(outInfos); 831 } 832 } 833 834 private void enforceNodeTreeConsistent(List<AccessibilityNodeInfo> nodes) { 835 LongSparseArray<AccessibilityNodeInfo> nodeMap = 836 new LongSparseArray<AccessibilityNodeInfo>(); 837 final int nodeCount = nodes.size(); 838 for (int i = 0; i < nodeCount; i++) { 839 AccessibilityNodeInfo node = nodes.get(i); 840 nodeMap.put(node.getSourceNodeId(), node); 841 } 842 843 // If the nodes are a tree it does not matter from 844 // which node we start to search for the root. 845 AccessibilityNodeInfo root = nodeMap.valueAt(0); 846 AccessibilityNodeInfo parent = root; 847 while (parent != null) { 848 root = parent; 849 parent = nodeMap.get(parent.getParentNodeId()); 850 } 851 852 // Traverse the tree and do some checks. 853 AccessibilityNodeInfo accessFocus = null; 854 AccessibilityNodeInfo inputFocus = null; 855 HashSet<AccessibilityNodeInfo> seen = new HashSet<AccessibilityNodeInfo>(); 856 Queue<AccessibilityNodeInfo> fringe = new LinkedList<AccessibilityNodeInfo>(); 857 fringe.add(root); 858 859 while (!fringe.isEmpty()) { 860 AccessibilityNodeInfo current = fringe.poll(); 861 862 // Check for duplicates 863 if (!seen.add(current)) { 864 throw new IllegalStateException("Duplicate node: " 865 + current + " in window:" 866 + mViewRootImpl.mAttachInfo.mAccessibilityWindowId); 867 } 868 869 // Check for one accessibility focus. 870 if (current.isAccessibilityFocused()) { 871 if (accessFocus != null) { 872 throw new IllegalStateException("Duplicate accessibility focus:" 873 + current 874 + " in window:" + mViewRootImpl.mAttachInfo.mAccessibilityWindowId); 875 } else { 876 accessFocus = current; 877 } 878 } 879 880 // Check for one input focus. 881 if (current.isFocused()) { 882 if (inputFocus != null) { 883 throw new IllegalStateException("Duplicate input focus: " 884 + current + " in window:" 885 + mViewRootImpl.mAttachInfo.mAccessibilityWindowId); 886 } else { 887 inputFocus = current; 888 } 889 } 890 891 final int childCount = current.getChildCount(); 892 for (int j = 0; j < childCount; j++) { 893 final long childId = current.getChildId(j); 894 final AccessibilityNodeInfo child = nodeMap.get(childId); 895 if (child != null) { 896 fringe.add(child); 897 } 898 } 899 } 900 901 // Check for disconnected nodes. 902 for (int j = nodeMap.size() - 1; j >= 0; j--) { 903 AccessibilityNodeInfo info = nodeMap.valueAt(j); 904 if (!seen.contains(info)) { 905 throw new IllegalStateException("Disconnected node: " + info); 906 } 907 } 908 } 909 910 private void prefetchPredecessorsOfRealNode(View view, 911 List<AccessibilityNodeInfo> outInfos) { 912 ViewParent parent = view.getParentForAccessibility(); 913 while (parent instanceof View 914 && outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) { 915 View parentView = (View) parent; 916 AccessibilityNodeInfo info = parentView.createAccessibilityNodeInfo(); 917 if (info != null) { 918 outInfos.add(info); 919 } 920 parent = parent.getParentForAccessibility(); 921 } 922 } 923 924 private void prefetchSiblingsOfRealNode(View current, 925 List<AccessibilityNodeInfo> outInfos) { 926 ViewParent parent = current.getParentForAccessibility(); 927 if (parent instanceof ViewGroup) { 928 ViewGroup parentGroup = (ViewGroup) parent; 929 ArrayList<View> children = mTempViewList; 930 children.clear(); 931 try { 932 parentGroup.addChildrenForAccessibility(children); 933 final int childCount = children.size(); 934 for (int i = 0; i < childCount; i++) { 935 if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) { 936 return; 937 } 938 View child = children.get(i); 939 if (child.getAccessibilityViewId() != current.getAccessibilityViewId() 940 && isShown(child)) { 941 AccessibilityNodeInfo info = null; 942 AccessibilityNodeProvider provider = 943 child.getAccessibilityNodeProvider(); 944 if (provider == null) { 945 info = child.createAccessibilityNodeInfo(); 946 } else { 947 info = provider.createAccessibilityNodeInfo( 948 AccessibilityNodeProvider.HOST_VIEW_ID); 949 } 950 if (info != null) { 951 outInfos.add(info); 952 } 953 } 954 } 955 } finally { 956 children.clear(); 957 } 958 } 959 } 960 961 private void prefetchDescendantsOfRealNode(View root, 962 List<AccessibilityNodeInfo> outInfos) { 963 if (!(root instanceof ViewGroup)) { 964 return; 965 } 966 HashMap<View, AccessibilityNodeInfo> addedChildren = 967 new HashMap<View, AccessibilityNodeInfo>(); 968 ArrayList<View> children = mTempViewList; 969 children.clear(); 970 try { 971 root.addChildrenForAccessibility(children); 972 final int childCount = children.size(); 973 for (int i = 0; i < childCount; i++) { 974 if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) { 975 return; 976 } 977 View child = children.get(i); 978 if (isShown(child)) { 979 AccessibilityNodeProvider provider = child.getAccessibilityNodeProvider(); 980 if (provider == null) { 981 AccessibilityNodeInfo info = child.createAccessibilityNodeInfo(); 982 if (info != null) { 983 outInfos.add(info); 984 addedChildren.put(child, null); 985 } 986 } else { 987 AccessibilityNodeInfo info = provider.createAccessibilityNodeInfo( 988 AccessibilityNodeProvider.HOST_VIEW_ID); 989 if (info != null) { 990 outInfos.add(info); 991 addedChildren.put(child, info); 992 } 993 } 994 } 995 } 996 } finally { 997 children.clear(); 998 } 999 if (outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) { 1000 for (Map.Entry<View, AccessibilityNodeInfo> entry : addedChildren.entrySet()) { 1001 View addedChild = entry.getKey(); 1002 AccessibilityNodeInfo virtualRoot = entry.getValue(); 1003 if (virtualRoot == null) { 1004 prefetchDescendantsOfRealNode(addedChild, outInfos); 1005 } else { 1006 AccessibilityNodeProvider provider = 1007 addedChild.getAccessibilityNodeProvider(); 1008 prefetchDescendantsOfVirtualNode(virtualRoot, provider, outInfos); 1009 } 1010 } 1011 } 1012 } 1013 1014 private void prefetchPredecessorsOfVirtualNode(AccessibilityNodeInfo root, 1015 View providerHost, AccessibilityNodeProvider provider, 1016 List<AccessibilityNodeInfo> outInfos) { 1017 final int initialResultSize = outInfos.size(); 1018 long parentNodeId = root.getParentNodeId(); 1019 int accessibilityViewId = AccessibilityNodeInfo.getAccessibilityViewId(parentNodeId); 1020 while (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) { 1021 if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) { 1022 return; 1023 } 1024 final int virtualDescendantId = 1025 AccessibilityNodeInfo.getVirtualDescendantId(parentNodeId); 1026 if (virtualDescendantId != AccessibilityNodeProvider.HOST_VIEW_ID 1027 || accessibilityViewId == providerHost.getAccessibilityViewId()) { 1028 final AccessibilityNodeInfo parent; 1029 parent = provider.createAccessibilityNodeInfo(virtualDescendantId); 1030 if (parent == null) { 1031 // Going up the parent relation we found a null predecessor, 1032 // so remove these disconnected nodes form the result. 1033 final int currentResultSize = outInfos.size(); 1034 for (int i = currentResultSize - 1; i >= initialResultSize; i--) { 1035 outInfos.remove(i); 1036 } 1037 // Couldn't obtain the parent, which means we have a 1038 // disconnected sub-tree. Abort prefetch immediately. 1039 return; 1040 } 1041 outInfos.add(parent); 1042 parentNodeId = parent.getParentNodeId(); 1043 accessibilityViewId = AccessibilityNodeInfo.getAccessibilityViewId( 1044 parentNodeId); 1045 } else { 1046 prefetchPredecessorsOfRealNode(providerHost, outInfos); 1047 return; 1048 } 1049 } 1050 } 1051 1052 private void prefetchSiblingsOfVirtualNode(AccessibilityNodeInfo current, View providerHost, 1053 AccessibilityNodeProvider provider, List<AccessibilityNodeInfo> outInfos) { 1054 final long parentNodeId = current.getParentNodeId(); 1055 final int parentAccessibilityViewId = 1056 AccessibilityNodeInfo.getAccessibilityViewId(parentNodeId); 1057 final int parentVirtualDescendantId = 1058 AccessibilityNodeInfo.getVirtualDescendantId(parentNodeId); 1059 if (parentVirtualDescendantId != AccessibilityNodeProvider.HOST_VIEW_ID 1060 || parentAccessibilityViewId == providerHost.getAccessibilityViewId()) { 1061 final AccessibilityNodeInfo parent = 1062 provider.createAccessibilityNodeInfo(parentVirtualDescendantId); 1063 if (parent != null) { 1064 final int childCount = parent.getChildCount(); 1065 for (int i = 0; i < childCount; i++) { 1066 if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) { 1067 return; 1068 } 1069 final long childNodeId = parent.getChildId(i); 1070 if (childNodeId != current.getSourceNodeId()) { 1071 final int childVirtualDescendantId = 1072 AccessibilityNodeInfo.getVirtualDescendantId(childNodeId); 1073 AccessibilityNodeInfo child = provider.createAccessibilityNodeInfo( 1074 childVirtualDescendantId); 1075 if (child != null) { 1076 outInfos.add(child); 1077 } 1078 } 1079 } 1080 } 1081 } else { 1082 prefetchSiblingsOfRealNode(providerHost, outInfos); 1083 } 1084 } 1085 1086 private void prefetchDescendantsOfVirtualNode(AccessibilityNodeInfo root, 1087 AccessibilityNodeProvider provider, List<AccessibilityNodeInfo> outInfos) { 1088 final int initialOutInfosSize = outInfos.size(); 1089 final int childCount = root.getChildCount(); 1090 for (int i = 0; i < childCount; i++) { 1091 if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) { 1092 return; 1093 } 1094 final long childNodeId = root.getChildId(i); 1095 AccessibilityNodeInfo child = provider.createAccessibilityNodeInfo( 1096 AccessibilityNodeInfo.getVirtualDescendantId(childNodeId)); 1097 if (child != null) { 1098 outInfos.add(child); 1099 } 1100 } 1101 if (outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) { 1102 final int addedChildCount = outInfos.size() - initialOutInfosSize; 1103 for (int i = 0; i < addedChildCount; i++) { 1104 AccessibilityNodeInfo child = outInfos.get(initialOutInfosSize + i); 1105 prefetchDescendantsOfVirtualNode(child, provider, outInfos); 1106 } 1107 } 1108 } 1109 } 1110 1111 private class PrivateHandler extends Handler { 1112 private static final int MSG_PERFORM_ACCESSIBILITY_ACTION = 1; 1113 private static final int MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_ACCESSIBILITY_ID = 2; 1114 private static final int MSG_FIND_ACCESSIBILITY_NODE_INFOS_BY_VIEW_ID = 3; 1115 private static final int MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_TEXT = 4; 1116 private static final int MSG_FIND_FOCUS = 5; 1117 private static final int MSG_FOCUS_SEARCH = 6; 1118 1119 public PrivateHandler(Looper looper) { 1120 super(looper); 1121 } 1122 1123 @Override 1124 public String getMessageName(Message message) { 1125 final int type = message.what; 1126 switch (type) { 1127 case MSG_PERFORM_ACCESSIBILITY_ACTION: 1128 return "MSG_PERFORM_ACCESSIBILITY_ACTION"; 1129 case MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_ACCESSIBILITY_ID: 1130 return "MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_ACCESSIBILITY_ID"; 1131 case MSG_FIND_ACCESSIBILITY_NODE_INFOS_BY_VIEW_ID: 1132 return "MSG_FIND_ACCESSIBILITY_NODE_INFOS_BY_VIEW_ID"; 1133 case MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_TEXT: 1134 return "MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_TEXT"; 1135 case MSG_FIND_FOCUS: 1136 return "MSG_FIND_FOCUS"; 1137 case MSG_FOCUS_SEARCH: 1138 return "MSG_FOCUS_SEARCH"; 1139 default: 1140 throw new IllegalArgumentException("Unknown message type: " + type); 1141 } 1142 } 1143 1144 @Override 1145 public void handleMessage(Message message) { 1146 final int type = message.what; 1147 switch (type) { 1148 case MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_ACCESSIBILITY_ID: { 1149 findAccessibilityNodeInfoByAccessibilityIdUiThread(message); 1150 } break; 1151 case MSG_PERFORM_ACCESSIBILITY_ACTION: { 1152 performAccessibilityActionUiThread(message); 1153 } break; 1154 case MSG_FIND_ACCESSIBILITY_NODE_INFOS_BY_VIEW_ID: { 1155 findAccessibilityNodeInfosByViewIdUiThread(message); 1156 } break; 1157 case MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_TEXT: { 1158 findAccessibilityNodeInfosByTextUiThread(message); 1159 } break; 1160 case MSG_FIND_FOCUS: { 1161 findFocusUiThread(message); 1162 } break; 1163 case MSG_FOCUS_SEARCH: { 1164 focusSearchUiThread(message); 1165 } break; 1166 default: 1167 throw new IllegalArgumentException("Unknown message type: " + type); 1168 } 1169 } 1170 } 1171 1172 private final class AddNodeInfosForViewId implements Predicate<View> { 1173 private int mViewId = View.NO_ID; 1174 private List<AccessibilityNodeInfo> mInfos; 1175 1176 public void init(int viewId, List<AccessibilityNodeInfo> infos) { 1177 mViewId = viewId; 1178 mInfos = infos; 1179 } 1180 1181 public void reset() { 1182 mViewId = View.NO_ID; 1183 mInfos = null; 1184 } 1185 1186 @Override 1187 public boolean test(View view) { 1188 if (view.getId() == mViewId && isShown(view)) { 1189 mInfos.add(view.createAccessibilityNodeInfo()); 1190 } 1191 return false; 1192 } 1193 } 1194} 1195