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