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 static com.android.internal.util.CollectionUtils.isEmpty;
     20 
     21 import android.annotation.Nullable;
     22 import android.os.Parcelable;
     23 import android.view.View;
     24 
     25 import java.util.ArrayList;
     26 import java.util.List;
     27 
     28 /**
     29  * Represents a record in an {@link AccessibilityEvent} and contains information
     30  * about state change of its source {@link android.view.View}. When a view fires
     31  * an accessibility event it requests from its parent to dispatch the
     32  * constructed event. The parent may optionally append a record for itself
     33  * for providing more context to
     34  * {@link android.accessibilityservice.AccessibilityService}s. Hence,
     35  * accessibility services can facilitate additional accessibility records
     36  * to enhance feedback.
     37  * </p>
     38  * <p>
     39  * Once the accessibility event containing a record is dispatched the record is
     40  * made immutable and calling a state mutation method generates an error.
     41  * </p>
     42  * <p>
     43  * <strong>Note:</strong> Not all properties are applicable to all accessibility
     44  * event types. For detailed information please refer to {@link AccessibilityEvent}.
     45  * </p>
     46  *
     47  * <div class="special reference">
     48  * <h3>Developer Guides</h3>
     49  * <p>For more information about creating and processing AccessibilityRecords, read the
     50  * <a href="{@docRoot}guide/topics/ui/accessibility/index.html">Accessibility</a>
     51  * developer guide.</p>
     52  * </div>
     53  *
     54  * @see AccessibilityEvent
     55  * @see AccessibilityManager
     56  * @see android.accessibilityservice.AccessibilityService
     57  * @see AccessibilityNodeInfo
     58  */
     59 public class AccessibilityRecord {
     60     /** @hide */
     61     protected static final boolean DEBUG_CONCISE_TOSTRING = false;
     62 
     63     private static final int UNDEFINED = -1;
     64 
     65     private static final int PROPERTY_CHECKED = 0x00000001;
     66     private static final int PROPERTY_ENABLED = 0x00000002;
     67     private static final int PROPERTY_PASSWORD = 0x00000004;
     68     private static final int PROPERTY_FULL_SCREEN = 0x00000080;
     69     private static final int PROPERTY_SCROLLABLE = 0x00000100;
     70     private static final int PROPERTY_IMPORTANT_FOR_ACCESSIBILITY = 0x00000200;
     71 
     72     private static final int GET_SOURCE_PREFETCH_FLAGS =
     73         AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS
     74         | AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS
     75         | AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS;
     76 
     77     // Housekeeping
     78     private static final int MAX_POOL_SIZE = 10;
     79     private static final Object sPoolLock = new Object();
     80     private static AccessibilityRecord sPool;
     81     private static int sPoolSize;
     82     private AccessibilityRecord mNext;
     83     private boolean mIsInPool;
     84 
     85     boolean mSealed;
     86     int mBooleanProperties = 0;
     87     int mCurrentItemIndex = UNDEFINED;
     88     int mItemCount = UNDEFINED;
     89     int mFromIndex = UNDEFINED;
     90     int mToIndex = UNDEFINED;
     91     int mScrollX = UNDEFINED;
     92     int mScrollY = UNDEFINED;
     93 
     94     int mScrollDeltaX = UNDEFINED;
     95     int mScrollDeltaY = UNDEFINED;
     96     int mMaxScrollX = UNDEFINED;
     97     int mMaxScrollY = UNDEFINED;
     98 
     99     int mAddedCount= UNDEFINED;
    100     int mRemovedCount = UNDEFINED;
    101     long mSourceNodeId = AccessibilityNodeInfo.UNDEFINED_NODE_ID;
    102     int mSourceWindowId = AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
    103 
    104     CharSequence mClassName;
    105     CharSequence mContentDescription;
    106     CharSequence mBeforeText;
    107     Parcelable mParcelableData;
    108 
    109     final List<CharSequence> mText = new ArrayList<CharSequence>();
    110 
    111     int mConnectionId = UNDEFINED;
    112 
    113     /*
    114      * Hide constructor.
    115      */
    116     AccessibilityRecord() {
    117     }
    118 
    119     /**
    120      * Sets the event source.
    121      *
    122      * @param source The source.
    123      *
    124      * @throws IllegalStateException If called from an AccessibilityService.
    125      */
    126     public void setSource(View source) {
    127         setSource(source, AccessibilityNodeProvider.HOST_VIEW_ID);
    128     }
    129 
    130     /**
    131      * Sets the source to be a virtual descendant of the given <code>root</code>.
    132      * If <code>virtualDescendantId</code> equals to {@link View#NO_ID} the root
    133      * is set as the source.
    134      * <p>
    135      * A virtual descendant is an imaginary View that is reported as a part of the view
    136      * hierarchy for accessibility purposes. This enables custom views that draw complex
    137      * content to report them selves as a tree of virtual views, thus conveying their
    138      * logical structure.
    139      * </p>
    140      *
    141      * @param root The root of the virtual subtree.
    142      * @param virtualDescendantId The id of the virtual descendant.
    143      */
    144     public void setSource(@Nullable View root, int virtualDescendantId) {
    145         enforceNotSealed();
    146         boolean important = true;
    147         int rootViewId = AccessibilityNodeInfo.UNDEFINED_ITEM_ID;
    148         mSourceWindowId = AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
    149         if (root != null) {
    150             important = root.isImportantForAccessibility();
    151             rootViewId = root.getAccessibilityViewId();
    152             mSourceWindowId = root.getAccessibilityWindowId();
    153         }
    154         setBooleanProperty(PROPERTY_IMPORTANT_FOR_ACCESSIBILITY, important);
    155         mSourceNodeId = AccessibilityNodeInfo.makeNodeId(rootViewId, virtualDescendantId);
    156     }
    157 
    158     /**
    159      * Set the source node ID directly
    160      *
    161      * @param sourceNodeId The source node Id
    162      * @hide
    163      */
    164     public void setSourceNodeId(long sourceNodeId) {
    165         mSourceNodeId = sourceNodeId;
    166     }
    167 
    168     /**
    169      * Gets the {@link AccessibilityNodeInfo} of the event source.
    170      * <p>
    171      *   <strong>Note:</strong> It is a client responsibility to recycle the received info
    172      *   by calling {@link AccessibilityNodeInfo#recycle() AccessibilityNodeInfo#recycle()}
    173      *   to avoid creating of multiple instances.
    174      * </p>
    175      * @return The info of the source.
    176      */
    177     public AccessibilityNodeInfo getSource() {
    178         enforceSealed();
    179         if ((mConnectionId == UNDEFINED)
    180                 || (mSourceWindowId == AccessibilityWindowInfo.UNDEFINED_WINDOW_ID)
    181                 || (AccessibilityNodeInfo.getAccessibilityViewId(mSourceNodeId)
    182                         == AccessibilityNodeInfo.UNDEFINED_ITEM_ID)) {
    183             return null;
    184         }
    185         AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
    186         return client.findAccessibilityNodeInfoByAccessibilityId(mConnectionId, mSourceWindowId,
    187                 mSourceNodeId, false, GET_SOURCE_PREFETCH_FLAGS, null);
    188     }
    189 
    190     /**
    191      * Sets the window id.
    192      *
    193      * @param windowId The window id.
    194      *
    195      * @hide
    196      */
    197     public void setWindowId(int windowId) {
    198         mSourceWindowId = windowId;
    199     }
    200 
    201     /**
    202      * Gets the id of the window from which the event comes from.
    203      *
    204      * @return The window id.
    205      */
    206     public int getWindowId() {
    207         return mSourceWindowId;
    208     }
    209 
    210     /**
    211      * Gets if the source is checked.
    212      *
    213      * @return True if the view is checked, false otherwise.
    214      */
    215     public boolean isChecked() {
    216         return getBooleanProperty(PROPERTY_CHECKED);
    217     }
    218 
    219     /**
    220      * Sets if the source is checked.
    221      *
    222      * @param isChecked True if the view is checked, false otherwise.
    223      *
    224      * @throws IllegalStateException If called from an AccessibilityService.
    225      */
    226     public void setChecked(boolean isChecked) {
    227         enforceNotSealed();
    228         setBooleanProperty(PROPERTY_CHECKED, isChecked);
    229     }
    230 
    231     /**
    232      * Gets if the source is enabled.
    233      *
    234      * @return True if the view is enabled, false otherwise.
    235      */
    236     public boolean isEnabled() {
    237         return getBooleanProperty(PROPERTY_ENABLED);
    238     }
    239 
    240     /**
    241      * Sets if the source is enabled.
    242      *
    243      * @param isEnabled True if the view is enabled, false otherwise.
    244      *
    245      * @throws IllegalStateException If called from an AccessibilityService.
    246      */
    247     public void setEnabled(boolean isEnabled) {
    248         enforceNotSealed();
    249         setBooleanProperty(PROPERTY_ENABLED, isEnabled);
    250     }
    251 
    252     /**
    253      * Gets if the source is a password field.
    254      *
    255      * @return True if the view is a password field, false otherwise.
    256      */
    257     public boolean isPassword() {
    258         return getBooleanProperty(PROPERTY_PASSWORD);
    259     }
    260 
    261     /**
    262      * Sets if the source is a password field.
    263      *
    264      * @param isPassword True if the view is a password field, false otherwise.
    265      *
    266      * @throws IllegalStateException If called from an AccessibilityService.
    267      */
    268     public void setPassword(boolean isPassword) {
    269         enforceNotSealed();
    270         setBooleanProperty(PROPERTY_PASSWORD, isPassword);
    271     }
    272 
    273     /**
    274      * Gets if the source is taking the entire screen.
    275      *
    276      * @return True if the source is full screen, false otherwise.
    277      */
    278     public boolean isFullScreen() {
    279         return getBooleanProperty(PROPERTY_FULL_SCREEN);
    280     }
    281 
    282     /**
    283      * Sets if the source is taking the entire screen.
    284      *
    285      * @param isFullScreen True if the source is full screen, false otherwise.
    286      *
    287      * @throws IllegalStateException If called from an AccessibilityService.
    288      */
    289     public void setFullScreen(boolean isFullScreen) {
    290         enforceNotSealed();
    291         setBooleanProperty(PROPERTY_FULL_SCREEN, isFullScreen);
    292     }
    293 
    294     /**
    295      * Gets if the source is scrollable.
    296      *
    297      * @return True if the source is scrollable, false otherwise.
    298      */
    299     public boolean isScrollable() {
    300         return getBooleanProperty(PROPERTY_SCROLLABLE);
    301     }
    302 
    303     /**
    304      * Sets if the source is scrollable.
    305      *
    306      * @param scrollable True if the source is scrollable, false otherwise.
    307      *
    308      * @throws IllegalStateException If called from an AccessibilityService.
    309      */
    310     public void setScrollable(boolean scrollable) {
    311         enforceNotSealed();
    312         setBooleanProperty(PROPERTY_SCROLLABLE, scrollable);
    313     }
    314 
    315     /**
    316      * Gets if the source is important for accessibility.
    317      *
    318      * <strong>Note:</strong> Used only internally to determine whether
    319      * to deliver the event to a given accessibility service since some
    320      * services may want to regard all views for accessibility while others
    321      * may want to regard only the important views for accessibility.
    322      *
    323      * @return True if the source is important for accessibility,
    324      *        false otherwise.
    325      *
    326      * @hide
    327      */
    328     public boolean isImportantForAccessibility() {
    329         return getBooleanProperty(PROPERTY_IMPORTANT_FOR_ACCESSIBILITY);
    330     }
    331 
    332     /**
    333      * Sets if the source is important for accessibility.
    334      *
    335      * @param importantForAccessibility True if the source is important for accessibility,
    336      *                                  false otherwise.
    337      *
    338      * @throws IllegalStateException If called from an AccessibilityService.
    339      * @hide
    340      */
    341     public void setImportantForAccessibility(boolean importantForAccessibility) {
    342         enforceNotSealed();
    343         setBooleanProperty(PROPERTY_IMPORTANT_FOR_ACCESSIBILITY, importantForAccessibility);
    344     }
    345 
    346     /**
    347      * Gets the number of items that can be visited.
    348      *
    349      * @return The number of items.
    350      */
    351     public int getItemCount() {
    352         return mItemCount;
    353     }
    354 
    355     /**
    356      * Sets the number of items that can be visited.
    357      *
    358      * @param itemCount The number of items.
    359      *
    360      * @throws IllegalStateException If called from an AccessibilityService.
    361      */
    362     public void setItemCount(int itemCount) {
    363         enforceNotSealed();
    364         mItemCount = itemCount;
    365     }
    366 
    367     /**
    368      * Gets the index of the source in the list of items the can be visited.
    369      *
    370      * @return The current item index.
    371      */
    372     public int getCurrentItemIndex() {
    373         return mCurrentItemIndex;
    374     }
    375 
    376     /**
    377      * Sets the index of the source in the list of items that can be visited.
    378      *
    379      * @param currentItemIndex The current item index.
    380      *
    381      * @throws IllegalStateException If called from an AccessibilityService.
    382      */
    383     public void setCurrentItemIndex(int currentItemIndex) {
    384         enforceNotSealed();
    385         mCurrentItemIndex = currentItemIndex;
    386     }
    387 
    388     /**
    389      * Gets the index of the first character of the changed sequence,
    390      * or the beginning of a text selection or the index of the first
    391      * visible item when scrolling.
    392      *
    393      * @return The index of the first character or selection
    394      *        start or the first visible item.
    395      */
    396     public int getFromIndex() {
    397         return mFromIndex;
    398     }
    399 
    400     /**
    401      * Sets the index of the first character of the changed sequence
    402      * or the beginning of a text selection or the index of the first
    403      * visible item when scrolling.
    404      *
    405      * @param fromIndex The index of the first character or selection
    406      *        start or the first visible item.
    407      *
    408      * @throws IllegalStateException If called from an AccessibilityService.
    409      */
    410     public void setFromIndex(int fromIndex) {
    411         enforceNotSealed();
    412         mFromIndex = fromIndex;
    413     }
    414 
    415     /**
    416      * Gets the index of text selection end or the index of the last
    417      * visible item when scrolling.
    418      *
    419      * @return The index of selection end or last item index.
    420      */
    421     public int getToIndex() {
    422         return mToIndex;
    423     }
    424 
    425     /**
    426      * Sets the index of text selection end or the index of the last
    427      * visible item when scrolling.
    428      *
    429      * @param toIndex The index of selection end or last item index.
    430      */
    431     public void setToIndex(int toIndex) {
    432         enforceNotSealed();
    433         mToIndex = toIndex;
    434     }
    435 
    436     /**
    437      * Gets the scroll offset of the source left edge in pixels.
    438      *
    439      * @return The scroll.
    440      */
    441     public int getScrollX() {
    442         return mScrollX;
    443     }
    444 
    445     /**
    446      * Sets the scroll offset of the source left edge in pixels.
    447      *
    448      * @param scrollX The scroll.
    449      */
    450     public void setScrollX(int scrollX) {
    451         enforceNotSealed();
    452         mScrollX = scrollX;
    453     }
    454 
    455     /**
    456      * Gets the scroll offset of the source top edge in pixels.
    457      *
    458      * @return The scroll.
    459      */
    460     public int getScrollY() {
    461         return mScrollY;
    462     }
    463 
    464     /**
    465      * Sets the scroll offset of the source top edge in pixels.
    466      *
    467      * @param scrollY The scroll.
    468      */
    469     public void setScrollY(int scrollY) {
    470         enforceNotSealed();
    471         mScrollY = scrollY;
    472     }
    473 
    474     /**
    475      * Gets the difference in pixels between the horizontal position before the scroll and the
    476      * current horizontal position
    477      *
    478      * @return the scroll delta x
    479      */
    480     public int getScrollDeltaX() {
    481         return mScrollDeltaX;
    482     }
    483 
    484     /**
    485      * Sets the difference in pixels between the horizontal position before the scroll and the
    486      * current horizontal position
    487      *
    488      * @param scrollDeltaX the scroll delta x
    489      */
    490     public void setScrollDeltaX(int scrollDeltaX) {
    491         enforceNotSealed();
    492         mScrollDeltaX = scrollDeltaX;
    493     }
    494 
    495     /**
    496      * Gets the difference in pixels between the vertical position before the scroll and the
    497      * current vertical position
    498      *
    499      * @return the scroll delta y
    500      */
    501     public int getScrollDeltaY() {
    502         return mScrollDeltaY;
    503     }
    504 
    505     /**
    506      * Sets the difference in pixels between the vertical position before the scroll and the
    507      * current vertical position
    508      *
    509      * @param scrollDeltaY the scroll delta y
    510      */
    511     public void setScrollDeltaY(int scrollDeltaY) {
    512         enforceNotSealed();
    513         mScrollDeltaY = scrollDeltaY;
    514     }
    515 
    516     /**
    517      * Gets the max scroll offset of the source left edge in pixels.
    518      *
    519      * @return The max scroll.
    520      */
    521     public int getMaxScrollX() {
    522         return mMaxScrollX;
    523     }
    524 
    525     /**
    526      * Sets the max scroll offset of the source left edge in pixels.
    527      *
    528      * @param maxScrollX The max scroll.
    529      */
    530     public void setMaxScrollX(int maxScrollX) {
    531         enforceNotSealed();
    532         mMaxScrollX = maxScrollX;
    533     }
    534 
    535     /**
    536      * Gets the max scroll offset of the source top edge in pixels.
    537      *
    538      * @return The max scroll.
    539      */
    540     public int getMaxScrollY() {
    541         return mMaxScrollY;
    542     }
    543 
    544     /**
    545      * Sets the max scroll offset of the source top edge in pixels.
    546      *
    547      * @param maxScrollY The max scroll.
    548      */
    549     public void setMaxScrollY(int maxScrollY) {
    550         enforceNotSealed();
    551         mMaxScrollY = maxScrollY;
    552     }
    553 
    554     /**
    555      * Gets the number of added characters.
    556      *
    557      * @return The number of added characters.
    558      */
    559     public int getAddedCount() {
    560         return mAddedCount;
    561     }
    562 
    563     /**
    564      * Sets the number of added characters.
    565      *
    566      * @param addedCount The number of added characters.
    567      *
    568      * @throws IllegalStateException If called from an AccessibilityService.
    569      */
    570     public void setAddedCount(int addedCount) {
    571         enforceNotSealed();
    572         mAddedCount = addedCount;
    573     }
    574 
    575     /**
    576      * Gets the number of removed characters.
    577      *
    578      * @return The number of removed characters.
    579      */
    580     public int getRemovedCount() {
    581         return mRemovedCount;
    582     }
    583 
    584     /**
    585      * Sets the number of removed characters.
    586      *
    587      * @param removedCount The number of removed characters.
    588      *
    589      * @throws IllegalStateException If called from an AccessibilityService.
    590      */
    591     public void setRemovedCount(int removedCount) {
    592         enforceNotSealed();
    593         mRemovedCount = removedCount;
    594     }
    595 
    596     /**
    597      * Gets the class name of the source.
    598      *
    599      * @return The class name.
    600      */
    601     public CharSequence getClassName() {
    602         return mClassName;
    603     }
    604 
    605     /**
    606      * Sets the class name of the source.
    607      *
    608      * @param className The lass name.
    609      *
    610      * @throws IllegalStateException If called from an AccessibilityService.
    611      */
    612     public void setClassName(CharSequence className) {
    613         enforceNotSealed();
    614         mClassName = className;
    615     }
    616 
    617     /**
    618      * Gets the text of the event. The index in the list represents the priority
    619      * of the text. Specifically, the lower the index the higher the priority.
    620      *
    621      * @return The text.
    622      */
    623     public List<CharSequence> getText() {
    624         return mText;
    625     }
    626 
    627     /**
    628      * Sets the text before a change.
    629      *
    630      * @return The text before the change.
    631      */
    632     public CharSequence getBeforeText() {
    633         return mBeforeText;
    634     }
    635 
    636     /**
    637      * Sets the text before a change.
    638      *
    639      * @param beforeText The text before the change.
    640      *
    641      * @throws IllegalStateException If called from an AccessibilityService.
    642      */
    643     public void setBeforeText(CharSequence beforeText) {
    644         enforceNotSealed();
    645         mBeforeText = (beforeText == null) ? null
    646                 : beforeText.subSequence(0, beforeText.length());
    647     }
    648 
    649     /**
    650      * Gets the description of the source.
    651      *
    652      * @return The description.
    653      */
    654     public CharSequence getContentDescription() {
    655         return mContentDescription;
    656     }
    657 
    658     /**
    659      * Sets the description of the source.
    660      *
    661      * @param contentDescription The description.
    662      *
    663      * @throws IllegalStateException If called from an AccessibilityService.
    664      */
    665     public void setContentDescription(CharSequence contentDescription) {
    666         enforceNotSealed();
    667         mContentDescription = (contentDescription == null) ? null
    668                 : contentDescription.subSequence(0, contentDescription.length());
    669     }
    670 
    671     /**
    672      * Gets the {@link Parcelable} data.
    673      *
    674      * @return The parcelable data.
    675      */
    676     public Parcelable getParcelableData() {
    677         return mParcelableData;
    678     }
    679 
    680     /**
    681      * Sets the {@link Parcelable} data of the event.
    682      *
    683      * @param parcelableData The parcelable data.
    684      *
    685      * @throws IllegalStateException If called from an AccessibilityService.
    686      */
    687     public void setParcelableData(Parcelable parcelableData) {
    688         enforceNotSealed();
    689         mParcelableData = parcelableData;
    690     }
    691 
    692     /**
    693      * Gets the id of the source node.
    694      *
    695      * @return The id.
    696      *
    697      * @hide
    698      */
    699     public long getSourceNodeId() {
    700         return mSourceNodeId;
    701     }
    702 
    703     /**
    704      * Sets the unique id of the IAccessibilityServiceConnection over which
    705      * this instance can send requests to the system.
    706      *
    707      * @param connectionId The connection id.
    708      *
    709      * @hide
    710      */
    711     public void setConnectionId(int connectionId) {
    712         enforceNotSealed();
    713         mConnectionId = connectionId;
    714     }
    715 
    716     /**
    717      * Sets if this instance is sealed.
    718      *
    719      * @param sealed Whether is sealed.
    720      *
    721      * @hide
    722      */
    723     public void setSealed(boolean sealed) {
    724         mSealed = sealed;
    725     }
    726 
    727     /**
    728      * Gets if this instance is sealed.
    729      *
    730      * @return Whether is sealed.
    731      */
    732     boolean isSealed() {
    733         return mSealed;
    734     }
    735 
    736     /**
    737      * Enforces that this instance is sealed.
    738      *
    739      * @throws IllegalStateException If this instance is not sealed.
    740      */
    741     void enforceSealed() {
    742         if (!isSealed()) {
    743             throw new IllegalStateException("Cannot perform this "
    744                     + "action on a not sealed instance.");
    745         }
    746     }
    747 
    748     /**
    749      * Enforces that this instance is not sealed.
    750      *
    751      * @throws IllegalStateException If this instance is sealed.
    752      */
    753     void enforceNotSealed() {
    754         if (isSealed()) {
    755             throw new IllegalStateException("Cannot perform this "
    756                     + "action on a sealed instance.");
    757         }
    758     }
    759 
    760     /**
    761      * Gets the value of a boolean property.
    762      *
    763      * @param property The property.
    764      * @return The value.
    765      */
    766     private boolean getBooleanProperty(int property) {
    767         return (mBooleanProperties & property) == property;
    768     }
    769 
    770     /**
    771      * Sets a boolean property.
    772      *
    773      * @param property The property.
    774      * @param value The value.
    775      */
    776     private void setBooleanProperty(int property, boolean value) {
    777         if (value) {
    778             mBooleanProperties |= property;
    779         } else {
    780             mBooleanProperties &= ~property;
    781         }
    782     }
    783 
    784     /**
    785      * Returns a cached instance if such is available or a new one is
    786      * instantiated. The instance is initialized with data from the
    787      * given record.
    788      *
    789      * @return An instance.
    790      */
    791     public static AccessibilityRecord obtain(AccessibilityRecord record) {
    792        AccessibilityRecord clone = AccessibilityRecord.obtain();
    793        clone.init(record);
    794        return clone;
    795     }
    796 
    797     /**
    798      * Returns a cached instance if such is available or a new one is
    799      * instantiated.
    800      *
    801      * @return An instance.
    802      */
    803     public static AccessibilityRecord obtain() {
    804         synchronized (sPoolLock) {
    805             if (sPool != null) {
    806                 AccessibilityRecord record = sPool;
    807                 sPool = sPool.mNext;
    808                 sPoolSize--;
    809                 record.mNext = null;
    810                 record.mIsInPool = false;
    811                 return record;
    812             }
    813             return new AccessibilityRecord();
    814         }
    815     }
    816 
    817     /**
    818      * Return an instance back to be reused.
    819      * <p>
    820      * <strong>Note:</strong> You must not touch the object after calling this function.
    821      *
    822      * @throws IllegalStateException If the record is already recycled.
    823      */
    824     public void recycle() {
    825         if (mIsInPool) {
    826             throw new IllegalStateException("Record already recycled!");
    827         }
    828         clear();
    829         synchronized (sPoolLock) {
    830             if (sPoolSize <= MAX_POOL_SIZE) {
    831                 mNext = sPool;
    832                 sPool = this;
    833                 mIsInPool = true;
    834                 sPoolSize++;
    835             }
    836         }
    837     }
    838 
    839     /**
    840      * Initialize this record from another one.
    841      *
    842      * @param record The to initialize from.
    843      */
    844     void init(AccessibilityRecord record) {
    845         mSealed = record.mSealed;
    846         mBooleanProperties = record.mBooleanProperties;
    847         mCurrentItemIndex = record.mCurrentItemIndex;
    848         mItemCount = record.mItemCount;
    849         mFromIndex = record.mFromIndex;
    850         mToIndex = record.mToIndex;
    851         mScrollX = record.mScrollX;
    852         mScrollY = record.mScrollY;
    853         mMaxScrollX = record.mMaxScrollX;
    854         mMaxScrollY = record.mMaxScrollY;
    855         mAddedCount = record.mAddedCount;
    856         mRemovedCount = record.mRemovedCount;
    857         mClassName = record.mClassName;
    858         mContentDescription = record.mContentDescription;
    859         mBeforeText = record.mBeforeText;
    860         mParcelableData = record.mParcelableData;
    861         mText.addAll(record.mText);
    862         mSourceWindowId = record.mSourceWindowId;
    863         mSourceNodeId = record.mSourceNodeId;
    864         mConnectionId = record.mConnectionId;
    865     }
    866 
    867     /**
    868      * Clears the state of this instance.
    869      */
    870     void clear() {
    871         mSealed = false;
    872         mBooleanProperties = 0;
    873         mCurrentItemIndex = UNDEFINED;
    874         mItemCount = UNDEFINED;
    875         mFromIndex = UNDEFINED;
    876         mToIndex = UNDEFINED;
    877         mScrollX = UNDEFINED;
    878         mScrollY = UNDEFINED;
    879         mMaxScrollX = UNDEFINED;
    880         mMaxScrollY = UNDEFINED;
    881         mAddedCount = UNDEFINED;
    882         mRemovedCount = UNDEFINED;
    883         mClassName = null;
    884         mContentDescription = null;
    885         mBeforeText = null;
    886         mParcelableData = null;
    887         mText.clear();
    888         mSourceNodeId = AccessibilityNodeInfo.UNDEFINED_ITEM_ID;
    889         mSourceWindowId = AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
    890         mConnectionId = UNDEFINED;
    891     }
    892 
    893     @Override
    894     public String toString() {
    895         return appendTo(new StringBuilder()).toString();
    896     }
    897 
    898     StringBuilder appendTo(StringBuilder builder) {
    899         builder.append(" [ ClassName: ").append(mClassName);
    900         if (!DEBUG_CONCISE_TOSTRING || !isEmpty(mText)) {
    901             appendPropName(builder, "Text").append(mText);
    902         }
    903         append(builder, "ContentDescription", mContentDescription);
    904         append(builder, "ItemCount", mItemCount);
    905         append(builder, "CurrentItemIndex", mCurrentItemIndex);
    906 
    907         appendUnless(true, PROPERTY_ENABLED, builder);
    908         appendUnless(false, PROPERTY_PASSWORD, builder);
    909         appendUnless(false, PROPERTY_CHECKED, builder);
    910         appendUnless(false, PROPERTY_FULL_SCREEN, builder);
    911         appendUnless(false, PROPERTY_SCROLLABLE, builder);
    912 
    913         append(builder, "BeforeText", mBeforeText);
    914         append(builder, "FromIndex", mFromIndex);
    915         append(builder, "ToIndex", mToIndex);
    916         append(builder, "ScrollX", mScrollX);
    917         append(builder, "ScrollY", mScrollY);
    918         append(builder, "MaxScrollX", mMaxScrollX);
    919         append(builder, "MaxScrollY", mMaxScrollY);
    920         append(builder, "AddedCount", mAddedCount);
    921         append(builder, "RemovedCount", mRemovedCount);
    922         append(builder, "ParcelableData", mParcelableData);
    923         builder.append(" ]");
    924         return builder;
    925     }
    926 
    927     private void appendUnless(boolean defValue, int prop, StringBuilder builder) {
    928         boolean value = getBooleanProperty(prop);
    929         if (DEBUG_CONCISE_TOSTRING && value == defValue) return;
    930         appendPropName(builder, singleBooleanPropertyToString(prop))
    931                 .append(value);
    932     }
    933 
    934     private static String singleBooleanPropertyToString(int prop) {
    935         switch (prop) {
    936             case PROPERTY_CHECKED: return "Checked";
    937             case PROPERTY_ENABLED: return "Enabled";
    938             case PROPERTY_PASSWORD: return "Password";
    939             case PROPERTY_FULL_SCREEN: return "FullScreen";
    940             case PROPERTY_SCROLLABLE: return "Scrollable";
    941             case PROPERTY_IMPORTANT_FOR_ACCESSIBILITY:
    942                 return "ImportantForAccessibility";
    943             default: return Integer.toHexString(prop);
    944         }
    945     }
    946 
    947     private void append(StringBuilder builder, String propName, int propValue) {
    948         if (DEBUG_CONCISE_TOSTRING && propValue == UNDEFINED) return;
    949         appendPropName(builder, propName).append(propValue);
    950     }
    951 
    952     private void append(StringBuilder builder, String propName, Object propValue) {
    953         if (DEBUG_CONCISE_TOSTRING && propValue == null) return;
    954         appendPropName(builder, propName).append(propValue);
    955     }
    956 
    957     private StringBuilder appendPropName(StringBuilder builder, String propName) {
    958         return builder.append("; ").append(propName).append(": ");
    959     }
    960 }
    961