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