AccessibilityNodeInfo.java revision 71b4e71c67df79f53b582fabb34b96ddbe23fe0f
1/* 2 * Copyright (C) 2011 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.accessibility; 18 19import android.accessibilityservice.IAccessibilityServiceConnection; 20import android.graphics.Rect; 21import android.os.Parcel; 22import android.os.Parcelable; 23import android.text.TextUtils; 24import android.util.SparseLongArray; 25import android.view.View; 26 27import java.util.Collections; 28import java.util.List; 29 30/** 31 * This class represents a node of the window content as well as actions that 32 * can be requested from its source. From the point of view of an 33 * {@link android.accessibilityservice.AccessibilityService} a window content is 34 * presented as tree of accessibility node info which may or may not map one-to-one 35 * to the view hierarchy. In other words, a custom view is free to report itself as 36 * a tree of accessibility node info. 37 * </p> 38 * <p> 39 * Once an accessibility node info is delivered to an accessibility service it is 40 * made immutable and calling a state mutation method generates an error. 41 * </p> 42 * <p> 43 * Please refer to {@link android.accessibilityservice.AccessibilityService} for 44 * details about how to obtain a handle to window content as a tree of accessibility 45 * node info as well as familiarizing with the security model. 46 * </p> 47 * 48 * @see android.accessibilityservice.AccessibilityService 49 * @see AccessibilityEvent 50 * @see AccessibilityManager 51 */ 52public class AccessibilityNodeInfo implements Parcelable { 53 54 private static final boolean DEBUG = false; 55 56 // Actions. 57 58 /** 59 * Action that focuses the node. 60 */ 61 public static final int ACTION_FOCUS = 0x00000001; 62 63 /** 64 * Action that unfocuses the node. 65 */ 66 public static final int ACTION_CLEAR_FOCUS = 0x00000002; 67 68 /** 69 * Action that selects the node. 70 */ 71 public static final int ACTION_SELECT = 0x00000004; 72 73 /** 74 * Action that unselects the node. 75 */ 76 public static final int ACTION_CLEAR_SELECTION = 0x00000008; 77 78 // Boolean attributes. 79 80 private static final int PROPERTY_CHECKABLE = 0x00000001; 81 82 private static final int PROPERTY_CHECKED = 0x00000002; 83 84 private static final int PROPERTY_FOCUSABLE = 0x00000004; 85 86 private static final int PROPERTY_FOCUSED = 0x00000008; 87 88 private static final int PROPERTY_SELECTED = 0x00000010; 89 90 private static final int PROPERTY_CLICKABLE = 0x00000020; 91 92 private static final int PROPERTY_LONG_CLICKABLE = 0x00000040; 93 94 private static final int PROPERTY_ENABLED = 0x00000080; 95 96 private static final int PROPERTY_PASSWORD = 0x00000100; 97 98 private static final int PROPERTY_SCROLLABLE = 0x00000200; 99 100 /** 101 * Bits that provide the id of a virtual descendant of a view. 102 */ 103 private static final long VIRTUAL_DESCENDANT_ID_MASK = 0xffffffff00000000L; 104 105 /** 106 * Bit shift of {@link #VIRTUAL_DESCENDANT_ID_MASK} to get to the id for a 107 * virtual descendant of a view. Such a descendant does not exist in the view 108 * hierarchy and is only reported via the accessibility APIs. 109 */ 110 private static final int VIRTUAL_DESCENDANT_ID_SHIFT = 32; 111 112 /** 113 * Gets the accessibility view id which identifies a View in the view three. 114 * 115 * @param accessibilityNodeId The id of an {@link AccessibilityNodeInfo}. 116 * @return The accessibility view id part of the node id. 117 * 118 * @hide 119 */ 120 public static int getAccessibilityViewId(long accessibilityNodeId) { 121 return (int) accessibilityNodeId; 122 } 123 124 /** 125 * Gets the virtual descendant id which identifies an imaginary view in a 126 * containing View. 127 * 128 * @param accessibilityNodeId The id of an {@link AccessibilityNodeInfo}. 129 * @return The virtual view id part of the node id. 130 * 131 * @hide 132 */ 133 public static int getVirtualDescendantId(long accessibilityNodeId) { 134 return (int) ((accessibilityNodeId & VIRTUAL_DESCENDANT_ID_MASK) 135 >> VIRTUAL_DESCENDANT_ID_SHIFT); 136 } 137 138 /** 139 * Makes a node id by shifting the <code>virtualDescendantId</code> 140 * by {@link #VIRTUAL_DESCENDANT_ID_SHIFT} and taking 141 * the bitwise or with the <code>accessibilityViewId</code>. 142 * 143 * @param accessibilityViewId A View accessibility id. 144 * @param virtualDescendantId A virtual descendant id. 145 * @return The node id. 146 * 147 * @hide 148 */ 149 public static long makeNodeId(int accessibilityViewId, int virtualDescendantId) { 150 return (((long) virtualDescendantId) << VIRTUAL_DESCENDANT_ID_SHIFT) | accessibilityViewId; 151 } 152 153 // Housekeeping. 154 private static final int MAX_POOL_SIZE = 50; 155 private static final Object sPoolLock = new Object(); 156 private static AccessibilityNodeInfo sPool; 157 private static int sPoolSize; 158 private AccessibilityNodeInfo mNext; 159 private boolean mIsInPool; 160 private boolean mSealed; 161 162 // Data. 163 private long mSourceNodeId = makeNodeId(View.NO_ID, View.NO_ID); 164 private int mWindowId = View.NO_ID; 165 private long mParentNodeId = makeNodeId(View.NO_ID, View.NO_ID); 166 private int mBooleanProperties; 167 private final Rect mBoundsInParent = new Rect(); 168 private final Rect mBoundsInScreen = new Rect(); 169 170 private CharSequence mPackageName; 171 private CharSequence mClassName; 172 private CharSequence mText; 173 private CharSequence mContentDescription; 174 175 private SparseLongArray mChildIds = new SparseLongArray(); 176 private int mActions; 177 178 private IAccessibilityServiceConnection mConnection; 179 180 /** 181 * Hide constructor from clients. 182 */ 183 private AccessibilityNodeInfo() { 184 /* do nothing */ 185 } 186 187 /** 188 * Sets the source. 189 * <p> 190 * <strong>Note:</strong> Cannot be called from an 191 * {@link android.accessibilityservice.AccessibilityService}. 192 * This class is made immutable before being delivered to an AccessibilityService. 193 * </p> 194 * 195 * @param source The info source. 196 */ 197 public void setSource(View source) { 198 setSource(source, View.NO_ID); 199 } 200 201 /** 202 * Sets the source to be a virtual descendant of the given <code>root</code>. 203 * If <code>virtualDescendantId</code> is {@link View#NO_ID} the root 204 * is set as the source. 205 * <p> 206 * A virtual descendant is an imaginary View that is reported as a part of the view 207 * hierarchy for accessibility purposes. This enables custom views that draw complex 208 * content to report themselves as a tree of virtual views, thus conveying their 209 * logical structure. 210 * </p> 211 * <p> 212 * <strong>Note:</strong> Cannot be called from an 213 * {@link android.accessibilityservice.AccessibilityService}. 214 * This class is made immutable before being delivered to an AccessibilityService. 215 * </p> 216 * 217 * @param root The root of the virtual subtree. 218 * @param virtualDescendantId The id of the virtual descendant. 219 */ 220 public void setSource(View root, int virtualDescendantId) { 221 enforceNotSealed(); 222 mWindowId = (root != null) ? root.getAccessibilityWindowId() : View.NO_ID; 223 final int rootAccessibilityViewId = 224 (root != null) ? root.getAccessibilityViewId() : View.NO_ID; 225 mSourceNodeId = makeNodeId(rootAccessibilityViewId, virtualDescendantId); 226 } 227 228 /** 229 * Gets the id of the window from which the info comes from. 230 * 231 * @return The window id. 232 */ 233 public int getWindowId() { 234 return mWindowId; 235 } 236 237 /** 238 * Gets the number of children. 239 * 240 * @return The child count. 241 */ 242 public int getChildCount() { 243 return mChildIds.size(); 244 } 245 246 /** 247 * Get the child at given index. 248 * <p> 249 * <strong>Note:</strong> It is a client responsibility to recycle the 250 * received info by calling {@link AccessibilityNodeInfo#recycle()} 251 * to avoid creating of multiple instances. 252 * </p> 253 * 254 * @param index The child index. 255 * @return The child node. 256 * 257 * @throws IllegalStateException If called outside of an AccessibilityService. 258 * 259 */ 260 public AccessibilityNodeInfo getChild(int index) { 261 enforceSealed(); 262 if (!canPerformRequestOverConnection(mSourceNodeId)) { 263 return null; 264 } 265 final long childId = mChildIds.get(index); 266 AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance(); 267 return client.findAccessibilityNodeInfoByAccessibilityId(mConnection, mWindowId, childId); 268 } 269 270 /** 271 * Adds a child. 272 * <p> 273 * <strong>Note:</strong> Cannot be called from an 274 * {@link android.accessibilityservice.AccessibilityService}. 275 * This class is made immutable before being delivered to an AccessibilityService. 276 * </p> 277 * 278 * @param child The child. 279 * 280 * @throws IllegalStateException If called from an AccessibilityService. 281 */ 282 public void addChild(View child) { 283 addChild(child, View.NO_ID); 284 } 285 286 /** 287 * Adds a virtual child which is a descendant of the given <code>root</code>. 288 * If <code>virtualDescendantId</code> is {@link View#NO_ID} the root 289 * is added as a child. 290 * <p> 291 * A virtual descendant is an imaginary View that is reported as a part of the view 292 * hierarchy for accessibility purposes. This enables custom views that draw complex 293 * content to report them selves as a tree of virtual views, thus conveying their 294 * logical structure. 295 * </p> 296 * 297 * @param root The root of the virtual subtree. 298 * @param virtualDescendantId The id of the virtual child. 299 */ 300 public void addChild(View root, int virtualDescendantId) { 301 enforceNotSealed(); 302 final int index = mChildIds.size(); 303 final int rootAccessibilityViewId = 304 (root != null) ? root.getAccessibilityViewId() : View.NO_ID; 305 final long childNodeId = makeNodeId(rootAccessibilityViewId, virtualDescendantId); 306 mChildIds.put(index, childNodeId); 307 } 308 309 /** 310 * Gets the actions that can be performed on the node. 311 * 312 * @return The bit mask of with actions. 313 * 314 * @see AccessibilityNodeInfo#ACTION_FOCUS 315 * @see AccessibilityNodeInfo#ACTION_CLEAR_FOCUS 316 * @see AccessibilityNodeInfo#ACTION_SELECT 317 * @see AccessibilityNodeInfo#ACTION_CLEAR_SELECTION 318 */ 319 public int getActions() { 320 return mActions; 321 } 322 323 /** 324 * Adds an action that can be performed on the node. 325 * <p> 326 * <strong>Note:</strong> Cannot be called from an 327 * {@link android.accessibilityservice.AccessibilityService}. 328 * This class is made immutable before being delivered to an AccessibilityService. 329 * </p> 330 * 331 * @param action The action. 332 * 333 * @throws IllegalStateException If called from an AccessibilityService. 334 */ 335 public void addAction(int action) { 336 enforceNotSealed(); 337 mActions |= action; 338 } 339 340 /** 341 * Performs an action on the node. 342 * <p> 343 * <strong>Note:</strong> An action can be performed only if the request is made 344 * from an {@link android.accessibilityservice.AccessibilityService}. 345 * </p> 346 * 347 * @param action The action to perform. 348 * @return True if the action was performed. 349 * 350 * @throws IllegalStateException If called outside of an AccessibilityService. 351 */ 352 public boolean performAction(int action) { 353 enforceSealed(); 354 if (!canPerformRequestOverConnection(mSourceNodeId)) { 355 return false; 356 } 357 AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance(); 358 return client.performAccessibilityAction(mConnection, mWindowId, mSourceNodeId, action); 359 } 360 361 /** 362 * Finds {@link AccessibilityNodeInfo}s by text. The match is case 363 * insensitive containment. The search is relative to this info i.e. 364 * this info is the root of the traversed tree. 365 * 366 * <p> 367 * <strong>Note:</strong> It is a client responsibility to recycle the 368 * received info by calling {@link AccessibilityNodeInfo#recycle()} 369 * to avoid creating of multiple instances. 370 * </p> 371 * 372 * @param text The searched text. 373 * @return A list of node info. 374 */ 375 public List<AccessibilityNodeInfo> findAccessibilityNodeInfosByText(String text) { 376 enforceSealed(); 377 if (!canPerformRequestOverConnection(mSourceNodeId)) { 378 return Collections.emptyList(); 379 } 380 AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance(); 381 return client.findAccessibilityNodeInfosByText(mConnection, text, mWindowId, mSourceNodeId); 382 } 383 384 /** 385 * Gets the parent. 386 * <p> 387 * <strong>Note:</strong> It is a client responsibility to recycle the 388 * received info by calling {@link AccessibilityNodeInfo#recycle()} 389 * to avoid creating of multiple instances. 390 * </p> 391 * 392 * @return The parent. 393 */ 394 public AccessibilityNodeInfo getParent() { 395 enforceSealed(); 396 if (!canPerformRequestOverConnection(mSourceNodeId)) { 397 return null; 398 } 399 AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance(); 400 return client.findAccessibilityNodeInfoByAccessibilityId(mConnection, 401 mWindowId, mParentNodeId); 402 } 403 404 /** 405 * Sets the parent. 406 * <p> 407 * <strong>Note:</strong> Cannot be called from an 408 * {@link android.accessibilityservice.AccessibilityService}. 409 * This class is made immutable before being delivered to an AccessibilityService. 410 * </p> 411 * 412 * @param parent The parent. 413 * 414 * @throws IllegalStateException If called from an AccessibilityService. 415 */ 416 public void setParent(View parent) { 417 setParent(parent, View.NO_ID); 418 } 419 420 /** 421 * Sets the parent to be a virtual descendant of the given <code>root</code>. 422 * If <code>virtualDescendantId</code> equals to {@link View#NO_ID} the root 423 * is set as the parent. 424 * <p> 425 * A virtual descendant is an imaginary View that is reported as a part of the view 426 * hierarchy for accessibility purposes. This enables custom views that draw complex 427 * content to report them selves as a tree of virtual views, thus conveying their 428 * logical structure. 429 * </p> 430 * <p> 431 * <strong>Note:</strong> Cannot be called from an 432 * {@link android.accessibilityservice.AccessibilityService}. 433 * This class is made immutable before being delivered to an AccessibilityService. 434 * </p> 435 * 436 * @param root The root of the virtual subtree. 437 * @param virtualDescendantId The id of the virtual descendant. 438 */ 439 public void setParent(View root, int virtualDescendantId) { 440 enforceNotSealed(); 441 final int rootAccessibilityViewId = 442 (root != null) ? root.getAccessibilityViewId() : View.NO_ID; 443 mParentNodeId = makeNodeId(rootAccessibilityViewId, virtualDescendantId); 444 } 445 446 /** 447 * Gets the node bounds in parent coordinates. 448 * 449 * @param outBounds The output node bounds. 450 */ 451 public void getBoundsInParent(Rect outBounds) { 452 outBounds.set(mBoundsInParent.left, mBoundsInParent.top, 453 mBoundsInParent.right, mBoundsInParent.bottom); 454 } 455 456 /** 457 * Sets the node bounds in parent coordinates. 458 * <p> 459 * <strong>Note:</strong> Cannot be called from an 460 * {@link android.accessibilityservice.AccessibilityService}. 461 * This class is made immutable before being delivered to an AccessibilityService. 462 * </p> 463 * 464 * @param bounds The node bounds. 465 * 466 * @throws IllegalStateException If called from an AccessibilityService. 467 */ 468 public void setBoundsInParent(Rect bounds) { 469 enforceNotSealed(); 470 mBoundsInParent.set(bounds.left, bounds.top, bounds.right, bounds.bottom); 471 } 472 473 /** 474 * Gets the node bounds in screen coordinates. 475 * 476 * @param outBounds The output node bounds. 477 */ 478 public void getBoundsInScreen(Rect outBounds) { 479 outBounds.set(mBoundsInScreen.left, mBoundsInScreen.top, 480 mBoundsInScreen.right, mBoundsInScreen.bottom); 481 } 482 483 /** 484 * Sets the node bounds in screen coordinates. 485 * <p> 486 * <strong>Note:</strong> Cannot be called from an 487 * {@link android.accessibilityservice.AccessibilityService}. 488 * This class is made immutable before being delivered to an AccessibilityService. 489 * </p> 490 * 491 * @param bounds The node bounds. 492 * 493 * @throws IllegalStateException If called from an AccessibilityService. 494 */ 495 public void setBoundsInScreen(Rect bounds) { 496 enforceNotSealed(); 497 mBoundsInScreen.set(bounds.left, bounds.top, bounds.right, bounds.bottom); 498 } 499 500 /** 501 * Gets whether this node is checkable. 502 * 503 * @return True if the node is checkable. 504 */ 505 public boolean isCheckable() { 506 return getBooleanProperty(PROPERTY_CHECKABLE); 507 } 508 509 /** 510 * Sets whether this node is checkable. 511 * <p> 512 * <strong>Note:</strong> Cannot be called from an 513 * {@link android.accessibilityservice.AccessibilityService}. 514 * This class is made immutable before being delivered to an AccessibilityService. 515 * </p> 516 * 517 * @param checkable True if the node is checkable. 518 * 519 * @throws IllegalStateException If called from an AccessibilityService. 520 */ 521 public void setCheckable(boolean checkable) { 522 setBooleanProperty(PROPERTY_CHECKABLE, checkable); 523 } 524 525 /** 526 * Gets whether this node is checked. 527 * 528 * @return True if the node is checked. 529 */ 530 public boolean isChecked() { 531 return getBooleanProperty(PROPERTY_CHECKED); 532 } 533 534 /** 535 * Sets whether this node is checked. 536 * <p> 537 * <strong>Note:</strong> Cannot be called from an 538 * {@link android.accessibilityservice.AccessibilityService}. 539 * This class is made immutable before being delivered to an AccessibilityService. 540 * </p> 541 * 542 * @param checked True if the node is checked. 543 * 544 * @throws IllegalStateException If called from an AccessibilityService. 545 */ 546 public void setChecked(boolean checked) { 547 setBooleanProperty(PROPERTY_CHECKED, checked); 548 } 549 550 /** 551 * Gets whether this node is focusable. 552 * 553 * @return True if the node is focusable. 554 */ 555 public boolean isFocusable() { 556 return getBooleanProperty(PROPERTY_FOCUSABLE); 557 } 558 559 /** 560 * Sets whether this node is focusable. 561 * <p> 562 * <strong>Note:</strong> Cannot be called from an 563 * {@link android.accessibilityservice.AccessibilityService}. 564 * This class is made immutable before being delivered to an AccessibilityService. 565 * </p> 566 * 567 * @param focusable True if the node is focusable. 568 * 569 * @throws IllegalStateException If called from an AccessibilityService. 570 */ 571 public void setFocusable(boolean focusable) { 572 setBooleanProperty(PROPERTY_FOCUSABLE, focusable); 573 } 574 575 /** 576 * Gets whether this node is focused. 577 * 578 * @return True if the node is focused. 579 */ 580 public boolean isFocused() { 581 return getBooleanProperty(PROPERTY_FOCUSED); 582 } 583 584 /** 585 * Sets whether this node is focused. 586 * <p> 587 * <strong>Note:</strong> Cannot be called from an 588 * {@link android.accessibilityservice.AccessibilityService}. 589 * This class is made immutable before being delivered to an AccessibilityService. 590 * </p> 591 * 592 * @param focused True if the node is focused. 593 * 594 * @throws IllegalStateException If called from an AccessibilityService. 595 */ 596 public void setFocused(boolean focused) { 597 setBooleanProperty(PROPERTY_FOCUSED, focused); 598 } 599 600 /** 601 * Gets whether this node is selected. 602 * 603 * @return True if the node is selected. 604 */ 605 public boolean isSelected() { 606 return getBooleanProperty(PROPERTY_SELECTED); 607 } 608 609 /** 610 * Sets whether this node is selected. 611 * <p> 612 * <strong>Note:</strong> Cannot be called from an 613 * {@link android.accessibilityservice.AccessibilityService}. 614 * This class is made immutable before being delivered to an AccessibilityService. 615 * </p> 616 * 617 * @param selected True if the node is selected. 618 * 619 * @throws IllegalStateException If called from an AccessibilityService. 620 */ 621 public void setSelected(boolean selected) { 622 setBooleanProperty(PROPERTY_SELECTED, selected); 623 } 624 625 /** 626 * Gets whether this node is clickable. 627 * 628 * @return True if the node is clickable. 629 */ 630 public boolean isClickable() { 631 return getBooleanProperty(PROPERTY_CLICKABLE); 632 } 633 634 /** 635 * Sets whether this node is clickable. 636 * <p> 637 * <strong>Note:</strong> Cannot be called from an 638 * {@link android.accessibilityservice.AccessibilityService}. 639 * This class is made immutable before being delivered to an AccessibilityService. 640 * </p> 641 * 642 * @param clickable True if the node is clickable. 643 * 644 * @throws IllegalStateException If called from an AccessibilityService. 645 */ 646 public void setClickable(boolean clickable) { 647 setBooleanProperty(PROPERTY_CLICKABLE, clickable); 648 } 649 650 /** 651 * Gets whether this node is long clickable. 652 * 653 * @return True if the node is long clickable. 654 */ 655 public boolean isLongClickable() { 656 return getBooleanProperty(PROPERTY_LONG_CLICKABLE); 657 } 658 659 /** 660 * Sets whether this node is long clickable. 661 * <p> 662 * <strong>Note:</strong> Cannot be called from an 663 * {@link android.accessibilityservice.AccessibilityService}. 664 * This class is made immutable before being delivered to an AccessibilityService. 665 * </p> 666 * 667 * @param longClickable True if the node is long clickable. 668 * 669 * @throws IllegalStateException If called from an AccessibilityService. 670 */ 671 public void setLongClickable(boolean longClickable) { 672 setBooleanProperty(PROPERTY_LONG_CLICKABLE, longClickable); 673 } 674 675 /** 676 * Gets whether this node is enabled. 677 * 678 * @return True if the node is enabled. 679 */ 680 public boolean isEnabled() { 681 return getBooleanProperty(PROPERTY_ENABLED); 682 } 683 684 /** 685 * Sets whether this node is enabled. 686 * <p> 687 * <strong>Note:</strong> Cannot be called from an 688 * {@link android.accessibilityservice.AccessibilityService}. 689 * This class is made immutable before being delivered to an AccessibilityService. 690 * </p> 691 * 692 * @param enabled True if the node is enabled. 693 * 694 * @throws IllegalStateException If called from an AccessibilityService. 695 */ 696 public void setEnabled(boolean enabled) { 697 setBooleanProperty(PROPERTY_ENABLED, enabled); 698 } 699 700 /** 701 * Gets whether this node is a password. 702 * 703 * @return True if the node is a password. 704 */ 705 public boolean isPassword() { 706 return getBooleanProperty(PROPERTY_PASSWORD); 707 } 708 709 /** 710 * Sets whether this node is a password. 711 * <p> 712 * <strong>Note:</strong> Cannot be called from an 713 * {@link android.accessibilityservice.AccessibilityService}. 714 * This class is made immutable before being delivered to an AccessibilityService. 715 * </p> 716 * 717 * @param password True if the node is a password. 718 * 719 * @throws IllegalStateException If called from an AccessibilityService. 720 */ 721 public void setPassword(boolean password) { 722 setBooleanProperty(PROPERTY_PASSWORD, password); 723 } 724 725 /** 726 * Gets if the node is scrollable. 727 * 728 * @return True if the node is scrollable, false otherwise. 729 */ 730 public boolean isScrollable() { 731 return getBooleanProperty(PROPERTY_SCROLLABLE); 732 } 733 734 /** 735 * Sets if the node is scrollable. 736 * <p> 737 * <strong>Note:</strong> Cannot be called from an 738 * {@link android.accessibilityservice.AccessibilityService}. 739 * This class is made immutable before being delivered to an AccessibilityService. 740 * </p> 741 * 742 * @param scrollable True if the node is scrollable, false otherwise. 743 * 744 * @throws IllegalStateException If called from an AccessibilityService. 745 */ 746 public void setScrollable(boolean scrollable) { 747 enforceNotSealed(); 748 setBooleanProperty(PROPERTY_SCROLLABLE, scrollable); 749 } 750 751 /** 752 * Gets the package this node comes from. 753 * 754 * @return The package name. 755 */ 756 public CharSequence getPackageName() { 757 return mPackageName; 758 } 759 760 /** 761 * Sets the package this node comes from. 762 * <p> 763 * <strong>Note:</strong> Cannot be called from an 764 * {@link android.accessibilityservice.AccessibilityService}. 765 * This class is made immutable before being delivered to an AccessibilityService. 766 * </p> 767 * 768 * @param packageName The package name. 769 * 770 * @throws IllegalStateException If called from an AccessibilityService. 771 */ 772 public void setPackageName(CharSequence packageName) { 773 enforceNotSealed(); 774 mPackageName = packageName; 775 } 776 777 /** 778 * Gets the class this node comes from. 779 * 780 * @return The class name. 781 */ 782 public CharSequence getClassName() { 783 return mClassName; 784 } 785 786 /** 787 * Sets the class this node comes from. 788 * <p> 789 * <strong>Note:</strong> Cannot be called from an 790 * {@link android.accessibilityservice.AccessibilityService}. 791 * This class is made immutable before being delivered to an AccessibilityService. 792 * </p> 793 * 794 * @param className The class name. 795 * 796 * @throws IllegalStateException If called from an AccessibilityService. 797 */ 798 public void setClassName(CharSequence className) { 799 enforceNotSealed(); 800 mClassName = className; 801 } 802 803 /** 804 * Gets the text of this node. 805 * 806 * @return The text. 807 */ 808 public CharSequence getText() { 809 return mText; 810 } 811 812 /** 813 * Sets the text of this node. 814 * <p> 815 * <strong>Note:</strong> Cannot be called from an 816 * {@link android.accessibilityservice.AccessibilityService}. 817 * This class is made immutable before being delivered to an AccessibilityService. 818 * </p> 819 * 820 * @param text The text. 821 * 822 * @throws IllegalStateException If called from an AccessibilityService. 823 */ 824 public void setText(CharSequence text) { 825 enforceNotSealed(); 826 mText = text; 827 } 828 829 /** 830 * Gets the content description of this node. 831 * 832 * @return The content description. 833 */ 834 public CharSequence getContentDescription() { 835 return mContentDescription; 836 } 837 838 /** 839 * Sets the content description of this node. 840 * <p> 841 * <strong>Note:</strong> Cannot be called from an 842 * {@link android.accessibilityservice.AccessibilityService}. 843 * This class is made immutable before being delivered to an AccessibilityService. 844 * </p> 845 * 846 * @param contentDescription The content description. 847 * 848 * @throws IllegalStateException If called from an AccessibilityService. 849 */ 850 public void setContentDescription(CharSequence contentDescription) { 851 enforceNotSealed(); 852 mContentDescription = contentDescription; 853 } 854 855 /** 856 * Gets the value of a boolean property. 857 * 858 * @param property The property. 859 * @return The value. 860 */ 861 private boolean getBooleanProperty(int property) { 862 return (mBooleanProperties & property) != 0; 863 } 864 865 /** 866 * Sets a boolean property. 867 * 868 * @param property The property. 869 * @param value The value. 870 * 871 * @throws IllegalStateException If called from an AccessibilityService. 872 */ 873 private void setBooleanProperty(int property, boolean value) { 874 enforceNotSealed(); 875 if (value) { 876 mBooleanProperties |= property; 877 } else { 878 mBooleanProperties &= ~property; 879 } 880 } 881 882 /** 883 * Sets the connection for interacting with the system. 884 * 885 * @param connection The client token. 886 * 887 * @hide 888 */ 889 public final void setConnection(IAccessibilityServiceConnection connection) { 890 enforceNotSealed(); 891 mConnection = connection; 892 } 893 894 /** 895 * {@inheritDoc} 896 */ 897 public int describeContents() { 898 return 0; 899 } 900 901 /** 902 * Sets if this instance is sealed. 903 * 904 * @param sealed Whether is sealed. 905 * 906 * @hide 907 */ 908 public void setSealed(boolean sealed) { 909 mSealed = sealed; 910 } 911 912 /** 913 * Gets if this instance is sealed. 914 * 915 * @return Whether is sealed. 916 * 917 * @hide 918 */ 919 public boolean isSealed() { 920 return mSealed; 921 } 922 923 /** 924 * Enforces that this instance is sealed. 925 * 926 * @throws IllegalStateException If this instance is not sealed. 927 * 928 * @hide 929 */ 930 protected void enforceSealed() { 931 if (!isSealed()) { 932 throw new IllegalStateException("Cannot perform this " 933 + "action on a not sealed instance."); 934 } 935 } 936 937 /** 938 * Enforces that this instance is not sealed. 939 * 940 * @throws IllegalStateException If this instance is sealed. 941 * 942 * @hide 943 */ 944 protected void enforceNotSealed() { 945 if (isSealed()) { 946 throw new IllegalStateException("Cannot perform this " 947 + "action on an sealed instance."); 948 } 949 } 950 951 /** 952 * Returns a cached instance if such is available otherwise a new one 953 * and sets the source. 954 * 955 * @param source The source view. 956 * @return An instance. 957 * 958 * @see #setSource(View) 959 */ 960 public static AccessibilityNodeInfo obtain(View source) { 961 AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain(); 962 info.setSource(source); 963 return info; 964 } 965 966 /** 967 * Returns a cached instance if such is available otherwise a new one 968 * and sets the source. 969 * 970 * @param root The root of the virtual subtree. 971 * @param virtualDescendantId The id of the virtual descendant. 972 * @return An instance. 973 * 974 * @see #setSource(View, int) 975 */ 976 public static AccessibilityNodeInfo obtain(View root, int virtualDescendantId) { 977 AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain(); 978 info.setSource(root, virtualDescendantId); 979 return info; 980 } 981 982 /** 983 * Returns a cached instance if such is available otherwise a new one. 984 * 985 * @return An instance. 986 */ 987 public static AccessibilityNodeInfo obtain() { 988 synchronized (sPoolLock) { 989 if (sPool != null) { 990 AccessibilityNodeInfo info = sPool; 991 sPool = sPool.mNext; 992 sPoolSize--; 993 info.mNext = null; 994 info.mIsInPool = false; 995 return info; 996 } 997 return new AccessibilityNodeInfo(); 998 } 999 } 1000 1001 /** 1002 * Returns a cached instance if such is available or a new one is 1003 * create. The returned instance is initialized from the given 1004 * <code>info</code>. 1005 * 1006 * @param info The other info. 1007 * @return An instance. 1008 */ 1009 public static AccessibilityNodeInfo obtain(AccessibilityNodeInfo info) { 1010 AccessibilityNodeInfo infoClone = AccessibilityNodeInfo.obtain(); 1011 infoClone.init(info); 1012 return infoClone; 1013 } 1014 1015 /** 1016 * Return an instance back to be reused. 1017 * <p> 1018 * <strong>Note:</strong> You must not touch the object after calling this function. 1019 * 1020 * @throws IllegalStateException If the info is already recycled. 1021 */ 1022 public void recycle() { 1023 if (mIsInPool) { 1024 throw new IllegalStateException("Info already recycled!"); 1025 } 1026 clear(); 1027 synchronized (sPoolLock) { 1028 if (sPoolSize <= MAX_POOL_SIZE) { 1029 mNext = sPool; 1030 sPool = this; 1031 mIsInPool = true; 1032 sPoolSize++; 1033 } 1034 } 1035 } 1036 1037 /** 1038 * {@inheritDoc} 1039 * <p> 1040 * <strong>Note:</strong> After the instance is written to a parcel it 1041 * is recycled. You must not touch the object after calling this function. 1042 * </p> 1043 */ 1044 public void writeToParcel(Parcel parcel, int flags) { 1045 if (mConnection == null) { 1046 parcel.writeInt(0); 1047 } else { 1048 parcel.writeInt(1); 1049 parcel.writeStrongBinder(mConnection.asBinder()); 1050 } 1051 parcel.writeInt(isSealed() ? 1 : 0); 1052 parcel.writeLong(mSourceNodeId); 1053 parcel.writeInt(mWindowId); 1054 parcel.writeLong(mParentNodeId); 1055 1056 SparseLongArray childIds = mChildIds; 1057 final int childIdsSize = childIds.size(); 1058 parcel.writeInt(childIdsSize); 1059 for (int i = 0; i < childIdsSize; i++) { 1060 parcel.writeLong(childIds.valueAt(i)); 1061 } 1062 1063 parcel.writeInt(mBoundsInParent.top); 1064 parcel.writeInt(mBoundsInParent.bottom); 1065 parcel.writeInt(mBoundsInParent.left); 1066 parcel.writeInt(mBoundsInParent.right); 1067 1068 parcel.writeInt(mBoundsInScreen.top); 1069 parcel.writeInt(mBoundsInScreen.bottom); 1070 parcel.writeInt(mBoundsInScreen.left); 1071 parcel.writeInt(mBoundsInScreen.right); 1072 1073 parcel.writeInt(mActions); 1074 1075 parcel.writeInt(mBooleanProperties); 1076 1077 TextUtils.writeToParcel(mPackageName, parcel, flags); 1078 TextUtils.writeToParcel(mClassName, parcel, flags); 1079 TextUtils.writeToParcel(mText, parcel, flags); 1080 TextUtils.writeToParcel(mContentDescription, parcel, flags); 1081 1082 // Since instances of this class are fetched via synchronous i.e. blocking 1083 // calls in IPCs we always recycle as soon as the instance is marshaled. 1084 recycle(); 1085 } 1086 1087 /** 1088 * Initializes this instance from another one. 1089 * 1090 * @param other The other instance. 1091 */ 1092 private void init(AccessibilityNodeInfo other) { 1093 mSealed = other.mSealed; 1094 mConnection = other.mConnection; 1095 mSourceNodeId = other.mSourceNodeId; 1096 mParentNodeId = other.mParentNodeId; 1097 mWindowId = other.mWindowId; 1098 mBoundsInParent.set(other.mBoundsInParent); 1099 mBoundsInScreen.set(other.mBoundsInScreen); 1100 mPackageName = other.mPackageName; 1101 mClassName = other.mClassName; 1102 mText = other.mText; 1103 mContentDescription = other.mContentDescription; 1104 mActions= other.mActions; 1105 mBooleanProperties = other.mBooleanProperties; 1106 mChildIds = other.mChildIds.clone(); 1107 } 1108 1109 /** 1110 * Creates a new instance from a {@link Parcel}. 1111 * 1112 * @param parcel A parcel containing the state of a {@link AccessibilityNodeInfo}. 1113 */ 1114 private void initFromParcel(Parcel parcel) { 1115 if (parcel.readInt() == 1) { 1116 mConnection = IAccessibilityServiceConnection.Stub.asInterface( 1117 parcel.readStrongBinder()); 1118 } 1119 mSealed = (parcel.readInt() == 1); 1120 mSourceNodeId = parcel.readLong(); 1121 mWindowId = parcel.readInt(); 1122 mParentNodeId = parcel.readLong(); 1123 1124 SparseLongArray childIds = mChildIds; 1125 final int childrenSize = parcel.readInt(); 1126 for (int i = 0; i < childrenSize; i++) { 1127 final long childId = parcel.readLong(); 1128 childIds.put(i, childId); 1129 } 1130 1131 mBoundsInParent.top = parcel.readInt(); 1132 mBoundsInParent.bottom = parcel.readInt(); 1133 mBoundsInParent.left = parcel.readInt(); 1134 mBoundsInParent.right = parcel.readInt(); 1135 1136 mBoundsInScreen.top = parcel.readInt(); 1137 mBoundsInScreen.bottom = parcel.readInt(); 1138 mBoundsInScreen.left = parcel.readInt(); 1139 mBoundsInScreen.right = parcel.readInt(); 1140 1141 mActions = parcel.readInt(); 1142 1143 mBooleanProperties = parcel.readInt(); 1144 1145 mPackageName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel); 1146 mClassName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel); 1147 mText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel); 1148 mContentDescription = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel); 1149 } 1150 1151 /** 1152 * Clears the state of this instance. 1153 */ 1154 private void clear() { 1155 mSealed = false; 1156 mConnection = null; 1157 mSourceNodeId = makeNodeId(View.NO_ID, View.NO_ID); 1158 mParentNodeId = makeNodeId(View.NO_ID, View.NO_ID); 1159 mWindowId = View.NO_ID; 1160 mChildIds.clear(); 1161 mBoundsInParent.set(0, 0, 0, 0); 1162 mBoundsInScreen.set(0, 0, 0, 0); 1163 mBooleanProperties = 0; 1164 mPackageName = null; 1165 mClassName = null; 1166 mText = null; 1167 mContentDescription = null; 1168 mActions = 0; 1169 } 1170 1171 /** 1172 * Gets the human readable action symbolic name. 1173 * 1174 * @param action The action. 1175 * @return The symbolic name. 1176 */ 1177 private static String getActionSymbolicName(int action) { 1178 switch (action) { 1179 case ACTION_FOCUS: 1180 return "ACTION_FOCUS"; 1181 case ACTION_CLEAR_FOCUS: 1182 return "ACTION_CLEAR_FOCUS"; 1183 case ACTION_SELECT: 1184 return "ACTION_SELECT"; 1185 case ACTION_CLEAR_SELECTION: 1186 return "ACTION_CLEAR_SELECTION"; 1187 default: 1188 throw new IllegalArgumentException("Unknown action: " + action); 1189 } 1190 } 1191 1192 private boolean canPerformRequestOverConnection(long accessibilityNodeId) { 1193 return (mWindowId != View.NO_ID 1194 && getAccessibilityViewId(accessibilityNodeId) != View.NO_ID 1195 && mConnection != null); 1196 } 1197 1198 @Override 1199 public boolean equals(Object object) { 1200 if (this == object) { 1201 return true; 1202 } 1203 if (object == null) { 1204 return false; 1205 } 1206 if (getClass() != object.getClass()) { 1207 return false; 1208 } 1209 AccessibilityNodeInfo other = (AccessibilityNodeInfo) object; 1210 if (mSourceNodeId != other.mSourceNodeId) { 1211 return false; 1212 } 1213 if (mWindowId != other.mWindowId) { 1214 return false; 1215 } 1216 return true; 1217 } 1218 1219 @Override 1220 public int hashCode() { 1221 final int prime = 31; 1222 int result = 1; 1223 result = prime * result + getAccessibilityViewId(mSourceNodeId); 1224 result = prime * result + getVirtualDescendantId(mSourceNodeId); 1225 result = prime * result + mWindowId; 1226 return result; 1227 } 1228 1229 @Override 1230 public String toString() { 1231 StringBuilder builder = new StringBuilder(); 1232 builder.append(super.toString()); 1233 1234 if (DEBUG) { 1235 builder.append("; accessibilityViewId: " + getAccessibilityViewId(mSourceNodeId)); 1236 builder.append("; virtualDescendantId: " + getVirtualDescendantId(mSourceNodeId)); 1237 builder.append("; mParentNodeId: " + mParentNodeId); 1238 SparseLongArray childIds = mChildIds; 1239 builder.append("; childAccessibilityIds: ["); 1240 for (int i = 0, count = childIds.size(); i < count; i++) { 1241 builder.append(childIds.valueAt(i)); 1242 if (i < count - 1) { 1243 builder.append(", "); 1244 } 1245 } 1246 builder.append("]"); 1247 } 1248 1249 builder.append("; boundsInParent: " + mBoundsInParent); 1250 builder.append("; boundsInScreen: " + mBoundsInScreen); 1251 1252 builder.append("; packageName: ").append(mPackageName); 1253 builder.append("; className: ").append(mClassName); 1254 builder.append("; text: ").append(mText); 1255 builder.append("; contentDescription: ").append(mContentDescription); 1256 1257 builder.append("; checkable: ").append(isCheckable()); 1258 builder.append("; checked: ").append(isChecked()); 1259 builder.append("; focusable: ").append(isFocusable()); 1260 builder.append("; focused: ").append(isFocused()); 1261 builder.append("; selected: ").append(isSelected()); 1262 builder.append("; clickable: ").append(isClickable()); 1263 builder.append("; longClickable: ").append(isLongClickable()); 1264 builder.append("; enabled: ").append(isEnabled()); 1265 builder.append("; password: ").append(isPassword()); 1266 builder.append("; scrollable: " + isScrollable()); 1267 1268 builder.append("; ["); 1269 1270 for (int actionBits = mActions; actionBits != 0;) { 1271 final int action = 1 << Integer.numberOfTrailingZeros(actionBits); 1272 actionBits &= ~action; 1273 builder.append(getActionSymbolicName(action)); 1274 if (actionBits != 0) { 1275 builder.append(", "); 1276 } 1277 } 1278 1279 builder.append("]"); 1280 1281 return builder.toString(); 1282 } 1283 1284 /** 1285 * @see Parcelable.Creator 1286 */ 1287 public static final Parcelable.Creator<AccessibilityNodeInfo> CREATOR = 1288 new Parcelable.Creator<AccessibilityNodeInfo>() { 1289 public AccessibilityNodeInfo createFromParcel(Parcel parcel) { 1290 AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain(); 1291 info.initFromParcel(parcel); 1292 return info; 1293 } 1294 1295 public AccessibilityNodeInfo[] newArray(int size) { 1296 return new AccessibilityNodeInfo[size]; 1297 } 1298 }; 1299} 1300