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