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