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