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