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 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 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 158 private boolean isShown(View view) { 159 return (view != null) && (view.getWindowVisibility() == View.VISIBLE && view.isShown()); 160 } 161 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 */ 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 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 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 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 296 private void requestPreparerTimeoutUiThread() { 297 synchronized (mLock) { 298 Slog.e(LOG_TAG, "AccessibilityRequestPreparer timed out"); 299 scheduleAllMessagesWaitingForRequestPreparerLocked(); 300 } 301 } 302 303 @GuardedBy("mLock") 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 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 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 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 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 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 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 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 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 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 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 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 */ 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 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 */ 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 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 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 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 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 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 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 833 private void applyAppScaleAndMagnificationSpecIfNeeded(AccessibilityNodeInfo info, 834 MagnificationSpec spec) { 835 if (info == null) { 836 return; 837 } 838 839 final float applicationScale = mViewRootImpl.mAttachInfo.mApplicationScale; 840 if (!shouldApplyAppScaleAndMagnificationSpec(applicationScale, spec)) { 841 return; 842 } 843 844 Rect boundsInParent = mTempRect; 845 Rect boundsInScreen = mTempRect1; 846 847 info.getBoundsInParent(boundsInParent); 848 info.getBoundsInScreen(boundsInScreen); 849 if (applicationScale != 1.0f) { 850 boundsInParent.scale(applicationScale); 851 boundsInScreen.scale(applicationScale); 852 } 853 if (spec != null) { 854 boundsInParent.scale(spec.scale); 855 // boundsInParent must not be offset. 856 boundsInScreen.scale(spec.scale); 857 boundsInScreen.offset((int) spec.offsetX, (int) spec.offsetY); 858 } 859 info.setBoundsInParent(boundsInParent); 860 info.setBoundsInScreen(boundsInScreen); 861 862 // Scale text locations if they are present 863 if (info.hasExtras()) { 864 Bundle extras = info.getExtras(); 865 Parcelable[] textLocations = 866 extras.getParcelableArray(EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY); 867 if (textLocations != null) { 868 for (int i = 0; i < textLocations.length; i++) { 869 // Unchecked cast - an app that puts other objects in this bundle with this 870 // key will crash. 871 RectF textLocation = ((RectF) textLocations[i]); 872 textLocation.scale(applicationScale); 873 if (spec != null) { 874 textLocation.scale(spec.scale); 875 textLocation.offset(spec.offsetX, spec.offsetY); 876 } 877 } 878 } 879 } 880 881 if (spec != null) { 882 AttachInfo attachInfo = mViewRootImpl.mAttachInfo; 883 if (attachInfo.mDisplay == null) { 884 return; 885 } 886 887 final float scale = attachInfo.mApplicationScale * spec.scale; 888 889 Rect visibleWinFrame = mTempRect1; 890 visibleWinFrame.left = (int) (attachInfo.mWindowLeft * scale + spec.offsetX); 891 visibleWinFrame.top = (int) (attachInfo.mWindowTop * scale + spec.offsetY); 892 visibleWinFrame.right = (int) (visibleWinFrame.left + mViewRootImpl.mWidth * scale); 893 visibleWinFrame.bottom = (int) (visibleWinFrame.top + mViewRootImpl.mHeight * scale); 894 895 attachInfo.mDisplay.getRealSize(mTempPoint); 896 final int displayWidth = mTempPoint.x; 897 final int displayHeight = mTempPoint.y; 898 899 Rect visibleDisplayFrame = mTempRect2; 900 visibleDisplayFrame.set(0, 0, displayWidth, displayHeight); 901 902 if (!visibleWinFrame.intersect(visibleDisplayFrame)) { 903 // If there's no intersection with display, set visibleWinFrame empty. 904 visibleDisplayFrame.setEmpty(); 905 } 906 907 if (!visibleWinFrame.intersects(boundsInScreen.left, boundsInScreen.top, 908 boundsInScreen.right, boundsInScreen.bottom)) { 909 info.setVisibleToUser(false); 910 } 911 } 912 } 913 914 private boolean shouldApplyAppScaleAndMagnificationSpec(float appScale, 915 MagnificationSpec spec) { 916 return (appScale != 1.0f || (spec != null && !spec.isNop())); 917 } 918 919 private void updateInfosForViewportAndReturnFindNodeResult(List<AccessibilityNodeInfo> infos, 920 IAccessibilityInteractionConnectionCallback callback, int interactionId, 921 MagnificationSpec spec, Region interactiveRegion) { 922 try { 923 mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0; 924 applyAppScaleAndMagnificationSpecIfNeeded(infos, spec); 925 adjustIsVisibleToUserIfNeeded(infos, interactiveRegion); 926 callback.setFindAccessibilityNodeInfosResult(infos, interactionId); 927 if (infos != null) { 928 infos.clear(); 929 } 930 } catch (RemoteException re) { 931 /* ignore - the other side will time out */ 932 } finally { 933 recycleMagnificationSpecAndRegionIfNeeded(spec, interactiveRegion); 934 } 935 } 936 937 private void updateInfoForViewportAndReturnFindNodeResult(AccessibilityNodeInfo info, 938 IAccessibilityInteractionConnectionCallback callback, int interactionId, 939 MagnificationSpec spec, Region interactiveRegion) { 940 try { 941 mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0; 942 applyAppScaleAndMagnificationSpecIfNeeded(info, spec); 943 adjustIsVisibleToUserIfNeeded(info, interactiveRegion); 944 callback.setFindAccessibilityNodeInfoResult(info, interactionId); 945 } catch (RemoteException re) { 946 /* ignore - the other side will time out */ 947 } finally { 948 recycleMagnificationSpecAndRegionIfNeeded(spec, interactiveRegion); 949 } 950 } 951 952 private void recycleMagnificationSpecAndRegionIfNeeded(MagnificationSpec spec, Region region) { 953 if (android.os.Process.myPid() != Binder.getCallingPid()) { 954 // Specs are cached in the system process and obtained from a pool when read from 955 // a parcel, so only recycle the spec if called from another process. 956 if (spec != null) { 957 spec.recycle(); 958 } 959 } else { 960 // Regions are obtained in the system process and instantiated when read from 961 // a parcel, so only recycle the region if caled from the same process. 962 if (region != null) { 963 region.recycle(); 964 } 965 } 966 } 967 968 private boolean handleClickableSpanActionUiThread( 969 View view, int virtualDescendantId, Bundle arguments) { 970 Parcelable span = arguments.getParcelable(ACTION_ARGUMENT_ACCESSIBLE_CLICKABLE_SPAN); 971 if (!(span instanceof AccessibilityClickableSpan)) { 972 return false; 973 } 974 975 // Find the original ClickableSpan if it's still on the screen 976 AccessibilityNodeInfo infoWithSpan = null; 977 AccessibilityNodeProvider provider = view.getAccessibilityNodeProvider(); 978 if (provider != null) { 979 infoWithSpan = provider.createAccessibilityNodeInfo(virtualDescendantId); 980 } else if (virtualDescendantId == AccessibilityNodeProvider.HOST_VIEW_ID) { 981 infoWithSpan = view.createAccessibilityNodeInfo(); 982 } 983 if (infoWithSpan == null) { 984 return false; 985 } 986 987 // Click on the corresponding span 988 ClickableSpan clickableSpan = ((AccessibilityClickableSpan) span).findClickableSpan( 989 infoWithSpan.getOriginalText()); 990 if (clickableSpan != null) { 991 clickableSpan.onClick(view); 992 return true; 993 } 994 return false; 995 } 996 997 /** 998 * This class encapsulates a prefetching strategy for the accessibility APIs for 999 * querying window content. It is responsible to prefetch a batch of 1000 * AccessibilityNodeInfos in addition to the one for a requested node. 1001 */ 1002 private class AccessibilityNodePrefetcher { 1003 1004 private static final int MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE = 50; 1005 1006 private final ArrayList<View> mTempViewList = new ArrayList<View>(); 1007 1008 public void prefetchAccessibilityNodeInfos(View view, int virtualViewId, int fetchFlags, 1009 List<AccessibilityNodeInfo> outInfos, Bundle arguments) { 1010 AccessibilityNodeProvider provider = view.getAccessibilityNodeProvider(); 1011 // Determine if we'll be populating extra data 1012 final String extraDataRequested = (arguments == null) ? null 1013 : arguments.getString(EXTRA_DATA_REQUESTED_KEY); 1014 if (provider == null) { 1015 AccessibilityNodeInfo root = view.createAccessibilityNodeInfo(); 1016 if (root != null) { 1017 if (extraDataRequested != null) { 1018 view.addExtraDataToAccessibilityNodeInfo( 1019 root, extraDataRequested, arguments); 1020 } 1021 outInfos.add(root); 1022 if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS) != 0) { 1023 prefetchPredecessorsOfRealNode(view, outInfos); 1024 } 1025 if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS) != 0) { 1026 prefetchSiblingsOfRealNode(view, outInfos); 1027 } 1028 if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS) != 0) { 1029 prefetchDescendantsOfRealNode(view, outInfos); 1030 } 1031 } 1032 } else { 1033 final AccessibilityNodeInfo root = 1034 provider.createAccessibilityNodeInfo(virtualViewId); 1035 if (root != null) { 1036 if (extraDataRequested != null) { 1037 provider.addExtraDataToAccessibilityNodeInfo( 1038 virtualViewId, root, extraDataRequested, arguments); 1039 } 1040 outInfos.add(root); 1041 if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS) != 0) { 1042 prefetchPredecessorsOfVirtualNode(root, view, provider, outInfos); 1043 } 1044 if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS) != 0) { 1045 prefetchSiblingsOfVirtualNode(root, view, provider, outInfos); 1046 } 1047 if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS) != 0) { 1048 prefetchDescendantsOfVirtualNode(root, provider, outInfos); 1049 } 1050 } 1051 } 1052 if (ENFORCE_NODE_TREE_CONSISTENT) { 1053 enforceNodeTreeConsistent(outInfos); 1054 } 1055 } 1056 1057 private void enforceNodeTreeConsistent(List<AccessibilityNodeInfo> nodes) { 1058 LongSparseArray<AccessibilityNodeInfo> nodeMap = 1059 new LongSparseArray<AccessibilityNodeInfo>(); 1060 final int nodeCount = nodes.size(); 1061 for (int i = 0; i < nodeCount; i++) { 1062 AccessibilityNodeInfo node = nodes.get(i); 1063 nodeMap.put(node.getSourceNodeId(), node); 1064 } 1065 1066 // If the nodes are a tree it does not matter from 1067 // which node we start to search for the root. 1068 AccessibilityNodeInfo root = nodeMap.valueAt(0); 1069 AccessibilityNodeInfo parent = root; 1070 while (parent != null) { 1071 root = parent; 1072 parent = nodeMap.get(parent.getParentNodeId()); 1073 } 1074 1075 // Traverse the tree and do some checks. 1076 AccessibilityNodeInfo accessFocus = null; 1077 AccessibilityNodeInfo inputFocus = null; 1078 HashSet<AccessibilityNodeInfo> seen = new HashSet<AccessibilityNodeInfo>(); 1079 Queue<AccessibilityNodeInfo> fringe = new LinkedList<AccessibilityNodeInfo>(); 1080 fringe.add(root); 1081 1082 while (!fringe.isEmpty()) { 1083 AccessibilityNodeInfo current = fringe.poll(); 1084 1085 // Check for duplicates 1086 if (!seen.add(current)) { 1087 throw new IllegalStateException("Duplicate node: " 1088 + current + " in window:" 1089 + mViewRootImpl.mAttachInfo.mAccessibilityWindowId); 1090 } 1091 1092 // Check for one accessibility focus. 1093 if (current.isAccessibilityFocused()) { 1094 if (accessFocus != null) { 1095 throw new IllegalStateException("Duplicate accessibility focus:" 1096 + current 1097 + " in window:" + mViewRootImpl.mAttachInfo.mAccessibilityWindowId); 1098 } else { 1099 accessFocus = current; 1100 } 1101 } 1102 1103 // Check for one input focus. 1104 if (current.isFocused()) { 1105 if (inputFocus != null) { 1106 throw new IllegalStateException("Duplicate input focus: " 1107 + current + " in window:" 1108 + mViewRootImpl.mAttachInfo.mAccessibilityWindowId); 1109 } else { 1110 inputFocus = current; 1111 } 1112 } 1113 1114 final int childCount = current.getChildCount(); 1115 for (int j = 0; j < childCount; j++) { 1116 final long childId = current.getChildId(j); 1117 final AccessibilityNodeInfo child = nodeMap.get(childId); 1118 if (child != null) { 1119 fringe.add(child); 1120 } 1121 } 1122 } 1123 1124 // Check for disconnected nodes. 1125 for (int j = nodeMap.size() - 1; j >= 0; j--) { 1126 AccessibilityNodeInfo info = nodeMap.valueAt(j); 1127 if (!seen.contains(info)) { 1128 throw new IllegalStateException("Disconnected node: " + info); 1129 } 1130 } 1131 } 1132 1133 private void prefetchPredecessorsOfRealNode(View view, 1134 List<AccessibilityNodeInfo> outInfos) { 1135 ViewParent parent = view.getParentForAccessibility(); 1136 while (parent instanceof View 1137 && outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) { 1138 View parentView = (View) parent; 1139 AccessibilityNodeInfo info = parentView.createAccessibilityNodeInfo(); 1140 if (info != null) { 1141 outInfos.add(info); 1142 } 1143 parent = parent.getParentForAccessibility(); 1144 } 1145 } 1146 1147 private void prefetchSiblingsOfRealNode(View current, 1148 List<AccessibilityNodeInfo> outInfos) { 1149 ViewParent parent = current.getParentForAccessibility(); 1150 if (parent instanceof ViewGroup) { 1151 ViewGroup parentGroup = (ViewGroup) parent; 1152 ArrayList<View> children = mTempViewList; 1153 children.clear(); 1154 try { 1155 parentGroup.addChildrenForAccessibility(children); 1156 final int childCount = children.size(); 1157 for (int i = 0; i < childCount; i++) { 1158 if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) { 1159 return; 1160 } 1161 View child = children.get(i); 1162 if (child.getAccessibilityViewId() != current.getAccessibilityViewId() 1163 && isShown(child)) { 1164 AccessibilityNodeInfo info = null; 1165 AccessibilityNodeProvider provider = 1166 child.getAccessibilityNodeProvider(); 1167 if (provider == null) { 1168 info = child.createAccessibilityNodeInfo(); 1169 } else { 1170 info = provider.createAccessibilityNodeInfo( 1171 AccessibilityNodeProvider.HOST_VIEW_ID); 1172 } 1173 if (info != null) { 1174 outInfos.add(info); 1175 } 1176 } 1177 } 1178 } finally { 1179 children.clear(); 1180 } 1181 } 1182 } 1183 1184 private void prefetchDescendantsOfRealNode(View root, 1185 List<AccessibilityNodeInfo> outInfos) { 1186 if (!(root instanceof ViewGroup)) { 1187 return; 1188 } 1189 HashMap<View, AccessibilityNodeInfo> addedChildren = 1190 new HashMap<View, AccessibilityNodeInfo>(); 1191 ArrayList<View> children = mTempViewList; 1192 children.clear(); 1193 try { 1194 root.addChildrenForAccessibility(children); 1195 final int childCount = children.size(); 1196 for (int i = 0; i < childCount; i++) { 1197 if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) { 1198 return; 1199 } 1200 View child = children.get(i); 1201 if (isShown(child)) { 1202 AccessibilityNodeProvider provider = child.getAccessibilityNodeProvider(); 1203 if (provider == null) { 1204 AccessibilityNodeInfo info = child.createAccessibilityNodeInfo(); 1205 if (info != null) { 1206 outInfos.add(info); 1207 addedChildren.put(child, null); 1208 } 1209 } else { 1210 AccessibilityNodeInfo info = provider.createAccessibilityNodeInfo( 1211 AccessibilityNodeProvider.HOST_VIEW_ID); 1212 if (info != null) { 1213 outInfos.add(info); 1214 addedChildren.put(child, info); 1215 } 1216 } 1217 } 1218 } 1219 } finally { 1220 children.clear(); 1221 } 1222 if (outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) { 1223 for (Map.Entry<View, AccessibilityNodeInfo> entry : addedChildren.entrySet()) { 1224 View addedChild = entry.getKey(); 1225 AccessibilityNodeInfo virtualRoot = entry.getValue(); 1226 if (virtualRoot == null) { 1227 prefetchDescendantsOfRealNode(addedChild, outInfos); 1228 } else { 1229 AccessibilityNodeProvider provider = 1230 addedChild.getAccessibilityNodeProvider(); 1231 prefetchDescendantsOfVirtualNode(virtualRoot, provider, outInfos); 1232 } 1233 } 1234 } 1235 } 1236 1237 private void prefetchPredecessorsOfVirtualNode(AccessibilityNodeInfo root, 1238 View providerHost, AccessibilityNodeProvider provider, 1239 List<AccessibilityNodeInfo> outInfos) { 1240 final int initialResultSize = outInfos.size(); 1241 long parentNodeId = root.getParentNodeId(); 1242 int accessibilityViewId = AccessibilityNodeInfo.getAccessibilityViewId(parentNodeId); 1243 while (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) { 1244 if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) { 1245 return; 1246 } 1247 final int virtualDescendantId = 1248 AccessibilityNodeInfo.getVirtualDescendantId(parentNodeId); 1249 if (virtualDescendantId != AccessibilityNodeProvider.HOST_VIEW_ID 1250 || accessibilityViewId == providerHost.getAccessibilityViewId()) { 1251 final AccessibilityNodeInfo parent; 1252 parent = provider.createAccessibilityNodeInfo(virtualDescendantId); 1253 if (parent == null) { 1254 // Going up the parent relation we found a null predecessor, 1255 // so remove these disconnected nodes form the result. 1256 final int currentResultSize = outInfos.size(); 1257 for (int i = currentResultSize - 1; i >= initialResultSize; i--) { 1258 outInfos.remove(i); 1259 } 1260 // Couldn't obtain the parent, which means we have a 1261 // disconnected sub-tree. Abort prefetch immediately. 1262 return; 1263 } 1264 outInfos.add(parent); 1265 parentNodeId = parent.getParentNodeId(); 1266 accessibilityViewId = AccessibilityNodeInfo.getAccessibilityViewId( 1267 parentNodeId); 1268 } else { 1269 prefetchPredecessorsOfRealNode(providerHost, outInfos); 1270 return; 1271 } 1272 } 1273 } 1274 1275 private void prefetchSiblingsOfVirtualNode(AccessibilityNodeInfo current, View providerHost, 1276 AccessibilityNodeProvider provider, List<AccessibilityNodeInfo> outInfos) { 1277 final long parentNodeId = current.getParentNodeId(); 1278 final int parentAccessibilityViewId = 1279 AccessibilityNodeInfo.getAccessibilityViewId(parentNodeId); 1280 final int parentVirtualDescendantId = 1281 AccessibilityNodeInfo.getVirtualDescendantId(parentNodeId); 1282 if (parentVirtualDescendantId != AccessibilityNodeProvider.HOST_VIEW_ID 1283 || parentAccessibilityViewId == providerHost.getAccessibilityViewId()) { 1284 final AccessibilityNodeInfo parent = 1285 provider.createAccessibilityNodeInfo(parentVirtualDescendantId); 1286 if (parent != null) { 1287 final int childCount = parent.getChildCount(); 1288 for (int i = 0; i < childCount; i++) { 1289 if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) { 1290 return; 1291 } 1292 final long childNodeId = parent.getChildId(i); 1293 if (childNodeId != current.getSourceNodeId()) { 1294 final int childVirtualDescendantId = 1295 AccessibilityNodeInfo.getVirtualDescendantId(childNodeId); 1296 AccessibilityNodeInfo child = provider.createAccessibilityNodeInfo( 1297 childVirtualDescendantId); 1298 if (child != null) { 1299 outInfos.add(child); 1300 } 1301 } 1302 } 1303 } 1304 } else { 1305 prefetchSiblingsOfRealNode(providerHost, outInfos); 1306 } 1307 } 1308 1309 private void prefetchDescendantsOfVirtualNode(AccessibilityNodeInfo root, 1310 AccessibilityNodeProvider provider, List<AccessibilityNodeInfo> outInfos) { 1311 final int initialOutInfosSize = outInfos.size(); 1312 final int childCount = root.getChildCount(); 1313 for (int i = 0; i < childCount; i++) { 1314 if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) { 1315 return; 1316 } 1317 final long childNodeId = root.getChildId(i); 1318 AccessibilityNodeInfo child = provider.createAccessibilityNodeInfo( 1319 AccessibilityNodeInfo.getVirtualDescendantId(childNodeId)); 1320 if (child != null) { 1321 outInfos.add(child); 1322 } 1323 } 1324 if (outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) { 1325 final int addedChildCount = outInfos.size() - initialOutInfosSize; 1326 for (int i = 0; i < addedChildCount; i++) { 1327 AccessibilityNodeInfo child = outInfos.get(initialOutInfosSize + i); 1328 prefetchDescendantsOfVirtualNode(child, provider, outInfos); 1329 } 1330 } 1331 } 1332 } 1333 1334 private class PrivateHandler extends Handler { 1335 private static final int MSG_PERFORM_ACCESSIBILITY_ACTION = 1; 1336 private static final int MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_ACCESSIBILITY_ID = 2; 1337 private static final int MSG_FIND_ACCESSIBILITY_NODE_INFOS_BY_VIEW_ID = 3; 1338 private static final int MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_TEXT = 4; 1339 private static final int MSG_FIND_FOCUS = 5; 1340 private static final int MSG_FOCUS_SEARCH = 6; 1341 private static final int MSG_PREPARE_FOR_EXTRA_DATA_REQUEST = 7; 1342 private static final int MSG_APP_PREPARATION_FINISHED = 8; 1343 private static final int MSG_APP_PREPARATION_TIMEOUT = 9; 1344 1345 // Uses FIRST_NO_ACCESSIBILITY_CALLBACK_MSG for messages that don't need to call back 1346 // results to interrogating client. 1347 private static final int FIRST_NO_ACCESSIBILITY_CALLBACK_MSG = 100; 1348 private static final int MSG_CLEAR_ACCESSIBILITY_FOCUS = 1349 FIRST_NO_ACCESSIBILITY_CALLBACK_MSG + 1; 1350 private static final int MSG_NOTIFY_OUTSIDE_TOUCH = 1351 FIRST_NO_ACCESSIBILITY_CALLBACK_MSG + 2; 1352 1353 public PrivateHandler(Looper looper) { 1354 super(looper); 1355 } 1356 1357 @Override 1358 public String getMessageName(Message message) { 1359 final int type = message.what; 1360 switch (type) { 1361 case MSG_PERFORM_ACCESSIBILITY_ACTION: 1362 return "MSG_PERFORM_ACCESSIBILITY_ACTION"; 1363 case MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_ACCESSIBILITY_ID: 1364 return "MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_ACCESSIBILITY_ID"; 1365 case MSG_FIND_ACCESSIBILITY_NODE_INFOS_BY_VIEW_ID: 1366 return "MSG_FIND_ACCESSIBILITY_NODE_INFOS_BY_VIEW_ID"; 1367 case MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_TEXT: 1368 return "MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_TEXT"; 1369 case MSG_FIND_FOCUS: 1370 return "MSG_FIND_FOCUS"; 1371 case MSG_FOCUS_SEARCH: 1372 return "MSG_FOCUS_SEARCH"; 1373 case MSG_PREPARE_FOR_EXTRA_DATA_REQUEST: 1374 return "MSG_PREPARE_FOR_EXTRA_DATA_REQUEST"; 1375 case MSG_APP_PREPARATION_FINISHED: 1376 return "MSG_APP_PREPARATION_FINISHED"; 1377 case MSG_APP_PREPARATION_TIMEOUT: 1378 return "MSG_APP_PREPARATION_TIMEOUT"; 1379 case MSG_CLEAR_ACCESSIBILITY_FOCUS: 1380 return "MSG_CLEAR_ACCESSIBILITY_FOCUS"; 1381 case MSG_NOTIFY_OUTSIDE_TOUCH: 1382 return "MSG_NOTIFY_OUTSIDE_TOUCH"; 1383 default: 1384 throw new IllegalArgumentException("Unknown message type: " + type); 1385 } 1386 } 1387 1388 @Override 1389 public void handleMessage(Message message) { 1390 final int type = message.what; 1391 switch (type) { 1392 case MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_ACCESSIBILITY_ID: { 1393 findAccessibilityNodeInfoByAccessibilityIdUiThread(message); 1394 } break; 1395 case MSG_PERFORM_ACCESSIBILITY_ACTION: { 1396 performAccessibilityActionUiThread(message); 1397 } break; 1398 case MSG_FIND_ACCESSIBILITY_NODE_INFOS_BY_VIEW_ID: { 1399 findAccessibilityNodeInfosByViewIdUiThread(message); 1400 } break; 1401 case MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_TEXT: { 1402 findAccessibilityNodeInfosByTextUiThread(message); 1403 } break; 1404 case MSG_FIND_FOCUS: { 1405 findFocusUiThread(message); 1406 } break; 1407 case MSG_FOCUS_SEARCH: { 1408 focusSearchUiThread(message); 1409 } break; 1410 case MSG_PREPARE_FOR_EXTRA_DATA_REQUEST: { 1411 prepareForExtraDataRequestUiThread(message); 1412 } break; 1413 case MSG_APP_PREPARATION_FINISHED: { 1414 requestPreparerDoneUiThread(message); 1415 } break; 1416 case MSG_APP_PREPARATION_TIMEOUT: { 1417 requestPreparerTimeoutUiThread(); 1418 } break; 1419 case MSG_CLEAR_ACCESSIBILITY_FOCUS: { 1420 clearAccessibilityFocusUiThread(); 1421 } break; 1422 case MSG_NOTIFY_OUTSIDE_TOUCH: { 1423 notifyOutsideTouchUiThread(); 1424 } break; 1425 default: 1426 throw new IllegalArgumentException("Unknown message type: " + type); 1427 } 1428 } 1429 1430 boolean hasAccessibilityCallback(Message message) { 1431 return message.what < FIRST_NO_ACCESSIBILITY_CALLBACK_MSG ? true : false; 1432 } 1433 } 1434 1435 private final class AddNodeInfosForViewId implements Predicate<View> { 1436 private int mViewId = View.NO_ID; 1437 private List<AccessibilityNodeInfo> mInfos; 1438 1439 public void init(int viewId, List<AccessibilityNodeInfo> infos) { 1440 mViewId = viewId; 1441 mInfos = infos; 1442 } 1443 1444 public void reset() { 1445 mViewId = View.NO_ID; 1446 mInfos = null; 1447 } 1448 1449 @Override 1450 public boolean test(View view) { 1451 if (view.getId() == mViewId && isShown(view)) { 1452 mInfos.add(view.createAccessibilityNodeInfo()); 1453 } 1454 return false; 1455 } 1456 } 1457 1458 private static final class MessageHolder { 1459 final Message mMessage; 1460 final int mInterrogatingPid; 1461 final long mInterrogatingTid; 1462 1463 MessageHolder(Message message, int interrogatingPid, long interrogatingTid) { 1464 mMessage = message; 1465 mInterrogatingPid = interrogatingPid; 1466 mInterrogatingTid = interrogatingTid; 1467 } 1468 } 1469 } 1470