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