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