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