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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 1053 private void prefetchPredecessorsOfVirtualNode(AccessibilityNodeInfo root, 1054 View providerHost, AccessibilityNodeProvider provider, 1055 List<AccessibilityNodeInfo> outInfos) { 1056 final int initialResultSize = outInfos.size(); 1057 long parentNodeId = root.getParentNodeId(); 1058 int accessibilityViewId = AccessibilityNodeInfo.getAccessibilityViewId(parentNodeId); 1059 while (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) { 1060 if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) { 1061 return; 1062 } 1063 final int virtualDescendantId = 1064 AccessibilityNodeInfo.getVirtualDescendantId(parentNodeId); 1065 if (virtualDescendantId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID 1066 || accessibilityViewId == providerHost.getAccessibilityViewId()) { 1067 final AccessibilityNodeInfo parent; 1068 if (virtualDescendantId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) { 1069 parent = provider.createAccessibilityNodeInfo(virtualDescendantId); 1070 } else { 1071 parent = provider.createAccessibilityNodeInfo( 1072 AccessibilityNodeProvider.HOST_VIEW_ID); 1073 } 1074 if (parent == null) { 1075 // Going up the parent relation we found a null predecessor, 1076 // so remove these disconnected nodes form the result. 1077 final int currentResultSize = outInfos.size(); 1078 for (int i = currentResultSize - 1; i >= initialResultSize; i--) { 1079 outInfos.remove(i); 1080 } 1081 // Couldn't obtain the parent, which means we have a 1082 // disconnected sub-tree. Abort prefetch immediately. 1083 return; 1084 } 1085 outInfos.add(parent); 1086 parentNodeId = parent.getParentNodeId(); 1087 accessibilityViewId = AccessibilityNodeInfo.getAccessibilityViewId( 1088 parentNodeId); 1089 } else { 1090 prefetchPredecessorsOfRealNode(providerHost, outInfos); 1091 return; 1092 } 1093 } 1094 } 1095 1096 private void prefetchSiblingsOfVirtualNode(AccessibilityNodeInfo current, View providerHost, 1097 AccessibilityNodeProvider provider, List<AccessibilityNodeInfo> outInfos) { 1098 final long parentNodeId = current.getParentNodeId(); 1099 final int parentAccessibilityViewId = 1100 AccessibilityNodeInfo.getAccessibilityViewId(parentNodeId); 1101 final int parentVirtualDescendantId = 1102 AccessibilityNodeInfo.getVirtualDescendantId(parentNodeId); 1103 if (parentVirtualDescendantId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID 1104 || parentAccessibilityViewId == providerHost.getAccessibilityViewId()) { 1105 final AccessibilityNodeInfo parent; 1106 if (parentVirtualDescendantId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) { 1107 parent = provider.createAccessibilityNodeInfo(parentVirtualDescendantId); 1108 } else { 1109 parent = provider.createAccessibilityNodeInfo( 1110 AccessibilityNodeProvider.HOST_VIEW_ID); 1111 } 1112 if (parent != null) { 1113 final int childCount = parent.getChildCount(); 1114 for (int i = 0; i < childCount; i++) { 1115 if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) { 1116 return; 1117 } 1118 final long childNodeId = parent.getChildId(i); 1119 if (childNodeId != current.getSourceNodeId()) { 1120 final int childVirtualDescendantId = 1121 AccessibilityNodeInfo.getVirtualDescendantId(childNodeId); 1122 AccessibilityNodeInfo child = provider.createAccessibilityNodeInfo( 1123 childVirtualDescendantId); 1124 if (child != null) { 1125 outInfos.add(child); 1126 } 1127 } 1128 } 1129 } 1130 } else { 1131 prefetchSiblingsOfRealNode(providerHost, outInfos); 1132 } 1133 } 1134 1135 private void prefetchDescendantsOfVirtualNode(AccessibilityNodeInfo root, 1136 AccessibilityNodeProvider provider, List<AccessibilityNodeInfo> outInfos) { 1137 final int initialOutInfosSize = outInfos.size(); 1138 final int childCount = root.getChildCount(); 1139 for (int i = 0; i < childCount; i++) { 1140 if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) { 1141 return; 1142 } 1143 final long childNodeId = root.getChildId(i); 1144 AccessibilityNodeInfo child = provider.createAccessibilityNodeInfo( 1145 AccessibilityNodeInfo.getVirtualDescendantId(childNodeId)); 1146 if (child != null) { 1147 outInfos.add(child); 1148 } 1149 } 1150 if (outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) { 1151 final int addedChildCount = outInfos.size() - initialOutInfosSize; 1152 for (int i = 0; i < addedChildCount; i++) { 1153 AccessibilityNodeInfo child = outInfos.get(initialOutInfosSize + i); 1154 prefetchDescendantsOfVirtualNode(child, provider, outInfos); 1155 } 1156 } 1157 } 1158 } 1159 1160 private class PrivateHandler extends Handler { 1161 private final static int MSG_PERFORM_ACCESSIBILITY_ACTION = 1; 1162 private final static int MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_ACCESSIBILITY_ID = 2; 1163 private final static int MSG_FIND_ACCESSIBILITY_NODE_INFOS_BY_VIEW_ID = 3; 1164 private final static int MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_TEXT = 4; 1165 private final static int MSG_FIND_FOCUS = 5; 1166 private final static int MSG_FOCUS_SEARCH = 6; 1167 1168 public PrivateHandler(Looper looper) { 1169 super(looper); 1170 } 1171 1172 @Override 1173 public String getMessageName(Message message) { 1174 final int type = message.what; 1175 switch (type) { 1176 case MSG_PERFORM_ACCESSIBILITY_ACTION: 1177 return "MSG_PERFORM_ACCESSIBILITY_ACTION"; 1178 case MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_ACCESSIBILITY_ID: 1179 return "MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_ACCESSIBILITY_ID"; 1180 case MSG_FIND_ACCESSIBILITY_NODE_INFOS_BY_VIEW_ID: 1181 return "MSG_FIND_ACCESSIBILITY_NODE_INFOS_BY_VIEW_ID"; 1182 case MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_TEXT: 1183 return "MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_TEXT"; 1184 case MSG_FIND_FOCUS: 1185 return "MSG_FIND_FOCUS"; 1186 case MSG_FOCUS_SEARCH: 1187 return "MSG_FOCUS_SEARCH"; 1188 default: 1189 throw new IllegalArgumentException("Unknown message type: " + type); 1190 } 1191 } 1192 1193 @Override 1194 public void handleMessage(Message message) { 1195 final int type = message.what; 1196 switch (type) { 1197 case MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_ACCESSIBILITY_ID: { 1198 findAccessibilityNodeInfoByAccessibilityIdUiThread(message); 1199 } break; 1200 case MSG_PERFORM_ACCESSIBILITY_ACTION: { 1201 performAccessibilityActionUiThread(message); 1202 } break; 1203 case MSG_FIND_ACCESSIBILITY_NODE_INFOS_BY_VIEW_ID: { 1204 findAccessibilityNodeInfosByViewIdUiThread(message); 1205 } break; 1206 case MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_TEXT: { 1207 findAccessibilityNodeInfosByTextUiThread(message); 1208 } break; 1209 case MSG_FIND_FOCUS: { 1210 findFocusUiThread(message); 1211 } break; 1212 case MSG_FOCUS_SEARCH: { 1213 focusSearchUiThread(message); 1214 } break; 1215 default: 1216 throw new IllegalArgumentException("Unknown message type: " + type); 1217 } 1218 } 1219 } 1220 1221 private final class AddNodeInfosForViewId implements Predicate<View> { 1222 private int mViewId = View.NO_ID; 1223 private List<AccessibilityNodeInfo> mInfos; 1224 1225 public void init(int viewId, List<AccessibilityNodeInfo> infos) { 1226 mViewId = viewId; 1227 mInfos = infos; 1228 } 1229 1230 public void reset() { 1231 mViewId = View.NO_ID; 1232 mInfos = null; 1233 } 1234 1235 @Override 1236 public boolean apply(View view) { 1237 if (view.getId() == mViewId && isShown(view)) { 1238 mInfos.add(view.createAccessibilityNodeInfo()); 1239 } 1240 return false; 1241 } 1242 } 1243 } 1244