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 applyAppScaleAndMagnificationSpecIfNeeded(AccessibilityNodeInfo info, MagnificationSpec spec)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 shouldApplyAppScaleAndMagnificationSpec(float appScale, MagnificationSpec spec)914 private boolean shouldApplyAppScaleAndMagnificationSpec(float appScale, 915 MagnificationSpec spec) { 916 return (appScale != 1.0f || (spec != null && !spec.isNop())); 917 } 918 updateInfosForViewportAndReturnFindNodeResult(List<AccessibilityNodeInfo> infos, IAccessibilityInteractionConnectionCallback callback, int interactionId, MagnificationSpec spec, Region interactiveRegion)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 updateInfoForViewportAndReturnFindNodeResult(AccessibilityNodeInfo info, IAccessibilityInteractionConnectionCallback callback, int interactionId, MagnificationSpec spec, Region interactiveRegion)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 recycleMagnificationSpecAndRegionIfNeeded(MagnificationSpec spec, Region region)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 handleClickableSpanActionUiThread( View view, int virtualDescendantId, Bundle arguments)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 prefetchAccessibilityNodeInfos(View view, int virtualViewId, int fetchFlags, List<AccessibilityNodeInfo> outInfos, Bundle arguments)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 enforceNodeTreeConsistent(List<AccessibilityNodeInfo> nodes)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 prefetchPredecessorsOfRealNode(View view, List<AccessibilityNodeInfo> outInfos)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 prefetchSiblingsOfRealNode(View current, List<AccessibilityNodeInfo> outInfos)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 prefetchDescendantsOfRealNode(View root, List<AccessibilityNodeInfo> outInfos)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 prefetchPredecessorsOfVirtualNode(AccessibilityNodeInfo root, View providerHost, AccessibilityNodeProvider provider, List<AccessibilityNodeInfo> outInfos)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 prefetchSiblingsOfVirtualNode(AccessibilityNodeInfo current, View providerHost, AccessibilityNodeProvider provider, List<AccessibilityNodeInfo> outInfos)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 prefetchDescendantsOfVirtualNode(AccessibilityNodeInfo root, AccessibilityNodeProvider provider, List<AccessibilityNodeInfo> outInfos)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 PrivateHandler(Looper looper)1353 public PrivateHandler(Looper looper) { 1354 super(looper); 1355 } 1356 1357 @Override getMessageName(Message message)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 handleMessage(Message message)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 hasAccessibilityCallback(Message message)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 init(int viewId, List<AccessibilityNodeInfo> infos)1439 public void init(int viewId, List<AccessibilityNodeInfo> infos) { 1440 mViewId = viewId; 1441 mInfos = infos; 1442 } 1443 reset()1444 public void reset() { 1445 mViewId = View.NO_ID; 1446 mInfos = null; 1447 } 1448 1449 @Override test(View view)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 MessageHolder(Message message, int interrogatingPid, long interrogatingTid)1463 MessageHolder(Message message, int interrogatingPid, long interrogatingTid) { 1464 mMessage = message; 1465 mInterrogatingPid = interrogatingPid; 1466 mInterrogatingTid = interrogatingTid; 1467 } 1468 } 1469 } 1470