AccessibilityRecord.java revision 52a623700f9bcba7ef8bfe3ba7ff6160a1bd65e8
1/* 2 * Copyright (C) 2011 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package android.view.accessibility; 18 19import android.os.Parcelable; 20import android.view.View; 21 22import java.util.ArrayList; 23import 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 */ 56public 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 = PROPERTY_IMPORTANT_FOR_ACCESSIBILITY; 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, GET_SOURCE_PREFETCH_FLAGS); 168 } 169 170 /** 171 * Gets the id of the window from which the event comes from. 172 * 173 * @return The window id. 174 */ 175 public int getWindowId() { 176 return mSourceWindowId; 177 } 178 179 /** 180 * Gets if the source is checked. 181 * 182 * @return True if the view is checked, false otherwise. 183 */ 184 public boolean isChecked() { 185 return getBooleanProperty(PROPERTY_CHECKED); 186 } 187 188 /** 189 * Sets if the source is checked. 190 * 191 * @param isChecked True if the view is checked, false otherwise. 192 * 193 * @throws IllegalStateException If called from an AccessibilityService. 194 */ 195 public void setChecked(boolean isChecked) { 196 enforceNotSealed(); 197 setBooleanProperty(PROPERTY_CHECKED, isChecked); 198 } 199 200 /** 201 * Gets if the source is enabled. 202 * 203 * @return True if the view is enabled, false otherwise. 204 */ 205 public boolean isEnabled() { 206 return getBooleanProperty(PROPERTY_ENABLED); 207 } 208 209 /** 210 * Sets if the source is enabled. 211 * 212 * @param isEnabled True if the view is enabled, false otherwise. 213 * 214 * @throws IllegalStateException If called from an AccessibilityService. 215 */ 216 public void setEnabled(boolean isEnabled) { 217 enforceNotSealed(); 218 setBooleanProperty(PROPERTY_ENABLED, isEnabled); 219 } 220 221 /** 222 * Gets if the source is a password field. 223 * 224 * @return True if the view is a password field, false otherwise. 225 */ 226 public boolean isPassword() { 227 return getBooleanProperty(PROPERTY_PASSWORD); 228 } 229 230 /** 231 * Sets if the source is a password field. 232 * 233 * @param isPassword True if the view is a password field, false otherwise. 234 * 235 * @throws IllegalStateException If called from an AccessibilityService. 236 */ 237 public void setPassword(boolean isPassword) { 238 enforceNotSealed(); 239 setBooleanProperty(PROPERTY_PASSWORD, isPassword); 240 } 241 242 /** 243 * Gets if the source is taking the entire screen. 244 * 245 * @return True if the source is full screen, false otherwise. 246 */ 247 public boolean isFullScreen() { 248 return getBooleanProperty(PROPERTY_FULL_SCREEN); 249 } 250 251 /** 252 * Sets if the source is taking the entire screen. 253 * 254 * @param isFullScreen True if the source is full screen, false otherwise. 255 * 256 * @throws IllegalStateException If called from an AccessibilityService. 257 */ 258 public void setFullScreen(boolean isFullScreen) { 259 enforceNotSealed(); 260 setBooleanProperty(PROPERTY_FULL_SCREEN, isFullScreen); 261 } 262 263 /** 264 * Gets if the source is scrollable. 265 * 266 * @return True if the source is scrollable, false otherwise. 267 */ 268 public boolean isScrollable() { 269 return getBooleanProperty(PROPERTY_SCROLLABLE); 270 } 271 272 /** 273 * Sets if the source is scrollable. 274 * 275 * @param scrollable True if the source is scrollable, false otherwise. 276 * 277 * @throws IllegalStateException If called from an AccessibilityService. 278 */ 279 public void setScrollable(boolean scrollable) { 280 enforceNotSealed(); 281 setBooleanProperty(PROPERTY_SCROLLABLE, scrollable); 282 } 283 284 /** 285 * Gets if the source is important for accessibility. 286 * 287 * <strong>Note:</strong> Used only internally to determine whether 288 * to deliver the event to a given accessibility service since some 289 * services may want to regard all views for accessibility while others 290 * may want to regard only the important views for accessibility. 291 * 292 * @return True if the source is important for accessibility, 293 * false otherwise. 294 * 295 * @hide 296 */ 297 public boolean isImportantForAccessibility() { 298 return getBooleanProperty(PROPERTY_IMPORTANT_FOR_ACCESSIBILITY); 299 } 300 301 /** 302 * Gets the number of items that can be visited. 303 * 304 * @return The number of items. 305 */ 306 public int getItemCount() { 307 return mItemCount; 308 } 309 310 /** 311 * Sets the number of items that can be visited. 312 * 313 * @param itemCount The number of items. 314 * 315 * @throws IllegalStateException If called from an AccessibilityService. 316 */ 317 public void setItemCount(int itemCount) { 318 enforceNotSealed(); 319 mItemCount = itemCount; 320 } 321 322 /** 323 * Gets the index of the source in the list of items the can be visited. 324 * 325 * @return The current item index. 326 */ 327 public int getCurrentItemIndex() { 328 return mCurrentItemIndex; 329 } 330 331 /** 332 * Sets the index of the source in the list of items that can be visited. 333 * 334 * @param currentItemIndex The current item index. 335 * 336 * @throws IllegalStateException If called from an AccessibilityService. 337 */ 338 public void setCurrentItemIndex(int currentItemIndex) { 339 enforceNotSealed(); 340 mCurrentItemIndex = currentItemIndex; 341 } 342 343 /** 344 * Gets the index of the first character of the changed sequence, 345 * or the beginning of a text selection or the index of the first 346 * visible item when scrolling. 347 * 348 * @return The index of the first character or selection 349 * start or the first visible item. 350 */ 351 public int getFromIndex() { 352 return mFromIndex; 353 } 354 355 /** 356 * Sets the index of the first character of the changed sequence 357 * or the beginning of a text selection or the index of the first 358 * visible item when scrolling. 359 * 360 * @param fromIndex The index of the first character or selection 361 * start or the first visible item. 362 * 363 * @throws IllegalStateException If called from an AccessibilityService. 364 */ 365 public void setFromIndex(int fromIndex) { 366 enforceNotSealed(); 367 mFromIndex = fromIndex; 368 } 369 370 /** 371 * Gets the index of text selection end or the index of the last 372 * visible item when scrolling. 373 * 374 * @return The index of selection end or last item index. 375 */ 376 public int getToIndex() { 377 return mToIndex; 378 } 379 380 /** 381 * Sets the index of text selection end or the index of the last 382 * visible item when scrolling. 383 * 384 * @param toIndex The index of selection end or last item index. 385 */ 386 public void setToIndex(int toIndex) { 387 enforceNotSealed(); 388 mToIndex = toIndex; 389 } 390 391 /** 392 * Gets the scroll offset of the source left edge in pixels. 393 * 394 * @return The scroll. 395 */ 396 public int getScrollX() { 397 return mScrollX; 398 } 399 400 /** 401 * Sets the scroll offset of the source left edge in pixels. 402 * 403 * @param scrollX The scroll. 404 */ 405 public void setScrollX(int scrollX) { 406 enforceNotSealed(); 407 mScrollX = scrollX; 408 } 409 410 /** 411 * Gets the scroll offset of the source top edge in pixels. 412 * 413 * @return The scroll. 414 */ 415 public int getScrollY() { 416 return mScrollY; 417 } 418 419 /** 420 * Sets the scroll offset of the source top edge in pixels. 421 * 422 * @param scrollY The scroll. 423 */ 424 public void setScrollY(int scrollY) { 425 enforceNotSealed(); 426 mScrollY = scrollY; 427 } 428 429 /** 430 * Gets the max scroll offset of the source left edge in pixels. 431 * 432 * @return The max scroll. 433 */ 434 public int getMaxScrollX() { 435 return mMaxScrollX; 436 } 437 438 /** 439 * Sets the max scroll offset of the source left edge in pixels. 440 * 441 * @param maxScrollX The max scroll. 442 */ 443 public void setMaxScrollX(int maxScrollX) { 444 enforceNotSealed(); 445 mMaxScrollX = maxScrollX; 446 } 447 448 /** 449 * Gets the max scroll offset of the source top edge in pixels. 450 * 451 * @return The max scroll. 452 */ 453 public int getMaxScrollY() { 454 return mMaxScrollY; 455 } 456 457 /** 458 * Sets the max scroll offset of the source top edge in pixels. 459 * 460 * @param maxScrollY The max scroll. 461 */ 462 public void setMaxScrollY(int maxScrollY) { 463 enforceNotSealed(); 464 mMaxScrollY = maxScrollY; 465 } 466 467 /** 468 * Gets the number of added characters. 469 * 470 * @return The number of added characters. 471 */ 472 public int getAddedCount() { 473 return mAddedCount; 474 } 475 476 /** 477 * Sets the number of added characters. 478 * 479 * @param addedCount The number of added characters. 480 * 481 * @throws IllegalStateException If called from an AccessibilityService. 482 */ 483 public void setAddedCount(int addedCount) { 484 enforceNotSealed(); 485 mAddedCount = addedCount; 486 } 487 488 /** 489 * Gets the number of removed characters. 490 * 491 * @return The number of removed characters. 492 */ 493 public int getRemovedCount() { 494 return mRemovedCount; 495 } 496 497 /** 498 * Sets the number of removed characters. 499 * 500 * @param removedCount The number of removed characters. 501 * 502 * @throws IllegalStateException If called from an AccessibilityService. 503 */ 504 public void setRemovedCount(int removedCount) { 505 enforceNotSealed(); 506 mRemovedCount = removedCount; 507 } 508 509 /** 510 * Gets the class name of the source. 511 * 512 * @return The class name. 513 */ 514 public CharSequence getClassName() { 515 return mClassName; 516 } 517 518 /** 519 * Sets the class name of the source. 520 * 521 * @param className The lass name. 522 * 523 * @throws IllegalStateException If called from an AccessibilityService. 524 */ 525 public void setClassName(CharSequence className) { 526 enforceNotSealed(); 527 mClassName = className; 528 } 529 530 /** 531 * Gets the text of the event. The index in the list represents the priority 532 * of the text. Specifically, the lower the index the higher the priority. 533 * 534 * @return The text. 535 */ 536 public List<CharSequence> getText() { 537 return mText; 538 } 539 540 /** 541 * Sets the text before a change. 542 * 543 * @return The text before the change. 544 */ 545 public CharSequence getBeforeText() { 546 return mBeforeText; 547 } 548 549 /** 550 * Sets the text before a change. 551 * 552 * @param beforeText The text before the change. 553 * 554 * @throws IllegalStateException If called from an AccessibilityService. 555 */ 556 public void setBeforeText(CharSequence beforeText) { 557 enforceNotSealed(); 558 mBeforeText = beforeText; 559 } 560 561 /** 562 * Gets the description of the source. 563 * 564 * @return The description. 565 */ 566 public CharSequence getContentDescription() { 567 return mContentDescription; 568 } 569 570 /** 571 * Sets the description of the source. 572 * 573 * @param contentDescription The description. 574 * 575 * @throws IllegalStateException If called from an AccessibilityService. 576 */ 577 public void setContentDescription(CharSequence contentDescription) { 578 enforceNotSealed(); 579 mContentDescription = contentDescription; 580 } 581 582 /** 583 * Gets the {@link Parcelable} data. 584 * 585 * @return The parcelable data. 586 */ 587 public Parcelable getParcelableData() { 588 return mParcelableData; 589 } 590 591 /** 592 * Sets the {@link Parcelable} data of the event. 593 * 594 * @param parcelableData The parcelable data. 595 * 596 * @throws IllegalStateException If called from an AccessibilityService. 597 */ 598 public void setParcelableData(Parcelable parcelableData) { 599 enforceNotSealed(); 600 mParcelableData = parcelableData; 601 } 602 603 /** 604 * Gets the id of the source node. 605 * 606 * @return The id. 607 * 608 * @hide 609 */ 610 public long getSourceNodeId() { 611 return mSourceNodeId; 612 } 613 614 /** 615 * Sets the unique id of the IAccessibilityServiceConnection over which 616 * this instance can send requests to the system. 617 * 618 * @param connectionId The connection id. 619 * 620 * @hide 621 */ 622 public void setConnectionId(int connectionId) { 623 enforceNotSealed(); 624 mConnectionId = connectionId; 625 } 626 627 /** 628 * Sets if this instance is sealed. 629 * 630 * @param sealed Whether is sealed. 631 * 632 * @hide 633 */ 634 public void setSealed(boolean sealed) { 635 mSealed = sealed; 636 } 637 638 /** 639 * Gets if this instance is sealed. 640 * 641 * @return Whether is sealed. 642 */ 643 boolean isSealed() { 644 return mSealed; 645 } 646 647 /** 648 * Enforces that this instance is sealed. 649 * 650 * @throws IllegalStateException If this instance is not sealed. 651 */ 652 void enforceSealed() { 653 if (!isSealed()) { 654 throw new IllegalStateException("Cannot perform this " 655 + "action on a not sealed instance."); 656 } 657 } 658 659 /** 660 * Enforces that this instance is not sealed. 661 * 662 * @throws IllegalStateException If this instance is sealed. 663 */ 664 void enforceNotSealed() { 665 if (isSealed()) { 666 throw new IllegalStateException("Cannot perform this " 667 + "action on a sealed instance."); 668 } 669 } 670 671 /** 672 * Gets the value of a boolean property. 673 * 674 * @param property The property. 675 * @return The value. 676 */ 677 private boolean getBooleanProperty(int property) { 678 return (mBooleanProperties & property) == property; 679 } 680 681 /** 682 * Sets a boolean property. 683 * 684 * @param property The property. 685 * @param value The value. 686 */ 687 private void setBooleanProperty(int property, boolean value) { 688 if (value) { 689 mBooleanProperties |= property; 690 } else { 691 mBooleanProperties &= ~property; 692 } 693 } 694 695 /** 696 * Returns a cached instance if such is available or a new one is 697 * instantiated. The instance is initialized with data from the 698 * given record. 699 * 700 * @return An instance. 701 */ 702 public static AccessibilityRecord obtain(AccessibilityRecord record) { 703 AccessibilityRecord clone = AccessibilityRecord.obtain(); 704 clone.init(record); 705 return clone; 706 } 707 708 /** 709 * Returns a cached instance if such is available or a new one is 710 * instantiated. 711 * 712 * @return An instance. 713 */ 714 public static AccessibilityRecord obtain() { 715 synchronized (sPoolLock) { 716 if (sPool != null) { 717 AccessibilityRecord record = sPool; 718 sPool = sPool.mNext; 719 sPoolSize--; 720 record.mNext = null; 721 record.mIsInPool = false; 722 return record; 723 } 724 return new AccessibilityRecord(); 725 } 726 } 727 728 /** 729 * Return an instance back to be reused. 730 * <p> 731 * <strong>Note:</strong> You must not touch the object after calling this function. 732 * 733 * @throws IllegalStateException If the record is already recycled. 734 */ 735 public void recycle() { 736 if (mIsInPool) { 737 throw new IllegalStateException("Record already recycled!"); 738 } 739 clear(); 740 synchronized (sPoolLock) { 741 if (sPoolSize <= MAX_POOL_SIZE) { 742 mNext = sPool; 743 sPool = this; 744 mIsInPool = true; 745 sPoolSize++; 746 } 747 } 748 } 749 750 /** 751 * Initialize this record from another one. 752 * 753 * @param record The to initialize from. 754 */ 755 void init(AccessibilityRecord record) { 756 mSealed = record.mSealed; 757 mBooleanProperties = record.mBooleanProperties; 758 mCurrentItemIndex = record.mCurrentItemIndex; 759 mItemCount = record.mItemCount; 760 mFromIndex = record.mFromIndex; 761 mToIndex = record.mToIndex; 762 mScrollX = record.mScrollX; 763 mScrollY = record.mScrollY; 764 mMaxScrollX = record.mMaxScrollX; 765 mMaxScrollY = record.mMaxScrollY; 766 mAddedCount = record.mAddedCount; 767 mRemovedCount = record.mRemovedCount; 768 mClassName = record.mClassName; 769 mContentDescription = record.mContentDescription; 770 mBeforeText = record.mBeforeText; 771 mParcelableData = record.mParcelableData; 772 mText.addAll(record.mText); 773 mSourceWindowId = record.mSourceWindowId; 774 mSourceNodeId = record.mSourceNodeId; 775 mConnectionId = record.mConnectionId; 776 } 777 778 /** 779 * Clears the state of this instance. 780 */ 781 void clear() { 782 mSealed = false; 783 mBooleanProperties = PROPERTY_IMPORTANT_FOR_ACCESSIBILITY; 784 mCurrentItemIndex = UNDEFINED; 785 mItemCount = UNDEFINED; 786 mFromIndex = UNDEFINED; 787 mToIndex = UNDEFINED; 788 mScrollX = UNDEFINED; 789 mScrollY = UNDEFINED; 790 mMaxScrollX = UNDEFINED; 791 mMaxScrollY = UNDEFINED; 792 mAddedCount = UNDEFINED; 793 mRemovedCount = UNDEFINED; 794 mClassName = null; 795 mContentDescription = null; 796 mBeforeText = null; 797 mParcelableData = null; 798 mText.clear(); 799 mSourceNodeId = AccessibilityNodeInfo.makeNodeId(UNDEFINED, UNDEFINED); 800 mSourceWindowId = UNDEFINED; 801 mConnectionId = UNDEFINED; 802 } 803 804 @Override 805 public String toString() { 806 StringBuilder builder = new StringBuilder(); 807 builder.append(" [ ClassName: " + mClassName); 808 builder.append("; Text: " + mText); 809 builder.append("; ContentDescription: " + mContentDescription); 810 builder.append("; ItemCount: " + mItemCount); 811 builder.append("; CurrentItemIndex: " + mCurrentItemIndex); 812 builder.append("; IsEnabled: " + getBooleanProperty(PROPERTY_ENABLED)); 813 builder.append("; IsPassword: " + getBooleanProperty(PROPERTY_PASSWORD)); 814 builder.append("; IsChecked: " + getBooleanProperty(PROPERTY_CHECKED)); 815 builder.append("; IsFullScreen: " + getBooleanProperty(PROPERTY_FULL_SCREEN)); 816 builder.append("; Scrollable: " + getBooleanProperty(PROPERTY_SCROLLABLE)); 817 builder.append("; BeforeText: " + mBeforeText); 818 builder.append("; FromIndex: " + mFromIndex); 819 builder.append("; ToIndex: " + mToIndex); 820 builder.append("; ScrollX: " + mScrollX); 821 builder.append("; ScrollY: " + mScrollY); 822 builder.append("; MaxScrollX: " + mMaxScrollX); 823 builder.append("; MaxScrollY: " + mMaxScrollY); 824 builder.append("; AddedCount: " + mAddedCount); 825 builder.append("; RemovedCount: " + mRemovedCount); 826 builder.append("; ParcelableData: " + mParcelableData); 827 builder.append(" ]"); 828 return builder.toString(); 829 } 830} 831