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