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