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