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 public void computeClickPointInScreenClientThread(long accessibilityNodeId, 640 Region interactiveRegion, int interactionId, 641 IAccessibilityInteractionConnectionCallback callback, int interrogatingPid, 642 long interrogatingTid, MagnificationSpec spec) { 643 Message message = mHandler.obtainMessage(); 644 message.what = PrivateHandler.MSG_COMPUTE_CLICK_POINT_IN_SCREEN; 645 646 SomeArgs args = SomeArgs.obtain(); 647 args.argi1 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId); 648 args.argi2 = AccessibilityNodeInfo.getVirtualDescendantId(accessibilityNodeId); 649 args.argi3 = interactionId; 650 args.arg1 = callback; 651 args.arg2 = spec; 652 args.arg3 = interactiveRegion; 653 654 message.obj = args; 655 656 // If the interrogation is performed by the same thread as the main UI 657 // thread in this process, set the message as a static reference so 658 // after this call completes the same thread but in the interrogating 659 // client can handle the message to generate the result. 660 if (interrogatingPid == mMyProcessId && interrogatingTid == mMyLooperThreadId) { 661 AccessibilityInteractionClient.getInstanceForThread( 662 interrogatingTid).setSameThreadMessage(message); 663 } else { 664 mHandler.sendMessage(message); 665 } 666 } 667 668 private void computeClickPointInScreenUiThread(Message message) { 669 SomeArgs args = (SomeArgs) message.obj; 670 final int accessibilityViewId = args.argi1; 671 final int virtualDescendantId = args.argi2; 672 final int interactionId = args.argi3; 673 final IAccessibilityInteractionConnectionCallback callback = 674 (IAccessibilityInteractionConnectionCallback) args.arg1; 675 final MagnificationSpec spec = (MagnificationSpec) args.arg2; 676 final Region interactiveRegion = (Region) args.arg3; 677 args.recycle(); 678 679 boolean succeeded = false; 680 Point point = mTempPoint; 681 try { 682 if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) { 683 return; 684 } 685 View target = null; 686 if (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) { 687 target = findViewByAccessibilityId(accessibilityViewId); 688 } else { 689 target = mViewRootImpl.mView; 690 } 691 if (target != null && isShown(target)) { 692 AccessibilityNodeProvider provider = target.getAccessibilityNodeProvider(); 693 if (provider != null) { 694 // For virtual views just use the center of the bounds in screen. 695 AccessibilityNodeInfo node = null; 696 if (virtualDescendantId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) { 697 node = provider.createAccessibilityNodeInfo(virtualDescendantId); 698 } else { 699 node = provider.createAccessibilityNodeInfo( 700 AccessibilityNodeProvider.HOST_VIEW_ID); 701 } 702 if (node != null) { 703 succeeded = true; 704 Rect boundsInScreen = mTempRect; 705 node.getBoundsInScreen(boundsInScreen); 706 point.set(boundsInScreen.centerX(), boundsInScreen.centerY()); 707 } 708 } else if (virtualDescendantId == AccessibilityNodeInfo.UNDEFINED_ITEM_ID) { 709 // For a real view, ask the view to compute the click point. 710 succeeded = target.computeClickPointInScreenForAccessibility( 711 interactiveRegion, point); 712 } 713 } 714 } finally { 715 try { 716 Point result = null; 717 if (succeeded) { 718 applyAppScaleAndMagnificationSpecIfNeeded(point, spec); 719 result = point; 720 } 721 callback.setComputeClickPointInScreenActionResult(result, interactionId); 722 } catch (RemoteException re) { 723 /* ignore - the other side will time out */ 724 } 725 } 726 } 727 728 private View findViewByAccessibilityId(int accessibilityId) { 729 View root = mViewRootImpl.mView; 730 if (root == null) { 731 return null; 732 } 733 View foundView = root.findViewByAccessibilityId(accessibilityId); 734 if (foundView != null && !isShown(foundView)) { 735 return null; 736 } 737 return foundView; 738 } 739 740 private void applyAppScaleAndMagnificationSpecIfNeeded(List<AccessibilityNodeInfo> infos, 741 MagnificationSpec spec) { 742 if (infos == null) { 743 return; 744 } 745 final float applicationScale = mViewRootImpl.mAttachInfo.mApplicationScale; 746 if (shouldApplyAppScaleAndMagnificationSpec(applicationScale, spec)) { 747 final int infoCount = infos.size(); 748 for (int i = 0; i < infoCount; i++) { 749 AccessibilityNodeInfo info = infos.get(i); 750 applyAppScaleAndMagnificationSpecIfNeeded(info, spec); 751 } 752 } 753 } 754 755 private void adjustIsVisibleToUserIfNeeded(List<AccessibilityNodeInfo> infos, 756 Region interactiveRegion) { 757 if (interactiveRegion == null || infos == null) { 758 return; 759 } 760 final int infoCount = infos.size(); 761 for (int i = 0; i < infoCount; i++) { 762 AccessibilityNodeInfo info = infos.get(i); 763 adjustIsVisibleToUserIfNeeded(info, interactiveRegion); 764 } 765 } 766 767 private void adjustIsVisibleToUserIfNeeded(AccessibilityNodeInfo info, 768 Region interactiveRegion) { 769 if (interactiveRegion == null || info == null) { 770 return; 771 } 772 Rect boundsInScreen = mTempRect; 773 info.getBoundsInScreen(boundsInScreen); 774 if (interactiveRegion.quickReject(boundsInScreen)) { 775 info.setVisibleToUser(false); 776 } 777 } 778 779 private void applyAppScaleAndMagnificationSpecIfNeeded(Point point, 780 MagnificationSpec spec) { 781 final float applicationScale = mViewRootImpl.mAttachInfo.mApplicationScale; 782 if (!shouldApplyAppScaleAndMagnificationSpec(applicationScale, spec)) { 783 return; 784 } 785 786 if (applicationScale != 1.0f) { 787 point.x *= applicationScale; 788 point.y *= applicationScale; 789 } 790 791 if (spec != null) { 792 point.x *= spec.scale; 793 point.y *= spec.scale; 794 point.x += (int) spec.offsetX; 795 point.y += (int) spec.offsetY; 796 } 797 } 798 799 private void applyAppScaleAndMagnificationSpecIfNeeded(AccessibilityNodeInfo info, 800 MagnificationSpec spec) { 801 if (info == null) { 802 return; 803 } 804 805 final float applicationScale = mViewRootImpl.mAttachInfo.mApplicationScale; 806 if (!shouldApplyAppScaleAndMagnificationSpec(applicationScale, spec)) { 807 return; 808 } 809 810 Rect boundsInParent = mTempRect; 811 Rect boundsInScreen = mTempRect1; 812 813 info.getBoundsInParent(boundsInParent); 814 info.getBoundsInScreen(boundsInScreen); 815 if (applicationScale != 1.0f) { 816 boundsInParent.scale(applicationScale); 817 boundsInScreen.scale(applicationScale); 818 } 819 if (spec != null) { 820 boundsInParent.scale(spec.scale); 821 // boundsInParent must not be offset. 822 boundsInScreen.scale(spec.scale); 823 boundsInScreen.offset((int) spec.offsetX, (int) spec.offsetY); 824 } 825 info.setBoundsInParent(boundsInParent); 826 info.setBoundsInScreen(boundsInScreen); 827 828 if (spec != null) { 829 AttachInfo attachInfo = mViewRootImpl.mAttachInfo; 830 if (attachInfo.mDisplay == null) { 831 return; 832 } 833 834 final float scale = attachInfo.mApplicationScale * spec.scale; 835 836 Rect visibleWinFrame = mTempRect1; 837 visibleWinFrame.left = (int) (attachInfo.mWindowLeft * scale + spec.offsetX); 838 visibleWinFrame.top = (int) (attachInfo.mWindowTop * scale + spec.offsetY); 839 visibleWinFrame.right = (int) (visibleWinFrame.left + mViewRootImpl.mWidth * scale); 840 visibleWinFrame.bottom = (int) (visibleWinFrame.top + mViewRootImpl.mHeight * scale); 841 842 attachInfo.mDisplay.getRealSize(mTempPoint); 843 final int displayWidth = mTempPoint.x; 844 final int displayHeight = mTempPoint.y; 845 846 Rect visibleDisplayFrame = mTempRect2; 847 visibleDisplayFrame.set(0, 0, displayWidth, displayHeight); 848 849 visibleWinFrame.intersect(visibleDisplayFrame); 850 851 if (!visibleWinFrame.intersects(boundsInScreen.left, boundsInScreen.top, 852 boundsInScreen.right, boundsInScreen.bottom)) { 853 info.setVisibleToUser(false); 854 } 855 } 856 } 857 858 private boolean shouldApplyAppScaleAndMagnificationSpec(float appScale, 859 MagnificationSpec spec) { 860 return (appScale != 1.0f || (spec != null && !spec.isNop())); 861 } 862 863 /** 864 * This class encapsulates a prefetching strategy for the accessibility APIs for 865 * querying window content. It is responsible to prefetch a batch of 866 * AccessibilityNodeInfos in addition to the one for a requested node. 867 */ 868 private class AccessibilityNodePrefetcher { 869 870 private static final int MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE = 50; 871 872 private final ArrayList<View> mTempViewList = new ArrayList<View>(); 873 874 public void prefetchAccessibilityNodeInfos(View view, int virtualViewId, int fetchFlags, 875 List<AccessibilityNodeInfo> outInfos) { 876 AccessibilityNodeProvider provider = view.getAccessibilityNodeProvider(); 877 if (provider == null) { 878 AccessibilityNodeInfo root = view.createAccessibilityNodeInfo(); 879 if (root != null) { 880 outInfos.add(root); 881 if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS) != 0) { 882 prefetchPredecessorsOfRealNode(view, outInfos); 883 } 884 if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS) != 0) { 885 prefetchSiblingsOfRealNode(view, outInfos); 886 } 887 if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS) != 0) { 888 prefetchDescendantsOfRealNode(view, outInfos); 889 } 890 } 891 } else { 892 final AccessibilityNodeInfo root; 893 if (virtualViewId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) { 894 root = provider.createAccessibilityNodeInfo(virtualViewId); 895 } else { 896 root = provider.createAccessibilityNodeInfo( 897 AccessibilityNodeProvider.HOST_VIEW_ID); 898 } 899 if (root != null) { 900 outInfos.add(root); 901 if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS) != 0) { 902 prefetchPredecessorsOfVirtualNode(root, view, provider, outInfos); 903 } 904 if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS) != 0) { 905 prefetchSiblingsOfVirtualNode(root, view, provider, outInfos); 906 } 907 if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS) != 0) { 908 prefetchDescendantsOfVirtualNode(root, provider, outInfos); 909 } 910 } 911 } 912 if (ENFORCE_NODE_TREE_CONSISTENT) { 913 enforceNodeTreeConsistent(outInfos); 914 } 915 } 916 917 private void enforceNodeTreeConsistent(List<AccessibilityNodeInfo> nodes) { 918 LongSparseArray<AccessibilityNodeInfo> nodeMap = 919 new LongSparseArray<AccessibilityNodeInfo>(); 920 final int nodeCount = nodes.size(); 921 for (int i = 0; i < nodeCount; i++) { 922 AccessibilityNodeInfo node = nodes.get(i); 923 nodeMap.put(node.getSourceNodeId(), node); 924 } 925 926 // If the nodes are a tree it does not matter from 927 // which node we start to search for the root. 928 AccessibilityNodeInfo root = nodeMap.valueAt(0); 929 AccessibilityNodeInfo parent = root; 930 while (parent != null) { 931 root = parent; 932 parent = nodeMap.get(parent.getParentNodeId()); 933 } 934 935 // Traverse the tree and do some checks. 936 AccessibilityNodeInfo accessFocus = null; 937 AccessibilityNodeInfo inputFocus = null; 938 HashSet<AccessibilityNodeInfo> seen = new HashSet<AccessibilityNodeInfo>(); 939 Queue<AccessibilityNodeInfo> fringe = new LinkedList<AccessibilityNodeInfo>(); 940 fringe.add(root); 941 942 while (!fringe.isEmpty()) { 943 AccessibilityNodeInfo current = fringe.poll(); 944 945 // Check for duplicates 946 if (!seen.add(current)) { 947 throw new IllegalStateException("Duplicate node: " 948 + current + " in window:" 949 + mViewRootImpl.mAttachInfo.mAccessibilityWindowId); 950 } 951 952 // Check for one accessibility focus. 953 if (current.isAccessibilityFocused()) { 954 if (accessFocus != null) { 955 throw new IllegalStateException("Duplicate accessibility focus:" 956 + current 957 + " in window:" + mViewRootImpl.mAttachInfo.mAccessibilityWindowId); 958 } else { 959 accessFocus = current; 960 } 961 } 962 963 // Check for one input focus. 964 if (current.isFocused()) { 965 if (inputFocus != null) { 966 throw new IllegalStateException("Duplicate input focus: " 967 + current + " in window:" 968 + mViewRootImpl.mAttachInfo.mAccessibilityWindowId); 969 } else { 970 inputFocus = current; 971 } 972 } 973 974 final int childCount = current.getChildCount(); 975 for (int j = 0; j < childCount; j++) { 976 final long childId = current.getChildId(j); 977 final AccessibilityNodeInfo child = nodeMap.get(childId); 978 if (child != null) { 979 fringe.add(child); 980 } 981 } 982 } 983 984 // Check for disconnected nodes. 985 for (int j = nodeMap.size() - 1; j >= 0; j--) { 986 AccessibilityNodeInfo info = nodeMap.valueAt(j); 987 if (!seen.contains(info)) { 988 throw new IllegalStateException("Disconnected node: " + info); 989 } 990 } 991 } 992 993 private void prefetchPredecessorsOfRealNode(View view, 994 List<AccessibilityNodeInfo> outInfos) { 995 ViewParent parent = view.getParentForAccessibility(); 996 while (parent instanceof View 997 && outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) { 998 View parentView = (View) parent; 999 AccessibilityNodeInfo info = parentView.createAccessibilityNodeInfo(); 1000 if (info != null) { 1001 outInfos.add(info); 1002 } 1003 parent = parent.getParentForAccessibility(); 1004 } 1005 } 1006 1007 private void prefetchSiblingsOfRealNode(View current, 1008 List<AccessibilityNodeInfo> outInfos) { 1009 ViewParent parent = current.getParentForAccessibility(); 1010 if (parent instanceof ViewGroup) { 1011 ViewGroup parentGroup = (ViewGroup) parent; 1012 ArrayList<View> children = mTempViewList; 1013 children.clear(); 1014 try { 1015 parentGroup.addChildrenForAccessibility(children); 1016 final int childCount = children.size(); 1017 for (int i = 0; i < childCount; i++) { 1018 if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) { 1019 return; 1020 } 1021 View child = children.get(i); 1022 if (child.getAccessibilityViewId() != current.getAccessibilityViewId() 1023 && isShown(child)) { 1024 AccessibilityNodeInfo info = null; 1025 AccessibilityNodeProvider provider = 1026 child.getAccessibilityNodeProvider(); 1027 if (provider == null) { 1028 info = child.createAccessibilityNodeInfo(); 1029 } else { 1030 info = provider.createAccessibilityNodeInfo( 1031 AccessibilityNodeProvider.HOST_VIEW_ID); 1032 } 1033 if (info != null) { 1034 outInfos.add(info); 1035 } 1036 } 1037 } 1038 } finally { 1039 children.clear(); 1040 } 1041 } 1042 } 1043 1044 private void prefetchDescendantsOfRealNode(View root, 1045 List<AccessibilityNodeInfo> outInfos) { 1046 if (!(root instanceof ViewGroup)) { 1047 return; 1048 } 1049 HashMap<View, AccessibilityNodeInfo> addedChildren = 1050 new HashMap<View, AccessibilityNodeInfo>(); 1051 ArrayList<View> children = mTempViewList; 1052 children.clear(); 1053 try { 1054 root.addChildrenForAccessibility(children); 1055 final int childCount = children.size(); 1056 for (int i = 0; i < childCount; i++) { 1057 if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) { 1058 return; 1059 } 1060 View child = children.get(i); 1061 if (isShown(child)) { 1062 AccessibilityNodeProvider provider = child.getAccessibilityNodeProvider(); 1063 if (provider == null) { 1064 AccessibilityNodeInfo info = child.createAccessibilityNodeInfo(); 1065 if (info != null) { 1066 outInfos.add(info); 1067 addedChildren.put(child, null); 1068 } 1069 } else { 1070 AccessibilityNodeInfo info = provider.createAccessibilityNodeInfo( 1071 AccessibilityNodeProvider.HOST_VIEW_ID); 1072 if (info != null) { 1073 outInfos.add(info); 1074 addedChildren.put(child, info); 1075 } 1076 } 1077 } 1078 } 1079 } finally { 1080 children.clear(); 1081 } 1082 if (outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) { 1083 for (Map.Entry<View, AccessibilityNodeInfo> entry : addedChildren.entrySet()) { 1084 View addedChild = entry.getKey(); 1085 AccessibilityNodeInfo virtualRoot = entry.getValue(); 1086 if (virtualRoot == null) { 1087 prefetchDescendantsOfRealNode(addedChild, outInfos); 1088 } else { 1089 AccessibilityNodeProvider provider = 1090 addedChild.getAccessibilityNodeProvider(); 1091 prefetchDescendantsOfVirtualNode(virtualRoot, provider, outInfos); 1092 } 1093 } 1094 } 1095 } 1096 1097 private void prefetchPredecessorsOfVirtualNode(AccessibilityNodeInfo root, 1098 View providerHost, AccessibilityNodeProvider provider, 1099 List<AccessibilityNodeInfo> outInfos) { 1100 long parentNodeId = root.getParentNodeId(); 1101 int accessibilityViewId = AccessibilityNodeInfo.getAccessibilityViewId(parentNodeId); 1102 while (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) { 1103 if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) { 1104 return; 1105 } 1106 final int virtualDescendantId = 1107 AccessibilityNodeInfo.getVirtualDescendantId(parentNodeId); 1108 if (virtualDescendantId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID 1109 || accessibilityViewId == providerHost.getAccessibilityViewId()) { 1110 final AccessibilityNodeInfo parent; 1111 if (virtualDescendantId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) { 1112 parent = provider.createAccessibilityNodeInfo( 1113 virtualDescendantId); 1114 } else { 1115 parent= provider.createAccessibilityNodeInfo( 1116 AccessibilityNodeProvider.HOST_VIEW_ID); 1117 } 1118 if (parent != null) { 1119 outInfos.add(parent); 1120 } 1121 parentNodeId = parent.getParentNodeId(); 1122 accessibilityViewId = AccessibilityNodeInfo.getAccessibilityViewId( 1123 parentNodeId); 1124 } else { 1125 prefetchPredecessorsOfRealNode(providerHost, outInfos); 1126 return; 1127 } 1128 } 1129 } 1130 1131 private void prefetchSiblingsOfVirtualNode(AccessibilityNodeInfo current, View providerHost, 1132 AccessibilityNodeProvider provider, List<AccessibilityNodeInfo> outInfos) { 1133 final long parentNodeId = current.getParentNodeId(); 1134 final int parentAccessibilityViewId = 1135 AccessibilityNodeInfo.getAccessibilityViewId(parentNodeId); 1136 final int parentVirtualDescendantId = 1137 AccessibilityNodeInfo.getVirtualDescendantId(parentNodeId); 1138 if (parentVirtualDescendantId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID 1139 || parentAccessibilityViewId == providerHost.getAccessibilityViewId()) { 1140 final AccessibilityNodeInfo parent; 1141 if (parentVirtualDescendantId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) { 1142 parent = provider.createAccessibilityNodeInfo(parentVirtualDescendantId); 1143 } else { 1144 parent = provider.createAccessibilityNodeInfo( 1145 AccessibilityNodeProvider.HOST_VIEW_ID); 1146 } 1147 if (parent != null) { 1148 final int childCount = parent.getChildCount(); 1149 for (int i = 0; i < childCount; i++) { 1150 if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) { 1151 return; 1152 } 1153 final long childNodeId = parent.getChildId(i); 1154 if (childNodeId != current.getSourceNodeId()) { 1155 final int childVirtualDescendantId = 1156 AccessibilityNodeInfo.getVirtualDescendantId(childNodeId); 1157 AccessibilityNodeInfo child = provider.createAccessibilityNodeInfo( 1158 childVirtualDescendantId); 1159 if (child != null) { 1160 outInfos.add(child); 1161 } 1162 } 1163 } 1164 } 1165 } else { 1166 prefetchSiblingsOfRealNode(providerHost, outInfos); 1167 } 1168 } 1169 1170 private void prefetchDescendantsOfVirtualNode(AccessibilityNodeInfo root, 1171 AccessibilityNodeProvider provider, List<AccessibilityNodeInfo> outInfos) { 1172 final int initialOutInfosSize = outInfos.size(); 1173 final int childCount = root.getChildCount(); 1174 for (int i = 0; i < childCount; i++) { 1175 if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) { 1176 return; 1177 } 1178 final long childNodeId = root.getChildId(i); 1179 AccessibilityNodeInfo child = provider.createAccessibilityNodeInfo( 1180 AccessibilityNodeInfo.getVirtualDescendantId(childNodeId)); 1181 if (child != null) { 1182 outInfos.add(child); 1183 } 1184 } 1185 if (outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) { 1186 final int addedChildCount = outInfos.size() - initialOutInfosSize; 1187 for (int i = 0; i < addedChildCount; i++) { 1188 AccessibilityNodeInfo child = outInfos.get(initialOutInfosSize + i); 1189 prefetchDescendantsOfVirtualNode(child, provider, outInfos); 1190 } 1191 } 1192 } 1193 } 1194 1195 private class PrivateHandler extends Handler { 1196 private final static int MSG_PERFORM_ACCESSIBILITY_ACTION = 1; 1197 private final static int MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_ACCESSIBILITY_ID = 2; 1198 private final static int MSG_FIND_ACCESSIBILITY_NODE_INFOS_BY_VIEW_ID = 3; 1199 private final static int MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_TEXT = 4; 1200 private final static int MSG_FIND_FOCUS = 5; 1201 private final static int MSG_FOCUS_SEARCH = 6; 1202 private final static int MSG_COMPUTE_CLICK_POINT_IN_SCREEN = 7; 1203 1204 public PrivateHandler(Looper looper) { 1205 super(looper); 1206 } 1207 1208 @Override 1209 public String getMessageName(Message message) { 1210 final int type = message.what; 1211 switch (type) { 1212 case MSG_PERFORM_ACCESSIBILITY_ACTION: 1213 return "MSG_PERFORM_ACCESSIBILITY_ACTION"; 1214 case MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_ACCESSIBILITY_ID: 1215 return "MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_ACCESSIBILITY_ID"; 1216 case MSG_FIND_ACCESSIBILITY_NODE_INFOS_BY_VIEW_ID: 1217 return "MSG_FIND_ACCESSIBILITY_NODE_INFOS_BY_VIEW_ID"; 1218 case MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_TEXT: 1219 return "MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_TEXT"; 1220 case MSG_FIND_FOCUS: 1221 return "MSG_FIND_FOCUS"; 1222 case MSG_FOCUS_SEARCH: 1223 return "MSG_FOCUS_SEARCH"; 1224 case MSG_COMPUTE_CLICK_POINT_IN_SCREEN: 1225 return "MSG_COMPUTE_CLICK_POINT_IN_SCREEN"; 1226 default: 1227 throw new IllegalArgumentException("Unknown message type: " + type); 1228 } 1229 } 1230 1231 @Override 1232 public void handleMessage(Message message) { 1233 final int type = message.what; 1234 switch (type) { 1235 case MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_ACCESSIBILITY_ID: { 1236 findAccessibilityNodeInfoByAccessibilityIdUiThread(message); 1237 } break; 1238 case MSG_PERFORM_ACCESSIBILITY_ACTION: { 1239 perfromAccessibilityActionUiThread(message); 1240 } break; 1241 case MSG_FIND_ACCESSIBILITY_NODE_INFOS_BY_VIEW_ID: { 1242 findAccessibilityNodeInfosByViewIdUiThread(message); 1243 } break; 1244 case MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_TEXT: { 1245 findAccessibilityNodeInfosByTextUiThread(message); 1246 } break; 1247 case MSG_FIND_FOCUS: { 1248 findFocusUiThread(message); 1249 } break; 1250 case MSG_FOCUS_SEARCH: { 1251 focusSearchUiThread(message); 1252 } break; 1253 case MSG_COMPUTE_CLICK_POINT_IN_SCREEN: { 1254 computeClickPointInScreenUiThread(message); 1255 } break; 1256 default: 1257 throw new IllegalArgumentException("Unknown message type: " + type); 1258 } 1259 } 1260 } 1261 1262 private final class AddNodeInfosForViewId implements Predicate<View> { 1263 private int mViewId = View.NO_ID; 1264 private List<AccessibilityNodeInfo> mInfos; 1265 1266 public void init(int viewId, List<AccessibilityNodeInfo> infos) { 1267 mViewId = viewId; 1268 mInfos = infos; 1269 } 1270 1271 public void reset() { 1272 mViewId = View.NO_ID; 1273 mInfos = null; 1274 } 1275 1276 @Override 1277 public boolean apply(View view) { 1278 if (view.getId() == mViewId && isShown(view)) { 1279 mInfos.add(view.createAccessibilityNodeInfo()); 1280 } 1281 return false; 1282 } 1283 } 1284 } 1285