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