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