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 android.graphics.Point; 20 import android.graphics.Rect; 21 import android.graphics.Region; 22 import android.os.Bundle; 23 import android.os.Handler; 24 import android.os.Looper; 25 import android.os.Message; 26 import android.os.Process; 27 import android.os.RemoteException; 28 import android.util.LongSparseArray; 29 import android.view.View.AttachInfo; 30 import android.view.accessibility.AccessibilityInteractionClient; 31 import android.view.accessibility.AccessibilityNodeInfo; 32 import android.view.accessibility.AccessibilityNodeProvider; 33 import android.view.accessibility.IAccessibilityInteractionConnectionCallback; 34 35 import com.android.internal.os.SomeArgs; 36 import com.android.internal.util.Predicate; 37 38 import java.util.ArrayList; 39 import java.util.HashMap; 40 import java.util.HashSet; 41 import java.util.LinkedList; 42 import java.util.List; 43 import java.util.Map; 44 import java.util.Queue; 45 46 /** 47 * Class for managing accessibility interactions initiated from the system 48 * and targeting the view hierarchy. A *ClientThread method is to be 49 * called from the interaction connection ViewAncestor gives the system to 50 * talk to it and a corresponding *UiThread method that is executed on the 51 * UI thread. 52 */ 53 final class AccessibilityInteractionController { 54 55 private static final boolean ENFORCE_NODE_TREE_CONSISTENT = false; 56 57 private final ArrayList<AccessibilityNodeInfo> mTempAccessibilityNodeInfoList = 58 new ArrayList<AccessibilityNodeInfo>(); 59 60 private final Handler mHandler; 61 62 private final ViewRootImpl mViewRootImpl; 63 64 private final AccessibilityNodePrefetcher mPrefetcher; 65 66 private final long mMyLooperThreadId; 67 68 private final int mMyProcessId; 69 70 private final ArrayList<View> mTempArrayList = new ArrayList<View>(); 71 72 private final Point mTempPoint = new Point(); 73 private final Rect mTempRect = new Rect(); 74 private final Rect mTempRect1 = new Rect(); 75 private final Rect mTempRect2 = new Rect(); 76 77 private AddNodeInfosForViewId mAddNodeInfosForViewId; 78 AccessibilityInteractionController(ViewRootImpl viewRootImpl)79 public AccessibilityInteractionController(ViewRootImpl viewRootImpl) { 80 Looper looper = viewRootImpl.mHandler.getLooper(); 81 mMyLooperThreadId = looper.getThread().getId(); 82 mMyProcessId = Process.myPid(); 83 mHandler = new PrivateHandler(looper); 84 mViewRootImpl = viewRootImpl; 85 mPrefetcher = new AccessibilityNodePrefetcher(); 86 } 87 isShown(View view)88 private boolean isShown(View view) { 89 // The first two checks are made also made by isShown() which 90 // however traverses the tree up to the parent to catch that. 91 // Therefore, we do some fail fast check to minimize the up 92 // tree traversal. 93 return (view.mAttachInfo != null 94 && view.mAttachInfo.mWindowVisibility == View.VISIBLE 95 && view.isShown()); 96 } 97 findAccessibilityNodeInfoByAccessibilityIdClientThread( long accessibilityNodeId, Region interactiveRegion, int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid, long interrogatingTid, MagnificationSpec spec)98 public void findAccessibilityNodeInfoByAccessibilityIdClientThread( 99 long accessibilityNodeId, Region interactiveRegion, int interactionId, 100 IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid, 101 long interrogatingTid, MagnificationSpec spec) { 102 Message message = mHandler.obtainMessage(); 103 message.what = PrivateHandler.MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_ACCESSIBILITY_ID; 104 message.arg1 = flags; 105 106 SomeArgs args = SomeArgs.obtain(); 107 args.argi1 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId); 108 args.argi2 = AccessibilityNodeInfo.getVirtualDescendantId(accessibilityNodeId); 109 args.argi3 = interactionId; 110 args.arg1 = callback; 111 args.arg2 = spec; 112 args.arg3 = interactiveRegion; 113 message.obj = args; 114 115 // If the interrogation is performed by the same thread as the main UI 116 // thread in this process, set the message as a static reference so 117 // after this call completes the same thread but in the interrogating 118 // client can handle the message to generate the result. 119 if (interrogatingPid == mMyProcessId && interrogatingTid == mMyLooperThreadId) { 120 AccessibilityInteractionClient.getInstanceForThread( 121 interrogatingTid).setSameThreadMessage(message); 122 } else { 123 mHandler.sendMessage(message); 124 } 125 } 126 findAccessibilityNodeInfoByAccessibilityIdUiThread(Message message)127 private void findAccessibilityNodeInfoByAccessibilityIdUiThread(Message message) { 128 final int flags = message.arg1; 129 130 SomeArgs args = (SomeArgs) message.obj; 131 final int accessibilityViewId = args.argi1; 132 final int virtualDescendantId = args.argi2; 133 final int interactionId = args.argi3; 134 final IAccessibilityInteractionConnectionCallback callback = 135 (IAccessibilityInteractionConnectionCallback) args.arg1; 136 final MagnificationSpec spec = (MagnificationSpec) args.arg2; 137 final Region interactiveRegion = (Region) args.arg3; 138 139 args.recycle(); 140 141 List<AccessibilityNodeInfo> infos = mTempAccessibilityNodeInfoList; 142 infos.clear(); 143 try { 144 if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) { 145 return; 146 } 147 mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags; 148 View root = null; 149 if (accessibilityViewId == AccessibilityNodeInfo.UNDEFINED_ITEM_ID) { 150 root = mViewRootImpl.mView; 151 } else { 152 root = findViewByAccessibilityId(accessibilityViewId); 153 } 154 if (root != null && isShown(root)) { 155 mPrefetcher.prefetchAccessibilityNodeInfos(root, virtualDescendantId, flags, infos); 156 } 157 } finally { 158 try { 159 mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0; 160 applyAppScaleAndMagnificationSpecIfNeeded(infos, spec); 161 if (spec != null) { 162 spec.recycle(); 163 } 164 adjustIsVisibleToUserIfNeeded(infos, interactiveRegion); 165 callback.setFindAccessibilityNodeInfosResult(infos, interactionId); 166 infos.clear(); 167 } catch (RemoteException re) { 168 /* ignore - the other side will time out */ 169 } 170 } 171 } 172 findAccessibilityNodeInfosByViewIdClientThread(long accessibilityNodeId, String viewId, Region interactiveRegion, int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid, long interrogatingTid, MagnificationSpec spec)173 public void findAccessibilityNodeInfosByViewIdClientThread(long accessibilityNodeId, 174 String viewId, Region interactiveRegion, int interactionId, 175 IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid, 176 long interrogatingTid, MagnificationSpec spec) { 177 Message message = mHandler.obtainMessage(); 178 message.what = PrivateHandler.MSG_FIND_ACCESSIBILITY_NODE_INFOS_BY_VIEW_ID; 179 message.arg1 = flags; 180 message.arg2 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId); 181 182 SomeArgs args = SomeArgs.obtain(); 183 args.argi1 = interactionId; 184 args.arg1 = callback; 185 args.arg2 = spec; 186 args.arg3 = viewId; 187 args.arg4 = interactiveRegion; 188 189 message.obj = args; 190 191 // If the interrogation is performed by the same thread as the main UI 192 // thread in this process, set the message as a static reference so 193 // after this call completes the same thread but in the interrogating 194 // client can handle the message to generate the result. 195 if (interrogatingPid == mMyProcessId && interrogatingTid == mMyLooperThreadId) { 196 AccessibilityInteractionClient.getInstanceForThread( 197 interrogatingTid).setSameThreadMessage(message); 198 } else { 199 mHandler.sendMessage(message); 200 } 201 } 202 findAccessibilityNodeInfosByViewIdUiThread(Message message)203 private void findAccessibilityNodeInfosByViewIdUiThread(Message message) { 204 final int flags = message.arg1; 205 final int accessibilityViewId = message.arg2; 206 207 SomeArgs args = (SomeArgs) message.obj; 208 final int interactionId = args.argi1; 209 final IAccessibilityInteractionConnectionCallback callback = 210 (IAccessibilityInteractionConnectionCallback) args.arg1; 211 final MagnificationSpec spec = (MagnificationSpec) args.arg2; 212 final String viewId = (String) args.arg3; 213 final Region interactiveRegion = (Region) args.arg4; 214 215 args.recycle(); 216 217 final List<AccessibilityNodeInfo> infos = mTempAccessibilityNodeInfoList; 218 infos.clear(); 219 try { 220 if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) { 221 return; 222 } 223 mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags; 224 View root = null; 225 if (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) { 226 root = findViewByAccessibilityId(accessibilityViewId); 227 } else { 228 root = mViewRootImpl.mView; 229 } 230 if (root != null) { 231 final int resolvedViewId = root.getContext().getResources() 232 .getIdentifier(viewId, null, null); 233 if (resolvedViewId <= 0) { 234 return; 235 } 236 if (mAddNodeInfosForViewId == null) { 237 mAddNodeInfosForViewId = new AddNodeInfosForViewId(); 238 } 239 mAddNodeInfosForViewId.init(resolvedViewId, infos); 240 root.findViewByPredicate(mAddNodeInfosForViewId); 241 mAddNodeInfosForViewId.reset(); 242 } 243 } finally { 244 try { 245 mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0; 246 applyAppScaleAndMagnificationSpecIfNeeded(infos, spec); 247 if (spec != null) { 248 spec.recycle(); 249 } 250 adjustIsVisibleToUserIfNeeded(infos, interactiveRegion); 251 callback.setFindAccessibilityNodeInfosResult(infos, interactionId); 252 } catch (RemoteException re) { 253 /* ignore - the other side will time out */ 254 } 255 } 256 } 257 findAccessibilityNodeInfosByTextClientThread(long accessibilityNodeId, String text, Region interactiveRegion, int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid, long interrogatingTid, MagnificationSpec spec)258 public void findAccessibilityNodeInfosByTextClientThread(long accessibilityNodeId, 259 String text, Region interactiveRegion, int interactionId, 260 IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid, 261 long interrogatingTid, MagnificationSpec spec) { 262 Message message = mHandler.obtainMessage(); 263 message.what = PrivateHandler.MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_TEXT; 264 message.arg1 = flags; 265 266 SomeArgs args = SomeArgs.obtain(); 267 args.arg1 = text; 268 args.arg2 = callback; 269 args.arg3 = spec; 270 args.argi1 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId); 271 args.argi2 = AccessibilityNodeInfo.getVirtualDescendantId(accessibilityNodeId); 272 args.argi3 = interactionId; 273 args.arg4 = interactiveRegion; 274 message.obj = args; 275 276 // If the interrogation is performed by the same thread as the main UI 277 // thread in this process, set the message as a static reference so 278 // after this call completes the same thread but in the interrogating 279 // client can handle the message to generate the result. 280 if (interrogatingPid == mMyProcessId && interrogatingTid == mMyLooperThreadId) { 281 AccessibilityInteractionClient.getInstanceForThread( 282 interrogatingTid).setSameThreadMessage(message); 283 } else { 284 mHandler.sendMessage(message); 285 } 286 } 287 findAccessibilityNodeInfosByTextUiThread(Message message)288 private void findAccessibilityNodeInfosByTextUiThread(Message message) { 289 final int flags = message.arg1; 290 291 SomeArgs args = (SomeArgs) message.obj; 292 final String text = (String) args.arg1; 293 final IAccessibilityInteractionConnectionCallback callback = 294 (IAccessibilityInteractionConnectionCallback) args.arg2; 295 final MagnificationSpec spec = (MagnificationSpec) args.arg3; 296 final int accessibilityViewId = args.argi1; 297 final int virtualDescendantId = args.argi2; 298 final int interactionId = args.argi3; 299 final Region interactiveRegion = (Region) args.arg4; 300 args.recycle(); 301 302 List<AccessibilityNodeInfo> infos = null; 303 try { 304 if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) { 305 return; 306 } 307 mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags; 308 View root = null; 309 if (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) { 310 root = findViewByAccessibilityId(accessibilityViewId); 311 } else { 312 root = mViewRootImpl.mView; 313 } 314 if (root != null && isShown(root)) { 315 AccessibilityNodeProvider provider = root.getAccessibilityNodeProvider(); 316 if (provider != null) { 317 if (virtualDescendantId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) { 318 infos = provider.findAccessibilityNodeInfosByText(text, 319 virtualDescendantId); 320 } else { 321 infos = provider.findAccessibilityNodeInfosByText(text, 322 AccessibilityNodeProvider.HOST_VIEW_ID); 323 } 324 } else if (virtualDescendantId == AccessibilityNodeInfo.UNDEFINED_ITEM_ID) { 325 ArrayList<View> foundViews = mTempArrayList; 326 foundViews.clear(); 327 root.findViewsWithText(foundViews, text, View.FIND_VIEWS_WITH_TEXT 328 | View.FIND_VIEWS_WITH_CONTENT_DESCRIPTION 329 | View.FIND_VIEWS_WITH_ACCESSIBILITY_NODE_PROVIDERS); 330 if (!foundViews.isEmpty()) { 331 infos = mTempAccessibilityNodeInfoList; 332 infos.clear(); 333 final int viewCount = foundViews.size(); 334 for (int i = 0; i < viewCount; i++) { 335 View foundView = foundViews.get(i); 336 if (isShown(foundView)) { 337 provider = foundView.getAccessibilityNodeProvider(); 338 if (provider != null) { 339 List<AccessibilityNodeInfo> infosFromProvider = 340 provider.findAccessibilityNodeInfosByText(text, 341 AccessibilityNodeProvider.HOST_VIEW_ID); 342 if (infosFromProvider != null) { 343 infos.addAll(infosFromProvider); 344 } 345 } else { 346 infos.add(foundView.createAccessibilityNodeInfo()); 347 } 348 } 349 } 350 } 351 } 352 } 353 } finally { 354 try { 355 mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0; 356 applyAppScaleAndMagnificationSpecIfNeeded(infos, spec); 357 if (spec != null) { 358 spec.recycle(); 359 } 360 adjustIsVisibleToUserIfNeeded(infos, interactiveRegion); 361 callback.setFindAccessibilityNodeInfosResult(infos, interactionId); 362 } catch (RemoteException re) { 363 /* ignore - the other side will time out */ 364 } 365 } 366 } 367 findFocusClientThread(long accessibilityNodeId, int focusType, Region interactiveRegion, int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags, int interogatingPid, long interrogatingTid, MagnificationSpec spec)368 public void findFocusClientThread(long accessibilityNodeId, int focusType, 369 Region interactiveRegion, int interactionId, 370 IAccessibilityInteractionConnectionCallback callback, int flags, int interogatingPid, 371 long interrogatingTid, MagnificationSpec spec) { 372 Message message = mHandler.obtainMessage(); 373 message.what = PrivateHandler.MSG_FIND_FOCUS; 374 message.arg1 = flags; 375 message.arg2 = focusType; 376 377 SomeArgs args = SomeArgs.obtain(); 378 args.argi1 = interactionId; 379 args.argi2 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId); 380 args.argi3 = AccessibilityNodeInfo.getVirtualDescendantId(accessibilityNodeId); 381 args.arg1 = callback; 382 args.arg2 = spec; 383 args.arg3 = interactiveRegion; 384 385 message.obj = args; 386 387 // If the interrogation is performed by the same thread as the main UI 388 // thread in this process, set the message as a static reference so 389 // after this call completes the same thread but in the interrogating 390 // client can handle the message to generate the result. 391 if (interogatingPid == mMyProcessId && interrogatingTid == mMyLooperThreadId) { 392 AccessibilityInteractionClient.getInstanceForThread( 393 interrogatingTid).setSameThreadMessage(message); 394 } else { 395 mHandler.sendMessage(message); 396 } 397 } 398 findFocusUiThread(Message message)399 private void findFocusUiThread(Message message) { 400 final int flags = message.arg1; 401 final int focusType = message.arg2; 402 403 SomeArgs args = (SomeArgs) message.obj; 404 final int interactionId = args.argi1; 405 final int accessibilityViewId = args.argi2; 406 final int virtualDescendantId = args.argi3; 407 final IAccessibilityInteractionConnectionCallback callback = 408 (IAccessibilityInteractionConnectionCallback) args.arg1; 409 final MagnificationSpec spec = (MagnificationSpec) args.arg2; 410 final Region interactiveRegion = (Region) args.arg3; 411 args.recycle(); 412 413 AccessibilityNodeInfo focused = null; 414 try { 415 if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) { 416 return; 417 } 418 mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags; 419 View root = null; 420 if (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) { 421 root = findViewByAccessibilityId(accessibilityViewId); 422 } else { 423 root = mViewRootImpl.mView; 424 } 425 if (root != null && isShown(root)) { 426 switch (focusType) { 427 case AccessibilityNodeInfo.FOCUS_ACCESSIBILITY: { 428 View host = mViewRootImpl.mAccessibilityFocusedHost; 429 // If there is no accessibility focus host or it is not a descendant 430 // of the root from which to start the search, then the search failed. 431 if (host == null || !ViewRootImpl.isViewDescendantOf(host, root)) { 432 break; 433 } 434 // The focused view not shown, we failed. 435 if (!isShown(host)) { 436 break; 437 } 438 // If the host has a provider ask this provider to search for the 439 // focus instead fetching all provider nodes to do the search here. 440 AccessibilityNodeProvider provider = host.getAccessibilityNodeProvider(); 441 if (provider != null) { 442 if (mViewRootImpl.mAccessibilityFocusedVirtualView != null) { 443 focused = AccessibilityNodeInfo.obtain( 444 mViewRootImpl.mAccessibilityFocusedVirtualView); 445 } 446 } else if (virtualDescendantId == AccessibilityNodeInfo.UNDEFINED_ITEM_ID) { 447 focused = host.createAccessibilityNodeInfo(); 448 } 449 } break; 450 case AccessibilityNodeInfo.FOCUS_INPUT: { 451 View target = root.findFocus(); 452 if (target == null || !isShown(target)) { 453 break; 454 } 455 AccessibilityNodeProvider provider = target.getAccessibilityNodeProvider(); 456 if (provider != null) { 457 focused = provider.findFocus(focusType); 458 } 459 if (focused == null) { 460 focused = target.createAccessibilityNodeInfo(); 461 } 462 } break; 463 default: 464 throw new IllegalArgumentException("Unknown focus type: " + focusType); 465 } 466 } 467 } finally { 468 try { 469 mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0; 470 applyAppScaleAndMagnificationSpecIfNeeded(focused, spec); 471 if (spec != null) { 472 spec.recycle(); 473 } 474 adjustIsVisibleToUserIfNeeded(focused, interactiveRegion); 475 callback.setFindAccessibilityNodeInfoResult(focused, interactionId); 476 } catch (RemoteException re) { 477 /* ignore - the other side will time out */ 478 } 479 } 480 } 481 focusSearchClientThread(long accessibilityNodeId, int direction, Region interactiveRegion, int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags, int interogatingPid, long interrogatingTid, MagnificationSpec spec)482 public void focusSearchClientThread(long accessibilityNodeId, int direction, 483 Region interactiveRegion, int interactionId, 484 IAccessibilityInteractionConnectionCallback callback, int flags, int interogatingPid, 485 long interrogatingTid, MagnificationSpec spec) { 486 Message message = mHandler.obtainMessage(); 487 message.what = PrivateHandler.MSG_FOCUS_SEARCH; 488 message.arg1 = flags; 489 message.arg2 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId); 490 491 SomeArgs args = SomeArgs.obtain(); 492 args.argi2 = direction; 493 args.argi3 = interactionId; 494 args.arg1 = callback; 495 args.arg2 = spec; 496 args.arg3 = interactiveRegion; 497 498 message.obj = args; 499 500 // If the interrogation is performed by the same thread as the main UI 501 // thread in this process, set the message as a static reference so 502 // after this call completes the same thread but in the interrogating 503 // client can handle the message to generate the result. 504 if (interogatingPid == mMyProcessId && interrogatingTid == mMyLooperThreadId) { 505 AccessibilityInteractionClient.getInstanceForThread( 506 interrogatingTid).setSameThreadMessage(message); 507 } else { 508 mHandler.sendMessage(message); 509 } 510 } 511 focusSearchUiThread(Message message)512 private void focusSearchUiThread(Message message) { 513 final int flags = message.arg1; 514 final int accessibilityViewId = message.arg2; 515 516 SomeArgs args = (SomeArgs) message.obj; 517 final int direction = args.argi2; 518 final int interactionId = args.argi3; 519 final IAccessibilityInteractionConnectionCallback callback = 520 (IAccessibilityInteractionConnectionCallback) args.arg1; 521 final MagnificationSpec spec = (MagnificationSpec) args.arg2; 522 final Region interactiveRegion = (Region) args.arg3; 523 524 args.recycle(); 525 526 AccessibilityNodeInfo next = null; 527 try { 528 if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) { 529 return; 530 } 531 mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags; 532 View root = null; 533 if (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) { 534 root = findViewByAccessibilityId(accessibilityViewId); 535 } else { 536 root = mViewRootImpl.mView; 537 } 538 if (root != null && isShown(root)) { 539 View nextView = root.focusSearch(direction); 540 if (nextView != null) { 541 next = nextView.createAccessibilityNodeInfo(); 542 } 543 } 544 } finally { 545 try { 546 mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0; 547 applyAppScaleAndMagnificationSpecIfNeeded(next, spec); 548 if (spec != null) { 549 spec.recycle(); 550 } 551 adjustIsVisibleToUserIfNeeded(next, interactiveRegion); 552 callback.setFindAccessibilityNodeInfoResult(next, interactionId); 553 } catch (RemoteException re) { 554 /* ignore - the other side will time out */ 555 } 556 } 557 } 558 performAccessibilityActionClientThread(long accessibilityNodeId, int action, Bundle arguments, int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags, int interogatingPid, long interrogatingTid)559 public void performAccessibilityActionClientThread(long accessibilityNodeId, int action, 560 Bundle arguments, int interactionId, 561 IAccessibilityInteractionConnectionCallback callback, int flags, int interogatingPid, 562 long interrogatingTid) { 563 Message message = mHandler.obtainMessage(); 564 message.what = PrivateHandler.MSG_PERFORM_ACCESSIBILITY_ACTION; 565 message.arg1 = flags; 566 message.arg2 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId); 567 568 SomeArgs args = SomeArgs.obtain(); 569 args.argi1 = AccessibilityNodeInfo.getVirtualDescendantId(accessibilityNodeId); 570 args.argi2 = action; 571 args.argi3 = interactionId; 572 args.arg1 = callback; 573 args.arg2 = arguments; 574 575 message.obj = args; 576 577 // If the interrogation is performed by the same thread as the main UI 578 // thread in this process, set the message as a static reference so 579 // after this call completes the same thread but in the interrogating 580 // client can handle the message to generate the result. 581 if (interogatingPid == mMyProcessId && interrogatingTid == mMyLooperThreadId) { 582 AccessibilityInteractionClient.getInstanceForThread( 583 interrogatingTid).setSameThreadMessage(message); 584 } else { 585 mHandler.sendMessage(message); 586 } 587 } 588 perfromAccessibilityActionUiThread(Message message)589 private void perfromAccessibilityActionUiThread(Message message) { 590 final int flags = message.arg1; 591 final int accessibilityViewId = message.arg2; 592 593 SomeArgs args = (SomeArgs) message.obj; 594 final int virtualDescendantId = args.argi1; 595 final int action = args.argi2; 596 final int interactionId = args.argi3; 597 final IAccessibilityInteractionConnectionCallback callback = 598 (IAccessibilityInteractionConnectionCallback) args.arg1; 599 Bundle arguments = (Bundle) args.arg2; 600 601 args.recycle(); 602 603 boolean succeeded = false; 604 try { 605 if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) { 606 return; 607 } 608 mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags; 609 View target = null; 610 if (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) { 611 target = findViewByAccessibilityId(accessibilityViewId); 612 } else { 613 target = mViewRootImpl.mView; 614 } 615 if (target != null && isShown(target)) { 616 AccessibilityNodeProvider provider = target.getAccessibilityNodeProvider(); 617 if (provider != null) { 618 if (virtualDescendantId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) { 619 succeeded = provider.performAction(virtualDescendantId, action, 620 arguments); 621 } else { 622 succeeded = provider.performAction(AccessibilityNodeProvider.HOST_VIEW_ID, 623 action, arguments); 624 } 625 } else if (virtualDescendantId == AccessibilityNodeInfo.UNDEFINED_ITEM_ID) { 626 succeeded = target.performAccessibilityAction(action, arguments); 627 } 628 } 629 } finally { 630 try { 631 mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0; 632 callback.setPerformAccessibilityActionResult(succeeded, interactionId); 633 } catch (RemoteException re) { 634 /* ignore - the other side will time out */ 635 } 636 } 637 } 638 findViewByAccessibilityId(int accessibilityId)639 private View findViewByAccessibilityId(int accessibilityId) { 640 View root = mViewRootImpl.mView; 641 if (root == null) { 642 return null; 643 } 644 View foundView = root.findViewByAccessibilityId(accessibilityId); 645 if (foundView != null && !isShown(foundView)) { 646 return null; 647 } 648 return foundView; 649 } 650 applyAppScaleAndMagnificationSpecIfNeeded(List<AccessibilityNodeInfo> infos, MagnificationSpec spec)651 private void applyAppScaleAndMagnificationSpecIfNeeded(List<AccessibilityNodeInfo> infos, 652 MagnificationSpec spec) { 653 if (infos == null) { 654 return; 655 } 656 final float applicationScale = mViewRootImpl.mAttachInfo.mApplicationScale; 657 if (shouldApplyAppScaleAndMagnificationSpec(applicationScale, spec)) { 658 final int infoCount = infos.size(); 659 for (int i = 0; i < infoCount; i++) { 660 AccessibilityNodeInfo info = infos.get(i); 661 applyAppScaleAndMagnificationSpecIfNeeded(info, spec); 662 } 663 } 664 } 665 adjustIsVisibleToUserIfNeeded(List<AccessibilityNodeInfo> infos, Region interactiveRegion)666 private void adjustIsVisibleToUserIfNeeded(List<AccessibilityNodeInfo> infos, 667 Region interactiveRegion) { 668 if (interactiveRegion == null || infos == null) { 669 return; 670 } 671 final int infoCount = infos.size(); 672 for (int i = 0; i < infoCount; i++) { 673 AccessibilityNodeInfo info = infos.get(i); 674 adjustIsVisibleToUserIfNeeded(info, interactiveRegion); 675 } 676 } 677 adjustIsVisibleToUserIfNeeded(AccessibilityNodeInfo info, Region interactiveRegion)678 private void adjustIsVisibleToUserIfNeeded(AccessibilityNodeInfo info, 679 Region interactiveRegion) { 680 if (interactiveRegion == null || info == null) { 681 return; 682 } 683 Rect boundsInScreen = mTempRect; 684 info.getBoundsInScreen(boundsInScreen); 685 if (interactiveRegion.quickReject(boundsInScreen)) { 686 info.setVisibleToUser(false); 687 } 688 } 689 applyAppScaleAndMagnificationSpecIfNeeded(Point point, MagnificationSpec spec)690 private void applyAppScaleAndMagnificationSpecIfNeeded(Point point, 691 MagnificationSpec spec) { 692 final float applicationScale = mViewRootImpl.mAttachInfo.mApplicationScale; 693 if (!shouldApplyAppScaleAndMagnificationSpec(applicationScale, spec)) { 694 return; 695 } 696 697 if (applicationScale != 1.0f) { 698 point.x *= applicationScale; 699 point.y *= applicationScale; 700 } 701 702 if (spec != null) { 703 point.x *= spec.scale; 704 point.y *= spec.scale; 705 point.x += (int) spec.offsetX; 706 point.y += (int) spec.offsetY; 707 } 708 } 709 applyAppScaleAndMagnificationSpecIfNeeded(AccessibilityNodeInfo info, MagnificationSpec spec)710 private void applyAppScaleAndMagnificationSpecIfNeeded(AccessibilityNodeInfo info, 711 MagnificationSpec spec) { 712 if (info == null) { 713 return; 714 } 715 716 final float applicationScale = mViewRootImpl.mAttachInfo.mApplicationScale; 717 if (!shouldApplyAppScaleAndMagnificationSpec(applicationScale, spec)) { 718 return; 719 } 720 721 Rect boundsInParent = mTempRect; 722 Rect boundsInScreen = mTempRect1; 723 724 info.getBoundsInParent(boundsInParent); 725 info.getBoundsInScreen(boundsInScreen); 726 if (applicationScale != 1.0f) { 727 boundsInParent.scale(applicationScale); 728 boundsInScreen.scale(applicationScale); 729 } 730 if (spec != null) { 731 boundsInParent.scale(spec.scale); 732 // boundsInParent must not be offset. 733 boundsInScreen.scale(spec.scale); 734 boundsInScreen.offset((int) spec.offsetX, (int) spec.offsetY); 735 } 736 info.setBoundsInParent(boundsInParent); 737 info.setBoundsInScreen(boundsInScreen); 738 739 if (spec != null) { 740 AttachInfo attachInfo = mViewRootImpl.mAttachInfo; 741 if (attachInfo.mDisplay == null) { 742 return; 743 } 744 745 final float scale = attachInfo.mApplicationScale * spec.scale; 746 747 Rect visibleWinFrame = mTempRect1; 748 visibleWinFrame.left = (int) (attachInfo.mWindowLeft * scale + spec.offsetX); 749 visibleWinFrame.top = (int) (attachInfo.mWindowTop * scale + spec.offsetY); 750 visibleWinFrame.right = (int) (visibleWinFrame.left + mViewRootImpl.mWidth * scale); 751 visibleWinFrame.bottom = (int) (visibleWinFrame.top + mViewRootImpl.mHeight * scale); 752 753 attachInfo.mDisplay.getRealSize(mTempPoint); 754 final int displayWidth = mTempPoint.x; 755 final int displayHeight = mTempPoint.y; 756 757 Rect visibleDisplayFrame = mTempRect2; 758 visibleDisplayFrame.set(0, 0, displayWidth, displayHeight); 759 760 visibleWinFrame.intersect(visibleDisplayFrame); 761 762 if (!visibleWinFrame.intersects(boundsInScreen.left, boundsInScreen.top, 763 boundsInScreen.right, boundsInScreen.bottom)) { 764 info.setVisibleToUser(false); 765 } 766 } 767 } 768 shouldApplyAppScaleAndMagnificationSpec(float appScale, MagnificationSpec spec)769 private boolean shouldApplyAppScaleAndMagnificationSpec(float appScale, 770 MagnificationSpec spec) { 771 return (appScale != 1.0f || (spec != null && !spec.isNop())); 772 } 773 774 /** 775 * This class encapsulates a prefetching strategy for the accessibility APIs for 776 * querying window content. It is responsible to prefetch a batch of 777 * AccessibilityNodeInfos in addition to the one for a requested node. 778 */ 779 private class AccessibilityNodePrefetcher { 780 781 private static final int MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE = 50; 782 783 private final ArrayList<View> mTempViewList = new ArrayList<View>(); 784 prefetchAccessibilityNodeInfos(View view, int virtualViewId, int fetchFlags, List<AccessibilityNodeInfo> outInfos)785 public void prefetchAccessibilityNodeInfos(View view, int virtualViewId, int fetchFlags, 786 List<AccessibilityNodeInfo> outInfos) { 787 AccessibilityNodeProvider provider = view.getAccessibilityNodeProvider(); 788 if (provider == null) { 789 AccessibilityNodeInfo root = view.createAccessibilityNodeInfo(); 790 if (root != null) { 791 outInfos.add(root); 792 if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS) != 0) { 793 prefetchPredecessorsOfRealNode(view, outInfos); 794 } 795 if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS) != 0) { 796 prefetchSiblingsOfRealNode(view, outInfos); 797 } 798 if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS) != 0) { 799 prefetchDescendantsOfRealNode(view, outInfos); 800 } 801 } 802 } else { 803 final AccessibilityNodeInfo root; 804 if (virtualViewId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) { 805 root = provider.createAccessibilityNodeInfo(virtualViewId); 806 } else { 807 root = provider.createAccessibilityNodeInfo( 808 AccessibilityNodeProvider.HOST_VIEW_ID); 809 } 810 if (root != null) { 811 outInfos.add(root); 812 if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS) != 0) { 813 prefetchPredecessorsOfVirtualNode(root, view, provider, outInfos); 814 } 815 if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS) != 0) { 816 prefetchSiblingsOfVirtualNode(root, view, provider, outInfos); 817 } 818 if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS) != 0) { 819 prefetchDescendantsOfVirtualNode(root, provider, outInfos); 820 } 821 } 822 } 823 if (ENFORCE_NODE_TREE_CONSISTENT) { 824 enforceNodeTreeConsistent(outInfos); 825 } 826 } 827 enforceNodeTreeConsistent(List<AccessibilityNodeInfo> nodes)828 private void enforceNodeTreeConsistent(List<AccessibilityNodeInfo> nodes) { 829 LongSparseArray<AccessibilityNodeInfo> nodeMap = 830 new LongSparseArray<AccessibilityNodeInfo>(); 831 final int nodeCount = nodes.size(); 832 for (int i = 0; i < nodeCount; i++) { 833 AccessibilityNodeInfo node = nodes.get(i); 834 nodeMap.put(node.getSourceNodeId(), node); 835 } 836 837 // If the nodes are a tree it does not matter from 838 // which node we start to search for the root. 839 AccessibilityNodeInfo root = nodeMap.valueAt(0); 840 AccessibilityNodeInfo parent = root; 841 while (parent != null) { 842 root = parent; 843 parent = nodeMap.get(parent.getParentNodeId()); 844 } 845 846 // Traverse the tree and do some checks. 847 AccessibilityNodeInfo accessFocus = null; 848 AccessibilityNodeInfo inputFocus = null; 849 HashSet<AccessibilityNodeInfo> seen = new HashSet<AccessibilityNodeInfo>(); 850 Queue<AccessibilityNodeInfo> fringe = new LinkedList<AccessibilityNodeInfo>(); 851 fringe.add(root); 852 853 while (!fringe.isEmpty()) { 854 AccessibilityNodeInfo current = fringe.poll(); 855 856 // Check for duplicates 857 if (!seen.add(current)) { 858 throw new IllegalStateException("Duplicate node: " 859 + current + " in window:" 860 + mViewRootImpl.mAttachInfo.mAccessibilityWindowId); 861 } 862 863 // Check for one accessibility focus. 864 if (current.isAccessibilityFocused()) { 865 if (accessFocus != null) { 866 throw new IllegalStateException("Duplicate accessibility focus:" 867 + current 868 + " in window:" + mViewRootImpl.mAttachInfo.mAccessibilityWindowId); 869 } else { 870 accessFocus = current; 871 } 872 } 873 874 // Check for one input focus. 875 if (current.isFocused()) { 876 if (inputFocus != null) { 877 throw new IllegalStateException("Duplicate input focus: " 878 + current + " in window:" 879 + mViewRootImpl.mAttachInfo.mAccessibilityWindowId); 880 } else { 881 inputFocus = current; 882 } 883 } 884 885 final int childCount = current.getChildCount(); 886 for (int j = 0; j < childCount; j++) { 887 final long childId = current.getChildId(j); 888 final AccessibilityNodeInfo child = nodeMap.get(childId); 889 if (child != null) { 890 fringe.add(child); 891 } 892 } 893 } 894 895 // Check for disconnected nodes. 896 for (int j = nodeMap.size() - 1; j >= 0; j--) { 897 AccessibilityNodeInfo info = nodeMap.valueAt(j); 898 if (!seen.contains(info)) { 899 throw new IllegalStateException("Disconnected node: " + info); 900 } 901 } 902 } 903 prefetchPredecessorsOfRealNode(View view, List<AccessibilityNodeInfo> outInfos)904 private void prefetchPredecessorsOfRealNode(View view, 905 List<AccessibilityNodeInfo> outInfos) { 906 ViewParent parent = view.getParentForAccessibility(); 907 while (parent instanceof View 908 && outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) { 909 View parentView = (View) parent; 910 AccessibilityNodeInfo info = parentView.createAccessibilityNodeInfo(); 911 if (info != null) { 912 outInfos.add(info); 913 } 914 parent = parent.getParentForAccessibility(); 915 } 916 } 917 prefetchSiblingsOfRealNode(View current, List<AccessibilityNodeInfo> outInfos)918 private void prefetchSiblingsOfRealNode(View current, 919 List<AccessibilityNodeInfo> outInfos) { 920 ViewParent parent = current.getParentForAccessibility(); 921 if (parent instanceof ViewGroup) { 922 ViewGroup parentGroup = (ViewGroup) parent; 923 ArrayList<View> children = mTempViewList; 924 children.clear(); 925 try { 926 parentGroup.addChildrenForAccessibility(children); 927 final int childCount = children.size(); 928 for (int i = 0; i < childCount; i++) { 929 if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) { 930 return; 931 } 932 View child = children.get(i); 933 if (child.getAccessibilityViewId() != current.getAccessibilityViewId() 934 && isShown(child)) { 935 AccessibilityNodeInfo info = null; 936 AccessibilityNodeProvider provider = 937 child.getAccessibilityNodeProvider(); 938 if (provider == null) { 939 info = child.createAccessibilityNodeInfo(); 940 } else { 941 info = provider.createAccessibilityNodeInfo( 942 AccessibilityNodeProvider.HOST_VIEW_ID); 943 } 944 if (info != null) { 945 outInfos.add(info); 946 } 947 } 948 } 949 } finally { 950 children.clear(); 951 } 952 } 953 } 954 prefetchDescendantsOfRealNode(View root, List<AccessibilityNodeInfo> outInfos)955 private void prefetchDescendantsOfRealNode(View root, 956 List<AccessibilityNodeInfo> outInfos) { 957 if (!(root instanceof ViewGroup)) { 958 return; 959 } 960 HashMap<View, AccessibilityNodeInfo> addedChildren = 961 new HashMap<View, AccessibilityNodeInfo>(); 962 ArrayList<View> children = mTempViewList; 963 children.clear(); 964 try { 965 root.addChildrenForAccessibility(children); 966 final int childCount = children.size(); 967 for (int i = 0; i < childCount; i++) { 968 if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) { 969 return; 970 } 971 View child = children.get(i); 972 if (isShown(child)) { 973 AccessibilityNodeProvider provider = child.getAccessibilityNodeProvider(); 974 if (provider == null) { 975 AccessibilityNodeInfo info = child.createAccessibilityNodeInfo(); 976 if (info != null) { 977 outInfos.add(info); 978 addedChildren.put(child, null); 979 } 980 } else { 981 AccessibilityNodeInfo info = provider.createAccessibilityNodeInfo( 982 AccessibilityNodeProvider.HOST_VIEW_ID); 983 if (info != null) { 984 outInfos.add(info); 985 addedChildren.put(child, info); 986 } 987 } 988 } 989 } 990 } finally { 991 children.clear(); 992 } 993 if (outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) { 994 for (Map.Entry<View, AccessibilityNodeInfo> entry : addedChildren.entrySet()) { 995 View addedChild = entry.getKey(); 996 AccessibilityNodeInfo virtualRoot = entry.getValue(); 997 if (virtualRoot == null) { 998 prefetchDescendantsOfRealNode(addedChild, outInfos); 999 } else { 1000 AccessibilityNodeProvider provider = 1001 addedChild.getAccessibilityNodeProvider(); 1002 prefetchDescendantsOfVirtualNode(virtualRoot, provider, outInfos); 1003 } 1004 } 1005 } 1006 } 1007 prefetchPredecessorsOfVirtualNode(AccessibilityNodeInfo root, View providerHost, AccessibilityNodeProvider provider, List<AccessibilityNodeInfo> outInfos)1008 private void prefetchPredecessorsOfVirtualNode(AccessibilityNodeInfo root, 1009 View providerHost, AccessibilityNodeProvider provider, 1010 List<AccessibilityNodeInfo> outInfos) { 1011 long parentNodeId = root.getParentNodeId(); 1012 int accessibilityViewId = AccessibilityNodeInfo.getAccessibilityViewId(parentNodeId); 1013 while (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) { 1014 if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) { 1015 return; 1016 } 1017 final int virtualDescendantId = 1018 AccessibilityNodeInfo.getVirtualDescendantId(parentNodeId); 1019 if (virtualDescendantId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID 1020 || accessibilityViewId == providerHost.getAccessibilityViewId()) { 1021 final AccessibilityNodeInfo parent; 1022 if (virtualDescendantId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) { 1023 parent = provider.createAccessibilityNodeInfo(virtualDescendantId); 1024 } else { 1025 parent = provider.createAccessibilityNodeInfo( 1026 AccessibilityNodeProvider.HOST_VIEW_ID); 1027 } 1028 if (parent == null) { 1029 // Couldn't obtain the parent, which means we have a 1030 // disconnected sub-tree. Abort prefetch immediately. 1031 return; 1032 } 1033 outInfos.add(parent); 1034 parentNodeId = parent.getParentNodeId(); 1035 accessibilityViewId = AccessibilityNodeInfo.getAccessibilityViewId( 1036 parentNodeId); 1037 } else { 1038 prefetchPredecessorsOfRealNode(providerHost, outInfos); 1039 return; 1040 } 1041 } 1042 } 1043 prefetchSiblingsOfVirtualNode(AccessibilityNodeInfo current, View providerHost, AccessibilityNodeProvider provider, List<AccessibilityNodeInfo> outInfos)1044 private void prefetchSiblingsOfVirtualNode(AccessibilityNodeInfo current, View providerHost, 1045 AccessibilityNodeProvider provider, List<AccessibilityNodeInfo> outInfos) { 1046 final long parentNodeId = current.getParentNodeId(); 1047 final int parentAccessibilityViewId = 1048 AccessibilityNodeInfo.getAccessibilityViewId(parentNodeId); 1049 final int parentVirtualDescendantId = 1050 AccessibilityNodeInfo.getVirtualDescendantId(parentNodeId); 1051 if (parentVirtualDescendantId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID 1052 || parentAccessibilityViewId == providerHost.getAccessibilityViewId()) { 1053 final AccessibilityNodeInfo parent; 1054 if (parentVirtualDescendantId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) { 1055 parent = provider.createAccessibilityNodeInfo(parentVirtualDescendantId); 1056 } else { 1057 parent = provider.createAccessibilityNodeInfo( 1058 AccessibilityNodeProvider.HOST_VIEW_ID); 1059 } 1060 if (parent != null) { 1061 final int childCount = parent.getChildCount(); 1062 for (int i = 0; i < childCount; i++) { 1063 if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) { 1064 return; 1065 } 1066 final long childNodeId = parent.getChildId(i); 1067 if (childNodeId != current.getSourceNodeId()) { 1068 final int childVirtualDescendantId = 1069 AccessibilityNodeInfo.getVirtualDescendantId(childNodeId); 1070 AccessibilityNodeInfo child = provider.createAccessibilityNodeInfo( 1071 childVirtualDescendantId); 1072 if (child != null) { 1073 outInfos.add(child); 1074 } 1075 } 1076 } 1077 } 1078 } else { 1079 prefetchSiblingsOfRealNode(providerHost, outInfos); 1080 } 1081 } 1082 prefetchDescendantsOfVirtualNode(AccessibilityNodeInfo root, AccessibilityNodeProvider provider, List<AccessibilityNodeInfo> outInfos)1083 private void prefetchDescendantsOfVirtualNode(AccessibilityNodeInfo root, 1084 AccessibilityNodeProvider provider, List<AccessibilityNodeInfo> outInfos) { 1085 final int initialOutInfosSize = outInfos.size(); 1086 final int childCount = root.getChildCount(); 1087 for (int i = 0; i < childCount; i++) { 1088 if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) { 1089 return; 1090 } 1091 final long childNodeId = root.getChildId(i); 1092 AccessibilityNodeInfo child = provider.createAccessibilityNodeInfo( 1093 AccessibilityNodeInfo.getVirtualDescendantId(childNodeId)); 1094 if (child != null) { 1095 outInfos.add(child); 1096 } 1097 } 1098 if (outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) { 1099 final int addedChildCount = outInfos.size() - initialOutInfosSize; 1100 for (int i = 0; i < addedChildCount; i++) { 1101 AccessibilityNodeInfo child = outInfos.get(initialOutInfosSize + i); 1102 prefetchDescendantsOfVirtualNode(child, provider, outInfos); 1103 } 1104 } 1105 } 1106 } 1107 1108 private class PrivateHandler extends Handler { 1109 private final static int MSG_PERFORM_ACCESSIBILITY_ACTION = 1; 1110 private final static int MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_ACCESSIBILITY_ID = 2; 1111 private final static int MSG_FIND_ACCESSIBILITY_NODE_INFOS_BY_VIEW_ID = 3; 1112 private final static int MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_TEXT = 4; 1113 private final static int MSG_FIND_FOCUS = 5; 1114 private final static int MSG_FOCUS_SEARCH = 6; 1115 PrivateHandler(Looper looper)1116 public PrivateHandler(Looper looper) { 1117 super(looper); 1118 } 1119 1120 @Override getMessageName(Message message)1121 public String getMessageName(Message message) { 1122 final int type = message.what; 1123 switch (type) { 1124 case MSG_PERFORM_ACCESSIBILITY_ACTION: 1125 return "MSG_PERFORM_ACCESSIBILITY_ACTION"; 1126 case MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_ACCESSIBILITY_ID: 1127 return "MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_ACCESSIBILITY_ID"; 1128 case MSG_FIND_ACCESSIBILITY_NODE_INFOS_BY_VIEW_ID: 1129 return "MSG_FIND_ACCESSIBILITY_NODE_INFOS_BY_VIEW_ID"; 1130 case MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_TEXT: 1131 return "MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_TEXT"; 1132 case MSG_FIND_FOCUS: 1133 return "MSG_FIND_FOCUS"; 1134 case MSG_FOCUS_SEARCH: 1135 return "MSG_FOCUS_SEARCH"; 1136 default: 1137 throw new IllegalArgumentException("Unknown message type: " + type); 1138 } 1139 } 1140 1141 @Override handleMessage(Message message)1142 public void handleMessage(Message message) { 1143 final int type = message.what; 1144 switch (type) { 1145 case MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_ACCESSIBILITY_ID: { 1146 findAccessibilityNodeInfoByAccessibilityIdUiThread(message); 1147 } break; 1148 case MSG_PERFORM_ACCESSIBILITY_ACTION: { 1149 perfromAccessibilityActionUiThread(message); 1150 } break; 1151 case MSG_FIND_ACCESSIBILITY_NODE_INFOS_BY_VIEW_ID: { 1152 findAccessibilityNodeInfosByViewIdUiThread(message); 1153 } break; 1154 case MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_TEXT: { 1155 findAccessibilityNodeInfosByTextUiThread(message); 1156 } break; 1157 case MSG_FIND_FOCUS: { 1158 findFocusUiThread(message); 1159 } break; 1160 case MSG_FOCUS_SEARCH: { 1161 focusSearchUiThread(message); 1162 } break; 1163 default: 1164 throw new IllegalArgumentException("Unknown message type: " + type); 1165 } 1166 } 1167 } 1168 1169 private final class AddNodeInfosForViewId implements Predicate<View> { 1170 private int mViewId = View.NO_ID; 1171 private List<AccessibilityNodeInfo> mInfos; 1172 init(int viewId, List<AccessibilityNodeInfo> infos)1173 public void init(int viewId, List<AccessibilityNodeInfo> infos) { 1174 mViewId = viewId; 1175 mInfos = infos; 1176 } 1177 reset()1178 public void reset() { 1179 mViewId = View.NO_ID; 1180 mInfos = null; 1181 } 1182 1183 @Override apply(View view)1184 public boolean apply(View view) { 1185 if (view.getId() == mViewId && isShown(view)) { 1186 mInfos.add(view.createAccessibilityNodeInfo()); 1187 } 1188 return false; 1189 } 1190 } 1191 } 1192