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