AccessibilityRecord.java revision eeee4d2c01d3c4ed99e4891dbc75c7de69a803fa
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
44    // Housekeeping
45    private static final int MAX_POOL_SIZE = 10;
46    private static final Object sPoolLock = new Object();
47    private static AccessibilityRecord sPool;
48    private static int sPoolSize;
49    private AccessibilityRecord mNext;
50    private boolean mIsInPool;
51
52    boolean mSealed;
53    int mBooleanProperties;
54    int mCurrentItemIndex;
55    int mItemCount;
56    int mFromIndex;
57    int mAddedCount;
58    int mRemovedCount;
59    int mSourceViewId = View.NO_ID;
60    int mSourceWindowId = View.NO_ID;
61
62    CharSequence mClassName;
63    CharSequence mContentDescription;
64    CharSequence mBeforeText;
65    Parcelable mParcelableData;
66
67    final List<CharSequence> mText = new ArrayList<CharSequence>();
68    IAccessibilityServiceConnection mConnection;
69
70    /*
71     * Hide constructor.
72     */
73    AccessibilityRecord() {
74
75    }
76
77    /**
78     * Initialize this record from another one.
79     *
80     * @param record The to initialize from.
81     */
82    void init(AccessibilityRecord record) {
83        mSealed = record.mSealed;
84        mBooleanProperties = record.mBooleanProperties;
85        mCurrentItemIndex = record.mCurrentItemIndex;
86        mItemCount = record.mItemCount;
87        mFromIndex = record.mFromIndex;
88        mAddedCount = record.mAddedCount;
89        mRemovedCount = record.mRemovedCount;
90        mClassName = record.mClassName;
91        mContentDescription = record.mContentDescription;
92        mBeforeText = record.mBeforeText;
93        mParcelableData = record.mParcelableData;
94        mText.addAll(record.mText);
95        mSourceWindowId = record.mSourceWindowId;
96        mSourceViewId = record.mSourceViewId;
97        mConnection = record.mConnection;
98    }
99
100    /**
101     * Sets the event source.
102     *
103     * @param source The source.
104     *
105     * @throws IllegalStateException If called from an AccessibilityService.
106     */
107    public void setSource(View source) {
108        enforceNotSealed();
109        if (source != null) {
110            mSourceWindowId = source.getAccessibilityWindowId();
111            mSourceViewId = source.getAccessibilityViewId();
112        } else {
113            mSourceWindowId = View.NO_ID;
114            mSourceViewId = View.NO_ID;
115        }
116    }
117
118    /**
119     * Gets the {@link AccessibilityNodeInfo} of the event source.
120     * <p>
121     *   <strong>
122     *     It is a client responsibility to recycle the received info by
123     *     calling {@link AccessibilityNodeInfo#recycle()} to avoid creating
124     *     of multiple instances.
125     *   </strong>
126     * </p>
127     * @return The info.
128     */
129    public AccessibilityNodeInfo getSource() {
130        enforceSealed();
131        if (mSourceWindowId == View.NO_ID || mSourceViewId == View.NO_ID || mConnection == null) {
132            return null;
133        }
134        try {
135            return mConnection.findAccessibilityNodeInfoByAccessibilityId(mSourceWindowId,
136                    mSourceViewId);
137        } catch (RemoteException e) {
138           /* ignore */
139        }
140        return null;
141    }
142
143    /**
144     * Sets the connection for interacting with the AccessibilityManagerService.
145     *
146     * @param connection The connection.
147     *
148     * @hide
149     */
150    public void setConnection(IAccessibilityServiceConnection connection) {
151        enforceNotSealed();
152        mConnection = connection;
153    }
154
155    /**
156     * Gets the id of the window from which the event comes from.
157     *
158     * @return The window id.
159     */
160    public int getWindowId() {
161        return mSourceWindowId;
162    }
163
164    /**
165     * Gets if the source is checked.
166     *
167     * @return True if the view is checked, false otherwise.
168     */
169    public boolean isChecked() {
170        return getBooleanProperty(PROPERTY_CHECKED);
171    }
172
173    /**
174     * Sets if the source is checked.
175     *
176     * @param isChecked True if the view is checked, false otherwise.
177     *
178     * @throws IllegalStateException If called from an AccessibilityService.
179     */
180    public void setChecked(boolean isChecked) {
181        enforceNotSealed();
182        setBooleanProperty(PROPERTY_CHECKED, isChecked);
183    }
184
185    /**
186     * Gets if the source is enabled.
187     *
188     * @return True if the view is enabled, false otherwise.
189     */
190    public boolean isEnabled() {
191        return getBooleanProperty(PROPERTY_ENABLED);
192    }
193
194    /**
195     * Sets if the source is enabled.
196     *
197     * @param isEnabled True if the view is enabled, false otherwise.
198     *
199     * @throws IllegalStateException If called from an AccessibilityService.
200     */
201    public void setEnabled(boolean isEnabled) {
202        enforceNotSealed();
203        setBooleanProperty(PROPERTY_ENABLED, isEnabled);
204    }
205
206    /**
207     * Gets if the source is a password field.
208     *
209     * @return True if the view is a password field, false otherwise.
210     */
211    public boolean isPassword() {
212        return getBooleanProperty(PROPERTY_PASSWORD);
213    }
214
215    /**
216     * Sets if the source is a password field.
217     *
218     * @param isPassword True if the view is a password field, false otherwise.
219     *
220     * @throws IllegalStateException If called from an AccessibilityService.
221     */
222    public void setPassword(boolean isPassword) {
223        enforceNotSealed();
224        setBooleanProperty(PROPERTY_PASSWORD, isPassword);
225    }
226
227    /**
228     * Gets if the source is taking the entire screen.
229     *
230     * @return True if the source is full screen, false otherwise.
231     */
232    public boolean isFullScreen() {
233        return getBooleanProperty(PROPERTY_FULL_SCREEN);
234    }
235
236    /**
237     * Sets if the source is taking the entire screen.
238     *
239     * @param isFullScreen True if the source is full screen, false otherwise.
240     *
241     * @throws IllegalStateException If called from an AccessibilityService.
242     */
243    public void setFullScreen(boolean isFullScreen) {
244        enforceNotSealed();
245        setBooleanProperty(PROPERTY_FULL_SCREEN, isFullScreen);
246    }
247
248    /**
249     * Gets the number of items that can be visited.
250     *
251     * @return The number of items.
252     */
253    public int getItemCount() {
254        return mItemCount;
255    }
256
257    /**
258     * Sets the number of items that can be visited.
259     *
260     * @param itemCount The number of items.
261     *
262     * @throws IllegalStateException If called from an AccessibilityService.
263     */
264    public void setItemCount(int itemCount) {
265        enforceNotSealed();
266        mItemCount = itemCount;
267    }
268
269    /**
270     * Gets the index of the source in the list of items the can be visited.
271     *
272     * @return The current item index.
273     */
274    public int getCurrentItemIndex() {
275        return mCurrentItemIndex;
276    }
277
278    /**
279     * Sets the index of the source in the list of items that can be visited.
280     *
281     * @param currentItemIndex The current item index.
282     *
283     * @throws IllegalStateException If called from an AccessibilityService.
284     */
285    public void setCurrentItemIndex(int currentItemIndex) {
286        enforceNotSealed();
287        mCurrentItemIndex = currentItemIndex;
288    }
289
290    /**
291     * Gets the index of the first character of the changed sequence.
292     *
293     * @return The index of the first character.
294     */
295    public int getFromIndex() {
296        return mFromIndex;
297    }
298
299    /**
300     * Sets the index of the first character of the changed sequence.
301     *
302     * @param fromIndex The index of the first character.
303     *
304     * @throws IllegalStateException If called from an AccessibilityService.
305     */
306    public void setFromIndex(int fromIndex) {
307        enforceNotSealed();
308        mFromIndex = fromIndex;
309    }
310
311    /**
312     * Gets the number of added characters.
313     *
314     * @return The number of added characters.
315     */
316    public int getAddedCount() {
317        return mAddedCount;
318    }
319
320    /**
321     * Sets the number of added characters.
322     *
323     * @param addedCount The number of added characters.
324     *
325     * @throws IllegalStateException If called from an AccessibilityService.
326     */
327    public void setAddedCount(int addedCount) {
328        enforceNotSealed();
329        mAddedCount = addedCount;
330    }
331
332    /**
333     * Gets the number of removed characters.
334     *
335     * @return The number of removed characters.
336     */
337    public int getRemovedCount() {
338        return mRemovedCount;
339    }
340
341    /**
342     * Sets the number of removed characters.
343     *
344     * @param removedCount The number of removed characters.
345     *
346     * @throws IllegalStateException If called from an AccessibilityService.
347     */
348    public void setRemovedCount(int removedCount) {
349        enforceNotSealed();
350        mRemovedCount = removedCount;
351    }
352
353    /**
354     * Gets the class name of the source.
355     *
356     * @return The class name.
357     */
358    public CharSequence getClassName() {
359        return mClassName;
360    }
361
362    /**
363     * Sets the class name of the source.
364     *
365     * @param className The lass name.
366     *
367     * @throws IllegalStateException If called from an AccessibilityService.
368     */
369    public void setClassName(CharSequence className) {
370        enforceNotSealed();
371        mClassName = className;
372    }
373
374    /**
375     * Gets the text of the event. The index in the list represents the priority
376     * of the text. Specifically, the lower the index the higher the priority.
377     *
378     * @return The text.
379     */
380    public List<CharSequence> getText() {
381        return mText;
382    }
383
384    /**
385     * Sets the text before a change.
386     *
387     * @return The text before the change.
388     */
389    public CharSequence getBeforeText() {
390        return mBeforeText;
391    }
392
393    /**
394     * Sets the text before a change.
395     *
396     * @param beforeText The text before the change.
397     *
398     * @throws IllegalStateException If called from an AccessibilityService.
399     */
400    public void setBeforeText(CharSequence beforeText) {
401        enforceNotSealed();
402        mBeforeText = beforeText;
403    }
404
405    /**
406     * Gets the description of the source.
407     *
408     * @return The description.
409     */
410    public CharSequence getContentDescription() {
411        return mContentDescription;
412    }
413
414    /**
415     * Sets the description of the source.
416     *
417     * @param contentDescription The description.
418     *
419     * @throws IllegalStateException If called from an AccessibilityService.
420     */
421    public void setContentDescription(CharSequence contentDescription) {
422        enforceNotSealed();
423        mContentDescription = contentDescription;
424    }
425
426    /**
427     * Gets the {@link Parcelable} data.
428     *
429     * @return The parcelable data.
430     */
431    public Parcelable getParcelableData() {
432        return mParcelableData;
433    }
434
435    /**
436     * Sets the {@link Parcelable} data of the event.
437     *
438     * @param parcelableData The parcelable data.
439     *
440     * @throws IllegalStateException If called from an AccessibilityService.
441     */
442    public void setParcelableData(Parcelable parcelableData) {
443        enforceNotSealed();
444        mParcelableData = parcelableData;
445    }
446
447    /**
448     * Sets if this instance is sealed.
449     *
450     * @param sealed Whether is sealed.
451     *
452     * @hide
453     */
454    public void setSealed(boolean sealed) {
455        mSealed = sealed;
456    }
457
458    /**
459     * Gets if this instance is sealed.
460     *
461     * @return Whether is sealed.
462     */
463    boolean isSealed() {
464        return mSealed;
465    }
466
467    /**
468     * Enforces that this instance is sealed.
469     *
470     * @throws IllegalStateException If this instance is not sealed.
471     */
472    void enforceSealed() {
473        if (!isSealed()) {
474            throw new IllegalStateException("Cannot perform this "
475                    + "action on a not sealed instance.");
476        }
477    }
478
479    /**
480     * Enforces that this instance is not sealed.
481     *
482     * @throws IllegalStateException If this instance is sealed.
483     */
484    void enforceNotSealed() {
485        if (isSealed()) {
486            throw new IllegalStateException("Cannot perform this "
487                    + "action on an sealed instance.");
488        }
489    }
490
491    /**
492     * Gets the value of a boolean property.
493     *
494     * @param property The property.
495     * @return The value.
496     */
497    private boolean getBooleanProperty(int property) {
498        return (mBooleanProperties & property) == property;
499    }
500
501    /**
502     * Sets a boolean property.
503     *
504     * @param property The property.
505     * @param value The value.
506     */
507    private void setBooleanProperty(int property, boolean value) {
508        if (value) {
509            mBooleanProperties |= property;
510        } else {
511            mBooleanProperties &= ~property;
512        }
513    }
514
515    /**
516     * Returns a cached instance if such is available or a new one is
517     * instantiated. The instance is initialized with data from the
518     * given record.
519     *
520     * @return An instance.
521     */
522    public static AccessibilityRecord obtain(AccessibilityRecord record) {
523       AccessibilityRecord clone = AccessibilityRecord.obtain();
524       clone.init(record);
525       return clone;
526    }
527
528    /**
529     * Returns a cached instance if such is available or a new one is
530     * instantiated.
531     *
532     * @return An instance.
533     */
534    public static AccessibilityRecord obtain() {
535        synchronized (sPoolLock) {
536            if (sPool != null) {
537                AccessibilityRecord record = sPool;
538                sPool = sPool.mNext;
539                sPoolSize--;
540                record.mNext = null;
541                record.mIsInPool = false;
542                return record;
543            }
544            return new AccessibilityRecord();
545        }
546    }
547
548    /**
549     * Return an instance back to be reused.
550     * <p>
551     * <b>Note: You must not touch the object after calling this function.</b>
552     *
553     * @throws IllegalStateException If the record is already recycled.
554     */
555    public void recycle() {
556        if (mIsInPool) {
557            throw new IllegalStateException("Record already recycled!");
558        }
559        clear();
560        synchronized (sPoolLock) {
561            if (sPoolSize <= MAX_POOL_SIZE) {
562                mNext = sPool;
563                sPool = this;
564                mIsInPool = true;
565                sPoolSize++;
566            }
567        }
568    }
569
570    /**
571     * Clears the state of this instance.
572     */
573    void clear() {
574        mSealed = false;
575        mBooleanProperties = 0;
576        mCurrentItemIndex = INVALID_POSITION;
577        mItemCount = 0;
578        mFromIndex = 0;
579        mAddedCount = 0;
580        mRemovedCount = 0;
581        mClassName = null;
582        mContentDescription = null;
583        mBeforeText = null;
584        mParcelableData = null;
585        mText.clear();
586        mSourceViewId = View.NO_ID;
587        mSourceWindowId = View.NO_ID;
588    }
589
590    @Override
591    public String toString() {
592        StringBuilder builder = new StringBuilder();
593        builder.append(" [ ClassName: " + mClassName);
594        builder.append("; Text: " + mText);
595        builder.append("; ContentDescription: " + mContentDescription);
596        builder.append("; ItemCount: " + mItemCount);
597        builder.append("; CurrentItemIndex: " + mCurrentItemIndex);
598        builder.append("; IsEnabled: " + getBooleanProperty(PROPERTY_ENABLED));
599        builder.append("; IsPassword: " + getBooleanProperty(PROPERTY_PASSWORD));
600        builder.append("; IsChecked: " + getBooleanProperty(PROPERTY_CHECKED));
601        builder.append("; IsFullScreen: " + getBooleanProperty(PROPERTY_FULL_SCREEN));
602        builder.append("; BeforeText: " + mBeforeText);
603        builder.append("; FromIndex: " + mFromIndex);
604        builder.append("; AddedCount: " + mAddedCount);
605        builder.append("; RemovedCount: " + mRemovedCount);
606        builder.append("; ParcelableData: " + mParcelableData);
607        builder.append(" ]");
608        return builder.toString();
609    }
610}
611