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