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