AccessibilityRecord.java revision d9ee72fddb8be40e414a831fb80458dc48699613
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     * @hide
396     */
397    public int getMaxScrollX() {
398        return mMaxScrollX;
399    }
400    /**
401     * Sets the max scroll offset of the source left edge in pixels.
402     *
403     * @param maxScrollX The max scroll.
404     *
405     * @hide
406     */
407    public void setMaxScrollX(int maxScrollX) {
408        enforceNotSealed();
409        mMaxScrollX = maxScrollX;
410    }
411
412    /**
413     * Gets the max scroll offset of the source top edge in pixels.
414     *
415     * @return The max scroll.
416     *
417     * @hide
418     */
419    public int getMaxScrollY() {
420        return mMaxScrollY;
421    }
422
423    /**
424     * Sets the max scroll offset of the source top edge in pixels.
425     *
426     * @param maxScrollY The max scroll.
427     *
428     * @hide
429     */
430    public void setMaxScrollY(int maxScrollY) {
431        enforceNotSealed();
432        mMaxScrollY = maxScrollY;
433    }
434
435    /**
436     * Gets the number of added characters.
437     *
438     * @return The number of added characters.
439     */
440    public int getAddedCount() {
441        return mAddedCount;
442    }
443
444    /**
445     * Sets the number of added characters.
446     *
447     * @param addedCount The number of added characters.
448     *
449     * @throws IllegalStateException If called from an AccessibilityService.
450     */
451    public void setAddedCount(int addedCount) {
452        enforceNotSealed();
453        mAddedCount = addedCount;
454    }
455
456    /**
457     * Gets the number of removed characters.
458     *
459     * @return The number of removed characters.
460     */
461    public int getRemovedCount() {
462        return mRemovedCount;
463    }
464
465    /**
466     * Sets the number of removed characters.
467     *
468     * @param removedCount The number of removed characters.
469     *
470     * @throws IllegalStateException If called from an AccessibilityService.
471     */
472    public void setRemovedCount(int removedCount) {
473        enforceNotSealed();
474        mRemovedCount = removedCount;
475    }
476
477    /**
478     * Gets the class name of the source.
479     *
480     * @return The class name.
481     */
482    public CharSequence getClassName() {
483        return mClassName;
484    }
485
486    /**
487     * Sets the class name of the source.
488     *
489     * @param className The lass name.
490     *
491     * @throws IllegalStateException If called from an AccessibilityService.
492     */
493    public void setClassName(CharSequence className) {
494        enforceNotSealed();
495        mClassName = className;
496    }
497
498    /**
499     * Gets the text of the event. The index in the list represents the priority
500     * of the text. Specifically, the lower the index the higher the priority.
501     *
502     * @return The text.
503     */
504    public List<CharSequence> getText() {
505        return mText;
506    }
507
508    /**
509     * Sets the text before a change.
510     *
511     * @return The text before the change.
512     */
513    public CharSequence getBeforeText() {
514        return mBeforeText;
515    }
516
517    /**
518     * Sets the text before a change.
519     *
520     * @param beforeText The text before the change.
521     *
522     * @throws IllegalStateException If called from an AccessibilityService.
523     */
524    public void setBeforeText(CharSequence beforeText) {
525        enforceNotSealed();
526        mBeforeText = beforeText;
527    }
528
529    /**
530     * Gets the description of the source.
531     *
532     * @return The description.
533     */
534    public CharSequence getContentDescription() {
535        return mContentDescription;
536    }
537
538    /**
539     * Sets the description of the source.
540     *
541     * @param contentDescription The description.
542     *
543     * @throws IllegalStateException If called from an AccessibilityService.
544     */
545    public void setContentDescription(CharSequence contentDescription) {
546        enforceNotSealed();
547        mContentDescription = contentDescription;
548    }
549
550    /**
551     * Gets the {@link Parcelable} data.
552     *
553     * @return The parcelable data.
554     */
555    public Parcelable getParcelableData() {
556        return mParcelableData;
557    }
558
559    /**
560     * Sets the {@link Parcelable} data of the event.
561     *
562     * @param parcelableData The parcelable data.
563     *
564     * @throws IllegalStateException If called from an AccessibilityService.
565     */
566    public void setParcelableData(Parcelable parcelableData) {
567        enforceNotSealed();
568        mParcelableData = parcelableData;
569    }
570
571    /**
572     * Sets if this instance is sealed.
573     *
574     * @param sealed Whether is sealed.
575     *
576     * @hide
577     */
578    public void setSealed(boolean sealed) {
579        mSealed = sealed;
580    }
581
582    /**
583     * Gets if this instance is sealed.
584     *
585     * @return Whether is sealed.
586     */
587    boolean isSealed() {
588        return mSealed;
589    }
590
591    /**
592     * Enforces that this instance is sealed.
593     *
594     * @throws IllegalStateException If this instance is not sealed.
595     */
596    void enforceSealed() {
597        if (!isSealed()) {
598            throw new IllegalStateException("Cannot perform this "
599                    + "action on a not sealed instance.");
600        }
601    }
602
603    /**
604     * Enforces that this instance is not sealed.
605     *
606     * @throws IllegalStateException If this instance is sealed.
607     */
608    void enforceNotSealed() {
609        if (isSealed()) {
610            throw new IllegalStateException("Cannot perform this "
611                    + "action on an sealed instance.");
612        }
613    }
614
615    /**
616     * Gets the value of a boolean property.
617     *
618     * @param property The property.
619     * @return The value.
620     */
621    private boolean getBooleanProperty(int property) {
622        return (mBooleanProperties & property) == property;
623    }
624
625    /**
626     * Sets a boolean property.
627     *
628     * @param property The property.
629     * @param value The value.
630     */
631    private void setBooleanProperty(int property, boolean value) {
632        if (value) {
633            mBooleanProperties |= property;
634        } else {
635            mBooleanProperties &= ~property;
636        }
637    }
638
639    /**
640     * Returns a cached instance if such is available or a new one is
641     * instantiated. The instance is initialized with data from the
642     * given record.
643     *
644     * @return An instance.
645     */
646    public static AccessibilityRecord obtain(AccessibilityRecord record) {
647       AccessibilityRecord clone = AccessibilityRecord.obtain();
648       clone.init(record);
649       return clone;
650    }
651
652    /**
653     * Returns a cached instance if such is available or a new one is
654     * instantiated.
655     *
656     * @return An instance.
657     */
658    public static AccessibilityRecord obtain() {
659        synchronized (sPoolLock) {
660            if (sPool != null) {
661                AccessibilityRecord record = sPool;
662                sPool = sPool.mNext;
663                sPoolSize--;
664                record.mNext = null;
665                record.mIsInPool = false;
666                return record;
667            }
668            return new AccessibilityRecord();
669        }
670    }
671
672    /**
673     * Return an instance back to be reused.
674     * <p>
675     * <strong>Note:</strong> You must not touch the object after calling this function.
676     *
677     * @throws IllegalStateException If the record is already recycled.
678     */
679    public void recycle() {
680        if (mIsInPool) {
681            throw new IllegalStateException("Record already recycled!");
682        }
683        clear();
684        synchronized (sPoolLock) {
685            if (sPoolSize <= MAX_POOL_SIZE) {
686                mNext = sPool;
687                sPool = this;
688                mIsInPool = true;
689                sPoolSize++;
690            }
691        }
692    }
693
694    /**
695     * Initialize this record from another one.
696     *
697     * @param record The to initialize from.
698     */
699    void init(AccessibilityRecord record) {
700        mSealed = record.mSealed;
701        mBooleanProperties = record.mBooleanProperties;
702        mCurrentItemIndex = record.mCurrentItemIndex;
703        mItemCount = record.mItemCount;
704        mFromIndex = record.mFromIndex;
705        mToIndex = record.mToIndex;
706        mScrollX = record.mScrollX;
707        mScrollY = record.mScrollY;
708        mMaxScrollX = record.mMaxScrollX;
709        mMaxScrollY = record.mMaxScrollY;
710        mAddedCount = record.mAddedCount;
711        mRemovedCount = record.mRemovedCount;
712        mClassName = record.mClassName;
713        mContentDescription = record.mContentDescription;
714        mBeforeText = record.mBeforeText;
715        mParcelableData = record.mParcelableData;
716        mText.addAll(record.mText);
717        mSourceWindowId = record.mSourceWindowId;
718        mSourceViewId = record.mSourceViewId;
719        mConnection = record.mConnection;
720    }
721
722    /**
723     * Clears the state of this instance.
724     */
725    void clear() {
726        mSealed = false;
727        mBooleanProperties = 0;
728        mCurrentItemIndex = UNDEFINED;
729        mItemCount = UNDEFINED;
730        mFromIndex = UNDEFINED;
731        mToIndex = UNDEFINED;
732        mScrollX = UNDEFINED;
733        mScrollY = UNDEFINED;
734        mMaxScrollX = UNDEFINED;
735        mMaxScrollY = UNDEFINED;
736        mAddedCount = UNDEFINED;
737        mRemovedCount = UNDEFINED;
738        mClassName = null;
739        mContentDescription = null;
740        mBeforeText = null;
741        mParcelableData = null;
742        mText.clear();
743        mSourceViewId = View.NO_ID;
744        mSourceWindowId = View.NO_ID;
745    }
746
747    @Override
748    public String toString() {
749        StringBuilder builder = new StringBuilder();
750        builder.append(" [ ClassName: " + mClassName);
751        builder.append("; Text: " + mText);
752        builder.append("; ContentDescription: " + mContentDescription);
753        builder.append("; ItemCount: " + mItemCount);
754        builder.append("; CurrentItemIndex: " + mCurrentItemIndex);
755        builder.append("; IsEnabled: " + getBooleanProperty(PROPERTY_ENABLED));
756        builder.append("; IsPassword: " + getBooleanProperty(PROPERTY_PASSWORD));
757        builder.append("; IsChecked: " + getBooleanProperty(PROPERTY_CHECKED));
758        builder.append("; IsFullScreen: " + getBooleanProperty(PROPERTY_FULL_SCREEN));
759        builder.append("; Scrollable: " + getBooleanProperty(PROPERTY_SCROLLABLE));
760        builder.append("; BeforeText: " + mBeforeText);
761        builder.append("; FromIndex: " + mFromIndex);
762        builder.append("; ToIndex: " + mToIndex);
763        builder.append("; ScrollX: " + mScrollX);
764        builder.append("; ScrollY: " + mScrollY);
765        builder.append("; MaxScrollX: " + mMaxScrollX);
766        builder.append("; MaxScrollY: " + mMaxScrollY);
767        builder.append("; AddedCount: " + mAddedCount);
768        builder.append("; RemovedCount: " + mRemovedCount);
769        builder.append("; ParcelableData: " + mParcelableData);
770        builder.append(" ]");
771        return builder.toString();
772    }
773}
774