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.WindowManager.LayoutParams.TYPE_INPUT_METHOD; 20 import static android.view.accessibility.AccessibilityNodeInfo.ACTION_ARGUMENT_ACCESSIBLE_CLICKABLE_SPAN; 21 import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_REQUESTED_KEY; 22 import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY; 23 24 import android.graphics.Matrix; 25 import android.graphics.Rect; 26 import android.graphics.RectF; 27 import android.graphics.Region; 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.os.SystemClock; 36 import android.text.style.AccessibilityClickableSpan; 37 import android.text.style.ClickableSpan; 38 import android.util.LongSparseArray; 39 import android.util.Slog; 40 import android.view.accessibility.AccessibilityInteractionClient; 41 import android.view.accessibility.AccessibilityManager; 42 import android.view.accessibility.AccessibilityNodeIdManager; 43 import android.view.accessibility.AccessibilityNodeInfo; 44 import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; 45 import android.view.accessibility.AccessibilityNodeProvider; 46 import android.view.accessibility.AccessibilityRequestPreparer; 47 import android.view.accessibility.IAccessibilityInteractionConnectionCallback; 48 49 import com.android.internal.R; 50 import com.android.internal.annotations.GuardedBy; 51 import com.android.internal.annotations.VisibleForTesting; 52 import com.android.internal.os.SomeArgs; 53 54 import java.util.ArrayDeque; 55 import java.util.ArrayList; 56 import java.util.HashSet; 57 import java.util.LinkedHashMap; 58 import java.util.LinkedList; 59 import java.util.List; 60 import java.util.Map; 61 import java.util.Queue; 62 import java.util.function.Predicate; 63 64 /** 65 * Class for managing accessibility interactions initiated from the system 66 * and targeting the view hierarchy. A *ClientThread method is to be 67 * called from the interaction connection ViewAncestor gives the system to 68 * talk to it and a corresponding *UiThread method that is executed on the 69 * UI thread. 70 * 71 * @hide 72 */ 73 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) 74 public final class AccessibilityInteractionController { 75 76 private static final String LOG_TAG = "AccessibilityInteractionController"; 77 78 // Debugging flag 79 private static final boolean ENFORCE_NODE_TREE_CONSISTENT = false; 80 81 // Constants for readability 82 private static final boolean IGNORE_REQUEST_PREPARERS = true; 83 private static final boolean CONSIDER_REQUEST_PREPARERS = false; 84 85 // If an app holds off accessibility for longer than this, the hold-off is canceled to prevent 86 // accessibility from hanging 87 private static final long REQUEST_PREPARER_TIMEOUT_MS = 500; 88 89 // Callbacks should have the same configuration of the flags below to allow satisfying a pending 90 // node request on prefetch 91 private static final int FLAGS_AFFECTING_REPORTED_DATA = 92 AccessibilityNodeInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS 93 | AccessibilityNodeInfo.FLAG_REPORT_VIEW_IDS; 94 95 private final ArrayList<AccessibilityNodeInfo> mTempAccessibilityNodeInfoList = 96 new ArrayList<AccessibilityNodeInfo>(); 97 98 private final Object mLock = new Object(); 99 100 private final PrivateHandler mHandler; 101 102 private final ViewRootImpl mViewRootImpl; 103 104 private final AccessibilityNodePrefetcher mPrefetcher; 105 106 private final long mMyLooperThreadId; 107 108 private final int mMyProcessId; 109 110 private final AccessibilityManager mA11yManager; 111 112 private final ArrayList<View> mTempArrayList = new ArrayList<View>(); 113 114 private final Rect mTempRect = new Rect(); 115 private final RectF mTempRectF = new RectF(); 116 117 private AddNodeInfosForViewId mAddNodeInfosForViewId; 118 119 @GuardedBy("mLock") 120 private ArrayList<Message> mPendingFindNodeByIdMessages; 121 122 @GuardedBy("mLock") 123 private int mNumActiveRequestPreparers; 124 @GuardedBy("mLock") 125 private List<MessageHolder> mMessagesWaitingForRequestPreparer; 126 @GuardedBy("mLock") 127 private int mActiveRequestPreparerId; 128 AccessibilityInteractionController(ViewRootImpl viewRootImpl)129 public AccessibilityInteractionController(ViewRootImpl viewRootImpl) { 130 Looper looper = viewRootImpl.mHandler.getLooper(); 131 mMyLooperThreadId = looper.getThread().getId(); 132 mMyProcessId = Process.myPid(); 133 mHandler = new PrivateHandler(looper); 134 mViewRootImpl = viewRootImpl; 135 mPrefetcher = new AccessibilityNodePrefetcher(); 136 mA11yManager = mViewRootImpl.mContext.getSystemService(AccessibilityManager.class); 137 mPendingFindNodeByIdMessages = new ArrayList<>(); 138 } 139 scheduleMessage(Message message, int interrogatingPid, long interrogatingTid, boolean ignoreRequestPreparers)140 private void scheduleMessage(Message message, int interrogatingPid, long interrogatingTid, 141 boolean ignoreRequestPreparers) { 142 if (ignoreRequestPreparers 143 || !holdOffMessageIfNeeded(message, interrogatingPid, interrogatingTid)) { 144 // If the interrogation is performed by the same thread as the main UI 145 // thread in this process, set the message as a static reference so 146 // after this call completes the same thread but in the interrogating 147 // client can handle the message to generate the result. 148 if (interrogatingPid == mMyProcessId && interrogatingTid == mMyLooperThreadId 149 && mHandler.hasAccessibilityCallback(message)) { 150 AccessibilityInteractionClient.getInstanceForThread( 151 interrogatingTid).setSameThreadMessage(message); 152 } else { 153 // For messages without callback of interrogating client, just handle the 154 // message immediately if this is UI thread. 155 if (!mHandler.hasAccessibilityCallback(message) 156 && Thread.currentThread().getId() == mMyLooperThreadId) { 157 mHandler.handleMessage(message); 158 } else { 159 mHandler.sendMessage(message); 160 } 161 } 162 } 163 } 164 isShown(View view)165 private boolean isShown(View view) { 166 return (view != null) && (view.getWindowVisibility() == View.VISIBLE && view.isShown()); 167 } 168 findAccessibilityNodeInfoByAccessibilityIdClientThread( long accessibilityNodeId, Region interactiveRegion, int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid, long interrogatingTid, MagnificationSpec spec, float[] matrixValues, Bundle arguments)169 public void findAccessibilityNodeInfoByAccessibilityIdClientThread( 170 long accessibilityNodeId, Region interactiveRegion, int interactionId, 171 IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid, 172 long interrogatingTid, MagnificationSpec spec, float[] matrixValues, 173 Bundle arguments) { 174 final Message message = mHandler.obtainMessage(); 175 message.what = PrivateHandler.MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_ACCESSIBILITY_ID; 176 message.arg1 = flags; 177 178 final SomeArgs args = SomeArgs.obtain(); 179 args.argi1 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId); 180 args.argi2 = AccessibilityNodeInfo.getVirtualDescendantId(accessibilityNodeId); 181 args.argi3 = interactionId; 182 args.arg1 = callback; 183 args.arg2 = spec; 184 args.arg3 = interactiveRegion; 185 args.arg4 = arguments; 186 args.arg5 = matrixValues; 187 message.obj = args; 188 189 synchronized (mLock) { 190 mPendingFindNodeByIdMessages.add(message); 191 scheduleMessage(message, interrogatingPid, interrogatingTid, 192 CONSIDER_REQUEST_PREPARERS); 193 } 194 } 195 196 /** 197 * Check if this message needs to be held off while the app prepares to meet either this 198 * request, or a request ahead of it. 199 * 200 * @param originalMessage The message to be processed 201 * @param callingPid The calling process id 202 * @param callingTid The calling thread id 203 * 204 * @return {@code true} if the message is held off and will be processed later, {@code false} if 205 * the message should be posted. 206 */ holdOffMessageIfNeeded( Message originalMessage, int callingPid, long callingTid)207 private boolean holdOffMessageIfNeeded( 208 Message originalMessage, int callingPid, long callingTid) { 209 synchronized (mLock) { 210 // If a request is already pending, queue this request for when it's finished 211 if (mNumActiveRequestPreparers != 0) { 212 queueMessageToHandleOncePrepared(originalMessage, callingPid, callingTid); 213 return true; 214 } 215 216 // Currently the only message that can hold things off is findByA11yId with extra data. 217 if (originalMessage.what 218 != PrivateHandler.MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_ACCESSIBILITY_ID) { 219 return false; 220 } 221 SomeArgs originalMessageArgs = (SomeArgs) originalMessage.obj; 222 Bundle requestArguments = (Bundle) originalMessageArgs.arg4; 223 if (requestArguments == null) { 224 return false; 225 } 226 227 // If nothing it registered for this view, nothing to do 228 int accessibilityViewId = originalMessageArgs.argi1; 229 final List<AccessibilityRequestPreparer> preparers = 230 mA11yManager.getRequestPreparersForAccessibilityId(accessibilityViewId); 231 if (preparers == null) { 232 return false; 233 } 234 235 // If the bundle doesn't request the extra data, nothing to do 236 final String extraDataKey = requestArguments.getString(EXTRA_DATA_REQUESTED_KEY); 237 if (extraDataKey == null) { 238 return false; 239 } 240 241 // Send the request to the AccessibilityRequestPreparers on the UI thread 242 mNumActiveRequestPreparers = preparers.size(); 243 for (int i = 0; i < preparers.size(); i++) { 244 final Message requestPreparerMessage = mHandler.obtainMessage( 245 PrivateHandler.MSG_PREPARE_FOR_EXTRA_DATA_REQUEST); 246 final SomeArgs requestPreparerArgs = SomeArgs.obtain(); 247 // virtualDescendentId 248 requestPreparerArgs.argi1 = 249 (originalMessageArgs.argi2 == AccessibilityNodeInfo.UNDEFINED_ITEM_ID) 250 ? AccessibilityNodeProvider.HOST_VIEW_ID : originalMessageArgs.argi2; 251 requestPreparerArgs.arg1 = preparers.get(i); 252 requestPreparerArgs.arg2 = extraDataKey; 253 requestPreparerArgs.arg3 = requestArguments; 254 Message preparationFinishedMessage = mHandler.obtainMessage( 255 PrivateHandler.MSG_APP_PREPARATION_FINISHED); 256 preparationFinishedMessage.arg1 = ++mActiveRequestPreparerId; 257 requestPreparerArgs.arg4 = preparationFinishedMessage; 258 259 requestPreparerMessage.obj = requestPreparerArgs; 260 scheduleMessage(requestPreparerMessage, callingPid, callingTid, 261 IGNORE_REQUEST_PREPARERS); 262 mHandler.obtainMessage(PrivateHandler.MSG_APP_PREPARATION_TIMEOUT); 263 mHandler.sendEmptyMessageDelayed(PrivateHandler.MSG_APP_PREPARATION_TIMEOUT, 264 REQUEST_PREPARER_TIMEOUT_MS); 265 } 266 267 // Set the initial request aside 268 queueMessageToHandleOncePrepared(originalMessage, callingPid, callingTid); 269 return true; 270 } 271 } 272 prepareForExtraDataRequestUiThread(Message message)273 private void prepareForExtraDataRequestUiThread(Message message) { 274 SomeArgs args = (SomeArgs) message.obj; 275 final int virtualDescendantId = args.argi1; 276 final AccessibilityRequestPreparer preparer = (AccessibilityRequestPreparer) args.arg1; 277 final String extraDataKey = (String) args.arg2; 278 final Bundle requestArguments = (Bundle) args.arg3; 279 final Message preparationFinishedMessage = (Message) args.arg4; 280 281 preparer.onPrepareExtraData(virtualDescendantId, extraDataKey, 282 requestArguments, preparationFinishedMessage); 283 } 284 queueMessageToHandleOncePrepared(Message message, int interrogatingPid, long interrogatingTid)285 private void queueMessageToHandleOncePrepared(Message message, int interrogatingPid, 286 long interrogatingTid) { 287 if (mMessagesWaitingForRequestPreparer == null) { 288 mMessagesWaitingForRequestPreparer = new ArrayList<>(1); 289 } 290 MessageHolder messageHolder = 291 new MessageHolder(message, interrogatingPid, interrogatingTid); 292 mMessagesWaitingForRequestPreparer.add(messageHolder); 293 } 294 requestPreparerDoneUiThread(Message message)295 private void requestPreparerDoneUiThread(Message message) { 296 synchronized (mLock) { 297 if (message.arg1 != mActiveRequestPreparerId) { 298 Slog.e(LOG_TAG, "Surprising AccessibilityRequestPreparer callback (likely late)"); 299 return; 300 } 301 mNumActiveRequestPreparers--; 302 if (mNumActiveRequestPreparers <= 0) { 303 mHandler.removeMessages(PrivateHandler.MSG_APP_PREPARATION_TIMEOUT); 304 scheduleAllMessagesWaitingForRequestPreparerLocked(); 305 } 306 } 307 } 308 requestPreparerTimeoutUiThread()309 private void requestPreparerTimeoutUiThread() { 310 synchronized (mLock) { 311 Slog.e(LOG_TAG, "AccessibilityRequestPreparer timed out"); 312 scheduleAllMessagesWaitingForRequestPreparerLocked(); 313 } 314 } 315 316 @GuardedBy("mLock") scheduleAllMessagesWaitingForRequestPreparerLocked()317 private void scheduleAllMessagesWaitingForRequestPreparerLocked() { 318 int numMessages = mMessagesWaitingForRequestPreparer.size(); 319 for (int i = 0; i < numMessages; i++) { 320 MessageHolder request = mMessagesWaitingForRequestPreparer.get(i); 321 scheduleMessage(request.mMessage, request.mInterrogatingPid, 322 request.mInterrogatingTid, 323 (i == 0) /* the app is ready for the first request */); 324 } 325 mMessagesWaitingForRequestPreparer.clear(); 326 mNumActiveRequestPreparers = 0; // Just to be safe - should be unnecessary 327 mActiveRequestPreparerId = -1; 328 } 329 findAccessibilityNodeInfoByAccessibilityIdUiThread(Message message)330 private void findAccessibilityNodeInfoByAccessibilityIdUiThread(Message message) { 331 synchronized (mLock) { 332 mPendingFindNodeByIdMessages.remove(message); 333 } 334 final int flags = message.arg1; 335 336 SomeArgs args = (SomeArgs) message.obj; 337 final int accessibilityViewId = args.argi1; 338 final int virtualDescendantId = args.argi2; 339 final int interactionId = args.argi3; 340 final IAccessibilityInteractionConnectionCallback callback = 341 (IAccessibilityInteractionConnectionCallback) args.arg1; 342 final MagnificationSpec spec = (MagnificationSpec) args.arg2; 343 final Region interactiveRegion = (Region) args.arg3; 344 final Bundle arguments = (Bundle) args.arg4; 345 final float[] matrixValues = (float[]) args.arg5; 346 347 args.recycle(); 348 349 View requestedView = null; 350 AccessibilityNodeInfo requestedNode = null; 351 boolean interruptPrefetch = 352 ((flags & AccessibilityNodeInfo.FLAG_PREFETCH_UNINTERRUPTIBLE) == 0); 353 354 ArrayList<AccessibilityNodeInfo> infos = mTempAccessibilityNodeInfoList; 355 infos.clear(); 356 try { 357 if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) { 358 return; 359 } 360 mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags; 361 requestedView = findViewByAccessibilityId(accessibilityViewId); 362 if (requestedView != null && isShown(requestedView)) { 363 requestedNode = populateAccessibilityNodeInfoForView( 364 requestedView, arguments, virtualDescendantId); 365 mPrefetcher.mInterruptPrefetch = interruptPrefetch; 366 mPrefetcher.mFetchFlags = flags & AccessibilityNodeInfo.FLAG_PREFETCH_MASK; 367 368 if (!interruptPrefetch) { 369 infos.add(requestedNode); 370 mPrefetcher.prefetchAccessibilityNodeInfos(requestedView, 371 requestedNode == null ? null : new AccessibilityNodeInfo(requestedNode), 372 infos); 373 mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0; 374 } 375 } 376 } finally { 377 if (!interruptPrefetch) { 378 // Return found node and prefetched nodes in one IPC. 379 updateInfosForViewportAndReturnFindNodeResult(infos, callback, interactionId, spec, 380 matrixValues, interactiveRegion); 381 382 final SatisfiedFindAccessibilityNodeByAccessibilityIdRequest satisfiedRequest = 383 getSatisfiedRequestInPrefetch(requestedNode == null ? null : requestedNode, 384 infos, flags); 385 if (satisfiedRequest != null) { 386 returnFindNodeResult(satisfiedRequest); 387 } 388 return; 389 } else { 390 // Return found node. 391 updateInfoForViewportAndReturnFindNodeResult( 392 requestedNode == null ? null : new AccessibilityNodeInfo(requestedNode), 393 callback, interactionId, spec, matrixValues, interactiveRegion); 394 } 395 } 396 mPrefetcher.prefetchAccessibilityNodeInfos(requestedView, 397 requestedNode == null ? null : new AccessibilityNodeInfo(requestedNode), infos); 398 mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0; 399 updateInfosForViewPort(infos, spec, matrixValues, interactiveRegion); 400 final SatisfiedFindAccessibilityNodeByAccessibilityIdRequest satisfiedRequest = 401 getSatisfiedRequestInPrefetch(requestedNode == null ? null : requestedNode, infos, 402 flags); 403 404 // Return prefetch result separately. 405 returnPrefetchResult(interactionId, infos, callback); 406 407 if (satisfiedRequest != null) { 408 returnFindNodeResult(satisfiedRequest); 409 } 410 } 411 populateAccessibilityNodeInfoForView( View view, Bundle arguments, int virtualViewId)412 private AccessibilityNodeInfo populateAccessibilityNodeInfoForView( 413 View view, Bundle arguments, int virtualViewId) { 414 AccessibilityNodeProvider provider = view.getAccessibilityNodeProvider(); 415 // Determine if we'll be populating extra data 416 final String extraDataRequested = (arguments == null) ? null 417 : arguments.getString(EXTRA_DATA_REQUESTED_KEY); 418 AccessibilityNodeInfo root = null; 419 if (provider == null) { 420 root = view.createAccessibilityNodeInfo(); 421 if (root != null) { 422 if (extraDataRequested != null) { 423 view.addExtraDataToAccessibilityNodeInfo(root, extraDataRequested, arguments); 424 } 425 } 426 } else { 427 root = provider.createAccessibilityNodeInfo(virtualViewId); 428 if (root != null) { 429 if (extraDataRequested != null) { 430 provider.addExtraDataToAccessibilityNodeInfo( 431 virtualViewId, root, extraDataRequested, arguments); 432 } 433 } 434 } 435 return root; 436 } 437 findAccessibilityNodeInfosByViewIdClientThread(long accessibilityNodeId, String viewId, Region interactiveRegion, int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid, long interrogatingTid, MagnificationSpec spec, float[] matrixValues)438 public void findAccessibilityNodeInfosByViewIdClientThread(long accessibilityNodeId, 439 String viewId, Region interactiveRegion, int interactionId, 440 IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid, 441 long interrogatingTid, MagnificationSpec spec, float[] matrixValues) { 442 Message message = mHandler.obtainMessage(); 443 message.what = PrivateHandler.MSG_FIND_ACCESSIBILITY_NODE_INFOS_BY_VIEW_ID; 444 message.arg1 = flags; 445 message.arg2 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId); 446 447 SomeArgs args = SomeArgs.obtain(); 448 args.argi1 = interactionId; 449 args.arg1 = callback; 450 args.arg2 = spec; 451 args.arg3 = viewId; 452 args.arg4 = interactiveRegion; 453 args.arg5 = matrixValues; 454 message.obj = args; 455 456 scheduleMessage(message, interrogatingPid, interrogatingTid, CONSIDER_REQUEST_PREPARERS); 457 } 458 findAccessibilityNodeInfosByViewIdUiThread(Message message)459 private void findAccessibilityNodeInfosByViewIdUiThread(Message message) { 460 final int flags = message.arg1; 461 final int accessibilityViewId = message.arg2; 462 463 SomeArgs args = (SomeArgs) message.obj; 464 final int interactionId = args.argi1; 465 final IAccessibilityInteractionConnectionCallback callback = 466 (IAccessibilityInteractionConnectionCallback) args.arg1; 467 final MagnificationSpec spec = (MagnificationSpec) args.arg2; 468 final String viewId = (String) args.arg3; 469 final Region interactiveRegion = (Region) args.arg4; 470 final float[] matrixValues = (float[]) args.arg5; 471 args.recycle(); 472 473 final List<AccessibilityNodeInfo> infos = mTempAccessibilityNodeInfoList; 474 infos.clear(); 475 try { 476 if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null 477 || viewId == null) { 478 return; 479 } 480 mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags; 481 final View root = findViewByAccessibilityId(accessibilityViewId); 482 if (root != null) { 483 final int resolvedViewId = root.getContext().getResources() 484 .getIdentifier(viewId, null, null); 485 if (resolvedViewId <= 0) { 486 return; 487 } 488 if (mAddNodeInfosForViewId == null) { 489 mAddNodeInfosForViewId = new AddNodeInfosForViewId(); 490 } 491 mAddNodeInfosForViewId.init(resolvedViewId, infos); 492 root.findViewByPredicate(mAddNodeInfosForViewId); 493 mAddNodeInfosForViewId.reset(); 494 } 495 } finally { 496 mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0; 497 updateInfosForViewportAndReturnFindNodeResult( 498 infos, callback, interactionId, spec, matrixValues, interactiveRegion); 499 } 500 } 501 findAccessibilityNodeInfosByTextClientThread(long accessibilityNodeId, String text, Region interactiveRegion, int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid, long interrogatingTid, MagnificationSpec spec, float[] matrixValues)502 public void findAccessibilityNodeInfosByTextClientThread(long accessibilityNodeId, 503 String text, Region interactiveRegion, int interactionId, 504 IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid, 505 long interrogatingTid, MagnificationSpec spec, float[] matrixValues) { 506 Message message = mHandler.obtainMessage(); 507 message.what = PrivateHandler.MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_TEXT; 508 message.arg1 = flags; 509 510 SomeArgs args = SomeArgs.obtain(); 511 args.arg1 = text; 512 args.arg2 = callback; 513 args.arg3 = spec; 514 args.argi1 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId); 515 args.argi2 = AccessibilityNodeInfo.getVirtualDescendantId(accessibilityNodeId); 516 args.argi3 = interactionId; 517 args.arg4 = interactiveRegion; 518 args.arg5 = matrixValues; 519 message.obj = args; 520 521 scheduleMessage(message, interrogatingPid, interrogatingTid, CONSIDER_REQUEST_PREPARERS); 522 } 523 findAccessibilityNodeInfosByTextUiThread(Message message)524 private void findAccessibilityNodeInfosByTextUiThread(Message message) { 525 final int flags = message.arg1; 526 527 SomeArgs args = (SomeArgs) message.obj; 528 final String text = (String) args.arg1; 529 final IAccessibilityInteractionConnectionCallback callback = 530 (IAccessibilityInteractionConnectionCallback) args.arg2; 531 final MagnificationSpec spec = (MagnificationSpec) args.arg3; 532 final int accessibilityViewId = args.argi1; 533 final int virtualDescendantId = args.argi2; 534 final int interactionId = args.argi3; 535 final Region interactiveRegion = (Region) args.arg4; 536 final float[] matrixValues = (float[]) args.arg5; 537 args.recycle(); 538 539 List<AccessibilityNodeInfo> infos = null; 540 try { 541 if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) { 542 return; 543 } 544 mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags; 545 final View root = findViewByAccessibilityId(accessibilityViewId); 546 if (root != null && isShown(root)) { 547 AccessibilityNodeProvider provider = root.getAccessibilityNodeProvider(); 548 if (provider != null) { 549 infos = provider.findAccessibilityNodeInfosByText(text, 550 virtualDescendantId); 551 } else if (virtualDescendantId == AccessibilityNodeProvider.HOST_VIEW_ID) { 552 ArrayList<View> foundViews = mTempArrayList; 553 foundViews.clear(); 554 root.findViewsWithText(foundViews, text, View.FIND_VIEWS_WITH_TEXT 555 | View.FIND_VIEWS_WITH_CONTENT_DESCRIPTION 556 | View.FIND_VIEWS_WITH_ACCESSIBILITY_NODE_PROVIDERS); 557 if (!foundViews.isEmpty()) { 558 infos = mTempAccessibilityNodeInfoList; 559 infos.clear(); 560 final int viewCount = foundViews.size(); 561 for (int i = 0; i < viewCount; i++) { 562 View foundView = foundViews.get(i); 563 if (isShown(foundView)) { 564 provider = foundView.getAccessibilityNodeProvider(); 565 if (provider != null) { 566 List<AccessibilityNodeInfo> infosFromProvider = 567 provider.findAccessibilityNodeInfosByText(text, 568 AccessibilityNodeProvider.HOST_VIEW_ID); 569 if (infosFromProvider != null) { 570 infos.addAll(infosFromProvider); 571 } 572 } else { 573 infos.add(foundView.createAccessibilityNodeInfo()); 574 } 575 } 576 } 577 } 578 } 579 } 580 } finally { 581 mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0; 582 updateInfosForViewportAndReturnFindNodeResult( 583 infos, callback, interactionId, spec, matrixValues, interactiveRegion); 584 } 585 } 586 findFocusClientThread(long accessibilityNodeId, int focusType, Region interactiveRegion, int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid, long interrogatingTid, MagnificationSpec spec, float[] matrixValues)587 public void findFocusClientThread(long accessibilityNodeId, int focusType, 588 Region interactiveRegion, int interactionId, 589 IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid, 590 long interrogatingTid, MagnificationSpec spec, float[] matrixValues) { 591 Message message = mHandler.obtainMessage(); 592 message.what = PrivateHandler.MSG_FIND_FOCUS; 593 message.arg1 = flags; 594 message.arg2 = focusType; 595 596 SomeArgs args = SomeArgs.obtain(); 597 args.argi1 = interactionId; 598 args.argi2 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId); 599 args.argi3 = AccessibilityNodeInfo.getVirtualDescendantId(accessibilityNodeId); 600 args.arg1 = callback; 601 args.arg2 = spec; 602 args.arg3 = interactiveRegion; 603 args.arg4 = matrixValues; 604 message.obj = args; 605 606 scheduleMessage(message, interrogatingPid, interrogatingTid, CONSIDER_REQUEST_PREPARERS); 607 } 608 findFocusUiThread(Message message)609 private void findFocusUiThread(Message message) { 610 final int flags = message.arg1; 611 final int focusType = message.arg2; 612 613 SomeArgs args = (SomeArgs) message.obj; 614 final int interactionId = args.argi1; 615 final int accessibilityViewId = args.argi2; 616 final int virtualDescendantId = args.argi3; 617 final IAccessibilityInteractionConnectionCallback callback = 618 (IAccessibilityInteractionConnectionCallback) args.arg1; 619 final MagnificationSpec spec = (MagnificationSpec) args.arg2; 620 final Region interactiveRegion = (Region) args.arg3; 621 final float[] matrixValues = (float[]) args.arg4; 622 args.recycle(); 623 624 AccessibilityNodeInfo focused = null; 625 try { 626 if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) { 627 return; 628 } 629 mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags; 630 final View root = findViewByAccessibilityId(accessibilityViewId); 631 if (root != null && isShown(root)) { 632 switch (focusType) { 633 case AccessibilityNodeInfo.FOCUS_ACCESSIBILITY: { 634 View host = mViewRootImpl.mAccessibilityFocusedHost; 635 // If there is no accessibility focus host or it is not a descendant 636 // of the root from which to start the search, then the search failed. 637 if (host == null || !ViewRootImpl.isViewDescendantOf(host, root)) { 638 break; 639 } 640 // The focused view not shown, we failed. 641 if (!isShown(host)) { 642 break; 643 } 644 // If the host has a provider ask this provider to search for the 645 // focus instead fetching all provider nodes to do the search here. 646 AccessibilityNodeProvider provider = host.getAccessibilityNodeProvider(); 647 if (provider != null) { 648 final AccessibilityNodeInfo focusNode = 649 mViewRootImpl.mAccessibilityFocusedVirtualView; 650 if (focusNode != null) { 651 final int virtualNodeId = AccessibilityNodeInfo 652 .getVirtualDescendantId(focusNode.getSourceNodeId()); 653 focused = provider.createAccessibilityNodeInfo(virtualNodeId); 654 } 655 } else if (virtualDescendantId == AccessibilityNodeProvider.HOST_VIEW_ID) { 656 focused = host.createAccessibilityNodeInfo(); 657 } 658 } break; 659 case AccessibilityNodeInfo.FOCUS_INPUT: { 660 View target = root.findFocus(); 661 if (!isShown(target)) { 662 break; 663 } 664 AccessibilityNodeProvider provider = target.getAccessibilityNodeProvider(); 665 if (provider != null) { 666 focused = provider.findFocus(focusType); 667 } 668 if (focused == null) { 669 focused = target.createAccessibilityNodeInfo(); 670 } 671 } break; 672 default: 673 throw new IllegalArgumentException("Unknown focus type: " + focusType); 674 } 675 } 676 } finally { 677 mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0; 678 updateInfoForViewportAndReturnFindNodeResult( 679 focused, callback, interactionId, spec, matrixValues, interactiveRegion); 680 } 681 } 682 focusSearchClientThread(long accessibilityNodeId, int direction, Region interactiveRegion, int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid, long interrogatingTid, MagnificationSpec spec, float[] matrixValues)683 public void focusSearchClientThread(long accessibilityNodeId, int direction, 684 Region interactiveRegion, int interactionId, 685 IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid, 686 long interrogatingTid, MagnificationSpec spec, float[] matrixValues) { 687 Message message = mHandler.obtainMessage(); 688 message.what = PrivateHandler.MSG_FOCUS_SEARCH; 689 message.arg1 = flags; 690 message.arg2 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId); 691 692 SomeArgs args = SomeArgs.obtain(); 693 args.argi2 = direction; 694 args.argi3 = interactionId; 695 args.arg1 = callback; 696 args.arg2 = spec; 697 args.arg3 = interactiveRegion; 698 args.arg4 = matrixValues; 699 700 message.obj = args; 701 702 scheduleMessage(message, interrogatingPid, interrogatingTid, CONSIDER_REQUEST_PREPARERS); 703 } 704 focusSearchUiThread(Message message)705 private void focusSearchUiThread(Message message) { 706 final int flags = message.arg1; 707 final int accessibilityViewId = message.arg2; 708 709 SomeArgs args = (SomeArgs) message.obj; 710 final int direction = args.argi2; 711 final int interactionId = args.argi3; 712 final IAccessibilityInteractionConnectionCallback callback = 713 (IAccessibilityInteractionConnectionCallback) args.arg1; 714 final MagnificationSpec spec = (MagnificationSpec) args.arg2; 715 final Region interactiveRegion = (Region) args.arg3; 716 final float[] matrixValues = (float[]) args.arg4; 717 args.recycle(); 718 719 AccessibilityNodeInfo next = null; 720 try { 721 if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) { 722 return; 723 } 724 mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags; 725 final View root = findViewByAccessibilityId(accessibilityViewId); 726 if (root != null && isShown(root)) { 727 View nextView = root.focusSearch(direction); 728 if (nextView != null) { 729 next = nextView.createAccessibilityNodeInfo(); 730 } 731 } 732 } finally { 733 mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0; 734 updateInfoForViewportAndReturnFindNodeResult( 735 next, callback, interactionId, spec, matrixValues, interactiveRegion); 736 } 737 } 738 performAccessibilityActionClientThread(long accessibilityNodeId, int action, Bundle arguments, int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid, long interrogatingTid)739 public void performAccessibilityActionClientThread(long accessibilityNodeId, int action, 740 Bundle arguments, int interactionId, 741 IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid, 742 long interrogatingTid) { 743 Message message = mHandler.obtainMessage(); 744 message.what = PrivateHandler.MSG_PERFORM_ACCESSIBILITY_ACTION; 745 message.arg1 = flags; 746 message.arg2 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId); 747 748 SomeArgs args = SomeArgs.obtain(); 749 args.argi1 = AccessibilityNodeInfo.getVirtualDescendantId(accessibilityNodeId); 750 args.argi2 = action; 751 args.argi3 = interactionId; 752 args.arg1 = callback; 753 args.arg2 = arguments; 754 755 message.obj = args; 756 757 scheduleMessage(message, interrogatingPid, interrogatingTid, CONSIDER_REQUEST_PREPARERS); 758 } 759 performAccessibilityActionUiThread(Message message)760 private void performAccessibilityActionUiThread(Message message) { 761 final int flags = message.arg1; 762 final int accessibilityViewId = message.arg2; 763 764 SomeArgs args = (SomeArgs) message.obj; 765 final int virtualDescendantId = args.argi1; 766 final int action = args.argi2; 767 final int interactionId = args.argi3; 768 final IAccessibilityInteractionConnectionCallback callback = 769 (IAccessibilityInteractionConnectionCallback) args.arg1; 770 Bundle arguments = (Bundle) args.arg2; 771 772 args.recycle(); 773 774 boolean succeeded = false; 775 try { 776 if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null || 777 mViewRootImpl.mStopped || mViewRootImpl.mPausedForTransition) { 778 return; 779 } 780 mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags; 781 final View target = findViewByAccessibilityId(accessibilityViewId); 782 if (target != null && isShown(target)) { 783 mA11yManager.notifyPerformingAction(action); 784 if (action == R.id.accessibilityActionClickOnClickableSpan) { 785 // Handle this hidden action separately 786 succeeded = handleClickableSpanActionUiThread( 787 target, virtualDescendantId, arguments); 788 } else { 789 AccessibilityNodeProvider provider = target.getAccessibilityNodeProvider(); 790 if (provider != null) { 791 succeeded = provider.performAction(virtualDescendantId, action, 792 arguments); 793 } else if (virtualDescendantId == AccessibilityNodeProvider.HOST_VIEW_ID) { 794 succeeded = target.performAccessibilityAction(action, arguments); 795 } 796 } 797 mA11yManager.notifyPerformingAction(0); 798 } 799 } finally { 800 try { 801 mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0; 802 callback.setPerformAccessibilityActionResult(succeeded, interactionId); 803 } catch (RemoteException re) { 804 /* ignore - the other side will time out */ 805 } 806 } 807 } 808 809 /** 810 * Finds the accessibility focused node in the root, and clears the accessibility focus. 811 */ clearAccessibilityFocusClientThread()812 public void clearAccessibilityFocusClientThread() { 813 final Message message = mHandler.obtainMessage(); 814 message.what = PrivateHandler.MSG_CLEAR_ACCESSIBILITY_FOCUS; 815 816 // Don't care about pid and tid because there's no interrogating client for this message. 817 scheduleMessage(message, 0, 0, CONSIDER_REQUEST_PREPARERS); 818 } 819 clearAccessibilityFocusUiThread()820 private void clearAccessibilityFocusUiThread() { 821 if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) { 822 return; 823 } 824 try { 825 mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 826 AccessibilityNodeInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS; 827 final View root = mViewRootImpl.mView; 828 if (root != null && isShown(root)) { 829 final View host = mViewRootImpl.mAccessibilityFocusedHost; 830 // If there is no accessibility focus host or it is not a descendant 831 // of the root from which to start the search, then the search failed. 832 if (host == null || !ViewRootImpl.isViewDescendantOf(host, root)) { 833 return; 834 } 835 final AccessibilityNodeProvider provider = host.getAccessibilityNodeProvider(); 836 final AccessibilityNodeInfo focusNode = 837 mViewRootImpl.mAccessibilityFocusedVirtualView; 838 if (provider != null && focusNode != null) { 839 final int virtualNodeId = AccessibilityNodeInfo.getVirtualDescendantId( 840 focusNode.getSourceNodeId()); 841 provider.performAction(virtualNodeId, 842 AccessibilityAction.ACTION_CLEAR_ACCESSIBILITY_FOCUS.getId(), 843 null); 844 } else { 845 host.performAccessibilityAction( 846 AccessibilityAction.ACTION_CLEAR_ACCESSIBILITY_FOCUS.getId(), 847 null); 848 } 849 } 850 } finally { 851 mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0; 852 } 853 } 854 855 /** 856 * Notify outside touch event to the target window. 857 */ notifyOutsideTouchClientThread()858 public void notifyOutsideTouchClientThread() { 859 final Message message = mHandler.obtainMessage(); 860 message.what = PrivateHandler.MSG_NOTIFY_OUTSIDE_TOUCH; 861 862 // Don't care about pid and tid because there's no interrogating client for this message. 863 scheduleMessage(message, 0, 0, CONSIDER_REQUEST_PREPARERS); 864 } 865 notifyOutsideTouchUiThread()866 private void notifyOutsideTouchUiThread() { 867 if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null 868 || mViewRootImpl.mStopped || mViewRootImpl.mPausedForTransition) { 869 return; 870 } 871 final View root = mViewRootImpl.mView; 872 if (root != null && isShown(root)) { 873 // trigger ACTION_OUTSIDE to notify windows 874 final long now = SystemClock.uptimeMillis(); 875 final MotionEvent event = MotionEvent.obtain(now, now, MotionEvent.ACTION_OUTSIDE, 876 0, 0, 0); 877 event.setSource(InputDevice.SOURCE_TOUCHSCREEN); 878 mViewRootImpl.dispatchInputEvent(event); 879 } 880 } 881 findViewByAccessibilityId(int accessibilityId)882 private View findViewByAccessibilityId(int accessibilityId) { 883 if (accessibilityId == AccessibilityNodeInfo.ROOT_ITEM_ID) { 884 return mViewRootImpl.mView; 885 } else { 886 return AccessibilityNodeIdManager.getInstance().findView(accessibilityId); 887 } 888 } 889 890 // The boundInScreen includes magnification effect, so we need to normalize it before 891 // determine the visibility. adjustIsVisibleToUserIfNeeded(AccessibilityNodeInfo info, Region interactiveRegion, MagnificationSpec spec)892 private void adjustIsVisibleToUserIfNeeded(AccessibilityNodeInfo info, 893 Region interactiveRegion, MagnificationSpec spec) { 894 if (interactiveRegion == null || info == null) { 895 return; 896 } 897 Rect boundsInScreen = mTempRect; 898 info.getBoundsInScreen(boundsInScreen); 899 if (spec != null && !spec.isNop()) { 900 boundsInScreen.offset((int) -spec.offsetX, (int) -spec.offsetY); 901 boundsInScreen.scale(1 / spec.scale); 902 } 903 904 if (interactiveRegion.quickReject(boundsInScreen) && !shouldBypassAdjustIsVisible()) { 905 info.setVisibleToUser(false); 906 } 907 } 908 shouldBypassAdjustIsVisible()909 private boolean shouldBypassAdjustIsVisible() { 910 final int windowType = mViewRootImpl.mOrigWindowType; 911 if (windowType == TYPE_INPUT_METHOD) { 912 return true; 913 } 914 return false; 915 } 916 917 /** 918 * Applies the host-window matrix to the embedded node. After this transform, The node bounds 919 * will be transformed from embedded window coordinates to host-window coordinates. 920 * 921 */ applyHostWindowMatrixIfNeeded(AccessibilityNodeInfo info)922 private void applyHostWindowMatrixIfNeeded(AccessibilityNodeInfo info) { 923 if (info == null || shouldBypassApplyWindowMatrix()) { 924 return; 925 } 926 final Rect boundsInScreen = mTempRect; 927 final RectF transformedBounds = mTempRectF; 928 final Matrix windowMatrix = mViewRootImpl.mAttachInfo.mWindowMatrixInEmbeddedHierarchy; 929 930 info.getBoundsInScreen(boundsInScreen); 931 transformedBounds.set(boundsInScreen); 932 windowMatrix.mapRect(transformedBounds); 933 boundsInScreen.set((int) transformedBounds.left, (int) transformedBounds.top, 934 (int) transformedBounds.right, (int) transformedBounds.bottom); 935 info.setBoundsInScreen(boundsInScreen); 936 } 937 shouldBypassApplyWindowMatrix()938 private boolean shouldBypassApplyWindowMatrix() { 939 final Matrix windowMatrix = mViewRootImpl.mAttachInfo.mWindowMatrixInEmbeddedHierarchy; 940 return windowMatrix == null || windowMatrix.isIdentity(); 941 } 942 associateLeashedParentIfNeeded(AccessibilityNodeInfo info)943 private void associateLeashedParentIfNeeded(AccessibilityNodeInfo info) { 944 if (info == null || shouldBypassAssociateLeashedParent()) { 945 return; 946 } 947 // The node id of root node in embedded maybe not be ROOT_NODE_ID so we compare the id 948 // with root view. 949 if (mViewRootImpl.mView.getAccessibilityViewId() 950 != AccessibilityNodeInfo.getAccessibilityViewId(info.getSourceNodeId())) { 951 return; 952 } 953 info.setLeashedParent(mViewRootImpl.mAttachInfo.mLeashedParentToken, 954 mViewRootImpl.mAttachInfo.mLeashedParentAccessibilityViewId); 955 } 956 shouldBypassAssociateLeashedParent()957 private boolean shouldBypassAssociateLeashedParent() { 958 return (mViewRootImpl.mAttachInfo.mLeashedParentToken == null 959 && mViewRootImpl.mAttachInfo.mLeashedParentAccessibilityViewId == View.NO_ID); 960 } 961 applyAppScaleAndMagnificationSpecIfNeeded(AccessibilityNodeInfo info, MagnificationSpec spec)962 private void applyAppScaleAndMagnificationSpecIfNeeded(AccessibilityNodeInfo info, 963 MagnificationSpec spec) { 964 if (info == null) { 965 return; 966 } 967 968 final float applicationScale = mViewRootImpl.mAttachInfo.mApplicationScale; 969 if (!shouldApplyAppScaleAndMagnificationSpec(applicationScale, spec)) { 970 return; 971 } 972 Rect boundsInParent = mTempRect; 973 974 info.getBoundsInParent(boundsInParent); 975 if (applicationScale != 1.0f) { 976 boundsInParent.scale(applicationScale); 977 } 978 if (spec != null) { 979 boundsInParent.scale(spec.scale); 980 // boundsInParent must not be offset. 981 } 982 info.setBoundsInParent(boundsInParent); 983 } 984 shouldApplyAppScaleAndMagnificationSpec(float appScale, MagnificationSpec spec)985 private boolean shouldApplyAppScaleAndMagnificationSpec(float appScale, 986 MagnificationSpec spec) { 987 return (appScale != 1.0f || (spec != null && !spec.isNop())); 988 } 989 updateInfosForViewPort(List<AccessibilityNodeInfo> infos, MagnificationSpec spec, float[] matrixValues, Region interactiveRegion)990 private void updateInfosForViewPort(List<AccessibilityNodeInfo> infos, MagnificationSpec spec, 991 float[] matrixValues, Region interactiveRegion) { 992 for (int i = 0; i < infos.size(); i++) { 993 updateInfoForViewPort(infos.get(i), spec, matrixValues, interactiveRegion); 994 } 995 } 996 updateInfoForViewPort(AccessibilityNodeInfo info, MagnificationSpec spec, float[] matrixValues, Region interactiveRegion)997 private void updateInfoForViewPort(AccessibilityNodeInfo info, MagnificationSpec spec, 998 float[] matrixValues, Region interactiveRegion) { 999 associateLeashedParentIfNeeded(info); 1000 1001 applyHostWindowMatrixIfNeeded(info); 1002 // Transform view bounds from window coordinates to screen coordinates. 1003 transformBoundsWithScreenMatrix(info, matrixValues); 1004 adjustIsVisibleToUserIfNeeded(info, interactiveRegion, spec); 1005 applyAppScaleAndMagnificationSpecIfNeeded(info, spec); 1006 } 1007 1008 1009 /** 1010 * Transforms the regions from local screen coordinate to global screen coordinate with the 1011 * given transform matrix used in on-screen coordinate. 1012 * 1013 * @param info the AccessibilityNodeInfo that has the region in application screen coordinate 1014 * @param matrixValues the matrix to be applied 1015 */ transformBoundsWithScreenMatrix(AccessibilityNodeInfo info, float[] matrixValues)1016 private void transformBoundsWithScreenMatrix(AccessibilityNodeInfo info, 1017 float[] matrixValues) { 1018 if (info == null || matrixValues == null) { 1019 return; 1020 } 1021 final Rect boundInScreen = mTempRect; 1022 final RectF transformedBounds = mTempRectF; 1023 1024 info.getBoundsInScreen(boundInScreen); 1025 transformedBounds.set(boundInScreen); 1026 1027 final Matrix transformMatrix = new Matrix(); 1028 transformMatrix.setValues(matrixValues); 1029 final float applicationScale = mViewRootImpl.mAttachInfo.mApplicationScale; 1030 if (applicationScale != 1f) { 1031 transformMatrix.preScale(applicationScale, applicationScale); 1032 } 1033 // Transform the bounds from application screen coordinates to global window coordinates. 1034 // For the embedded node, the bounds we get is already in window coordinates, so we don't 1035 // need to do it. 1036 if (mViewRootImpl.mAttachInfo.mWindowMatrixInEmbeddedHierarchy == null) { 1037 transformMatrix.preTranslate(-mViewRootImpl.mAttachInfo.mWindowLeft, 1038 -mViewRootImpl.mAttachInfo.mWindowTop); 1039 } 1040 1041 if (transformMatrix.isIdentity()) { 1042 return; 1043 } 1044 transformMatrix.mapRect(transformedBounds); 1045 // Offset 0.5f to round after casting. 1046 transformedBounds.offset(0.5f, 0.5f); 1047 boundInScreen.set((int) (transformedBounds.left), (int) transformedBounds.top, 1048 (int) transformedBounds.right, (int) transformedBounds.bottom); 1049 info.setBoundsInScreen(boundInScreen); 1050 // Scale text locations if they are present 1051 if (info.hasExtras()) { 1052 final Bundle extras = info.getExtras(); 1053 final RectF[] textLocations = 1054 extras.getParcelableArray(EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY, RectF.class); 1055 if (textLocations != null) { 1056 for (int i = 0; i < textLocations.length; i++) { 1057 // Unchecked cast - an app that puts other objects in this bundle with this 1058 // key will crash. 1059 final RectF textLocation = textLocations[i]; 1060 if (textLocation != null) { 1061 transformMatrix.mapRect(textLocation); 1062 } 1063 } 1064 } 1065 } 1066 } 1067 updateInfosForViewportAndReturnFindNodeResult(List<AccessibilityNodeInfo> infos, IAccessibilityInteractionConnectionCallback callback, int interactionId, MagnificationSpec spec, float[] matrixValues, Region interactiveRegion)1068 private void updateInfosForViewportAndReturnFindNodeResult(List<AccessibilityNodeInfo> infos, 1069 IAccessibilityInteractionConnectionCallback callback, int interactionId, 1070 MagnificationSpec spec, float[] matrixValues, Region interactiveRegion) { 1071 if (infos != null) { 1072 updateInfosForViewPort(infos, spec, matrixValues, interactiveRegion); 1073 } 1074 returnFindNodesResult(infos, callback, interactionId); 1075 } 1076 returnFindNodeResult(AccessibilityNodeInfo info, IAccessibilityInteractionConnectionCallback callback, int interactionId)1077 private void returnFindNodeResult(AccessibilityNodeInfo info, 1078 IAccessibilityInteractionConnectionCallback callback, 1079 int interactionId) { 1080 try { 1081 callback.setFindAccessibilityNodeInfoResult(info, interactionId); 1082 } catch (RemoteException re) { 1083 /* ignore - the other side will time out */ 1084 } 1085 } 1086 returnFindNodeResult(SatisfiedFindAccessibilityNodeByAccessibilityIdRequest satisfiedRequest)1087 private void returnFindNodeResult(SatisfiedFindAccessibilityNodeByAccessibilityIdRequest 1088 satisfiedRequest) { 1089 try { 1090 final AccessibilityNodeInfo info = satisfiedRequest.mSatisfiedRequestNode; 1091 final IAccessibilityInteractionConnectionCallback callback = 1092 satisfiedRequest.mSatisfiedRequestCallback; 1093 final int interactionId = satisfiedRequest.mSatisfiedRequestInteractionId; 1094 callback.setFindAccessibilityNodeInfoResult(info, interactionId); 1095 } catch (RemoteException re) { 1096 /* ignore - the other side will time out */ 1097 } 1098 } 1099 returnFindNodesResult(List<AccessibilityNodeInfo> infos, IAccessibilityInteractionConnectionCallback callback, int interactionId)1100 private void returnFindNodesResult(List<AccessibilityNodeInfo> infos, 1101 IAccessibilityInteractionConnectionCallback callback, int interactionId) { 1102 try { 1103 callback.setFindAccessibilityNodeInfosResult(infos, interactionId); 1104 if (infos != null) { 1105 infos.clear(); 1106 } 1107 } catch (RemoteException re) { 1108 /* ignore - the other side will time out */ 1109 } 1110 } 1111 getSatisfiedRequestInPrefetch( AccessibilityNodeInfo requestedNode, List<AccessibilityNodeInfo> infos, int flags)1112 private SatisfiedFindAccessibilityNodeByAccessibilityIdRequest getSatisfiedRequestInPrefetch( 1113 AccessibilityNodeInfo requestedNode, List<AccessibilityNodeInfo> infos, int flags) { 1114 SatisfiedFindAccessibilityNodeByAccessibilityIdRequest satisfiedRequest = null; 1115 synchronized (mLock) { 1116 for (int i = 0; i < mPendingFindNodeByIdMessages.size(); i++) { 1117 final Message pendingMessage = mPendingFindNodeByIdMessages.get(i); 1118 final int pendingFlags = pendingMessage.arg1; 1119 if ((pendingFlags & FLAGS_AFFECTING_REPORTED_DATA) 1120 != (flags & FLAGS_AFFECTING_REPORTED_DATA)) { 1121 continue; 1122 } 1123 SomeArgs args = (SomeArgs) pendingMessage.obj; 1124 final int accessibilityViewId = args.argi1; 1125 final int virtualDescendantId = args.argi2; 1126 1127 final AccessibilityNodeInfo satisfiedRequestNode = nodeWithIdFromList(requestedNode, 1128 infos, AccessibilityNodeInfo.makeNodeId( 1129 accessibilityViewId, virtualDescendantId)); 1130 1131 if (satisfiedRequestNode != null) { 1132 mHandler.removeMessages( 1133 PrivateHandler.MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_ACCESSIBILITY_ID, 1134 pendingMessage.obj); 1135 final IAccessibilityInteractionConnectionCallback satisfiedRequestCallback = 1136 (IAccessibilityInteractionConnectionCallback) args.arg1; 1137 final int satisfiedRequestInteractionId = args.argi3; 1138 satisfiedRequest = new SatisfiedFindAccessibilityNodeByAccessibilityIdRequest( 1139 satisfiedRequestNode, satisfiedRequestCallback, 1140 satisfiedRequestInteractionId); 1141 args.recycle(); 1142 break; 1143 } 1144 } 1145 mPendingFindNodeByIdMessages.clear(); 1146 // Remove node from prefetched infos. 1147 if (satisfiedRequest != null && satisfiedRequest.mSatisfiedRequestNode 1148 != requestedNode) { 1149 infos.remove(satisfiedRequest.mSatisfiedRequestNode); 1150 } 1151 return satisfiedRequest; 1152 } 1153 } 1154 nodeWithIdFromList(AccessibilityNodeInfo requestedNode, List<AccessibilityNodeInfo> infos, long nodeId)1155 private AccessibilityNodeInfo nodeWithIdFromList(AccessibilityNodeInfo requestedNode, 1156 List<AccessibilityNodeInfo> infos, long nodeId) { 1157 if (requestedNode != null && requestedNode.getSourceNodeId() == nodeId) { 1158 return requestedNode; 1159 } 1160 for (int j = 0; j < infos.size(); j++) { 1161 AccessibilityNodeInfo info = infos.get(j); 1162 if (info.getSourceNodeId() == nodeId) { 1163 return info; 1164 } 1165 } 1166 return null; 1167 } 1168 returnPrefetchResult(int interactionId, List<AccessibilityNodeInfo> infos, IAccessibilityInteractionConnectionCallback callback)1169 private void returnPrefetchResult(int interactionId, List<AccessibilityNodeInfo> infos, 1170 IAccessibilityInteractionConnectionCallback callback) { 1171 if (infos.size() > 0) { 1172 try { 1173 callback.setPrefetchAccessibilityNodeInfoResult(infos, interactionId); 1174 } catch (RemoteException re) { 1175 /* ignore - other side isn't too bothered if this doesn't arrive */ 1176 } 1177 } 1178 } 1179 updateInfoForViewportAndReturnFindNodeResult(AccessibilityNodeInfo info, IAccessibilityInteractionConnectionCallback callback, int interactionId, MagnificationSpec spec, float[] matrixValues, Region interactiveRegion)1180 private void updateInfoForViewportAndReturnFindNodeResult(AccessibilityNodeInfo info, 1181 IAccessibilityInteractionConnectionCallback callback, int interactionId, 1182 MagnificationSpec spec, float[] matrixValues, Region interactiveRegion) { 1183 updateInfoForViewPort(info, spec, matrixValues, interactiveRegion); 1184 returnFindNodeResult(info, callback, interactionId); 1185 } 1186 handleClickableSpanActionUiThread( View view, int virtualDescendantId, Bundle arguments)1187 private boolean handleClickableSpanActionUiThread( 1188 View view, int virtualDescendantId, Bundle arguments) { 1189 Parcelable span = arguments.getParcelable(ACTION_ARGUMENT_ACCESSIBLE_CLICKABLE_SPAN); 1190 if (!(span instanceof AccessibilityClickableSpan)) { 1191 return false; 1192 } 1193 1194 // Find the original ClickableSpan if it's still on the screen 1195 AccessibilityNodeInfo infoWithSpan = null; 1196 AccessibilityNodeProvider provider = view.getAccessibilityNodeProvider(); 1197 if (provider != null) { 1198 infoWithSpan = provider.createAccessibilityNodeInfo(virtualDescendantId); 1199 } else if (virtualDescendantId == AccessibilityNodeProvider.HOST_VIEW_ID) { 1200 infoWithSpan = view.createAccessibilityNodeInfo(); 1201 } 1202 if (infoWithSpan == null) { 1203 return false; 1204 } 1205 1206 // Click on the corresponding span 1207 ClickableSpan clickableSpan = ((AccessibilityClickableSpan) span).findClickableSpan( 1208 infoWithSpan.getOriginalText()); 1209 if (clickableSpan != null) { 1210 clickableSpan.onClick(view); 1211 return true; 1212 } 1213 return false; 1214 } 1215 1216 /** 1217 * This class encapsulates a prefetching strategy for the accessibility APIs for 1218 * querying window content. It is responsible to prefetch a batch of 1219 * AccessibilityNodeInfos in addition to the one for a requested node. 1220 */ 1221 private class AccessibilityNodePrefetcher { 1222 1223 private final ArrayList<View> mTempViewList = new ArrayList<View>(); 1224 private boolean mInterruptPrefetch; 1225 private int mFetchFlags; 1226 prefetchAccessibilityNodeInfos(View view, AccessibilityNodeInfo root, List<AccessibilityNodeInfo> outInfos)1227 public void prefetchAccessibilityNodeInfos(View view, AccessibilityNodeInfo root, 1228 List<AccessibilityNodeInfo> outInfos) { 1229 if (root == null) { 1230 return; 1231 } 1232 AccessibilityNodeProvider provider = view.getAccessibilityNodeProvider(); 1233 final boolean prefetchPredecessors = 1234 isFlagSet(AccessibilityNodeInfo.FLAG_PREFETCH_ANCESTORS); 1235 if (provider == null) { 1236 if (prefetchPredecessors) { 1237 prefetchPredecessorsOfRealNode(view, outInfos); 1238 } 1239 if (isFlagSet(AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS)) { 1240 prefetchSiblingsOfRealNode(view, outInfos, prefetchPredecessors); 1241 } 1242 if (isFlagSet(AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS_HYBRID)) { 1243 prefetchDescendantsOfRealNode(view, outInfos); 1244 } 1245 } else { 1246 if (prefetchPredecessors) { 1247 prefetchPredecessorsOfVirtualNode(root, view, provider, outInfos); 1248 } 1249 if (isFlagSet(AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS)) { 1250 prefetchSiblingsOfVirtualNode(root, view, provider, outInfos, 1251 prefetchPredecessors); 1252 } 1253 if (isFlagSet(AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS_HYBRID)) { 1254 prefetchDescendantsOfVirtualNode(root, provider, outInfos); 1255 } 1256 } 1257 if (isFlagSet(AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS_DEPTH_FIRST) 1258 || isFlagSet(AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS_BREADTH_FIRST)) { 1259 if (shouldStopPrefetching(outInfos)) { 1260 return; 1261 } 1262 PrefetchDeque<DequeNode> deque = new PrefetchDeque<>( 1263 mFetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS_MASK, 1264 outInfos); 1265 addChildrenOfRoot(view, root, provider, deque); 1266 deque.performTraversalAndPrefetch(); 1267 } 1268 if (ENFORCE_NODE_TREE_CONSISTENT) { 1269 enforceNodeTreeConsistent(root, outInfos); 1270 } 1271 } 1272 addChildrenOfRoot(View root, AccessibilityNodeInfo rootInfo, AccessibilityNodeProvider rootProvider, PrefetchDeque deque)1273 private void addChildrenOfRoot(View root, AccessibilityNodeInfo rootInfo, 1274 AccessibilityNodeProvider rootProvider, PrefetchDeque deque) { 1275 DequeNode rootDequeNode; 1276 if (rootProvider == null) { 1277 rootDequeNode = new ViewNode(root); 1278 } else { 1279 rootDequeNode = new VirtualNode( 1280 AccessibilityNodeProvider.HOST_VIEW_ID, rootProvider); 1281 } 1282 rootDequeNode.addChildren(rootInfo, deque); 1283 } 1284 isFlagSet(@ccessibilityNodeInfo.PrefetchingStrategy int strategy)1285 private boolean isFlagSet(@AccessibilityNodeInfo.PrefetchingStrategy int strategy) { 1286 return (mFetchFlags & strategy) != 0; 1287 } 1288 shouldStopPrefetching(List prefetchedInfos)1289 public boolean shouldStopPrefetching(List prefetchedInfos) { 1290 return ((mHandler.hasUserInteractiveMessagesWaiting() && mInterruptPrefetch) 1291 || prefetchedInfos.size() 1292 >= AccessibilityNodeInfo.MAX_NUMBER_OF_PREFETCHED_NODES); 1293 } 1294 enforceNodeTreeConsistent( AccessibilityNodeInfo root, List<AccessibilityNodeInfo> nodes)1295 private void enforceNodeTreeConsistent( 1296 AccessibilityNodeInfo root, List<AccessibilityNodeInfo> nodes) { 1297 LongSparseArray<AccessibilityNodeInfo> nodeMap = 1298 new LongSparseArray<AccessibilityNodeInfo>(); 1299 final int nodeCount = nodes.size(); 1300 for (int i = 0; i < nodeCount; i++) { 1301 AccessibilityNodeInfo node = nodes.get(i); 1302 nodeMap.put(node.getSourceNodeId(), node); 1303 } 1304 1305 // If the nodes are a tree it does not matter from 1306 // which node we start to search for the root. 1307 AccessibilityNodeInfo parent = root; 1308 while (parent != null) { 1309 root = parent; 1310 parent = nodeMap.get(parent.getParentNodeId()); 1311 } 1312 1313 // Traverse the tree and do some checks. 1314 AccessibilityNodeInfo accessFocus = null; 1315 AccessibilityNodeInfo inputFocus = null; 1316 HashSet<AccessibilityNodeInfo> seen = new HashSet<AccessibilityNodeInfo>(); 1317 Queue<AccessibilityNodeInfo> fringe = new LinkedList<AccessibilityNodeInfo>(); 1318 fringe.add(root); 1319 1320 while (!fringe.isEmpty()) { 1321 AccessibilityNodeInfo current = fringe.poll(); 1322 1323 // Check for duplicates 1324 if (!seen.add(current)) { 1325 throw new IllegalStateException("Duplicate node: " 1326 + current + " in window:" 1327 + mViewRootImpl.mAttachInfo.mAccessibilityWindowId); 1328 } 1329 1330 // Check for one accessibility focus. 1331 if (current.isAccessibilityFocused()) { 1332 if (accessFocus != null) { 1333 throw new IllegalStateException("Duplicate accessibility focus:" 1334 + current 1335 + " in window:" + mViewRootImpl.mAttachInfo.mAccessibilityWindowId); 1336 } else { 1337 accessFocus = current; 1338 } 1339 } 1340 1341 // Check for one input focus. 1342 if (current.isFocused()) { 1343 if (inputFocus != null) { 1344 throw new IllegalStateException("Duplicate input focus: " 1345 + current + " in window:" 1346 + mViewRootImpl.mAttachInfo.mAccessibilityWindowId); 1347 } else { 1348 inputFocus = current; 1349 } 1350 } 1351 1352 final int childCount = current.getChildCount(); 1353 for (int j = 0; j < childCount; j++) { 1354 final long childId = current.getChildId(j); 1355 final AccessibilityNodeInfo child = nodeMap.get(childId); 1356 if (child != null) { 1357 fringe.add(child); 1358 } 1359 } 1360 } 1361 1362 // Check for disconnected nodes. 1363 for (int j = nodeMap.size() - 1; j >= 0; j--) { 1364 AccessibilityNodeInfo info = nodeMap.valueAt(j); 1365 if (!seen.contains(info)) { 1366 throw new IllegalStateException("Disconnected node: " + info); 1367 } 1368 } 1369 } 1370 prefetchPredecessorsOfRealNode(View view, List<AccessibilityNodeInfo> outInfos)1371 private void prefetchPredecessorsOfRealNode(View view, 1372 List<AccessibilityNodeInfo> outInfos) { 1373 if (shouldStopPrefetching(outInfos)) { 1374 return; 1375 } 1376 ViewParent parent = view.getParentForAccessibility(); 1377 while (parent instanceof View && !shouldStopPrefetching(outInfos)) { 1378 View parentView = (View) parent; 1379 AccessibilityNodeInfo info = parentView.createAccessibilityNodeInfo(); 1380 if (info != null) { 1381 outInfos.add(info); 1382 } 1383 parent = parent.getParentForAccessibility(); 1384 } 1385 } 1386 prefetchSiblingsOfRealNode(View current, List<AccessibilityNodeInfo> outInfos, boolean predecessorsPrefetched)1387 private void prefetchSiblingsOfRealNode(View current, 1388 List<AccessibilityNodeInfo> outInfos, boolean predecessorsPrefetched) { 1389 if (shouldStopPrefetching(outInfos)) { 1390 return; 1391 } 1392 ViewParent parent = current.getParentForAccessibility(); 1393 if (parent instanceof ViewGroup) { 1394 ViewGroup parentGroup = (ViewGroup) parent; 1395 ArrayList<View> children = mTempViewList; 1396 children.clear(); 1397 try { 1398 if (!predecessorsPrefetched) { 1399 AccessibilityNodeInfo parentInfo = 1400 ((ViewGroup) parent).createAccessibilityNodeInfo(); 1401 if (parentInfo != null) { 1402 outInfos.add(parentInfo); 1403 } 1404 } 1405 parentGroup.addChildrenForAccessibility(children); 1406 final int childCount = children.size(); 1407 for (int i = 0; i < childCount; i++) { 1408 if (shouldStopPrefetching(outInfos)) { 1409 return; 1410 } 1411 View child = children.get(i); 1412 if (child.getAccessibilityViewId() != current.getAccessibilityViewId() 1413 && isShown(child)) { 1414 AccessibilityNodeInfo info = null; 1415 AccessibilityNodeProvider provider = 1416 child.getAccessibilityNodeProvider(); 1417 if (provider == null) { 1418 info = child.createAccessibilityNodeInfo(); 1419 } else { 1420 info = provider.createAccessibilityNodeInfo( 1421 AccessibilityNodeProvider.HOST_VIEW_ID); 1422 } 1423 if (info != null) { 1424 outInfos.add(info); 1425 } 1426 } 1427 } 1428 } finally { 1429 children.clear(); 1430 } 1431 } 1432 } 1433 prefetchDescendantsOfRealNode(View root, List<AccessibilityNodeInfo> outInfos)1434 private void prefetchDescendantsOfRealNode(View root, 1435 List<AccessibilityNodeInfo> outInfos) { 1436 if (shouldStopPrefetching(outInfos) || !(root instanceof ViewGroup)) { 1437 return; 1438 } 1439 LinkedHashMap<View, AccessibilityNodeInfo> addedChildren = 1440 new LinkedHashMap<View, AccessibilityNodeInfo>(); 1441 ArrayList<View> children = mTempViewList; 1442 children.clear(); 1443 try { 1444 root.addChildrenForAccessibility(children); 1445 final int childCount = children.size(); 1446 for (int i = 0; i < childCount; i++) { 1447 if (shouldStopPrefetching(outInfos)) { 1448 return; 1449 } 1450 View child = children.get(i); 1451 if (isShown(child)) { 1452 AccessibilityNodeProvider provider = child.getAccessibilityNodeProvider(); 1453 if (provider == null) { 1454 AccessibilityNodeInfo info = child.createAccessibilityNodeInfo(); 1455 if (info != null) { 1456 outInfos.add(info); 1457 addedChildren.put(child, null); 1458 } 1459 } else { 1460 AccessibilityNodeInfo info = provider.createAccessibilityNodeInfo( 1461 AccessibilityNodeProvider.HOST_VIEW_ID); 1462 if (info != null) { 1463 outInfos.add(info); 1464 addedChildren.put(child, info); 1465 } 1466 } 1467 } 1468 } 1469 } finally { 1470 children.clear(); 1471 } 1472 if (!shouldStopPrefetching(outInfos)) { 1473 for (Map.Entry<View, AccessibilityNodeInfo> entry : addedChildren.entrySet()) { 1474 View addedChild = entry.getKey(); 1475 AccessibilityNodeInfo virtualRoot = entry.getValue(); 1476 if (virtualRoot == null) { 1477 prefetchDescendantsOfRealNode(addedChild, outInfos); 1478 } else { 1479 AccessibilityNodeProvider provider = 1480 addedChild.getAccessibilityNodeProvider(); 1481 prefetchDescendantsOfVirtualNode(virtualRoot, provider, outInfos); 1482 } 1483 } 1484 } 1485 } 1486 prefetchPredecessorsOfVirtualNode(AccessibilityNodeInfo root, View providerHost, AccessibilityNodeProvider provider, List<AccessibilityNodeInfo> outInfos)1487 private void prefetchPredecessorsOfVirtualNode(AccessibilityNodeInfo root, 1488 View providerHost, AccessibilityNodeProvider provider, 1489 List<AccessibilityNodeInfo> outInfos) { 1490 final int initialResultSize = outInfos.size(); 1491 long parentNodeId = root.getParentNodeId(); 1492 int accessibilityViewId = AccessibilityNodeInfo.getAccessibilityViewId(parentNodeId); 1493 while (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) { 1494 if (shouldStopPrefetching(outInfos)) { 1495 return; 1496 } 1497 final int virtualDescendantId = 1498 AccessibilityNodeInfo.getVirtualDescendantId(parentNodeId); 1499 if (virtualDescendantId != AccessibilityNodeProvider.HOST_VIEW_ID 1500 || accessibilityViewId == providerHost.getAccessibilityViewId()) { 1501 final AccessibilityNodeInfo parent; 1502 parent = provider.createAccessibilityNodeInfo(virtualDescendantId); 1503 if (parent == null) { 1504 // Going up the parent relation we found a null predecessor, 1505 // so remove these disconnected nodes form the result. 1506 final int currentResultSize = outInfos.size(); 1507 for (int i = currentResultSize - 1; i >= initialResultSize; i--) { 1508 outInfos.remove(i); 1509 } 1510 // Couldn't obtain the parent, which means we have a 1511 // disconnected sub-tree. Abort prefetch immediately. 1512 return; 1513 } 1514 outInfos.add(parent); 1515 parentNodeId = parent.getParentNodeId(); 1516 accessibilityViewId = AccessibilityNodeInfo.getAccessibilityViewId( 1517 parentNodeId); 1518 } else { 1519 prefetchPredecessorsOfRealNode(providerHost, outInfos); 1520 return; 1521 } 1522 } 1523 } 1524 prefetchSiblingsOfVirtualNode(AccessibilityNodeInfo current, View providerHost, AccessibilityNodeProvider provider, List<AccessibilityNodeInfo> outInfos, boolean predecessorsPrefetched)1525 private void prefetchSiblingsOfVirtualNode(AccessibilityNodeInfo current, View providerHost, 1526 AccessibilityNodeProvider provider, List<AccessibilityNodeInfo> outInfos, 1527 boolean predecessorsPrefetched) { 1528 final long parentNodeId = current.getParentNodeId(); 1529 final int parentAccessibilityViewId = 1530 AccessibilityNodeInfo.getAccessibilityViewId(parentNodeId); 1531 final int parentVirtualDescendantId = 1532 AccessibilityNodeInfo.getVirtualDescendantId(parentNodeId); 1533 if (parentVirtualDescendantId != AccessibilityNodeProvider.HOST_VIEW_ID 1534 || parentAccessibilityViewId == providerHost.getAccessibilityViewId()) { 1535 final AccessibilityNodeInfo parent = 1536 provider.createAccessibilityNodeInfo(parentVirtualDescendantId); 1537 if (parent != null) { 1538 if (!predecessorsPrefetched) { 1539 outInfos.add(parent); 1540 } 1541 final int childCount = parent.getChildCount(); 1542 for (int i = 0; i < childCount; i++) { 1543 if (shouldStopPrefetching(outInfos)) { 1544 return; 1545 } 1546 final long childNodeId = parent.getChildId(i); 1547 if (childNodeId != current.getSourceNodeId()) { 1548 final int childVirtualDescendantId = 1549 AccessibilityNodeInfo.getVirtualDescendantId(childNodeId); 1550 AccessibilityNodeInfo child = provider.createAccessibilityNodeInfo( 1551 childVirtualDescendantId); 1552 if (child != null) { 1553 outInfos.add(child); 1554 } 1555 } 1556 } 1557 } 1558 } else { 1559 prefetchSiblingsOfRealNode(providerHost, outInfos, predecessorsPrefetched); 1560 } 1561 } 1562 prefetchDescendantsOfVirtualNode(AccessibilityNodeInfo root, AccessibilityNodeProvider provider, List<AccessibilityNodeInfo> outInfos)1563 private void prefetchDescendantsOfVirtualNode(AccessibilityNodeInfo root, 1564 AccessibilityNodeProvider provider, List<AccessibilityNodeInfo> outInfos) { 1565 final int initialOutInfosSize = outInfos.size(); 1566 final int childCount = root.getChildCount(); 1567 for (int i = 0; i < childCount; i++) { 1568 if (shouldStopPrefetching(outInfos)) { 1569 return; 1570 } 1571 final long childNodeId = root.getChildId(i); 1572 AccessibilityNodeInfo child = provider.createAccessibilityNodeInfo( 1573 AccessibilityNodeInfo.getVirtualDescendantId(childNodeId)); 1574 if (child != null) { 1575 outInfos.add(child); 1576 } 1577 } 1578 if (!shouldStopPrefetching(outInfos)) { 1579 final int addedChildCount = outInfos.size() - initialOutInfosSize; 1580 for (int i = 0; i < addedChildCount; i++) { 1581 AccessibilityNodeInfo child = outInfos.get(initialOutInfosSize + i); 1582 prefetchDescendantsOfVirtualNode(child, provider, outInfos); 1583 } 1584 } 1585 } 1586 } 1587 1588 private class PrivateHandler extends Handler { 1589 private static final int MSG_PERFORM_ACCESSIBILITY_ACTION = 1; 1590 private static final int MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_ACCESSIBILITY_ID = 2; 1591 private static final int MSG_FIND_ACCESSIBILITY_NODE_INFOS_BY_VIEW_ID = 3; 1592 private static final int MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_TEXT = 4; 1593 private static final int MSG_FIND_FOCUS = 5; 1594 private static final int MSG_FOCUS_SEARCH = 6; 1595 private static final int MSG_PREPARE_FOR_EXTRA_DATA_REQUEST = 7; 1596 private static final int MSG_APP_PREPARATION_FINISHED = 8; 1597 private static final int MSG_APP_PREPARATION_TIMEOUT = 9; 1598 1599 // Uses FIRST_NO_ACCESSIBILITY_CALLBACK_MSG for messages that don't need to call back 1600 // results to interrogating client. 1601 private static final int FIRST_NO_ACCESSIBILITY_CALLBACK_MSG = 100; 1602 private static final int MSG_CLEAR_ACCESSIBILITY_FOCUS = 1603 FIRST_NO_ACCESSIBILITY_CALLBACK_MSG + 1; 1604 private static final int MSG_NOTIFY_OUTSIDE_TOUCH = 1605 FIRST_NO_ACCESSIBILITY_CALLBACK_MSG + 2; 1606 PrivateHandler(Looper looper)1607 public PrivateHandler(Looper looper) { 1608 super(looper); 1609 } 1610 1611 @Override getMessageName(Message message)1612 public String getMessageName(Message message) { 1613 final int type = message.what; 1614 switch (type) { 1615 case MSG_PERFORM_ACCESSIBILITY_ACTION: 1616 return "MSG_PERFORM_ACCESSIBILITY_ACTION"; 1617 case MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_ACCESSIBILITY_ID: 1618 return "MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_ACCESSIBILITY_ID"; 1619 case MSG_FIND_ACCESSIBILITY_NODE_INFOS_BY_VIEW_ID: 1620 return "MSG_FIND_ACCESSIBILITY_NODE_INFOS_BY_VIEW_ID"; 1621 case MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_TEXT: 1622 return "MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_TEXT"; 1623 case MSG_FIND_FOCUS: 1624 return "MSG_FIND_FOCUS"; 1625 case MSG_FOCUS_SEARCH: 1626 return "MSG_FOCUS_SEARCH"; 1627 case MSG_PREPARE_FOR_EXTRA_DATA_REQUEST: 1628 return "MSG_PREPARE_FOR_EXTRA_DATA_REQUEST"; 1629 case MSG_APP_PREPARATION_FINISHED: 1630 return "MSG_APP_PREPARATION_FINISHED"; 1631 case MSG_APP_PREPARATION_TIMEOUT: 1632 return "MSG_APP_PREPARATION_TIMEOUT"; 1633 case MSG_CLEAR_ACCESSIBILITY_FOCUS: 1634 return "MSG_CLEAR_ACCESSIBILITY_FOCUS"; 1635 case MSG_NOTIFY_OUTSIDE_TOUCH: 1636 return "MSG_NOTIFY_OUTSIDE_TOUCH"; 1637 default: 1638 throw new IllegalArgumentException("Unknown message type: " + type); 1639 } 1640 } 1641 1642 @Override handleMessage(Message message)1643 public void handleMessage(Message message) { 1644 final int type = message.what; 1645 switch (type) { 1646 case MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_ACCESSIBILITY_ID: { 1647 findAccessibilityNodeInfoByAccessibilityIdUiThread(message); 1648 } break; 1649 case MSG_PERFORM_ACCESSIBILITY_ACTION: { 1650 performAccessibilityActionUiThread(message); 1651 } break; 1652 case MSG_FIND_ACCESSIBILITY_NODE_INFOS_BY_VIEW_ID: { 1653 findAccessibilityNodeInfosByViewIdUiThread(message); 1654 } break; 1655 case MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_TEXT: { 1656 findAccessibilityNodeInfosByTextUiThread(message); 1657 } break; 1658 case MSG_FIND_FOCUS: { 1659 findFocusUiThread(message); 1660 } break; 1661 case MSG_FOCUS_SEARCH: { 1662 focusSearchUiThread(message); 1663 } break; 1664 case MSG_PREPARE_FOR_EXTRA_DATA_REQUEST: { 1665 prepareForExtraDataRequestUiThread(message); 1666 } break; 1667 case MSG_APP_PREPARATION_FINISHED: { 1668 requestPreparerDoneUiThread(message); 1669 } break; 1670 case MSG_APP_PREPARATION_TIMEOUT: { 1671 requestPreparerTimeoutUiThread(); 1672 } break; 1673 case MSG_CLEAR_ACCESSIBILITY_FOCUS: { 1674 clearAccessibilityFocusUiThread(); 1675 } break; 1676 case MSG_NOTIFY_OUTSIDE_TOUCH: { 1677 notifyOutsideTouchUiThread(); 1678 } break; 1679 default: 1680 throw new IllegalArgumentException("Unknown message type: " + type); 1681 } 1682 } 1683 hasAccessibilityCallback(Message message)1684 boolean hasAccessibilityCallback(Message message) { 1685 return message.what < FIRST_NO_ACCESSIBILITY_CALLBACK_MSG ? true : false; 1686 } 1687 hasUserInteractiveMessagesWaiting()1688 boolean hasUserInteractiveMessagesWaiting() { 1689 return hasMessagesOrCallbacks(); 1690 } 1691 } 1692 1693 private final class AddNodeInfosForViewId implements Predicate<View> { 1694 private int mViewId = View.NO_ID; 1695 private List<AccessibilityNodeInfo> mInfos; 1696 init(int viewId, List<AccessibilityNodeInfo> infos)1697 public void init(int viewId, List<AccessibilityNodeInfo> infos) { 1698 mViewId = viewId; 1699 mInfos = infos; 1700 } 1701 reset()1702 public void reset() { 1703 mViewId = View.NO_ID; 1704 mInfos = null; 1705 } 1706 1707 @Override test(View view)1708 public boolean test(View view) { 1709 if (view.getId() == mViewId && isShown(view)) { 1710 mInfos.add(view.createAccessibilityNodeInfo()); 1711 } 1712 return false; 1713 } 1714 } 1715 1716 private static final class MessageHolder { 1717 final Message mMessage; 1718 final int mInterrogatingPid; 1719 final long mInterrogatingTid; 1720 MessageHolder(Message message, int interrogatingPid, long interrogatingTid)1721 MessageHolder(Message message, int interrogatingPid, long interrogatingTid) { 1722 mMessage = message; 1723 mInterrogatingPid = interrogatingPid; 1724 mInterrogatingTid = interrogatingTid; 1725 } 1726 } 1727 1728 private static class SatisfiedFindAccessibilityNodeByAccessibilityIdRequest { 1729 final AccessibilityNodeInfo mSatisfiedRequestNode; 1730 final IAccessibilityInteractionConnectionCallback mSatisfiedRequestCallback; 1731 final int mSatisfiedRequestInteractionId; 1732 SatisfiedFindAccessibilityNodeByAccessibilityIdRequest( AccessibilityNodeInfo satisfiedRequestNode, IAccessibilityInteractionConnectionCallback satisfiedRequestCallback, int satisfiedRequestInteractionId)1733 SatisfiedFindAccessibilityNodeByAccessibilityIdRequest( 1734 AccessibilityNodeInfo satisfiedRequestNode, 1735 IAccessibilityInteractionConnectionCallback satisfiedRequestCallback, 1736 int satisfiedRequestInteractionId) { 1737 mSatisfiedRequestNode = satisfiedRequestNode; 1738 mSatisfiedRequestCallback = satisfiedRequestCallback; 1739 mSatisfiedRequestInteractionId = satisfiedRequestInteractionId; 1740 } 1741 } 1742 1743 private class PrefetchDeque<E extends DequeNode> 1744 extends ArrayDeque<E> { 1745 int mStrategy; 1746 List<AccessibilityNodeInfo> mPrefetchOutput; 1747 PrefetchDeque(int strategy, List<AccessibilityNodeInfo> output)1748 PrefetchDeque(int strategy, List<AccessibilityNodeInfo> output) { 1749 mStrategy = strategy; 1750 mPrefetchOutput = output; 1751 } 1752 1753 /** Performs depth-first or breadth-first traversal. 1754 * 1755 * For depth-first search, we iterate through the children in backwards order and push them 1756 * to the stack before taking from the head. For breadth-first search, we iterate through 1757 * the children in order and push them to the stack before taking from the tail. 1758 * 1759 * Depth-first search: 0 has children 0, 1, 2, 4. 1 has children 5 and 6. 1760 * Head Tail 1761 * 1 2 3 4 -> pop: 1 -> 5 6 2 3 4 1762 * 1763 * Breadth-first search 1764 * Head Tail 1765 * 4 3 2 1 -> remove last: 1 -> 6 5 3 2 1766 * 1767 **/ performTraversalAndPrefetch()1768 void performTraversalAndPrefetch() { 1769 try { 1770 while (!isEmpty()) { 1771 E child = getNext(); 1772 AccessibilityNodeInfo childInfo = child.getA11yNodeInfo(); 1773 if (childInfo != null) { 1774 mPrefetchOutput.add(childInfo); 1775 } 1776 if (mPrefetcher.shouldStopPrefetching(mPrefetchOutput)) { 1777 return; 1778 } 1779 // Add children to deque. 1780 child.addChildren(childInfo, this); 1781 } 1782 } finally { 1783 clear(); 1784 } 1785 } 1786 getNext()1787 E getNext() { 1788 if (isStack()) { 1789 return pop(); 1790 } 1791 return removeLast(); 1792 } 1793 isStack()1794 boolean isStack() { 1795 return (mStrategy & AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS_DEPTH_FIRST) != 0; 1796 } 1797 } 1798 1799 interface DequeNode { getA11yNodeInfo()1800 AccessibilityNodeInfo getA11yNodeInfo(); addChildren(AccessibilityNodeInfo virtualRoot, PrefetchDeque deque)1801 void addChildren(AccessibilityNodeInfo virtualRoot, PrefetchDeque deque); 1802 } 1803 1804 private class ViewNode implements DequeNode { 1805 View mView; 1806 private final ArrayList<View> mTempViewList = new ArrayList<>(); 1807 ViewNode(View view)1808 ViewNode(View view) { 1809 mView = view; 1810 } 1811 1812 @Override getA11yNodeInfo()1813 public AccessibilityNodeInfo getA11yNodeInfo() { 1814 if (mView == null) { 1815 return null; 1816 } 1817 return mView.createAccessibilityNodeInfo(); 1818 } 1819 1820 @Override addChildren(AccessibilityNodeInfo virtualRoot, PrefetchDeque deque)1821 public void addChildren(AccessibilityNodeInfo virtualRoot, PrefetchDeque deque) { 1822 if (mView == null) { 1823 return; 1824 } 1825 if (!(mView instanceof ViewGroup)) { 1826 return; 1827 } 1828 ArrayList<View> children = mTempViewList; 1829 children.clear(); 1830 try { 1831 mView.addChildrenForAccessibility(children); 1832 final int childCount = children.size(); 1833 1834 if (deque.isStack()) { 1835 for (int i = childCount - 1; i >= 0; i--) { 1836 addChild(deque, children.get(i)); 1837 } 1838 } else { 1839 for (int i = 0; i < childCount; i++) { 1840 addChild(deque, children.get(i)); 1841 } 1842 } 1843 } finally { 1844 children.clear(); 1845 } 1846 } 1847 addChild(ArrayDeque deque, View child)1848 private void addChild(ArrayDeque deque, View child) { 1849 if (isShown(child)) { 1850 AccessibilityNodeProvider provider = child.getAccessibilityNodeProvider(); 1851 if (provider == null) { 1852 deque.push(new ViewNode(child)); 1853 } else { 1854 deque.push(new VirtualNode(AccessibilityNodeProvider.HOST_VIEW_ID, 1855 provider)); 1856 } 1857 } 1858 } 1859 } 1860 1861 private class VirtualNode implements DequeNode { 1862 long mInfoId; 1863 AccessibilityNodeProvider mProvider; 1864 VirtualNode(long id, AccessibilityNodeProvider provider)1865 VirtualNode(long id, AccessibilityNodeProvider provider) { 1866 mInfoId = id; 1867 mProvider = provider; 1868 } 1869 @Override getA11yNodeInfo()1870 public AccessibilityNodeInfo getA11yNodeInfo() { 1871 if (mProvider == null) { 1872 return null; 1873 } 1874 return mProvider.createAccessibilityNodeInfo( 1875 AccessibilityNodeInfo.getVirtualDescendantId(mInfoId)); 1876 } 1877 1878 @Override addChildren(AccessibilityNodeInfo virtualRoot, PrefetchDeque deque)1879 public void addChildren(AccessibilityNodeInfo virtualRoot, PrefetchDeque deque) { 1880 if (virtualRoot == null) { 1881 return; 1882 } 1883 final int childCount = virtualRoot.getChildCount(); 1884 if (deque.isStack()) { 1885 for (int i = childCount - 1; i >= 0; i--) { 1886 final long childNodeId = virtualRoot.getChildId(i); 1887 deque.push(new VirtualNode(childNodeId, mProvider)); 1888 } 1889 } else { 1890 for (int i = 0; i < childCount; i++) { 1891 final long childNodeId = virtualRoot.getChildId(i); 1892 deque.push(new VirtualNode(childNodeId, mProvider)); 1893 } 1894 } 1895 } 1896 } 1897 } 1898