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