1 /* 2 * Copyright (C) 2011 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.view.accessibility; 18 19 import static com.android.internal.util.CollectionUtils.isEmpty; 20 21 import android.annotation.Nullable; 22 import android.os.Parcelable; 23 import android.view.View; 24 25 import java.util.ArrayList; 26 import java.util.List; 27 28 /** 29 * Represents a record in an {@link AccessibilityEvent} and contains information 30 * about state change of its source {@link android.view.View}. When a view fires 31 * an accessibility event it requests from its parent to dispatch the 32 * constructed event. The parent may optionally append a record for itself 33 * for providing more context to 34 * {@link android.accessibilityservice.AccessibilityService}s. Hence, 35 * accessibility services can facilitate additional accessibility records 36 * to enhance feedback. 37 * </p> 38 * <p> 39 * Once the accessibility event containing a record is dispatched the record is 40 * made immutable and calling a state mutation method generates an error. 41 * </p> 42 * <p> 43 * <strong>Note:</strong> Not all properties are applicable to all accessibility 44 * event types. For detailed information please refer to {@link AccessibilityEvent}. 45 * </p> 46 * 47 * <div class="special reference"> 48 * <h3>Developer Guides</h3> 49 * <p>For more information about creating and processing AccessibilityRecords, read the 50 * <a href="{@docRoot}guide/topics/ui/accessibility/index.html">Accessibility</a> 51 * developer guide.</p> 52 * </div> 53 * 54 * @see AccessibilityEvent 55 * @see AccessibilityManager 56 * @see android.accessibilityservice.AccessibilityService 57 * @see AccessibilityNodeInfo 58 */ 59 public class AccessibilityRecord { 60 /** @hide */ 61 protected static final boolean DEBUG_CONCISE_TOSTRING = false; 62 63 private static final int UNDEFINED = -1; 64 65 private static final int PROPERTY_CHECKED = 0x00000001; 66 private static final int PROPERTY_ENABLED = 0x00000002; 67 private static final int PROPERTY_PASSWORD = 0x00000004; 68 private static final int PROPERTY_FULL_SCREEN = 0x00000080; 69 private static final int PROPERTY_SCROLLABLE = 0x00000100; 70 private static final int PROPERTY_IMPORTANT_FOR_ACCESSIBILITY = 0x00000200; 71 72 private static final int GET_SOURCE_PREFETCH_FLAGS = 73 AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS 74 | AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS 75 | AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS; 76 77 // Housekeeping 78 private static final int MAX_POOL_SIZE = 10; 79 private static final Object sPoolLock = new Object(); 80 private static AccessibilityRecord sPool; 81 private static int sPoolSize; 82 private AccessibilityRecord mNext; 83 private boolean mIsInPool; 84 85 boolean mSealed; 86 int mBooleanProperties = 0; 87 int mCurrentItemIndex = UNDEFINED; 88 int mItemCount = UNDEFINED; 89 int mFromIndex = UNDEFINED; 90 int mToIndex = UNDEFINED; 91 int mScrollX = UNDEFINED; 92 int mScrollY = UNDEFINED; 93 94 int mScrollDeltaX = UNDEFINED; 95 int mScrollDeltaY = UNDEFINED; 96 int mMaxScrollX = UNDEFINED; 97 int mMaxScrollY = UNDEFINED; 98 99 int mAddedCount= UNDEFINED; 100 int mRemovedCount = UNDEFINED; 101 long mSourceNodeId = AccessibilityNodeInfo.UNDEFINED_NODE_ID; 102 int mSourceWindowId = AccessibilityWindowInfo.UNDEFINED_WINDOW_ID; 103 104 CharSequence mClassName; 105 CharSequence mContentDescription; 106 CharSequence mBeforeText; 107 Parcelable mParcelableData; 108 109 final List<CharSequence> mText = new ArrayList<CharSequence>(); 110 111 int mConnectionId = UNDEFINED; 112 113 /* 114 * Hide constructor. 115 */ AccessibilityRecord()116 AccessibilityRecord() { 117 } 118 119 /** 120 * Sets the event source. 121 * 122 * @param source The source. 123 * 124 * @throws IllegalStateException If called from an AccessibilityService. 125 */ setSource(View source)126 public void setSource(View source) { 127 setSource(source, AccessibilityNodeProvider.HOST_VIEW_ID); 128 } 129 130 /** 131 * Sets the source to be a virtual descendant of the given <code>root</code>. 132 * If <code>virtualDescendantId</code> equals to {@link View#NO_ID} the root 133 * is set as the source. 134 * <p> 135 * A virtual descendant is an imaginary View that is reported as a part of the view 136 * hierarchy for accessibility purposes. This enables custom views that draw complex 137 * content to report them selves as a tree of virtual views, thus conveying their 138 * logical structure. 139 * </p> 140 * 141 * @param root The root of the virtual subtree. 142 * @param virtualDescendantId The id of the virtual descendant. 143 */ setSource(@ullable View root, int virtualDescendantId)144 public void setSource(@Nullable View root, int virtualDescendantId) { 145 enforceNotSealed(); 146 boolean important = true; 147 int rootViewId = AccessibilityNodeInfo.UNDEFINED_ITEM_ID; 148 mSourceWindowId = AccessibilityWindowInfo.UNDEFINED_WINDOW_ID; 149 if (root != null) { 150 important = root.isImportantForAccessibility(); 151 rootViewId = root.getAccessibilityViewId(); 152 mSourceWindowId = root.getAccessibilityWindowId(); 153 } 154 setBooleanProperty(PROPERTY_IMPORTANT_FOR_ACCESSIBILITY, important); 155 mSourceNodeId = AccessibilityNodeInfo.makeNodeId(rootViewId, virtualDescendantId); 156 } 157 158 /** 159 * Set the source node ID directly 160 * 161 * @param sourceNodeId The source node Id 162 * @hide 163 */ setSourceNodeId(long sourceNodeId)164 public void setSourceNodeId(long sourceNodeId) { 165 mSourceNodeId = sourceNodeId; 166 } 167 168 /** 169 * Gets the {@link AccessibilityNodeInfo} of the event source. 170 * <p> 171 * <strong>Note:</strong> It is a client responsibility to recycle the received info 172 * by calling {@link AccessibilityNodeInfo#recycle() AccessibilityNodeInfo#recycle()} 173 * to avoid creating of multiple instances. 174 * </p> 175 * @return The info of the source. 176 */ getSource()177 public AccessibilityNodeInfo getSource() { 178 enforceSealed(); 179 if ((mConnectionId == UNDEFINED) 180 || (mSourceWindowId == AccessibilityWindowInfo.UNDEFINED_WINDOW_ID) 181 || (AccessibilityNodeInfo.getAccessibilityViewId(mSourceNodeId) 182 == AccessibilityNodeInfo.UNDEFINED_ITEM_ID)) { 183 return null; 184 } 185 AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance(); 186 return client.findAccessibilityNodeInfoByAccessibilityId(mConnectionId, mSourceWindowId, 187 mSourceNodeId, false, GET_SOURCE_PREFETCH_FLAGS, null); 188 } 189 190 /** 191 * Sets the window id. 192 * 193 * @param windowId The window id. 194 * 195 * @hide 196 */ setWindowId(int windowId)197 public void setWindowId(int windowId) { 198 mSourceWindowId = windowId; 199 } 200 201 /** 202 * Gets the id of the window from which the event comes from. 203 * 204 * @return The window id. 205 */ getWindowId()206 public int getWindowId() { 207 return mSourceWindowId; 208 } 209 210 /** 211 * Gets if the source is checked. 212 * 213 * @return True if the view is checked, false otherwise. 214 */ isChecked()215 public boolean isChecked() { 216 return getBooleanProperty(PROPERTY_CHECKED); 217 } 218 219 /** 220 * Sets if the source is checked. 221 * 222 * @param isChecked True if the view is checked, false otherwise. 223 * 224 * @throws IllegalStateException If called from an AccessibilityService. 225 */ setChecked(boolean isChecked)226 public void setChecked(boolean isChecked) { 227 enforceNotSealed(); 228 setBooleanProperty(PROPERTY_CHECKED, isChecked); 229 } 230 231 /** 232 * Gets if the source is enabled. 233 * 234 * @return True if the view is enabled, false otherwise. 235 */ isEnabled()236 public boolean isEnabled() { 237 return getBooleanProperty(PROPERTY_ENABLED); 238 } 239 240 /** 241 * Sets if the source is enabled. 242 * 243 * @param isEnabled True if the view is enabled, false otherwise. 244 * 245 * @throws IllegalStateException If called from an AccessibilityService. 246 */ setEnabled(boolean isEnabled)247 public void setEnabled(boolean isEnabled) { 248 enforceNotSealed(); 249 setBooleanProperty(PROPERTY_ENABLED, isEnabled); 250 } 251 252 /** 253 * Gets if the source is a password field. 254 * 255 * @return True if the view is a password field, false otherwise. 256 */ isPassword()257 public boolean isPassword() { 258 return getBooleanProperty(PROPERTY_PASSWORD); 259 } 260 261 /** 262 * Sets if the source is a password field. 263 * 264 * @param isPassword True if the view is a password field, false otherwise. 265 * 266 * @throws IllegalStateException If called from an AccessibilityService. 267 */ setPassword(boolean isPassword)268 public void setPassword(boolean isPassword) { 269 enforceNotSealed(); 270 setBooleanProperty(PROPERTY_PASSWORD, isPassword); 271 } 272 273 /** 274 * Gets if the source is taking the entire screen. 275 * 276 * @return True if the source is full screen, false otherwise. 277 */ isFullScreen()278 public boolean isFullScreen() { 279 return getBooleanProperty(PROPERTY_FULL_SCREEN); 280 } 281 282 /** 283 * Sets if the source is taking the entire screen. 284 * 285 * @param isFullScreen True if the source is full screen, false otherwise. 286 * 287 * @throws IllegalStateException If called from an AccessibilityService. 288 */ setFullScreen(boolean isFullScreen)289 public void setFullScreen(boolean isFullScreen) { 290 enforceNotSealed(); 291 setBooleanProperty(PROPERTY_FULL_SCREEN, isFullScreen); 292 } 293 294 /** 295 * Gets if the source is scrollable. 296 * 297 * @return True if the source is scrollable, false otherwise. 298 */ isScrollable()299 public boolean isScrollable() { 300 return getBooleanProperty(PROPERTY_SCROLLABLE); 301 } 302 303 /** 304 * Sets if the source is scrollable. 305 * 306 * @param scrollable True if the source is scrollable, false otherwise. 307 * 308 * @throws IllegalStateException If called from an AccessibilityService. 309 */ setScrollable(boolean scrollable)310 public void setScrollable(boolean scrollable) { 311 enforceNotSealed(); 312 setBooleanProperty(PROPERTY_SCROLLABLE, scrollable); 313 } 314 315 /** 316 * Gets if the source is important for accessibility. 317 * 318 * <strong>Note:</strong> Used only internally to determine whether 319 * to deliver the event to a given accessibility service since some 320 * services may want to regard all views for accessibility while others 321 * may want to regard only the important views for accessibility. 322 * 323 * @return True if the source is important for accessibility, 324 * false otherwise. 325 * 326 * @hide 327 */ isImportantForAccessibility()328 public boolean isImportantForAccessibility() { 329 return getBooleanProperty(PROPERTY_IMPORTANT_FOR_ACCESSIBILITY); 330 } 331 332 /** 333 * Sets if the source is important for accessibility. 334 * 335 * @param importantForAccessibility True if the source is important for accessibility, 336 * false otherwise. 337 * 338 * @throws IllegalStateException If called from an AccessibilityService. 339 * @hide 340 */ setImportantForAccessibility(boolean importantForAccessibility)341 public void setImportantForAccessibility(boolean importantForAccessibility) { 342 enforceNotSealed(); 343 setBooleanProperty(PROPERTY_IMPORTANT_FOR_ACCESSIBILITY, importantForAccessibility); 344 } 345 346 /** 347 * Gets the number of items that can be visited. 348 * 349 * @return The number of items. 350 */ getItemCount()351 public int getItemCount() { 352 return mItemCount; 353 } 354 355 /** 356 * Sets the number of items that can be visited. 357 * 358 * @param itemCount The number of items. 359 * 360 * @throws IllegalStateException If called from an AccessibilityService. 361 */ setItemCount(int itemCount)362 public void setItemCount(int itemCount) { 363 enforceNotSealed(); 364 mItemCount = itemCount; 365 } 366 367 /** 368 * Gets the index of the source in the list of items the can be visited. 369 * 370 * @return The current item index. 371 */ getCurrentItemIndex()372 public int getCurrentItemIndex() { 373 return mCurrentItemIndex; 374 } 375 376 /** 377 * Sets the index of the source in the list of items that can be visited. 378 * 379 * @param currentItemIndex The current item index. 380 * 381 * @throws IllegalStateException If called from an AccessibilityService. 382 */ setCurrentItemIndex(int currentItemIndex)383 public void setCurrentItemIndex(int currentItemIndex) { 384 enforceNotSealed(); 385 mCurrentItemIndex = currentItemIndex; 386 } 387 388 /** 389 * Gets the index of the first character of the changed sequence, 390 * or the beginning of a text selection or the index of the first 391 * visible item when scrolling. 392 * 393 * @return The index of the first character or selection 394 * start or the first visible item. 395 */ getFromIndex()396 public int getFromIndex() { 397 return mFromIndex; 398 } 399 400 /** 401 * Sets the index of the first character of the changed sequence 402 * or the beginning of a text selection or the index of the first 403 * visible item when scrolling. 404 * 405 * @param fromIndex The index of the first character or selection 406 * start or the first visible item. 407 * 408 * @throws IllegalStateException If called from an AccessibilityService. 409 */ setFromIndex(int fromIndex)410 public void setFromIndex(int fromIndex) { 411 enforceNotSealed(); 412 mFromIndex = fromIndex; 413 } 414 415 /** 416 * Gets the index of text selection end or the index of the last 417 * visible item when scrolling. 418 * 419 * @return The index of selection end or last item index. 420 */ getToIndex()421 public int getToIndex() { 422 return mToIndex; 423 } 424 425 /** 426 * Sets the index of text selection end or the index of the last 427 * visible item when scrolling. 428 * 429 * @param toIndex The index of selection end or last item index. 430 */ setToIndex(int toIndex)431 public void setToIndex(int toIndex) { 432 enforceNotSealed(); 433 mToIndex = toIndex; 434 } 435 436 /** 437 * Gets the scroll offset of the source left edge in pixels. 438 * 439 * @return The scroll. 440 */ getScrollX()441 public int getScrollX() { 442 return mScrollX; 443 } 444 445 /** 446 * Sets the scroll offset of the source left edge in pixels. 447 * 448 * @param scrollX The scroll. 449 */ setScrollX(int scrollX)450 public void setScrollX(int scrollX) { 451 enforceNotSealed(); 452 mScrollX = scrollX; 453 } 454 455 /** 456 * Gets the scroll offset of the source top edge in pixels. 457 * 458 * @return The scroll. 459 */ getScrollY()460 public int getScrollY() { 461 return mScrollY; 462 } 463 464 /** 465 * Sets the scroll offset of the source top edge in pixels. 466 * 467 * @param scrollY The scroll. 468 */ setScrollY(int scrollY)469 public void setScrollY(int scrollY) { 470 enforceNotSealed(); 471 mScrollY = scrollY; 472 } 473 474 /** 475 * Gets the difference in pixels between the horizontal position before the scroll and the 476 * current horizontal position 477 * 478 * @return the scroll delta x 479 */ getScrollDeltaX()480 public int getScrollDeltaX() { 481 return mScrollDeltaX; 482 } 483 484 /** 485 * Sets the difference in pixels between the horizontal position before the scroll and the 486 * current horizontal position 487 * 488 * @param scrollDeltaX the scroll delta x 489 */ setScrollDeltaX(int scrollDeltaX)490 public void setScrollDeltaX(int scrollDeltaX) { 491 enforceNotSealed(); 492 mScrollDeltaX = scrollDeltaX; 493 } 494 495 /** 496 * Gets the difference in pixels between the vertical position before the scroll and the 497 * current vertical position 498 * 499 * @return the scroll delta y 500 */ getScrollDeltaY()501 public int getScrollDeltaY() { 502 return mScrollDeltaY; 503 } 504 505 /** 506 * Sets the difference in pixels between the vertical position before the scroll and the 507 * current vertical position 508 * 509 * @param scrollDeltaY the scroll delta y 510 */ setScrollDeltaY(int scrollDeltaY)511 public void setScrollDeltaY(int scrollDeltaY) { 512 enforceNotSealed(); 513 mScrollDeltaY = scrollDeltaY; 514 } 515 516 /** 517 * Gets the max scroll offset of the source left edge in pixels. 518 * 519 * @return The max scroll. 520 */ getMaxScrollX()521 public int getMaxScrollX() { 522 return mMaxScrollX; 523 } 524 525 /** 526 * Sets the max scroll offset of the source left edge in pixels. 527 * 528 * @param maxScrollX The max scroll. 529 */ setMaxScrollX(int maxScrollX)530 public void setMaxScrollX(int maxScrollX) { 531 enforceNotSealed(); 532 mMaxScrollX = maxScrollX; 533 } 534 535 /** 536 * Gets the max scroll offset of the source top edge in pixels. 537 * 538 * @return The max scroll. 539 */ getMaxScrollY()540 public int getMaxScrollY() { 541 return mMaxScrollY; 542 } 543 544 /** 545 * Sets the max scroll offset of the source top edge in pixels. 546 * 547 * @param maxScrollY The max scroll. 548 */ setMaxScrollY(int maxScrollY)549 public void setMaxScrollY(int maxScrollY) { 550 enforceNotSealed(); 551 mMaxScrollY = maxScrollY; 552 } 553 554 /** 555 * Gets the number of added characters. 556 * 557 * @return The number of added characters. 558 */ getAddedCount()559 public int getAddedCount() { 560 return mAddedCount; 561 } 562 563 /** 564 * Sets the number of added characters. 565 * 566 * @param addedCount The number of added characters. 567 * 568 * @throws IllegalStateException If called from an AccessibilityService. 569 */ setAddedCount(int addedCount)570 public void setAddedCount(int addedCount) { 571 enforceNotSealed(); 572 mAddedCount = addedCount; 573 } 574 575 /** 576 * Gets the number of removed characters. 577 * 578 * @return The number of removed characters. 579 */ getRemovedCount()580 public int getRemovedCount() { 581 return mRemovedCount; 582 } 583 584 /** 585 * Sets the number of removed characters. 586 * 587 * @param removedCount The number of removed characters. 588 * 589 * @throws IllegalStateException If called from an AccessibilityService. 590 */ setRemovedCount(int removedCount)591 public void setRemovedCount(int removedCount) { 592 enforceNotSealed(); 593 mRemovedCount = removedCount; 594 } 595 596 /** 597 * Gets the class name of the source. 598 * 599 * @return The class name. 600 */ getClassName()601 public CharSequence getClassName() { 602 return mClassName; 603 } 604 605 /** 606 * Sets the class name of the source. 607 * 608 * @param className The lass name. 609 * 610 * @throws IllegalStateException If called from an AccessibilityService. 611 */ setClassName(CharSequence className)612 public void setClassName(CharSequence className) { 613 enforceNotSealed(); 614 mClassName = className; 615 } 616 617 /** 618 * Gets the text of the event. The index in the list represents the priority 619 * of the text. Specifically, the lower the index the higher the priority. 620 * 621 * @return The text. 622 */ getText()623 public List<CharSequence> getText() { 624 return mText; 625 } 626 627 /** 628 * Sets the text before a change. 629 * 630 * @return The text before the change. 631 */ getBeforeText()632 public CharSequence getBeforeText() { 633 return mBeforeText; 634 } 635 636 /** 637 * Sets the text before a change. 638 * 639 * @param beforeText The text before the change. 640 * 641 * @throws IllegalStateException If called from an AccessibilityService. 642 */ setBeforeText(CharSequence beforeText)643 public void setBeforeText(CharSequence beforeText) { 644 enforceNotSealed(); 645 mBeforeText = (beforeText == null) ? null 646 : beforeText.subSequence(0, beforeText.length()); 647 } 648 649 /** 650 * Gets the description of the source. 651 * 652 * @return The description. 653 */ getContentDescription()654 public CharSequence getContentDescription() { 655 return mContentDescription; 656 } 657 658 /** 659 * Sets the description of the source. 660 * 661 * @param contentDescription The description. 662 * 663 * @throws IllegalStateException If called from an AccessibilityService. 664 */ setContentDescription(CharSequence contentDescription)665 public void setContentDescription(CharSequence contentDescription) { 666 enforceNotSealed(); 667 mContentDescription = (contentDescription == null) ? null 668 : contentDescription.subSequence(0, contentDescription.length()); 669 } 670 671 /** 672 * Gets the {@link Parcelable} data. 673 * 674 * @return The parcelable data. 675 */ getParcelableData()676 public Parcelable getParcelableData() { 677 return mParcelableData; 678 } 679 680 /** 681 * Sets the {@link Parcelable} data of the event. 682 * 683 * @param parcelableData The parcelable data. 684 * 685 * @throws IllegalStateException If called from an AccessibilityService. 686 */ setParcelableData(Parcelable parcelableData)687 public void setParcelableData(Parcelable parcelableData) { 688 enforceNotSealed(); 689 mParcelableData = parcelableData; 690 } 691 692 /** 693 * Gets the id of the source node. 694 * 695 * @return The id. 696 * 697 * @hide 698 */ getSourceNodeId()699 public long getSourceNodeId() { 700 return mSourceNodeId; 701 } 702 703 /** 704 * Sets the unique id of the IAccessibilityServiceConnection over which 705 * this instance can send requests to the system. 706 * 707 * @param connectionId The connection id. 708 * 709 * @hide 710 */ setConnectionId(int connectionId)711 public void setConnectionId(int connectionId) { 712 enforceNotSealed(); 713 mConnectionId = connectionId; 714 } 715 716 /** 717 * Sets if this instance is sealed. 718 * 719 * @param sealed Whether is sealed. 720 * 721 * @hide 722 */ setSealed(boolean sealed)723 public void setSealed(boolean sealed) { 724 mSealed = sealed; 725 } 726 727 /** 728 * Gets if this instance is sealed. 729 * 730 * @return Whether is sealed. 731 */ isSealed()732 boolean isSealed() { 733 return mSealed; 734 } 735 736 /** 737 * Enforces that this instance is sealed. 738 * 739 * @throws IllegalStateException If this instance is not sealed. 740 */ enforceSealed()741 void enforceSealed() { 742 if (!isSealed()) { 743 throw new IllegalStateException("Cannot perform this " 744 + "action on a not sealed instance."); 745 } 746 } 747 748 /** 749 * Enforces that this instance is not sealed. 750 * 751 * @throws IllegalStateException If this instance is sealed. 752 */ enforceNotSealed()753 void enforceNotSealed() { 754 if (isSealed()) { 755 throw new IllegalStateException("Cannot perform this " 756 + "action on a sealed instance."); 757 } 758 } 759 760 /** 761 * Gets the value of a boolean property. 762 * 763 * @param property The property. 764 * @return The value. 765 */ getBooleanProperty(int property)766 private boolean getBooleanProperty(int property) { 767 return (mBooleanProperties & property) == property; 768 } 769 770 /** 771 * Sets a boolean property. 772 * 773 * @param property The property. 774 * @param value The value. 775 */ setBooleanProperty(int property, boolean value)776 private void setBooleanProperty(int property, boolean value) { 777 if (value) { 778 mBooleanProperties |= property; 779 } else { 780 mBooleanProperties &= ~property; 781 } 782 } 783 784 /** 785 * Returns a cached instance if such is available or a new one is 786 * instantiated. The instance is initialized with data from the 787 * given record. 788 * 789 * @return An instance. 790 */ obtain(AccessibilityRecord record)791 public static AccessibilityRecord obtain(AccessibilityRecord record) { 792 AccessibilityRecord clone = AccessibilityRecord.obtain(); 793 clone.init(record); 794 return clone; 795 } 796 797 /** 798 * Returns a cached instance if such is available or a new one is 799 * instantiated. 800 * 801 * @return An instance. 802 */ obtain()803 public static AccessibilityRecord obtain() { 804 synchronized (sPoolLock) { 805 if (sPool != null) { 806 AccessibilityRecord record = sPool; 807 sPool = sPool.mNext; 808 sPoolSize--; 809 record.mNext = null; 810 record.mIsInPool = false; 811 return record; 812 } 813 return new AccessibilityRecord(); 814 } 815 } 816 817 /** 818 * Return an instance back to be reused. 819 * <p> 820 * <strong>Note:</strong> You must not touch the object after calling this function. 821 * 822 * @throws IllegalStateException If the record is already recycled. 823 */ recycle()824 public void recycle() { 825 if (mIsInPool) { 826 throw new IllegalStateException("Record already recycled!"); 827 } 828 clear(); 829 synchronized (sPoolLock) { 830 if (sPoolSize <= MAX_POOL_SIZE) { 831 mNext = sPool; 832 sPool = this; 833 mIsInPool = true; 834 sPoolSize++; 835 } 836 } 837 } 838 839 /** 840 * Initialize this record from another one. 841 * 842 * @param record The to initialize from. 843 */ init(AccessibilityRecord record)844 void init(AccessibilityRecord record) { 845 mSealed = record.mSealed; 846 mBooleanProperties = record.mBooleanProperties; 847 mCurrentItemIndex = record.mCurrentItemIndex; 848 mItemCount = record.mItemCount; 849 mFromIndex = record.mFromIndex; 850 mToIndex = record.mToIndex; 851 mScrollX = record.mScrollX; 852 mScrollY = record.mScrollY; 853 mMaxScrollX = record.mMaxScrollX; 854 mMaxScrollY = record.mMaxScrollY; 855 mAddedCount = record.mAddedCount; 856 mRemovedCount = record.mRemovedCount; 857 mClassName = record.mClassName; 858 mContentDescription = record.mContentDescription; 859 mBeforeText = record.mBeforeText; 860 mParcelableData = record.mParcelableData; 861 mText.addAll(record.mText); 862 mSourceWindowId = record.mSourceWindowId; 863 mSourceNodeId = record.mSourceNodeId; 864 mConnectionId = record.mConnectionId; 865 } 866 867 /** 868 * Clears the state of this instance. 869 */ clear()870 void clear() { 871 mSealed = false; 872 mBooleanProperties = 0; 873 mCurrentItemIndex = UNDEFINED; 874 mItemCount = UNDEFINED; 875 mFromIndex = UNDEFINED; 876 mToIndex = UNDEFINED; 877 mScrollX = UNDEFINED; 878 mScrollY = UNDEFINED; 879 mMaxScrollX = UNDEFINED; 880 mMaxScrollY = UNDEFINED; 881 mAddedCount = UNDEFINED; 882 mRemovedCount = UNDEFINED; 883 mClassName = null; 884 mContentDescription = null; 885 mBeforeText = null; 886 mParcelableData = null; 887 mText.clear(); 888 mSourceNodeId = AccessibilityNodeInfo.UNDEFINED_ITEM_ID; 889 mSourceWindowId = AccessibilityWindowInfo.UNDEFINED_WINDOW_ID; 890 mConnectionId = UNDEFINED; 891 } 892 893 @Override toString()894 public String toString() { 895 return appendTo(new StringBuilder()).toString(); 896 } 897 appendTo(StringBuilder builder)898 StringBuilder appendTo(StringBuilder builder) { 899 builder.append(" [ ClassName: ").append(mClassName); 900 if (!DEBUG_CONCISE_TOSTRING || !isEmpty(mText)) { 901 appendPropName(builder, "Text").append(mText); 902 } 903 append(builder, "ContentDescription", mContentDescription); 904 append(builder, "ItemCount", mItemCount); 905 append(builder, "CurrentItemIndex", mCurrentItemIndex); 906 907 appendUnless(true, PROPERTY_ENABLED, builder); 908 appendUnless(false, PROPERTY_PASSWORD, builder); 909 appendUnless(false, PROPERTY_CHECKED, builder); 910 appendUnless(false, PROPERTY_FULL_SCREEN, builder); 911 appendUnless(false, PROPERTY_SCROLLABLE, builder); 912 913 append(builder, "BeforeText", mBeforeText); 914 append(builder, "FromIndex", mFromIndex); 915 append(builder, "ToIndex", mToIndex); 916 append(builder, "ScrollX", mScrollX); 917 append(builder, "ScrollY", mScrollY); 918 append(builder, "MaxScrollX", mMaxScrollX); 919 append(builder, "MaxScrollY", mMaxScrollY); 920 append(builder, "AddedCount", mAddedCount); 921 append(builder, "RemovedCount", mRemovedCount); 922 append(builder, "ParcelableData", mParcelableData); 923 builder.append(" ]"); 924 return builder; 925 } 926 appendUnless(boolean defValue, int prop, StringBuilder builder)927 private void appendUnless(boolean defValue, int prop, StringBuilder builder) { 928 boolean value = getBooleanProperty(prop); 929 if (DEBUG_CONCISE_TOSTRING && value == defValue) return; 930 appendPropName(builder, singleBooleanPropertyToString(prop)) 931 .append(value); 932 } 933 singleBooleanPropertyToString(int prop)934 private static String singleBooleanPropertyToString(int prop) { 935 switch (prop) { 936 case PROPERTY_CHECKED: return "Checked"; 937 case PROPERTY_ENABLED: return "Enabled"; 938 case PROPERTY_PASSWORD: return "Password"; 939 case PROPERTY_FULL_SCREEN: return "FullScreen"; 940 case PROPERTY_SCROLLABLE: return "Scrollable"; 941 case PROPERTY_IMPORTANT_FOR_ACCESSIBILITY: 942 return "ImportantForAccessibility"; 943 default: return Integer.toHexString(prop); 944 } 945 } 946 append(StringBuilder builder, String propName, int propValue)947 private void append(StringBuilder builder, String propName, int propValue) { 948 if (DEBUG_CONCISE_TOSTRING && propValue == UNDEFINED) return; 949 appendPropName(builder, propName).append(propValue); 950 } 951 append(StringBuilder builder, String propName, Object propValue)952 private void append(StringBuilder builder, String propName, Object propValue) { 953 if (DEBUG_CONCISE_TOSTRING && propValue == null) return; 954 appendPropName(builder, propName).append(propValue); 955 } 956 appendPropName(StringBuilder builder, String propName)957 private StringBuilder appendPropName(StringBuilder builder, String propName) { 958 return builder.append("; ").append(propName).append(": "); 959 } 960 } 961