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 AccessibilityInteractionController(ViewRootImpl viewRootImpl)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 getNextPoolable()105 public SomeArgs getNextPoolable() { 106 return mNext; 107 } 108 isPooled()109 public boolean isPooled() { 110 return mIsPooled; 111 } 112 setNextPoolable(SomeArgs args)113 public void setNextPoolable(SomeArgs args) { 114 mNext = args; 115 } 116 setPooled(boolean isPooled)117 public void setPooled(boolean isPooled) { 118 mIsPooled = isPooled; 119 } 120 clear()121 private void clear() { 122 arg1 = null; 123 arg2 = null; 124 argi1 = 0; 125 argi2 = 0; 126 argi3 = 0; 127 } 128 } 129 isShown(View view)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 findAccessibilityNodeInfoByAccessibilityIdClientThread( long accessibilityNodeId, int windowLeft, int windowTop, int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid, long interrogatingTid)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 findAccessibilityNodeInfoByAccessibilityIdUiThread(Message message)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 findAccessibilityNodeInfoByViewIdClientThread(long accessibilityNodeId, int viewId, int windowLeft, int windowTop, int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid, long interrogatingTid)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 findAccessibilityNodeInfoByViewIdUiThread(Message message)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 findAccessibilityNodeInfosByTextClientThread(long accessibilityNodeId, String text, int windowLeft, int windowTop, int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid, long interrogatingTid)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 findAccessibilityNodeInfosByTextUiThread(Message message)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 findFocusClientThread(long accessibilityNodeId, int focusType, int windowLeft, int windowTop, int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags, int interogatingPid, long interrogatingTid)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 findFocusUiThread(Message message)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 focusSearchClientThread(long accessibilityNodeId, int direction, int windowLeft, int windowTop, int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags, int interogatingPid, long interrogatingTid)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 focusSearchUiThread(Message message)545 private void focusSearchUiThread(Message message) { 546 final int flags = message.arg1; 547 final int accessibilityViewId = message.arg2; 548 549 SomeArgs args = (SomeArgs) message.obj; 550 final int 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 performAccessibilityActionClientThread(long accessibilityNodeId, int action, Bundle arguments, int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags, int interogatingPid, long interrogatingTid)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 perfromAccessibilityActionUiThread(Message message)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 findViewByAccessibilityId(int accessibilityId)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 prefetchAccessibilityNodeInfos(View view, int virtualViewId, int prefetchFlags, List<AccessibilityNodeInfo> outInfos)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 prefetchPredecessorsOfRealNode(View view, List<AccessibilityNodeInfo> outInfos)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 prefetchSiblingsOfRealNode(View current, List<AccessibilityNodeInfo> outInfos)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 prefetchDescendantsOfRealNode(View root, List<AccessibilityNodeInfo> outInfos)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 prefetchPredecessorsOfVirtualNode(AccessibilityNodeInfo root, View providerHost, AccessibilityNodeProvider provider, List<AccessibilityNodeInfo> outInfos)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 prefetchSiblingsOfVirtualNode(AccessibilityNodeInfo current, View providerHost, AccessibilityNodeProvider provider, List<AccessibilityNodeInfo> outInfos)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 prefetchDescendantsOfVirtualNode(AccessibilityNodeInfo root, AccessibilityNodeProvider provider, List<AccessibilityNodeInfo> outInfos)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 PrivateHandler(Looper looper)955 public PrivateHandler(Looper looper) { 956 super(looper); 957 } 958 959 @Override getMessageName(Message message)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 handleMessage(Message message)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