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 // If the host has a provider ask this provider to search for the 410 // focus instead fetching all provider nodes to do the search here. 411 AccessibilityNodeProvider provider = host.getAccessibilityNodeProvider(); 412 if (provider != null) { 413 if (mViewRootImpl.mAccessibilityFocusedVirtualView != null) { 414 focused = AccessibilityNodeInfo.obtain( 415 mViewRootImpl.mAccessibilityFocusedVirtualView); 416 } 417 } else if (virtualDescendantId == View.NO_ID) { 418 focused = host.createAccessibilityNodeInfo(); 419 } 420 } break; 421 case AccessibilityNodeInfo.FOCUS_INPUT: { 422 // Input focus cannot go to virtual views. 423 View target = root.findFocus(); 424 if (target != null && isShown(target)) { 425 focused = target.createAccessibilityNodeInfo(); 426 } 427 } break; 428 default: 429 throw new IllegalArgumentException("Unknown focus type: " + focusType); 430 } 431 } 432 } finally { 433 try { 434 mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0; 435 applyAppScaleAndMagnificationSpecIfNeeded(focused, spec); 436 if (spec != null) { 437 spec.recycle(); 438 } 439 callback.setFindAccessibilityNodeInfoResult(focused, interactionId); 440 } catch (RemoteException re) { 441 /* ignore - the other side will time out */ 442 } 443 } 444 } 445 446 public void focusSearchClientThread(long accessibilityNodeId, int direction, int interactionId, 447 IAccessibilityInteractionConnectionCallback callback, int flags, int interogatingPid, 448 long interrogatingTid, MagnificationSpec spec) { 449 Message message = mHandler.obtainMessage(); 450 message.what = PrivateHandler.MSG_FOCUS_SEARCH; 451 message.arg1 = flags; 452 message.arg2 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId); 453 454 SomeArgs args = SomeArgs.obtain(); 455 args.argi2 = direction; 456 args.argi3 = interactionId; 457 args.arg1 = callback; 458 args.arg2 = spec; 459 460 message.obj = args; 461 462 // If the interrogation is performed by the same thread as the main UI 463 // thread in this process, set the message as a static reference so 464 // after this call completes the same thread but in the interrogating 465 // client can handle the message to generate the result. 466 if (interogatingPid == mMyProcessId && interrogatingTid == mMyLooperThreadId) { 467 AccessibilityInteractionClient.getInstanceForThread( 468 interrogatingTid).setSameThreadMessage(message); 469 } else { 470 mHandler.sendMessage(message); 471 } 472 } 473 474 private void focusSearchUiThread(Message message) { 475 final int flags = message.arg1; 476 final int accessibilityViewId = message.arg2; 477 478 SomeArgs args = (SomeArgs) message.obj; 479 final int direction = args.argi2; 480 final int interactionId = args.argi3; 481 final IAccessibilityInteractionConnectionCallback callback = 482 (IAccessibilityInteractionConnectionCallback) args.arg1; 483 final MagnificationSpec spec = (MagnificationSpec) args.arg2; 484 485 args.recycle(); 486 487 AccessibilityNodeInfo next = null; 488 try { 489 if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) { 490 return; 491 } 492 mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags; 493 View root = null; 494 if (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED) { 495 root = findViewByAccessibilityId(accessibilityViewId); 496 } else { 497 root = mViewRootImpl.mView; 498 } 499 if (root != null && isShown(root)) { 500 View nextView = root.focusSearch(direction); 501 if (nextView != null) { 502 next = nextView.createAccessibilityNodeInfo(); 503 } 504 } 505 } finally { 506 try { 507 mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0; 508 applyAppScaleAndMagnificationSpecIfNeeded(next, spec); 509 if (spec != null) { 510 spec.recycle(); 511 } 512 callback.setFindAccessibilityNodeInfoResult(next, interactionId); 513 } catch (RemoteException re) { 514 /* ignore - the other side will time out */ 515 } 516 } 517 } 518 519 public void performAccessibilityActionClientThread(long accessibilityNodeId, int action, 520 Bundle arguments, int interactionId, 521 IAccessibilityInteractionConnectionCallback callback, int flags, int interogatingPid, 522 long interrogatingTid) { 523 Message message = mHandler.obtainMessage(); 524 message.what = PrivateHandler.MSG_PERFORM_ACCESSIBILITY_ACTION; 525 message.arg1 = flags; 526 message.arg2 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId); 527 528 SomeArgs args = SomeArgs.obtain(); 529 args.argi1 = AccessibilityNodeInfo.getVirtualDescendantId(accessibilityNodeId); 530 args.argi2 = action; 531 args.argi3 = interactionId; 532 args.arg1 = callback; 533 args.arg2 = arguments; 534 535 message.obj = args; 536 537 // If the interrogation is performed by the same thread as the main UI 538 // thread in this process, set the message as a static reference so 539 // after this call completes the same thread but in the interrogating 540 // client can handle the message to generate the result. 541 if (interogatingPid == mMyProcessId && interrogatingTid == mMyLooperThreadId) { 542 AccessibilityInteractionClient.getInstanceForThread( 543 interrogatingTid).setSameThreadMessage(message); 544 } else { 545 mHandler.sendMessage(message); 546 } 547 } 548 549 private void perfromAccessibilityActionUiThread(Message message) { 550 final int flags = message.arg1; 551 final int accessibilityViewId = message.arg2; 552 553 SomeArgs args = (SomeArgs) message.obj; 554 final int virtualDescendantId = args.argi1; 555 final int action = args.argi2; 556 final int interactionId = args.argi3; 557 final IAccessibilityInteractionConnectionCallback callback = 558 (IAccessibilityInteractionConnectionCallback) args.arg1; 559 Bundle arguments = (Bundle) args.arg2; 560 561 args.recycle(); 562 563 boolean succeeded = false; 564 try { 565 if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) { 566 return; 567 } 568 mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags; 569 View target = null; 570 if (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED) { 571 target = findViewByAccessibilityId(accessibilityViewId); 572 } else { 573 target = mViewRootImpl.mView; 574 } 575 if (target != null && isShown(target)) { 576 AccessibilityNodeProvider provider = target.getAccessibilityNodeProvider(); 577 if (provider != null) { 578 succeeded = provider.performAction(virtualDescendantId, action, 579 arguments); 580 } else if (virtualDescendantId == View.NO_ID) { 581 succeeded = target.performAccessibilityAction(action, arguments); 582 } 583 } 584 } finally { 585 try { 586 mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0; 587 callback.setPerformAccessibilityActionResult(succeeded, interactionId); 588 } catch (RemoteException re) { 589 /* ignore - the other side will time out */ 590 } 591 } 592 } 593 594 private View findViewByAccessibilityId(int accessibilityId) { 595 View root = mViewRootImpl.mView; 596 if (root == null) { 597 return null; 598 } 599 View foundView = root.findViewByAccessibilityId(accessibilityId); 600 if (foundView != null && !isShown(foundView)) { 601 return null; 602 } 603 return foundView; 604 } 605 606 private void applyAppScaleAndMagnificationSpecIfNeeded(List<AccessibilityNodeInfo> infos, 607 MagnificationSpec spec) { 608 if (infos == null) { 609 return; 610 } 611 final float applicationScale = mViewRootImpl.mAttachInfo.mApplicationScale; 612 if (shouldApplyAppScaleAndMagnificationSpec(applicationScale, spec)) { 613 final int infoCount = infos.size(); 614 for (int i = 0; i < infoCount; i++) { 615 AccessibilityNodeInfo info = infos.get(i); 616 applyAppScaleAndMagnificationSpecIfNeeded(info, spec); 617 } 618 } 619 } 620 621 private void applyAppScaleAndMagnificationSpecIfNeeded(AccessibilityNodeInfo info, 622 MagnificationSpec spec) { 623 if (info == null) { 624 return; 625 } 626 627 final float applicationScale = mViewRootImpl.mAttachInfo.mApplicationScale; 628 if (!shouldApplyAppScaleAndMagnificationSpec(applicationScale, spec)) { 629 return; 630 } 631 632 Rect boundsInParent = mTempRect; 633 Rect boundsInScreen = mTempRect1; 634 635 info.getBoundsInParent(boundsInParent); 636 info.getBoundsInScreen(boundsInScreen); 637 if (applicationScale != 1.0f) { 638 boundsInParent.scale(applicationScale); 639 boundsInScreen.scale(applicationScale); 640 } 641 if (spec != null) { 642 boundsInParent.scale(spec.scale); 643 // boundsInParent must not be offset. 644 boundsInScreen.scale(spec.scale); 645 boundsInScreen.offset((int) spec.offsetX, (int) spec.offsetY); 646 } 647 info.setBoundsInParent(boundsInParent); 648 info.setBoundsInScreen(boundsInScreen); 649 650 if (spec != null) { 651 AttachInfo attachInfo = mViewRootImpl.mAttachInfo; 652 if (attachInfo.mDisplay == null) { 653 return; 654 } 655 656 final float scale = attachInfo.mApplicationScale * spec.scale; 657 658 Rect visibleWinFrame = mTempRect1; 659 visibleWinFrame.left = (int) (attachInfo.mWindowLeft * scale + spec.offsetX); 660 visibleWinFrame.top = (int) (attachInfo.mWindowTop * scale + spec.offsetY); 661 visibleWinFrame.right = (int) (visibleWinFrame.left + mViewRootImpl.mWidth * scale); 662 visibleWinFrame.bottom = (int) (visibleWinFrame.top + mViewRootImpl.mHeight * scale); 663 664 attachInfo.mDisplay.getRealSize(mTempPoint); 665 final int displayWidth = mTempPoint.x; 666 final int displayHeight = mTempPoint.y; 667 668 Rect visibleDisplayFrame = mTempRect2; 669 visibleDisplayFrame.set(0, 0, displayWidth, displayHeight); 670 671 visibleWinFrame.intersect(visibleDisplayFrame); 672 673 if (!visibleWinFrame.intersects(boundsInScreen.left, boundsInScreen.top, 674 boundsInScreen.right, boundsInScreen.bottom)) { 675 info.setVisibleToUser(false); 676 } 677 } 678 } 679 680 private boolean shouldApplyAppScaleAndMagnificationSpec(float appScale, 681 MagnificationSpec spec) { 682 return (appScale != 1.0f || (spec != null && !spec.isNop())); 683 } 684 685 /** 686 * This class encapsulates a prefetching strategy for the accessibility APIs for 687 * querying window content. It is responsible to prefetch a batch of 688 * AccessibilityNodeInfos in addition to the one for a requested node. 689 */ 690 private class AccessibilityNodePrefetcher { 691 692 private static final int MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE = 50; 693 694 private final ArrayList<View> mTempViewList = new ArrayList<View>(); 695 696 public void prefetchAccessibilityNodeInfos(View view, int virtualViewId, int fetchFlags, 697 List<AccessibilityNodeInfo> outInfos) { 698 AccessibilityNodeProvider provider = view.getAccessibilityNodeProvider(); 699 if (provider == null) { 700 AccessibilityNodeInfo root = view.createAccessibilityNodeInfo(); 701 if (root != null) { 702 outInfos.add(root); 703 if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS) != 0) { 704 prefetchPredecessorsOfRealNode(view, outInfos); 705 } 706 if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS) != 0) { 707 prefetchSiblingsOfRealNode(view, outInfos); 708 } 709 if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS) != 0) { 710 prefetchDescendantsOfRealNode(view, outInfos); 711 } 712 } 713 } else { 714 AccessibilityNodeInfo root = provider.createAccessibilityNodeInfo(virtualViewId); 715 if (root != null) { 716 outInfos.add(root); 717 if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS) != 0) { 718 prefetchPredecessorsOfVirtualNode(root, view, provider, outInfos); 719 } 720 if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS) != 0) { 721 prefetchSiblingsOfVirtualNode(root, view, provider, outInfos); 722 } 723 if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS) != 0) { 724 prefetchDescendantsOfVirtualNode(root, provider, outInfos); 725 } 726 } 727 } 728 } 729 730 private void prefetchPredecessorsOfRealNode(View view, 731 List<AccessibilityNodeInfo> outInfos) { 732 ViewParent parent = view.getParentForAccessibility(); 733 while (parent instanceof View 734 && outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) { 735 View parentView = (View) parent; 736 AccessibilityNodeInfo info = parentView.createAccessibilityNodeInfo(); 737 if (info != null) { 738 outInfos.add(info); 739 } 740 parent = parent.getParentForAccessibility(); 741 } 742 } 743 744 private void prefetchSiblingsOfRealNode(View current, 745 List<AccessibilityNodeInfo> outInfos) { 746 ViewParent parent = current.getParentForAccessibility(); 747 if (parent instanceof ViewGroup) { 748 ViewGroup parentGroup = (ViewGroup) parent; 749 ArrayList<View> children = mTempViewList; 750 children.clear(); 751 try { 752 parentGroup.addChildrenForAccessibility(children); 753 final int childCount = children.size(); 754 for (int i = 0; i < childCount; i++) { 755 if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) { 756 return; 757 } 758 View child = children.get(i); 759 if (child.getAccessibilityViewId() != current.getAccessibilityViewId() 760 && isShown(child)) { 761 AccessibilityNodeInfo info = null; 762 AccessibilityNodeProvider provider = 763 child.getAccessibilityNodeProvider(); 764 if (provider == null) { 765 info = child.createAccessibilityNodeInfo(); 766 } else { 767 info = provider.createAccessibilityNodeInfo( 768 AccessibilityNodeInfo.UNDEFINED); 769 } 770 if (info != null) { 771 outInfos.add(info); 772 } 773 } 774 } 775 } finally { 776 children.clear(); 777 } 778 } 779 } 780 781 private void prefetchDescendantsOfRealNode(View root, 782 List<AccessibilityNodeInfo> outInfos) { 783 if (!(root instanceof ViewGroup)) { 784 return; 785 } 786 HashMap<View, AccessibilityNodeInfo> addedChildren = 787 new HashMap<View, AccessibilityNodeInfo>(); 788 ArrayList<View> children = mTempViewList; 789 children.clear(); 790 try { 791 root.addChildrenForAccessibility(children); 792 final int childCount = children.size(); 793 for (int i = 0; i < childCount; i++) { 794 if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) { 795 return; 796 } 797 View child = children.get(i); 798 if (isShown(child)) { 799 AccessibilityNodeProvider provider = child.getAccessibilityNodeProvider(); 800 if (provider == null) { 801 AccessibilityNodeInfo info = child.createAccessibilityNodeInfo(); 802 if (info != null) { 803 outInfos.add(info); 804 addedChildren.put(child, null); 805 } 806 } else { 807 AccessibilityNodeInfo info = provider.createAccessibilityNodeInfo( 808 AccessibilityNodeInfo.UNDEFINED); 809 if (info != null) { 810 outInfos.add(info); 811 addedChildren.put(child, info); 812 } 813 } 814 } 815 } 816 } finally { 817 children.clear(); 818 } 819 if (outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) { 820 for (Map.Entry<View, AccessibilityNodeInfo> entry : addedChildren.entrySet()) { 821 View addedChild = entry.getKey(); 822 AccessibilityNodeInfo virtualRoot = entry.getValue(); 823 if (virtualRoot == null) { 824 prefetchDescendantsOfRealNode(addedChild, outInfos); 825 } else { 826 AccessibilityNodeProvider provider = 827 addedChild.getAccessibilityNodeProvider(); 828 prefetchDescendantsOfVirtualNode(virtualRoot, provider, outInfos); 829 } 830 } 831 } 832 } 833 834 private void prefetchPredecessorsOfVirtualNode(AccessibilityNodeInfo root, 835 View providerHost, AccessibilityNodeProvider provider, 836 List<AccessibilityNodeInfo> outInfos) { 837 long parentNodeId = root.getParentNodeId(); 838 int accessibilityViewId = AccessibilityNodeInfo.getAccessibilityViewId(parentNodeId); 839 while (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED) { 840 if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) { 841 return; 842 } 843 final int virtualDescendantId = 844 AccessibilityNodeInfo.getVirtualDescendantId(parentNodeId); 845 if (virtualDescendantId != AccessibilityNodeInfo.UNDEFINED 846 || accessibilityViewId == providerHost.getAccessibilityViewId()) { 847 AccessibilityNodeInfo parent = provider.createAccessibilityNodeInfo( 848 virtualDescendantId); 849 if (parent != null) { 850 outInfos.add(parent); 851 } 852 parentNodeId = parent.getParentNodeId(); 853 accessibilityViewId = AccessibilityNodeInfo.getAccessibilityViewId( 854 parentNodeId); 855 } else { 856 prefetchPredecessorsOfRealNode(providerHost, outInfos); 857 return; 858 } 859 } 860 } 861 862 private void prefetchSiblingsOfVirtualNode(AccessibilityNodeInfo current, View providerHost, 863 AccessibilityNodeProvider provider, List<AccessibilityNodeInfo> outInfos) { 864 final long parentNodeId = current.getParentNodeId(); 865 final int parentAccessibilityViewId = 866 AccessibilityNodeInfo.getAccessibilityViewId(parentNodeId); 867 final int parentVirtualDescendantId = 868 AccessibilityNodeInfo.getVirtualDescendantId(parentNodeId); 869 if (parentVirtualDescendantId != AccessibilityNodeInfo.UNDEFINED 870 || parentAccessibilityViewId == providerHost.getAccessibilityViewId()) { 871 AccessibilityNodeInfo parent = 872 provider.createAccessibilityNodeInfo(parentVirtualDescendantId); 873 if (parent != null) { 874 SparseLongArray childNodeIds = parent.getChildNodeIds(); 875 final int childCount = childNodeIds.size(); 876 for (int i = 0; i < childCount; i++) { 877 if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) { 878 return; 879 } 880 final long childNodeId = childNodeIds.get(i); 881 if (childNodeId != current.getSourceNodeId()) { 882 final int childVirtualDescendantId = 883 AccessibilityNodeInfo.getVirtualDescendantId(childNodeId); 884 AccessibilityNodeInfo child = provider.createAccessibilityNodeInfo( 885 childVirtualDescendantId); 886 if (child != null) { 887 outInfos.add(child); 888 } 889 } 890 } 891 } 892 } else { 893 prefetchSiblingsOfRealNode(providerHost, outInfos); 894 } 895 } 896 897 private void prefetchDescendantsOfVirtualNode(AccessibilityNodeInfo root, 898 AccessibilityNodeProvider provider, List<AccessibilityNodeInfo> outInfos) { 899 SparseLongArray childNodeIds = root.getChildNodeIds(); 900 final int initialOutInfosSize = outInfos.size(); 901 final int childCount = childNodeIds.size(); 902 for (int i = 0; i < childCount; i++) { 903 if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) { 904 return; 905 } 906 final long childNodeId = childNodeIds.get(i); 907 AccessibilityNodeInfo child = provider.createAccessibilityNodeInfo( 908 AccessibilityNodeInfo.getVirtualDescendantId(childNodeId)); 909 if (child != null) { 910 outInfos.add(child); 911 } 912 } 913 if (outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) { 914 final int addedChildCount = outInfos.size() - initialOutInfosSize; 915 for (int i = 0; i < addedChildCount; i++) { 916 AccessibilityNodeInfo child = outInfos.get(initialOutInfosSize + i); 917 prefetchDescendantsOfVirtualNode(child, provider, outInfos); 918 } 919 } 920 } 921 } 922 923 private class PrivateHandler extends Handler { 924 private final static int MSG_PERFORM_ACCESSIBILITY_ACTION = 1; 925 private final static int MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID = 2; 926 private final static int MSG_FIND_ACCESSIBLITY_NODE_INFOS_BY_VIEW_ID = 3; 927 private final static int MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_TEXT = 4; 928 private final static int MSG_FIND_FOCUS = 5; 929 private final static int MSG_FOCUS_SEARCH = 6; 930 931 public PrivateHandler(Looper looper) { 932 super(looper); 933 } 934 935 @Override 936 public String getMessageName(Message message) { 937 final int type = message.what; 938 switch (type) { 939 case MSG_PERFORM_ACCESSIBILITY_ACTION: 940 return "MSG_PERFORM_ACCESSIBILITY_ACTION"; 941 case MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID: 942 return "MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID"; 943 case MSG_FIND_ACCESSIBLITY_NODE_INFOS_BY_VIEW_ID: 944 return "MSG_FIND_ACCESSIBLITY_NODE_INFOS_BY_VIEW_ID"; 945 case MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_TEXT: 946 return "MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_TEXT"; 947 case MSG_FIND_FOCUS: 948 return "MSG_FIND_FOCUS"; 949 case MSG_FOCUS_SEARCH: 950 return "MSG_FOCUS_SEARCH"; 951 default: 952 throw new IllegalArgumentException("Unknown message type: " + type); 953 } 954 } 955 956 @Override 957 public void handleMessage(Message message) { 958 final int type = message.what; 959 switch (type) { 960 case MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID: { 961 findAccessibilityNodeInfoByAccessibilityIdUiThread(message); 962 } break; 963 case MSG_PERFORM_ACCESSIBILITY_ACTION: { 964 perfromAccessibilityActionUiThread(message); 965 } break; 966 case MSG_FIND_ACCESSIBLITY_NODE_INFOS_BY_VIEW_ID: { 967 findAccessibilityNodeInfosByViewIdUiThread(message); 968 } break; 969 case MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_TEXT: { 970 findAccessibilityNodeInfosByTextUiThread(message); 971 } break; 972 case MSG_FIND_FOCUS: { 973 findFocusUiThread(message); 974 } break; 975 case MSG_FOCUS_SEARCH: { 976 focusSearchUiThread(message); 977 } break; 978 default: 979 throw new IllegalArgumentException("Unknown message type: " + type); 980 } 981 } 982 } 983 984 private final class AddNodeInfosForViewId implements Predicate<View> { 985 private int mViewId = View.NO_ID; 986 private List<AccessibilityNodeInfo> mInfos; 987 988 public void init(int viewId, List<AccessibilityNodeInfo> infos) { 989 mViewId = viewId; 990 mInfos = infos; 991 } 992 993 public void reset() { 994 mViewId = View.NO_ID; 995 mInfos = null; 996 } 997 998 @Override 999 public boolean apply(View view) { 1000 if (view.getId() == mViewId && isShown(view)) { 1001 mInfos.add(view.createAccessibilityNodeInfo()); 1002 } 1003 return false; 1004 } 1005 } 1006 } 1007