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