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