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 AccessibilityInteractionController(ViewRootImpl viewRootImpl)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 scheduleMessage(Message message, int interrogatingPid, long interrogatingTid, boolean ignoreRequestPreparers)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 isShown(View view)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 findAccessibilityNodeInfoByAccessibilityIdClientThread( long accessibilityNodeId, Region interactiveRegion, int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid, long interrogatingTid, MagnificationSpec spec, Bundle arguments)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 */ holdOffMessageIfNeeded( Message originalMessage, int callingPid, long callingTid)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 prepareForExtraDataRequestUiThread(Message message)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 queueMessageToHandleOncePrepared(Message message, int interrogatingPid, long interrogatingTid)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 requestPreparerDoneUiThread(Message message)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 requestPreparerTimeoutUiThread()286 private void requestPreparerTimeoutUiThread() { 287 synchronized (mLock) { 288 Slog.e(LOG_TAG, "AccessibilityRequestPreparer timed out"); 289 scheduleAllMessagesWaitingForRequestPreparerLocked(); 290 } 291 } 292 293 @GuardedBy("mLock") scheduleAllMessagesWaitingForRequestPreparerLocked()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 findAccessibilityNodeInfoByAccessibilityIdUiThread(Message message)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 findAccessibilityNodeInfosByViewIdClientThread(long accessibilityNodeId, String viewId, Region interactiveRegion, int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid, long interrogatingTid, MagnificationSpec spec)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 findAccessibilityNodeInfosByViewIdUiThread(Message message)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 findAccessibilityNodeInfosByTextClientThread(long accessibilityNodeId, String text, Region interactiveRegion, int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid, long interrogatingTid, MagnificationSpec spec)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 findAccessibilityNodeInfosByTextUiThread(Message message)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 findFocusClientThread(long accessibilityNodeId, int focusType, Region interactiveRegion, int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid, long interrogatingTid, MagnificationSpec spec)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 findFocusUiThread(Message message)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 focusSearchClientThread(long accessibilityNodeId, int direction, Region interactiveRegion, int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid, long interrogatingTid, MagnificationSpec spec)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 focusSearchUiThread(Message message)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 performAccessibilityActionClientThread(long accessibilityNodeId, int action, Bundle arguments, int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid, long interrogatingTid)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 performAccessibilityActionUiThread(Message message)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 findViewByAccessibilityId(int accessibilityId)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 applyAppScaleAndMagnificationSpecIfNeeded(List<AccessibilityNodeInfo> infos, MagnificationSpec spec)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 adjustIsVisibleToUserIfNeeded(List<AccessibilityNodeInfo> infos, Region interactiveRegion)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 adjustIsVisibleToUserIfNeeded(AccessibilityNodeInfo info, Region interactiveRegion)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 applyAppScaleAndMagnificationSpecIfNeeded(AccessibilityNodeInfo info, MagnificationSpec spec)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 shouldApplyAppScaleAndMagnificationSpec(float appScale, MagnificationSpec spec)857 private boolean shouldApplyAppScaleAndMagnificationSpec(float appScale, 858 MagnificationSpec spec) { 859 return (appScale != 1.0f || (spec != null && !spec.isNop())); 860 } 861 updateInfosForViewportAndReturnFindNodeResult(List<AccessibilityNodeInfo> infos, IAccessibilityInteractionConnectionCallback callback, int interactionId, MagnificationSpec spec, Region interactiveRegion)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 updateInfoForViewportAndReturnFindNodeResult(AccessibilityNodeInfo info, IAccessibilityInteractionConnectionCallback callback, int interactionId, MagnificationSpec spec, Region interactiveRegion)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 recycleMagnificationSpecAndRegionIfNeeded(MagnificationSpec spec, Region region)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 handleClickableSpanActionUiThread( View view, int virtualDescendantId, Bundle arguments)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 prefetchAccessibilityNodeInfos(View view, int virtualViewId, int fetchFlags, List<AccessibilityNodeInfo> outInfos, Bundle arguments)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 enforceNodeTreeConsistent(List<AccessibilityNodeInfo> nodes)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 prefetchPredecessorsOfRealNode(View view, List<AccessibilityNodeInfo> outInfos)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 prefetchSiblingsOfRealNode(View current, List<AccessibilityNodeInfo> outInfos)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 prefetchDescendantsOfRealNode(View root, List<AccessibilityNodeInfo> outInfos)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 prefetchPredecessorsOfVirtualNode(AccessibilityNodeInfo root, View providerHost, AccessibilityNodeProvider provider, List<AccessibilityNodeInfo> outInfos)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 prefetchSiblingsOfVirtualNode(AccessibilityNodeInfo current, View providerHost, AccessibilityNodeProvider provider, List<AccessibilityNodeInfo> outInfos)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 prefetchDescendantsOfVirtualNode(AccessibilityNodeInfo root, AccessibilityNodeProvider provider, List<AccessibilityNodeInfo> outInfos)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 PrivateHandler(Looper looper)1288 public PrivateHandler(Looper looper) { 1289 super(looper); 1290 } 1291 1292 @Override getMessageName(Message message)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 handleMessage(Message message)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 init(int viewId, List<AccessibilityNodeInfo> infos)1360 public void init(int viewId, List<AccessibilityNodeInfo> infos) { 1361 mViewId = viewId; 1362 mInfos = infos; 1363 } 1364 reset()1365 public void reset() { 1366 mViewId = View.NO_ID; 1367 mInfos = null; 1368 } 1369 1370 @Override test(View view)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 MessageHolder(Message message, int interrogatingPid, long interrogatingTid)1384 MessageHolder(Message message, int interrogatingPid, long interrogatingTid) { 1385 mMessage = message; 1386 mInterrogatingPid = interrogatingPid; 1387 mInterrogatingTid = interrogatingTid; 1388 } 1389 } 1390 } 1391