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