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