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