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