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