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