AccessibilityRecord.java revision a20cdc06e599c6fef784a0a479e8329f95e4bd09
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.os.RemoteException;
22import android.view.View;
23
24import java.util.ArrayList;
25import java.util.List;
26
27/**
28 * Represents a record in an accessibility event. This class encapsulates
29 * the information for a {@link android.view.View}. Note that not all properties
30 * are applicable to all view types. For detailed information please refer to
31 * {@link AccessibilityEvent}.
32 *
33 * @see AccessibilityEvent
34 */
35public class AccessibilityRecord {
36
37    private static final int INVALID_POSITION = -1;
38
39    private static final int PROPERTY_CHECKED = 0x00000001;
40    private static final int PROPERTY_ENABLED = 0x00000002;
41    private static final int PROPERTY_PASSWORD = 0x00000004;
42    private static final int PROPERTY_FULL_SCREEN = 0x00000080;
43    private static final int PROPERTY_SCROLLABLE = 0x00000100;
44
45    // Housekeeping
46    private static final int MAX_POOL_SIZE = 10;
47    private static final Object sPoolLock = new Object();
48    private static AccessibilityRecord sPool;
49    private static int sPoolSize;
50    private AccessibilityRecord mNext;
51    private boolean mIsInPool;
52
53    boolean mSealed;
54    int mBooleanProperties;
55    int mCurrentItemIndex;
56    int mItemCount;
57    int mFromIndex;
58    int mToIndex;
59    int mScrollX;
60    int mScrollY;
61
62    int mAddedCount;
63    int mRemovedCount;
64    int mSourceViewId = View.NO_ID;
65    int mSourceWindowId = View.NO_ID;
66
67    CharSequence mClassName;
68    CharSequence mContentDescription;
69    CharSequence mBeforeText;
70    Parcelable mParcelableData;
71
72    final List<CharSequence> mText = new ArrayList<CharSequence>();
73    IAccessibilityServiceConnection mConnection;
74
75    /*
76     * Hide constructor.
77     */
78    AccessibilityRecord() {
79    }
80
81    /**
82     * Initialize this record from another one.
83     *
84     * @param record The to initialize from.
85     */
86    void init(AccessibilityRecord record) {
87        mSealed = record.mSealed;
88        mBooleanProperties = record.mBooleanProperties;
89        mCurrentItemIndex = record.mCurrentItemIndex;
90        mItemCount = record.mItemCount;
91        mFromIndex = record.mFromIndex;
92        mToIndex = record.mToIndex;
93        mScrollX = record.mScrollX;
94        mScrollY = record.mScrollY;
95        mAddedCount = record.mAddedCount;
96        mRemovedCount = record.mRemovedCount;
97        mClassName = record.mClassName;
98        mContentDescription = record.mContentDescription;
99        mBeforeText = record.mBeforeText;
100        mParcelableData = record.mParcelableData;
101        mText.addAll(record.mText);
102        mSourceWindowId = record.mSourceWindowId;
103        mSourceViewId = record.mSourceViewId;
104        mConnection = record.mConnection;
105    }
106
107    /**
108     * Sets the event source.
109     *
110     * @param source The source.
111     *
112     * @throws IllegalStateException If called from an AccessibilityService.
113     */
114    public void setSource(View source) {
115        enforceNotSealed();
116        if (source != null) {
117            mSourceWindowId = source.getAccessibilityWindowId();
118            mSourceViewId = source.getAccessibilityViewId();
119        } else {
120            mSourceWindowId = View.NO_ID;
121            mSourceViewId = View.NO_ID;
122        }
123    }
124
125    /**
126     * Gets the {@link AccessibilityNodeInfo} of the event source.
127     * <p>
128     *   <strong>
129     *     It is a client responsibility to recycle the received info by
130     *     calling {@link AccessibilityNodeInfo#recycle()} to avoid creating
131     *     of multiple instances.
132     *   </strong>
133     * </p>
134     * @return The info.
135     */
136    public AccessibilityNodeInfo getSource() {
137        enforceSealed();
138        if (mSourceWindowId == View.NO_ID || mSourceViewId == View.NO_ID || mConnection == null) {
139            return null;
140        }
141        try {
142            return mConnection.findAccessibilityNodeInfoByAccessibilityId(mSourceWindowId,
143                    mSourceViewId);
144        } catch (RemoteException e) {
145           /* ignore */
146        }
147        return null;
148    }
149
150    /**
151     * Sets the connection for interacting with the AccessibilityManagerService.
152     *
153     * @param connection The connection.
154     *
155     * @hide
156     */
157    public void setConnection(IAccessibilityServiceConnection connection) {
158        enforceNotSealed();
159        mConnection = connection;
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 position of the source along the X axis.
368     *
369     * @return The scroll along the X axis.
370     */
371    public int getScrollX() {
372        return mScrollX;
373    }
374
375    /**
376     * Sets the scroll position of the source along the X axis.
377     *
378     * @param scrollX The scroll along the X axis.
379     */
380    public void setScrollX(int scrollX) {
381        enforceNotSealed();
382        mScrollX = scrollX;
383    }
384
385    /**
386     * Gets the scroll position of the source along the Y axis.
387     *
388     * @return The scroll along the Y axis.
389     */
390    public int getScrollY() {
391        return mScrollY;
392    }
393
394    /**
395     * Sets the scroll position of the source along the Y axis.
396     *
397     * @param scrollY The scroll along the Y axis.
398     */
399    public void setScrollY(int scrollY) {
400        enforceNotSealed();
401        mScrollY = scrollY;
402    }
403
404    /**
405     * Gets the number of added characters.
406     *
407     * @return The number of added characters.
408     */
409    public int getAddedCount() {
410        return mAddedCount;
411    }
412
413    /**
414     * Sets the number of added characters.
415     *
416     * @param addedCount The number of added characters.
417     *
418     * @throws IllegalStateException If called from an AccessibilityService.
419     */
420    public void setAddedCount(int addedCount) {
421        enforceNotSealed();
422        mAddedCount = addedCount;
423    }
424
425    /**
426     * Gets the number of removed characters.
427     *
428     * @return The number of removed characters.
429     */
430    public int getRemovedCount() {
431        return mRemovedCount;
432    }
433
434    /**
435     * Sets the number of removed characters.
436     *
437     * @param removedCount The number of removed characters.
438     *
439     * @throws IllegalStateException If called from an AccessibilityService.
440     */
441    public void setRemovedCount(int removedCount) {
442        enforceNotSealed();
443        mRemovedCount = removedCount;
444    }
445
446    /**
447     * Gets the class name of the source.
448     *
449     * @return The class name.
450     */
451    public CharSequence getClassName() {
452        return mClassName;
453    }
454
455    /**
456     * Sets the class name of the source.
457     *
458     * @param className The lass name.
459     *
460     * @throws IllegalStateException If called from an AccessibilityService.
461     */
462    public void setClassName(CharSequence className) {
463        enforceNotSealed();
464        mClassName = className;
465    }
466
467    /**
468     * Gets the text of the event. The index in the list represents the priority
469     * of the text. Specifically, the lower the index the higher the priority.
470     *
471     * @return The text.
472     */
473    public List<CharSequence> getText() {
474        return mText;
475    }
476
477    /**
478     * Sets the text before a change.
479     *
480     * @return The text before the change.
481     */
482    public CharSequence getBeforeText() {
483        return mBeforeText;
484    }
485
486    /**
487     * Sets the text before a change.
488     *
489     * @param beforeText The text before the change.
490     *
491     * @throws IllegalStateException If called from an AccessibilityService.
492     */
493    public void setBeforeText(CharSequence beforeText) {
494        enforceNotSealed();
495        mBeforeText = beforeText;
496    }
497
498    /**
499     * Gets the description of the source.
500     *
501     * @return The description.
502     */
503    public CharSequence getContentDescription() {
504        return mContentDescription;
505    }
506
507    /**
508     * Sets the description of the source.
509     *
510     * @param contentDescription The description.
511     *
512     * @throws IllegalStateException If called from an AccessibilityService.
513     */
514    public void setContentDescription(CharSequence contentDescription) {
515        enforceNotSealed();
516        mContentDescription = contentDescription;
517    }
518
519    /**
520     * Gets the {@link Parcelable} data.
521     *
522     * @return The parcelable data.
523     */
524    public Parcelable getParcelableData() {
525        return mParcelableData;
526    }
527
528    /**
529     * Sets the {@link Parcelable} data of the event.
530     *
531     * @param parcelableData The parcelable data.
532     *
533     * @throws IllegalStateException If called from an AccessibilityService.
534     */
535    public void setParcelableData(Parcelable parcelableData) {
536        enforceNotSealed();
537        mParcelableData = parcelableData;
538    }
539
540    /**
541     * Sets if this instance is sealed.
542     *
543     * @param sealed Whether is sealed.
544     *
545     * @hide
546     */
547    public void setSealed(boolean sealed) {
548        mSealed = sealed;
549    }
550
551    /**
552     * Gets if this instance is sealed.
553     *
554     * @return Whether is sealed.
555     */
556    boolean isSealed() {
557        return mSealed;
558    }
559
560    /**
561     * Enforces that this instance is sealed.
562     *
563     * @throws IllegalStateException If this instance is not sealed.
564     */
565    void enforceSealed() {
566        if (!isSealed()) {
567            throw new IllegalStateException("Cannot perform this "
568                    + "action on a not sealed instance.");
569        }
570    }
571
572    /**
573     * Enforces that this instance is not sealed.
574     *
575     * @throws IllegalStateException If this instance is sealed.
576     */
577    void enforceNotSealed() {
578        if (isSealed()) {
579            throw new IllegalStateException("Cannot perform this "
580                    + "action on an sealed instance.");
581        }
582    }
583
584    /**
585     * Gets the value of a boolean property.
586     *
587     * @param property The property.
588     * @return The value.
589     */
590    private boolean getBooleanProperty(int property) {
591        return (mBooleanProperties & property) == property;
592    }
593
594    /**
595     * Sets a boolean property.
596     *
597     * @param property The property.
598     * @param value The value.
599     */
600    private void setBooleanProperty(int property, boolean value) {
601        if (value) {
602            mBooleanProperties |= property;
603        } else {
604            mBooleanProperties &= ~property;
605        }
606    }
607
608    /**
609     * Returns a cached instance if such is available or a new one is
610     * instantiated. The instance is initialized with data from the
611     * given record.
612     *
613     * @return An instance.
614     */
615    public static AccessibilityRecord obtain(AccessibilityRecord record) {
616       AccessibilityRecord clone = AccessibilityRecord.obtain();
617       clone.init(record);
618       return clone;
619    }
620
621    /**
622     * Returns a cached instance if such is available or a new one is
623     * instantiated.
624     *
625     * @return An instance.
626     */
627    public static AccessibilityRecord obtain() {
628        synchronized (sPoolLock) {
629            if (sPool != null) {
630                AccessibilityRecord record = sPool;
631                sPool = sPool.mNext;
632                sPoolSize--;
633                record.mNext = null;
634                record.mIsInPool = false;
635                return record;
636            }
637            return new AccessibilityRecord();
638        }
639    }
640
641    /**
642     * Return an instance back to be reused.
643     * <p>
644     * <b>Note: You must not touch the object after calling this function.</b>
645     *
646     * @throws IllegalStateException If the record is already recycled.
647     */
648    public void recycle() {
649        if (mIsInPool) {
650            throw new IllegalStateException("Record already recycled!");
651        }
652        clear();
653        synchronized (sPoolLock) {
654            if (sPoolSize <= MAX_POOL_SIZE) {
655                mNext = sPool;
656                sPool = this;
657                mIsInPool = true;
658                sPoolSize++;
659            }
660        }
661    }
662
663    /**
664     * Clears the state of this instance.
665     */
666    void clear() {
667        mSealed = false;
668        mBooleanProperties = 0;
669        mCurrentItemIndex = INVALID_POSITION;
670        mItemCount = 0;
671        mFromIndex = 0;
672        mToIndex = 0;
673        mScrollX = 0;
674        mScrollY = 0;
675        mAddedCount = 0;
676        mRemovedCount = 0;
677        mClassName = null;
678        mContentDescription = null;
679        mBeforeText = null;
680        mParcelableData = null;
681        mText.clear();
682        mSourceViewId = View.NO_ID;
683        mSourceWindowId = View.NO_ID;
684    }
685
686    @Override
687    public String toString() {
688        StringBuilder builder = new StringBuilder();
689        builder.append(" [ ClassName: " + mClassName);
690        builder.append("; Text: " + mText);
691        builder.append("; ContentDescription: " + mContentDescription);
692        builder.append("; ItemCount: " + mItemCount);
693        builder.append("; CurrentItemIndex: " + mCurrentItemIndex);
694        builder.append("; IsEnabled: " + getBooleanProperty(PROPERTY_ENABLED));
695        builder.append("; IsPassword: " + getBooleanProperty(PROPERTY_PASSWORD));
696        builder.append("; IsChecked: " + getBooleanProperty(PROPERTY_CHECKED));
697        builder.append("; IsFullScreen: " + getBooleanProperty(PROPERTY_FULL_SCREEN));
698        builder.append("; Scrollable: " + getBooleanProperty(PROPERTY_SCROLLABLE));
699        builder.append("; BeforeText: " + mBeforeText);
700        builder.append("; FromIndex: " + mFromIndex);
701        builder.append("; ToIndex: " + mToIndex);
702        builder.append("; ScrollX: " + mScrollX);
703        builder.append("; ScrollY: " + mScrollY);
704        builder.append("; AddedCount: " + mAddedCount);
705        builder.append("; RemovedCount: " + mRemovedCount);
706        builder.append("; ParcelableData: " + mParcelableData);
707        builder.append(" ]");
708        return builder.toString();
709    }
710}
711