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