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