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