1 /* 2 * Copyright (C) 2012 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.view; 18 19 import static android.view.accessibility.AccessibilityNodeInfo.ACTION_ARGUMENT_ACCESSIBLE_CLICKABLE_SPAN; 20 import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY; 21 22 import android.graphics.Point; 23 import android.graphics.Rect; 24 import android.graphics.RectF; 25 import android.graphics.Region; 26 import android.os.Binder; 27 import android.os.Bundle; 28 import android.os.Handler; 29 import android.os.Looper; 30 import android.os.Message; 31 import android.os.Parcelable; 32 import android.os.Process; 33 import android.os.RemoteException; 34 import android.text.style.AccessibilityClickableSpan; 35 import android.text.style.ClickableSpan; 36 import android.util.LongSparseArray; 37 import android.view.View.AttachInfo; 38 import android.view.accessibility.AccessibilityInteractionClient; 39 import android.view.accessibility.AccessibilityNodeInfo; 40 import android.view.accessibility.AccessibilityNodeProvider; 41 import android.view.accessibility.IAccessibilityInteractionConnectionCallback; 42 43 import com.android.internal.R; 44 import com.android.internal.os.SomeArgs; 45 46 import java.util.ArrayList; 47 import java.util.HashMap; 48 import java.util.HashSet; 49 import java.util.LinkedList; 50 import java.util.List; 51 import java.util.Map; 52 import java.util.Queue; 53 import 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 */ 62 final 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 AccessibilityInteractionController(ViewRootImpl viewRootImpl)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 scheduleMessage(Message message, int interrogatingPid, long interrogatingTid)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 isShown(View view)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 findAccessibilityNodeInfoByAccessibilityIdClientThread( long accessibilityNodeId, Region interactiveRegion, int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid, long interrogatingTid, MagnificationSpec spec, Bundle arguments)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 findAccessibilityNodeInfoByAccessibilityIdUiThread(Message message)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 findAccessibilityNodeInfosByViewIdClientThread(long accessibilityNodeId, String viewId, Region interactiveRegion, int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid, long interrogatingTid, MagnificationSpec spec)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 findAccessibilityNodeInfosByViewIdUiThread(Message message)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 findAccessibilityNodeInfosByTextClientThread(long accessibilityNodeId, String text, Region interactiveRegion, int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid, long interrogatingTid, MagnificationSpec spec)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 findAccessibilityNodeInfosByTextUiThread(Message message)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 findFocusClientThread(long accessibilityNodeId, int focusType, Region interactiveRegion, int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid, long interrogatingTid, MagnificationSpec spec)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 findFocusUiThread(Message message)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 focusSearchClientThread(long accessibilityNodeId, int direction, Region interactiveRegion, int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid, long interrogatingTid, MagnificationSpec spec)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 focusSearchUiThread(Message message)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 performAccessibilityActionClientThread(long accessibilityNodeId, int action, Bundle arguments, int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid, long interrogatingTid)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 performAccessibilityActionUiThread(Message message)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 findViewByAccessibilityId(int accessibilityId)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 applyAppScaleAndMagnificationSpecIfNeeded(List<AccessibilityNodeInfo> infos, MagnificationSpec spec)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 adjustIsVisibleToUserIfNeeded(List<AccessibilityNodeInfo> infos, Region interactiveRegion)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 adjustIsVisibleToUserIfNeeded(AccessibilityNodeInfo info, Region interactiveRegion)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 applyAppScaleAndMagnificationSpecIfNeeded(AccessibilityNodeInfo info, MagnificationSpec spec)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 shouldApplyAppScaleAndMagnificationSpec(float appScale, MagnificationSpec spec)691 private boolean shouldApplyAppScaleAndMagnificationSpec(float appScale, 692 MagnificationSpec spec) { 693 return (appScale != 1.0f || (spec != null && !spec.isNop())); 694 } 695 updateInfosForViewportAndReturnFindNodeResult(List<AccessibilityNodeInfo> infos, IAccessibilityInteractionConnectionCallback callback, int interactionId, MagnificationSpec spec, Region interactiveRegion)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 updateInfoForViewportAndReturnFindNodeResult(AccessibilityNodeInfo info, IAccessibilityInteractionConnectionCallback callback, int interactionId, MagnificationSpec spec, Region interactiveRegion)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 recycleMagnificationSpecAndRegionIfNeeded(MagnificationSpec spec, Region region)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 handleClickableSpanActionUiThread( View view, int virtualDescendantId, Bundle arguments)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 prefetchAccessibilityNodeInfos(View view, int virtualViewId, int fetchFlags, List<AccessibilityNodeInfo> outInfos, Bundle arguments)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 enforceNodeTreeConsistent(List<AccessibilityNodeInfo> nodes)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 prefetchPredecessorsOfRealNode(View view, List<AccessibilityNodeInfo> outInfos)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 prefetchSiblingsOfRealNode(View current, List<AccessibilityNodeInfo> outInfos)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 prefetchDescendantsOfRealNode(View root, List<AccessibilityNodeInfo> outInfos)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 prefetchPredecessorsOfVirtualNode(AccessibilityNodeInfo root, View providerHost, AccessibilityNodeProvider provider, List<AccessibilityNodeInfo> outInfos)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 prefetchSiblingsOfVirtualNode(AccessibilityNodeInfo current, View providerHost, AccessibilityNodeProvider provider, List<AccessibilityNodeInfo> outInfos)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 prefetchDescendantsOfVirtualNode(AccessibilityNodeInfo root, AccessibilityNodeProvider provider, List<AccessibilityNodeInfo> outInfos)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 PrivateHandler(Looper looper)1119 public PrivateHandler(Looper looper) { 1120 super(looper); 1121 } 1122 1123 @Override getMessageName(Message message)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 handleMessage(Message message)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 init(int viewId, List<AccessibilityNodeInfo> infos)1176 public void init(int viewId, List<AccessibilityNodeInfo> infos) { 1177 mViewId = viewId; 1178 mInfos = infos; 1179 } 1180 reset()1181 public void reset() { 1182 mViewId = View.NO_ID; 1183 mInfos = null; 1184 } 1185 1186 @Override test(View view)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