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