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