AccessibilityInteractionController.java revision 86783474fdec98a22bc22e224462767eab13e273
1/* 2 * Copyright (C) 2012 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package android.view; 18 19import static android.view.accessibility.AccessibilityNodeInfo.INCLUDE_NOT_IMPORTANT_VIEWS; 20 21import android.os.Bundle; 22import android.os.Handler; 23import android.os.Looper; 24import android.os.Message; 25import android.os.Process; 26import android.os.RemoteException; 27import android.util.Pool; 28import android.util.Poolable; 29import android.util.PoolableManager; 30import android.util.Pools; 31import android.util.SparseLongArray; 32import android.view.accessibility.AccessibilityInteractionClient; 33import android.view.accessibility.AccessibilityNodeInfo; 34import android.view.accessibility.AccessibilityNodeProvider; 35import android.view.accessibility.IAccessibilityInteractionConnectionCallback; 36 37import java.util.ArrayList; 38import java.util.HashMap; 39import java.util.List; 40import 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 */ 49final 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 focused = provider.findAccessibilityFocus(virtualDescendantId); 484 } else if (virtualDescendantId == View.NO_ID) { 485 focused = host.createAccessibilityNodeInfo(); 486 } 487 } break; 488 case AccessibilityNodeInfo.FOCUS_INPUT: { 489 // Input focus cannot go to virtual views. 490 View target = root.findFocus(); 491 if (target != null && isShown(target)) { 492 focused = target.createAccessibilityNodeInfo(); 493 } 494 } break; 495 default: 496 throw new IllegalArgumentException("Unknown focus type: " + focusType); 497 } 498 } 499 } finally { 500 try { 501 mViewRootImpl.mAttachInfo.mIncludeNotImportantViews = false; 502 callback.setFindAccessibilityNodeInfoResult(focused, interactionId); 503 } catch (RemoteException re) { 504 /* ignore - the other side will time out */ 505 } 506 } 507 } 508 509 public void focusSearchClientThread(long accessibilityNodeId, int direction, int windowLeft, 510 int windowTop, int interactionId, IAccessibilityInteractionConnectionCallback callback, 511 int flags, int interogatingPid, long interrogatingTid) { 512 Message message = mHandler.obtainMessage(); 513 message.what = PrivateHandler.MSG_FOCUS_SEARCH; 514 message.arg1 = flags; 515 message.arg2 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId); 516 517 SomeArgs args = mPool.acquire(); 518 args.argi1 = AccessibilityNodeInfo.getVirtualDescendantId(accessibilityNodeId); 519 args.argi2 = direction; 520 args.argi3 = interactionId; 521 args.arg1 = callback; 522 523 SomeArgs moreArgs = mPool.acquire(); 524 moreArgs.argi1 = windowLeft; 525 moreArgs.argi2 = windowTop; 526 args.arg2 = moreArgs; 527 528 message.obj = args; 529 530 // If the interrogation is performed by the same thread as the main UI 531 // thread in this process, set the message as a static reference so 532 // after this call completes the same thread but in the interrogating 533 // client can handle the message to generate the result. 534 if (interogatingPid == mMyProcessId && interrogatingTid == mMyLooperThreadId) { 535 AccessibilityInteractionClient.getInstanceForThread( 536 interrogatingTid).setSameThreadMessage(message); 537 } else { 538 mHandler.sendMessage(message); 539 } 540 } 541 542 private void focusSearchUiThread(Message message) { 543 final int flags = message.arg1; 544 final int accessibilityViewId = message.arg2; 545 546 SomeArgs args = (SomeArgs) message.obj; 547 final int virtualDescendantId = args.argi1; 548 final int direction = args.argi2; 549 final int interactionId = args.argi3; 550 final IAccessibilityInteractionConnectionCallback callback = 551 (IAccessibilityInteractionConnectionCallback) args.arg1; 552 553 SomeArgs moreArgs = (SomeArgs) args.arg2; 554 mViewRootImpl.mAttachInfo.mActualWindowLeft = moreArgs.argi1; 555 mViewRootImpl.mAttachInfo.mActualWindowTop = moreArgs.argi2; 556 557 mPool.release(moreArgs); 558 mPool.release(args); 559 560 AccessibilityNodeInfo next = null; 561 try { 562 if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) { 563 return; 564 } 565 mViewRootImpl.mAttachInfo.mIncludeNotImportantViews = 566 (flags & INCLUDE_NOT_IMPORTANT_VIEWS) != 0; 567 View root = null; 568 if (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED) { 569 root = findViewByAccessibilityId(accessibilityViewId); 570 } else { 571 root = mViewRootImpl.mView; 572 } 573 if (root != null && isShown(root)) { 574 if ((direction & View.FOCUS_ACCESSIBILITY) == View.FOCUS_ACCESSIBILITY) { 575 AccessibilityNodeProvider provider = root.getAccessibilityNodeProvider(); 576 if (provider != null) { 577 next = provider.accessibilityFocusSearch(direction, virtualDescendantId); 578 if (next != null) { 579 return; 580 } 581 } 582 View nextView = root.focusSearch(direction); 583 while (nextView != null) { 584 // If the focus search reached a node with a provider 585 // we delegate to the provider to find the next one. 586 // If the provider does not return a virtual view to 587 // take accessibility focus we try the next view found 588 // by the focus search algorithm. 589 provider = nextView.getAccessibilityNodeProvider(); 590 if (provider != null) { 591 next = provider.accessibilityFocusSearch(direction, View.NO_ID); 592 if (next != null) { 593 break; 594 } 595 nextView = nextView.focusSearch(direction); 596 } else { 597 next = nextView.createAccessibilityNodeInfo(); 598 break; 599 } 600 } 601 } else { 602 View nextView = root.focusSearch(direction); 603 if (nextView != null) { 604 next = nextView.createAccessibilityNodeInfo(); 605 } 606 } 607 } 608 } finally { 609 try { 610 mViewRootImpl.mAttachInfo.mIncludeNotImportantViews = false; 611 callback.setFindAccessibilityNodeInfoResult(next, interactionId); 612 } catch (RemoteException re) { 613 /* ignore - the other side will time out */ 614 } 615 } 616 } 617 618 public void performAccessibilityActionClientThread(long accessibilityNodeId, int action, 619 Bundle arguments, int interactionId, 620 IAccessibilityInteractionConnectionCallback callback, int flags, int interogatingPid, 621 long interrogatingTid) { 622 Message message = mHandler.obtainMessage(); 623 message.what = PrivateHandler.MSG_PERFORM_ACCESSIBILITY_ACTION; 624 message.arg1 = flags; 625 message.arg2 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId); 626 627 SomeArgs args = mPool.acquire(); 628 args.argi1 = AccessibilityNodeInfo.getVirtualDescendantId(accessibilityNodeId); 629 args.argi2 = action; 630 args.argi3 = interactionId; 631 args.arg1 = callback; 632 args.arg2 = arguments; 633 634 message.obj = args; 635 636 // If the interrogation is performed by the same thread as the main UI 637 // thread in this process, set the message as a static reference so 638 // after this call completes the same thread but in the interrogating 639 // client can handle the message to generate the result. 640 if (interogatingPid == mMyProcessId && interrogatingTid == mMyLooperThreadId) { 641 AccessibilityInteractionClient.getInstanceForThread( 642 interrogatingTid).setSameThreadMessage(message); 643 } else { 644 mHandler.sendMessage(message); 645 } 646 } 647 648 private void perfromAccessibilityActionUiThread(Message message) { 649 final int flags = message.arg1; 650 final int accessibilityViewId = message.arg2; 651 652 SomeArgs args = (SomeArgs) message.obj; 653 final int virtualDescendantId = args.argi1; 654 final int action = args.argi2; 655 final int interactionId = args.argi3; 656 final IAccessibilityInteractionConnectionCallback callback = 657 (IAccessibilityInteractionConnectionCallback) args.arg1; 658 Bundle arguments = (Bundle) args.arg2; 659 660 mPool.release(args); 661 662 boolean succeeded = false; 663 try { 664 if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) { 665 return; 666 } 667 mViewRootImpl.mAttachInfo.mIncludeNotImportantViews = 668 (flags & INCLUDE_NOT_IMPORTANT_VIEWS) != 0; 669 View target = null; 670 if (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED) { 671 target = findViewByAccessibilityId(accessibilityViewId); 672 } else { 673 target = mViewRootImpl.mView; 674 } 675 if (target != null && isShown(target)) { 676 AccessibilityNodeProvider provider = target.getAccessibilityNodeProvider(); 677 if (provider != null) { 678 succeeded = provider.performAction(virtualDescendantId, action, 679 arguments); 680 } else if (virtualDescendantId == View.NO_ID) { 681 succeeded = target.performAccessibilityAction(action, arguments); 682 } 683 } 684 } finally { 685 try { 686 mViewRootImpl.mAttachInfo.mIncludeNotImportantViews = false; 687 callback.setPerformAccessibilityActionResult(succeeded, interactionId); 688 } catch (RemoteException re) { 689 /* ignore - the other side will time out */ 690 } 691 } 692 } 693 694 private View findViewByAccessibilityId(int accessibilityId) { 695 View root = mViewRootImpl.mView; 696 if (root == null) { 697 return null; 698 } 699 View foundView = root.findViewByAccessibilityId(accessibilityId); 700 if (foundView != null && !isShown(foundView)) { 701 return null; 702 } 703 return foundView; 704 } 705 706 /** 707 * This class encapsulates a prefetching strategy for the accessibility APIs for 708 * querying window content. It is responsible to prefetch a batch of 709 * AccessibilityNodeInfos in addition to the one for a requested node. 710 */ 711 private class AccessibilityNodePrefetcher { 712 713 private static final int MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE = 50; 714 715 private final ArrayList<View> mTempViewList = new ArrayList<View>(); 716 717 public void prefetchAccessibilityNodeInfos(View view, int virtualViewId, int prefetchFlags, 718 List<AccessibilityNodeInfo> outInfos) { 719 AccessibilityNodeProvider provider = view.getAccessibilityNodeProvider(); 720 if (provider == null) { 721 AccessibilityNodeInfo root = view.createAccessibilityNodeInfo(); 722 if (root != null) { 723 outInfos.add(root); 724 if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS) != 0) { 725 prefetchPredecessorsOfRealNode(view, outInfos); 726 } 727 if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS) != 0) { 728 prefetchSiblingsOfRealNode(view, outInfos); 729 } 730 if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS) != 0) { 731 prefetchDescendantsOfRealNode(view, outInfos); 732 } 733 } 734 } else { 735 AccessibilityNodeInfo root = provider.createAccessibilityNodeInfo(virtualViewId); 736 if (root != null) { 737 outInfos.add(root); 738 if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS) != 0) { 739 prefetchPredecessorsOfVirtualNode(root, view, provider, outInfos); 740 } 741 if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS) != 0) { 742 prefetchSiblingsOfVirtualNode(root, view, provider, outInfos); 743 } 744 if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS) != 0) { 745 prefetchDescendantsOfVirtualNode(root, provider, outInfos); 746 } 747 } 748 } 749 } 750 751 private void prefetchPredecessorsOfRealNode(View view, 752 List<AccessibilityNodeInfo> outInfos) { 753 ViewParent parent = view.getParentForAccessibility(); 754 while (parent instanceof View 755 && outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) { 756 View parentView = (View) parent; 757 AccessibilityNodeInfo info = parentView.createAccessibilityNodeInfo(); 758 if (info != null) { 759 outInfos.add(info); 760 } 761 parent = parent.getParentForAccessibility(); 762 } 763 } 764 765 private void prefetchSiblingsOfRealNode(View current, 766 List<AccessibilityNodeInfo> outInfos) { 767 ViewParent parent = current.getParentForAccessibility(); 768 if (parent instanceof ViewGroup) { 769 ViewGroup parentGroup = (ViewGroup) parent; 770 ArrayList<View> children = mTempViewList; 771 children.clear(); 772 try { 773 parentGroup.addChildrenForAccessibility(children); 774 final int childCount = children.size(); 775 for (int i = 0; i < childCount; i++) { 776 if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) { 777 return; 778 } 779 View child = children.get(i); 780 if (child.getAccessibilityViewId() != current.getAccessibilityViewId() 781 && isShown(child)) { 782 AccessibilityNodeInfo info = null; 783 AccessibilityNodeProvider provider = 784 child.getAccessibilityNodeProvider(); 785 if (provider == null) { 786 info = child.createAccessibilityNodeInfo(); 787 } else { 788 info = provider.createAccessibilityNodeInfo( 789 AccessibilityNodeInfo.UNDEFINED); 790 } 791 if (info != null) { 792 outInfos.add(info); 793 } 794 } 795 } 796 } finally { 797 children.clear(); 798 } 799 } 800 } 801 802 private void prefetchDescendantsOfRealNode(View root, 803 List<AccessibilityNodeInfo> outInfos) { 804 if (!(root instanceof ViewGroup)) { 805 return; 806 } 807 ViewGroup rootGroup = (ViewGroup) root; 808 HashMap<View, AccessibilityNodeInfo> addedChildren = 809 new HashMap<View, AccessibilityNodeInfo>(); 810 ArrayList<View> children = mTempViewList; 811 children.clear(); 812 try { 813 root.addChildrenForAccessibility(children); 814 final int childCount = children.size(); 815 for (int i = 0; i < childCount; i++) { 816 if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) { 817 return; 818 } 819 View child = children.get(i); 820 if (isShown(child)) { 821 AccessibilityNodeProvider provider = child.getAccessibilityNodeProvider(); 822 if (provider == null) { 823 AccessibilityNodeInfo info = child.createAccessibilityNodeInfo(); 824 if (info != null) { 825 outInfos.add(info); 826 addedChildren.put(child, null); 827 } 828 } else { 829 AccessibilityNodeInfo info = provider.createAccessibilityNodeInfo( 830 AccessibilityNodeInfo.UNDEFINED); 831 if (info != null) { 832 outInfos.add(info); 833 addedChildren.put(child, info); 834 } 835 } 836 } 837 } 838 } finally { 839 children.clear(); 840 } 841 if (outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) { 842 for (Map.Entry<View, AccessibilityNodeInfo> entry : addedChildren.entrySet()) { 843 View addedChild = entry.getKey(); 844 AccessibilityNodeInfo virtualRoot = entry.getValue(); 845 if (virtualRoot == null) { 846 prefetchDescendantsOfRealNode(addedChild, outInfos); 847 } else { 848 AccessibilityNodeProvider provider = 849 addedChild.getAccessibilityNodeProvider(); 850 prefetchDescendantsOfVirtualNode(virtualRoot, provider, outInfos); 851 } 852 } 853 } 854 } 855 856 private void prefetchPredecessorsOfVirtualNode(AccessibilityNodeInfo root, 857 View providerHost, AccessibilityNodeProvider provider, 858 List<AccessibilityNodeInfo> outInfos) { 859 long parentNodeId = root.getParentNodeId(); 860 int accessibilityViewId = AccessibilityNodeInfo.getAccessibilityViewId(parentNodeId); 861 while (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED) { 862 if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) { 863 return; 864 } 865 final int virtualDescendantId = 866 AccessibilityNodeInfo.getVirtualDescendantId(parentNodeId); 867 if (virtualDescendantId != AccessibilityNodeInfo.UNDEFINED 868 || accessibilityViewId == providerHost.getAccessibilityViewId()) { 869 AccessibilityNodeInfo parent = provider.createAccessibilityNodeInfo( 870 virtualDescendantId); 871 if (parent != null) { 872 outInfos.add(parent); 873 } 874 parentNodeId = parent.getParentNodeId(); 875 accessibilityViewId = AccessibilityNodeInfo.getAccessibilityViewId( 876 parentNodeId); 877 } else { 878 prefetchPredecessorsOfRealNode(providerHost, outInfos); 879 return; 880 } 881 } 882 } 883 884 private void prefetchSiblingsOfVirtualNode(AccessibilityNodeInfo current, View providerHost, 885 AccessibilityNodeProvider provider, List<AccessibilityNodeInfo> outInfos) { 886 final long parentNodeId = current.getParentNodeId(); 887 final int parentAccessibilityViewId = 888 AccessibilityNodeInfo.getAccessibilityViewId(parentNodeId); 889 final int parentVirtualDescendantId = 890 AccessibilityNodeInfo.getVirtualDescendantId(parentNodeId); 891 if (parentVirtualDescendantId != AccessibilityNodeInfo.UNDEFINED 892 || parentAccessibilityViewId == providerHost.getAccessibilityViewId()) { 893 AccessibilityNodeInfo parent = 894 provider.createAccessibilityNodeInfo(parentVirtualDescendantId); 895 if (parent != null) { 896 SparseLongArray childNodeIds = parent.getChildNodeIds(); 897 final int childCount = childNodeIds.size(); 898 for (int i = 0; i < childCount; i++) { 899 if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) { 900 return; 901 } 902 final long childNodeId = childNodeIds.get(i); 903 if (childNodeId != current.getSourceNodeId()) { 904 final int childVirtualDescendantId = 905 AccessibilityNodeInfo.getVirtualDescendantId(childNodeId); 906 AccessibilityNodeInfo child = provider.createAccessibilityNodeInfo( 907 childVirtualDescendantId); 908 if (child != null) { 909 outInfos.add(child); 910 } 911 } 912 } 913 } 914 } else { 915 prefetchSiblingsOfRealNode(providerHost, outInfos); 916 } 917 } 918 919 private void prefetchDescendantsOfVirtualNode(AccessibilityNodeInfo root, 920 AccessibilityNodeProvider provider, List<AccessibilityNodeInfo> outInfos) { 921 SparseLongArray childNodeIds = root.getChildNodeIds(); 922 final int initialOutInfosSize = outInfos.size(); 923 final int childCount = childNodeIds.size(); 924 for (int i = 0; i < childCount; i++) { 925 if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) { 926 return; 927 } 928 final long childNodeId = childNodeIds.get(i); 929 AccessibilityNodeInfo child = provider.createAccessibilityNodeInfo( 930 AccessibilityNodeInfo.getVirtualDescendantId(childNodeId)); 931 if (child != null) { 932 outInfos.add(child); 933 } 934 } 935 if (outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) { 936 final int addedChildCount = outInfos.size() - initialOutInfosSize; 937 for (int i = 0; i < addedChildCount; i++) { 938 AccessibilityNodeInfo child = outInfos.get(initialOutInfosSize + i); 939 prefetchDescendantsOfVirtualNode(child, provider, outInfos); 940 } 941 } 942 } 943 } 944 945 private class PrivateHandler extends Handler { 946 private final static int MSG_PERFORM_ACCESSIBILITY_ACTION = 1; 947 private final static int MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID = 2; 948 private final static int MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_ID = 3; 949 private final static int MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_TEXT = 4; 950 private final static int MSG_FIND_FOCUS = 5; 951 private final static int MSG_FOCUS_SEARCH = 6; 952 953 public PrivateHandler(Looper looper) { 954 super(looper); 955 } 956 957 @Override 958 public String getMessageName(Message message) { 959 final int type = message.what; 960 switch (type) { 961 case MSG_PERFORM_ACCESSIBILITY_ACTION: 962 return "MSG_PERFORM_ACCESSIBILITY_ACTION"; 963 case MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID: 964 return "MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID"; 965 case MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_ID: 966 return "MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_ID"; 967 case MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_TEXT: 968 return "MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_TEXT"; 969 case MSG_FIND_FOCUS: 970 return "MSG_FIND_FOCUS"; 971 case MSG_FOCUS_SEARCH: 972 return "MSG_FOCUS_SEARCH"; 973 default: 974 throw new IllegalArgumentException("Unknown message type: " + type); 975 } 976 } 977 978 @Override 979 public void handleMessage(Message message) { 980 final int type = message.what; 981 switch (type) { 982 case MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID: { 983 findAccessibilityNodeInfoByAccessibilityIdUiThread(message); 984 } break; 985 case MSG_PERFORM_ACCESSIBILITY_ACTION: { 986 perfromAccessibilityActionUiThread(message); 987 } break; 988 case MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_ID: { 989 findAccessibilityNodeInfoByViewIdUiThread(message); 990 } break; 991 case MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_TEXT: { 992 findAccessibilityNodeInfosByTextUiThread(message); 993 } break; 994 case MSG_FIND_FOCUS: { 995 findFocusUiThread(message); 996 } break; 997 case MSG_FOCUS_SEARCH: { 998 focusSearchUiThread(message); 999 } break; 1000 default: 1001 throw new IllegalArgumentException("Unknown message type: " + type); 1002 } 1003 } 1004 } 1005} 1006