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