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.graphics.Rect;
     20 import android.os.Parcel;
     21 import android.os.Parcelable;
     22 import android.text.TextUtils;
     23 import android.util.SparseIntArray;
     24 import android.view.View;
     25 
     26 import java.util.Collections;
     27 import java.util.List;
     28 
     29 /**
     30  * This class represents a node of the window content as well as actions that
     31  * can be requested from its source. From the point of view of an
     32  * {@link android.accessibilityservice.AccessibilityService} a window content is
     33  * presented as tree of accessibility node info which may or may not map one-to-one
     34  * to the view hierarchy. In other words, a custom view is free to report itself as
     35  * a tree of accessibility node info.
     36  * </p>
     37  * <p>
     38  * Once an accessibility node info is delivered to an accessibility service it is
     39  * made immutable and calling a state mutation method generates an error.
     40  * </p>
     41  * <p>
     42  * Please refer to {@link android.accessibilityservice.AccessibilityService} for
     43  * details about how to obtain a handle to window content as a tree of accessibility
     44  * node info as well as familiarizing with the security model.
     45  * </p>
     46  *
     47  * @see android.accessibilityservice.AccessibilityService
     48  * @see AccessibilityEvent
     49  * @see AccessibilityManager
     50  */
     51 public class AccessibilityNodeInfo implements Parcelable {
     52 
     53     private static final boolean DEBUG = false;
     54 
     55     private static final int UNDEFINED = -1;
     56 
     57     // Actions.
     58 
     59     /**
     60      * Action that focuses the node.
     61      */
     62     public static final int ACTION_FOCUS =  0x00000001;
     63 
     64     /**
     65      * Action that unfocuses the node.
     66      */
     67     public static final int ACTION_CLEAR_FOCUS =  0x00000002;
     68 
     69     /**
     70      * Action that selects the node.
     71      */
     72     public static final int ACTION_SELECT =  0x00000004;
     73 
     74     /**
     75      * Action that unselects the node.
     76      */
     77     public static final int ACTION_CLEAR_SELECTION =  0x00000008;
     78 
     79     // Boolean attributes.
     80 
     81     private static final int PROPERTY_CHECKABLE = 0x00000001;
     82 
     83     private static final int PROPERTY_CHECKED = 0x00000002;
     84 
     85     private static final int PROPERTY_FOCUSABLE = 0x00000004;
     86 
     87     private static final int PROPERTY_FOCUSED = 0x00000008;
     88 
     89     private static final int PROPERTY_SELECTED = 0x00000010;
     90 
     91     private static final int PROPERTY_CLICKABLE = 0x00000020;
     92 
     93     private static final int PROPERTY_LONG_CLICKABLE = 0x00000040;
     94 
     95     private static final int PROPERTY_ENABLED = 0x00000080;
     96 
     97     private static final int PROPERTY_PASSWORD = 0x00000100;
     98 
     99     private static final int PROPERTY_SCROLLABLE = 0x00000200;
    100 
    101     // Housekeeping.
    102     private static final int MAX_POOL_SIZE = 50;
    103     private static final Object sPoolLock = new Object();
    104     private static AccessibilityNodeInfo sPool;
    105     private static int sPoolSize;
    106     private AccessibilityNodeInfo mNext;
    107     private boolean mIsInPool;
    108     private boolean mSealed;
    109 
    110     // Data.
    111     private int mAccessibilityViewId = UNDEFINED;
    112     private int mAccessibilityWindowId = UNDEFINED;
    113     private int mParentAccessibilityViewId = UNDEFINED;
    114     private int mBooleanProperties;
    115     private final Rect mBoundsInParent = new Rect();
    116     private final Rect mBoundsInScreen = new Rect();
    117 
    118     private CharSequence mPackageName;
    119     private CharSequence mClassName;
    120     private CharSequence mText;
    121     private CharSequence mContentDescription;
    122 
    123     private SparseIntArray mChildAccessibilityIds = new SparseIntArray();
    124     private int mActions;
    125 
    126     private int mConnectionId = UNDEFINED;
    127 
    128     /**
    129      * Hide constructor from clients.
    130      */
    131     private AccessibilityNodeInfo() {
    132         /* do nothing */
    133     }
    134 
    135     /**
    136      * Sets the source.
    137      *
    138      * @param source The info source.
    139      */
    140     public void setSource(View source) {
    141         enforceNotSealed();
    142         mAccessibilityViewId = source.getAccessibilityViewId();
    143         mAccessibilityWindowId = source.getAccessibilityWindowId();
    144     }
    145 
    146     /**
    147      * Gets the id of the window from which the info comes from.
    148      *
    149      * @return The window id.
    150      */
    151     public int getWindowId() {
    152         return mAccessibilityWindowId;
    153     }
    154 
    155     /**
    156      * Gets the number of children.
    157      *
    158      * @return The child count.
    159      */
    160     public int getChildCount() {
    161         return mChildAccessibilityIds.size();
    162     }
    163 
    164     /**
    165      * Get the child at given index.
    166      * <p>
    167      *   <strong>Note:</strong> It is a client responsibility to recycle the
    168      *     received info by calling {@link AccessibilityNodeInfo#recycle()}
    169      *     to avoid creating of multiple instances.
    170      * </p>
    171      *
    172      * @param index The child index.
    173      * @return The child node.
    174      *
    175      * @throws IllegalStateException If called outside of an AccessibilityService.
    176      *
    177      */
    178     public AccessibilityNodeInfo getChild(int index) {
    179         enforceSealed();
    180         final int childAccessibilityViewId = mChildAccessibilityIds.get(index);
    181         if (!canPerformRequestOverConnection(childAccessibilityViewId)) {
    182             return null;
    183         }
    184         AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
    185         return client.findAccessibilityNodeInfoByAccessibilityId(mConnectionId,
    186                 mAccessibilityWindowId, childAccessibilityViewId);
    187     }
    188 
    189     /**
    190      * Adds a child.
    191      * <p>
    192      *   <strong>Note:</strong> Cannot be called from an
    193      *   {@link android.accessibilityservice.AccessibilityService}.
    194      *   This class is made immutable before being delivered to an AccessibilityService.
    195      * </p>
    196      *
    197      * @param child The child.
    198      *
    199      * @throws IllegalStateException If called from an AccessibilityService.
    200      */
    201     public void addChild(View child) {
    202         enforceNotSealed();
    203         final int childAccessibilityViewId = child.getAccessibilityViewId();
    204         final int index = mChildAccessibilityIds.size();
    205         mChildAccessibilityIds.put(index, childAccessibilityViewId);
    206     }
    207 
    208     /**
    209      * Gets the actions that can be performed on the node.
    210      *
    211      * @return The bit mask of with actions.
    212      *
    213      * @see AccessibilityNodeInfo#ACTION_FOCUS
    214      * @see AccessibilityNodeInfo#ACTION_CLEAR_FOCUS
    215      * @see AccessibilityNodeInfo#ACTION_SELECT
    216      * @see AccessibilityNodeInfo#ACTION_CLEAR_SELECTION
    217      */
    218     public int getActions() {
    219         return mActions;
    220     }
    221 
    222     /**
    223      * Adds an action that can be performed on the node.
    224      * <p>
    225      *   <strong>Note:</strong> Cannot be called from an
    226      *   {@link android.accessibilityservice.AccessibilityService}.
    227      *   This class is made immutable before being delivered to an AccessibilityService.
    228      * </p>
    229      *
    230      * @param action The action.
    231      *
    232      * @throws IllegalStateException If called from an AccessibilityService.
    233      */
    234     public void addAction(int action) {
    235         enforceNotSealed();
    236         mActions |= action;
    237     }
    238 
    239     /**
    240      * Performs an action on the node.
    241      * <p>
    242      *   <strong>Note:</strong> An action can be performed only if the request is made
    243      *   from an {@link android.accessibilityservice.AccessibilityService}.
    244      * </p>
    245      *
    246      * @param action The action to perform.
    247      * @return True if the action was performed.
    248      *
    249      * @throws IllegalStateException If called outside of an AccessibilityService.
    250      */
    251     public boolean performAction(int action) {
    252         enforceSealed();
    253         if (!canPerformRequestOverConnection(mAccessibilityViewId)) {
    254             return false;
    255         }
    256         AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
    257         return client.performAccessibilityAction(mConnectionId, mAccessibilityWindowId,
    258                 mAccessibilityViewId, action);
    259     }
    260 
    261     /**
    262      * Finds {@link AccessibilityNodeInfo}s by text. The match is case
    263      * insensitive containment. The search is relative to this info i.e.
    264      * this info is the root of the traversed tree.
    265      *
    266      * <p>
    267      *   <strong>Note:</strong> It is a client responsibility to recycle the
    268      *     received info by calling {@link AccessibilityNodeInfo#recycle()}
    269      *     to avoid creating of multiple instances.
    270      * </p>
    271      *
    272      * @param text The searched text.
    273      * @return A list of node info.
    274      */
    275     public List<AccessibilityNodeInfo> findAccessibilityNodeInfosByText(String text) {
    276         enforceSealed();
    277         if (!canPerformRequestOverConnection(mAccessibilityViewId)) {
    278             return Collections.emptyList();
    279         }
    280         AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
    281         return client.findAccessibilityNodeInfosByViewText(mConnectionId, text,
    282                 mAccessibilityWindowId, mAccessibilityViewId);
    283     }
    284 
    285     /**
    286      * Gets the parent.
    287      * <p>
    288      *   <strong>Note:</strong> It is a client responsibility to recycle the
    289      *     received info by calling {@link AccessibilityNodeInfo#recycle()}
    290      *     to avoid creating of multiple instances.
    291      * </p>
    292      *
    293      * @return The parent.
    294      */
    295     public AccessibilityNodeInfo getParent() {
    296         enforceSealed();
    297         if (!canPerformRequestOverConnection(mParentAccessibilityViewId)) {
    298             return null;
    299         }
    300         AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
    301         return client.findAccessibilityNodeInfoByAccessibilityId(mConnectionId,
    302                 mAccessibilityWindowId, mParentAccessibilityViewId);
    303     }
    304 
    305     /**
    306      * Sets the parent.
    307      * <p>
    308      *   <strong>Note:</strong> Cannot be called from an
    309      *   {@link android.accessibilityservice.AccessibilityService}.
    310      *   This class is made immutable before being delivered to an AccessibilityService.
    311      * </p>
    312      *
    313      * @param parent The parent.
    314      *
    315      * @throws IllegalStateException If called from an AccessibilityService.
    316      */
    317     public void setParent(View parent) {
    318         enforceNotSealed();
    319         mParentAccessibilityViewId = parent.getAccessibilityViewId();
    320     }
    321 
    322     /**
    323      * Gets the node bounds in parent coordinates.
    324      *
    325      * @param outBounds The output node bounds.
    326      */
    327     public void getBoundsInParent(Rect outBounds) {
    328         outBounds.set(mBoundsInParent.left, mBoundsInParent.top,
    329                 mBoundsInParent.right, mBoundsInParent.bottom);
    330     }
    331 
    332     /**
    333      * Sets the node bounds in parent coordinates.
    334      * <p>
    335      *   <strong>Note:</strong> Cannot be called from an
    336      *   {@link android.accessibilityservice.AccessibilityService}.
    337      *   This class is made immutable before being delivered to an AccessibilityService.
    338      * </p>
    339      *
    340      * @param bounds The node bounds.
    341      *
    342      * @throws IllegalStateException If called from an AccessibilityService.
    343      */
    344     public void setBoundsInParent(Rect bounds) {
    345         enforceNotSealed();
    346         mBoundsInParent.set(bounds.left, bounds.top, bounds.right, bounds.bottom);
    347     }
    348 
    349     /**
    350      * Gets the node bounds in screen coordinates.
    351      *
    352      * @param outBounds The output node bounds.
    353      */
    354     public void getBoundsInScreen(Rect outBounds) {
    355         outBounds.set(mBoundsInScreen.left, mBoundsInScreen.top,
    356                 mBoundsInScreen.right, mBoundsInScreen.bottom);
    357     }
    358 
    359     /**
    360      * Sets the node bounds in screen coordinates.
    361      * <p>
    362      *   <strong>Note:</strong> Cannot be called from an
    363      *   {@link android.accessibilityservice.AccessibilityService}.
    364      *   This class is made immutable before being delivered to an AccessibilityService.
    365      * </p>
    366      *
    367      * @param bounds The node bounds.
    368      *
    369      * @throws IllegalStateException If called from an AccessibilityService.
    370      */
    371     public void setBoundsInScreen(Rect bounds) {
    372         enforceNotSealed();
    373         mBoundsInScreen.set(bounds.left, bounds.top, bounds.right, bounds.bottom);
    374     }
    375 
    376     /**
    377      * Gets whether this node is checkable.
    378      *
    379      * @return True if the node is checkable.
    380      */
    381     public boolean isCheckable() {
    382         return getBooleanProperty(PROPERTY_CHECKABLE);
    383     }
    384 
    385     /**
    386      * Sets whether this node is checkable.
    387      * <p>
    388      *   <strong>Note:</strong> Cannot be called from an
    389      *   {@link android.accessibilityservice.AccessibilityService}.
    390      *   This class is made immutable before being delivered to an AccessibilityService.
    391      * </p>
    392      *
    393      * @param checkable True if the node is checkable.
    394      *
    395      * @throws IllegalStateException If called from an AccessibilityService.
    396      */
    397     public void setCheckable(boolean checkable) {
    398         setBooleanProperty(PROPERTY_CHECKABLE, checkable);
    399     }
    400 
    401     /**
    402      * Gets whether this node is checked.
    403      *
    404      * @return True if the node is checked.
    405      */
    406     public boolean isChecked() {
    407         return getBooleanProperty(PROPERTY_CHECKED);
    408     }
    409 
    410     /**
    411      * Sets whether this node is checked.
    412      * <p>
    413      *   <strong>Note:</strong> Cannot be called from an
    414      *   {@link android.accessibilityservice.AccessibilityService}.
    415      *   This class is made immutable before being delivered to an AccessibilityService.
    416      * </p>
    417      *
    418      * @param checked True if the node is checked.
    419      *
    420      * @throws IllegalStateException If called from an AccessibilityService.
    421      */
    422     public void setChecked(boolean checked) {
    423         setBooleanProperty(PROPERTY_CHECKED, checked);
    424     }
    425 
    426     /**
    427      * Gets whether this node is focusable.
    428      *
    429      * @return True if the node is focusable.
    430      */
    431     public boolean isFocusable() {
    432         return getBooleanProperty(PROPERTY_FOCUSABLE);
    433     }
    434 
    435     /**
    436      * Sets whether this node is focusable.
    437      * <p>
    438      *   <strong>Note:</strong> Cannot be called from an
    439      *   {@link android.accessibilityservice.AccessibilityService}.
    440      *   This class is made immutable before being delivered to an AccessibilityService.
    441      * </p>
    442      *
    443      * @param focusable True if the node is focusable.
    444      *
    445      * @throws IllegalStateException If called from an AccessibilityService.
    446      */
    447     public void setFocusable(boolean focusable) {
    448         setBooleanProperty(PROPERTY_FOCUSABLE, focusable);
    449     }
    450 
    451     /**
    452      * Gets whether this node is focused.
    453      *
    454      * @return True if the node is focused.
    455      */
    456     public boolean isFocused() {
    457         return getBooleanProperty(PROPERTY_FOCUSED);
    458     }
    459 
    460     /**
    461      * Sets whether this node is focused.
    462      * <p>
    463      *   <strong>Note:</strong> Cannot be called from an
    464      *   {@link android.accessibilityservice.AccessibilityService}.
    465      *   This class is made immutable before being delivered to an AccessibilityService.
    466      * </p>
    467      *
    468      * @param focused True if the node is focused.
    469      *
    470      * @throws IllegalStateException If called from an AccessibilityService.
    471      */
    472     public void setFocused(boolean focused) {
    473         setBooleanProperty(PROPERTY_FOCUSED, focused);
    474     }
    475 
    476     /**
    477      * Gets whether this node is selected.
    478      *
    479      * @return True if the node is selected.
    480      */
    481     public boolean isSelected() {
    482         return getBooleanProperty(PROPERTY_SELECTED);
    483     }
    484 
    485     /**
    486      * Sets whether this node is selected.
    487      * <p>
    488      *   <strong>Note:</strong> Cannot be called from an
    489      *   {@link android.accessibilityservice.AccessibilityService}.
    490      *   This class is made immutable before being delivered to an AccessibilityService.
    491      * </p>
    492      *
    493      * @param selected True if the node is selected.
    494      *
    495      * @throws IllegalStateException If called from an AccessibilityService.
    496      */
    497     public void setSelected(boolean selected) {
    498         setBooleanProperty(PROPERTY_SELECTED, selected);
    499     }
    500 
    501     /**
    502      * Gets whether this node is clickable.
    503      *
    504      * @return True if the node is clickable.
    505      */
    506     public boolean isClickable() {
    507         return getBooleanProperty(PROPERTY_CLICKABLE);
    508     }
    509 
    510     /**
    511      * Sets whether this node is clickable.
    512      * <p>
    513      *   <strong>Note:</strong> Cannot be called from an
    514      *   {@link android.accessibilityservice.AccessibilityService}.
    515      *   This class is made immutable before being delivered to an AccessibilityService.
    516      * </p>
    517      *
    518      * @param clickable True if the node is clickable.
    519      *
    520      * @throws IllegalStateException If called from an AccessibilityService.
    521      */
    522     public void setClickable(boolean clickable) {
    523         setBooleanProperty(PROPERTY_CLICKABLE, clickable);
    524     }
    525 
    526     /**
    527      * Gets whether this node is long clickable.
    528      *
    529      * @return True if the node is long clickable.
    530      */
    531     public boolean isLongClickable() {
    532         return getBooleanProperty(PROPERTY_LONG_CLICKABLE);
    533     }
    534 
    535     /**
    536      * Sets whether this node is long clickable.
    537      * <p>
    538      *   <strong>Note:</strong> Cannot be called from an
    539      *   {@link android.accessibilityservice.AccessibilityService}.
    540      *   This class is made immutable before being delivered to an AccessibilityService.
    541      * </p>
    542      *
    543      * @param longClickable True if the node is long clickable.
    544      *
    545      * @throws IllegalStateException If called from an AccessibilityService.
    546      */
    547     public void setLongClickable(boolean longClickable) {
    548         setBooleanProperty(PROPERTY_LONG_CLICKABLE, longClickable);
    549     }
    550 
    551     /**
    552      * Gets whether this node is enabled.
    553      *
    554      * @return True if the node is enabled.
    555      */
    556     public boolean isEnabled() {
    557         return getBooleanProperty(PROPERTY_ENABLED);
    558     }
    559 
    560     /**
    561      * Sets whether this node is enabled.
    562      * <p>
    563      *   <strong>Note:</strong> Cannot be called from an
    564      *   {@link android.accessibilityservice.AccessibilityService}.
    565      *   This class is made immutable before being delivered to an AccessibilityService.
    566      * </p>
    567      *
    568      * @param enabled True if the node is enabled.
    569      *
    570      * @throws IllegalStateException If called from an AccessibilityService.
    571      */
    572     public void setEnabled(boolean enabled) {
    573         setBooleanProperty(PROPERTY_ENABLED, enabled);
    574     }
    575 
    576     /**
    577      * Gets whether this node is a password.
    578      *
    579      * @return True if the node is a password.
    580      */
    581     public boolean isPassword() {
    582         return getBooleanProperty(PROPERTY_PASSWORD);
    583     }
    584 
    585     /**
    586      * Sets whether this node is a password.
    587      * <p>
    588      *   <strong>Note:</strong> Cannot be called from an
    589      *   {@link android.accessibilityservice.AccessibilityService}.
    590      *   This class is made immutable before being delivered to an AccessibilityService.
    591      * </p>
    592      *
    593      * @param password True if the node is a password.
    594      *
    595      * @throws IllegalStateException If called from an AccessibilityService.
    596      */
    597     public void setPassword(boolean password) {
    598         setBooleanProperty(PROPERTY_PASSWORD, password);
    599     }
    600 
    601     /**
    602      * Gets if the node is scrollable.
    603      *
    604      * @return True if the node is scrollable, false otherwise.
    605      */
    606     public boolean isScrollable() {
    607         return getBooleanProperty(PROPERTY_SCROLLABLE);
    608     }
    609 
    610     /**
    611      * Sets if the node is scrollable.
    612      * <p>
    613      *   <strong>Note:</strong> Cannot be called from an
    614      *   {@link android.accessibilityservice.AccessibilityService}.
    615      *   This class is made immutable before being delivered to an AccessibilityService.
    616      * </p>
    617      *
    618      * @param scrollable True if the node is scrollable, false otherwise.
    619      *
    620      * @throws IllegalStateException If called from an AccessibilityService.
    621      */
    622     public void setScrollable(boolean scrollable) {
    623         enforceNotSealed();
    624         setBooleanProperty(PROPERTY_SCROLLABLE, scrollable);
    625     }
    626 
    627     /**
    628      * Gets the package this node comes from.
    629      *
    630      * @return The package name.
    631      */
    632     public CharSequence getPackageName() {
    633         return mPackageName;
    634     }
    635 
    636     /**
    637      * Sets the package this node comes from.
    638      * <p>
    639      *   <strong>Note:</strong> Cannot be called from an
    640      *   {@link android.accessibilityservice.AccessibilityService}.
    641      *   This class is made immutable before being delivered to an AccessibilityService.
    642      * </p>
    643      *
    644      * @param packageName The package name.
    645      *
    646      * @throws IllegalStateException If called from an AccessibilityService.
    647      */
    648     public void setPackageName(CharSequence packageName) {
    649         enforceNotSealed();
    650         mPackageName = packageName;
    651     }
    652 
    653     /**
    654      * Gets the class this node comes from.
    655      *
    656      * @return The class name.
    657      */
    658     public CharSequence getClassName() {
    659         return mClassName;
    660     }
    661 
    662     /**
    663      * Sets the class this node comes from.
    664      * <p>
    665      *   <strong>Note:</strong> Cannot be called from an
    666      *   {@link android.accessibilityservice.AccessibilityService}.
    667      *   This class is made immutable before being delivered to an AccessibilityService.
    668      * </p>
    669      *
    670      * @param className The class name.
    671      *
    672      * @throws IllegalStateException If called from an AccessibilityService.
    673      */
    674     public void setClassName(CharSequence className) {
    675         enforceNotSealed();
    676         mClassName = className;
    677     }
    678 
    679     /**
    680      * Gets the text of this node.
    681      *
    682      * @return The text.
    683      */
    684     public CharSequence getText() {
    685         return mText;
    686     }
    687 
    688     /**
    689      * Sets the text of this node.
    690      * <p>
    691      *   <strong>Note:</strong> Cannot be called from an
    692      *   {@link android.accessibilityservice.AccessibilityService}.
    693      *   This class is made immutable before being delivered to an AccessibilityService.
    694      * </p>
    695      *
    696      * @param text The text.
    697      *
    698      * @throws IllegalStateException If called from an AccessibilityService.
    699      */
    700     public void setText(CharSequence text) {
    701         enforceNotSealed();
    702         mText = text;
    703     }
    704 
    705     /**
    706      * Gets the content description of this node.
    707      *
    708      * @return The content description.
    709      */
    710     public CharSequence getContentDescription() {
    711         return mContentDescription;
    712     }
    713 
    714     /**
    715      * Sets the content description of this node.
    716      * <p>
    717      *   <strong>Note:</strong> Cannot be called from an
    718      *   {@link android.accessibilityservice.AccessibilityService}.
    719      *   This class is made immutable before being delivered to an AccessibilityService.
    720      * </p>
    721      *
    722      * @param contentDescription The content description.
    723      *
    724      * @throws IllegalStateException If called from an AccessibilityService.
    725      */
    726     public void setContentDescription(CharSequence contentDescription) {
    727         enforceNotSealed();
    728         mContentDescription = contentDescription;
    729     }
    730 
    731     /**
    732      * Gets the value of a boolean property.
    733      *
    734      * @param property The property.
    735      * @return The value.
    736      */
    737     private boolean getBooleanProperty(int property) {
    738         return (mBooleanProperties & property) != 0;
    739     }
    740 
    741     /**
    742      * Sets a boolean property.
    743      *
    744      * @param property The property.
    745      * @param value The value.
    746      *
    747      * @throws IllegalStateException If called from an AccessibilityService.
    748      */
    749     private void setBooleanProperty(int property, boolean value) {
    750         enforceNotSealed();
    751         if (value) {
    752             mBooleanProperties |= property;
    753         } else {
    754             mBooleanProperties &= ~property;
    755         }
    756     }
    757 
    758     /**
    759      * Sets the unique id of the IAccessibilityServiceConnection over which
    760      * this instance can send requests to the system.
    761      *
    762      * @param connectionId The connection id.
    763      *
    764      * @hide
    765      */
    766     public void setConnectionId(int connectionId) {
    767         enforceNotSealed();
    768         mConnectionId = connectionId;
    769     }
    770 
    771     /**
    772      * {@inheritDoc}
    773      */
    774     public int describeContents() {
    775         return 0;
    776     }
    777 
    778     /**
    779      * Sets if this instance is sealed.
    780      *
    781      * @param sealed Whether is sealed.
    782      *
    783      * @hide
    784      */
    785     public void setSealed(boolean sealed) {
    786         mSealed = sealed;
    787     }
    788 
    789     /**
    790      * Gets if this instance is sealed.
    791      *
    792      * @return Whether is sealed.
    793      *
    794      * @hide
    795      */
    796     public boolean isSealed() {
    797         return mSealed;
    798     }
    799 
    800     /**
    801      * Enforces that this instance is sealed.
    802      *
    803      * @throws IllegalStateException If this instance is not sealed.
    804      *
    805      * @hide
    806      */
    807     protected void enforceSealed() {
    808         if (!isSealed()) {
    809             throw new IllegalStateException("Cannot perform this "
    810                     + "action on a not sealed instance.");
    811         }
    812     }
    813 
    814     /**
    815      * Enforces that this instance is not sealed.
    816      *
    817      * @throws IllegalStateException If this instance is sealed.
    818      *
    819      * @hide
    820      */
    821     protected void enforceNotSealed() {
    822         if (isSealed()) {
    823             throw new IllegalStateException("Cannot perform this "
    824                     + "action on an sealed instance.");
    825         }
    826     }
    827 
    828     /**
    829      * Returns a cached instance if such is available otherwise a new one
    830      * and sets the source.
    831      *
    832      * @return An instance.
    833      *
    834      * @see #setSource(View)
    835      */
    836     public static AccessibilityNodeInfo obtain(View source) {
    837         AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain();
    838         info.setSource(source);
    839         return info;
    840     }
    841 
    842     /**
    843      * Returns a cached instance if such is available otherwise a new one.
    844      *
    845      * @return An instance.
    846      */
    847     public static AccessibilityNodeInfo obtain() {
    848         synchronized (sPoolLock) {
    849             if (sPool != null) {
    850                 AccessibilityNodeInfo info = sPool;
    851                 sPool = sPool.mNext;
    852                 sPoolSize--;
    853                 info.mNext = null;
    854                 info.mIsInPool = false;
    855                 return info;
    856             }
    857             return new AccessibilityNodeInfo();
    858         }
    859     }
    860 
    861     /**
    862      * Returns a cached instance if such is available or a new one is
    863      * create. The returned instance is initialized from the given
    864      * <code>info</code>.
    865      *
    866      * @param info The other info.
    867      * @return An instance.
    868      */
    869     public static AccessibilityNodeInfo obtain(AccessibilityNodeInfo info) {
    870         AccessibilityNodeInfo infoClone = AccessibilityNodeInfo.obtain();
    871         infoClone.init(info);
    872         return infoClone;
    873     }
    874 
    875     /**
    876      * Return an instance back to be reused.
    877      * <p>
    878      * <strong>Note:</strong> You must not touch the object after calling this function.
    879      *
    880      * @throws IllegalStateException If the info is already recycled.
    881      */
    882     public void recycle() {
    883         if (mIsInPool) {
    884             throw new IllegalStateException("Info already recycled!");
    885         }
    886         clear();
    887         synchronized (sPoolLock) {
    888             if (sPoolSize <= MAX_POOL_SIZE) {
    889                 mNext = sPool;
    890                 sPool = this;
    891                 mIsInPool = true;
    892                 sPoolSize++;
    893             }
    894         }
    895     }
    896 
    897     /**
    898      * {@inheritDoc}
    899      * <p>
    900      *   <strong>Note:</strong> After the instance is written to a parcel it
    901      *      is recycled. You must not touch the object after calling this function.
    902      * </p>
    903      */
    904     public void writeToParcel(Parcel parcel, int flags) {
    905         parcel.writeInt(isSealed() ? 1 : 0);
    906         parcel.writeInt(mAccessibilityViewId);
    907         parcel.writeInt(mAccessibilityWindowId);
    908         parcel.writeInt(mParentAccessibilityViewId);
    909         parcel.writeInt(mConnectionId);
    910 
    911         SparseIntArray childIds = mChildAccessibilityIds;
    912         final int childIdsSize = childIds.size();
    913         parcel.writeInt(childIdsSize);
    914         for (int i = 0; i < childIdsSize; i++) {
    915             parcel.writeInt(childIds.valueAt(i));
    916         }
    917 
    918         parcel.writeInt(mBoundsInParent.top);
    919         parcel.writeInt(mBoundsInParent.bottom);
    920         parcel.writeInt(mBoundsInParent.left);
    921         parcel.writeInt(mBoundsInParent.right);
    922 
    923         parcel.writeInt(mBoundsInScreen.top);
    924         parcel.writeInt(mBoundsInScreen.bottom);
    925         parcel.writeInt(mBoundsInScreen.left);
    926         parcel.writeInt(mBoundsInScreen.right);
    927 
    928         parcel.writeInt(mActions);
    929 
    930         parcel.writeInt(mBooleanProperties);
    931 
    932         TextUtils.writeToParcel(mPackageName, parcel, flags);
    933         TextUtils.writeToParcel(mClassName, parcel, flags);
    934         TextUtils.writeToParcel(mText, parcel, flags);
    935         TextUtils.writeToParcel(mContentDescription, parcel, flags);
    936 
    937         // Since instances of this class are fetched via synchronous i.e. blocking
    938         // calls in IPCs we always recycle as soon as the instance is marshaled.
    939         recycle();
    940     }
    941 
    942     /**
    943      * Initializes this instance from another one.
    944      *
    945      * @param other The other instance.
    946      */
    947     private void init(AccessibilityNodeInfo other) {
    948         mSealed = other.mSealed;
    949         mAccessibilityViewId = other.mAccessibilityViewId;
    950         mParentAccessibilityViewId = other.mParentAccessibilityViewId;
    951         mAccessibilityWindowId = other.mAccessibilityWindowId;
    952         mConnectionId = other.mConnectionId;
    953         mBoundsInParent.set(other.mBoundsInParent);
    954         mBoundsInScreen.set(other.mBoundsInScreen);
    955         mPackageName = other.mPackageName;
    956         mClassName = other.mClassName;
    957         mText = other.mText;
    958         mContentDescription = other.mContentDescription;
    959         mActions= other.mActions;
    960         mBooleanProperties = other.mBooleanProperties;
    961         mChildAccessibilityIds = other.mChildAccessibilityIds.clone();
    962     }
    963 
    964     /**
    965      * Creates a new instance from a {@link Parcel}.
    966      *
    967      * @param parcel A parcel containing the state of a {@link AccessibilityNodeInfo}.
    968      */
    969     private void initFromParcel(Parcel parcel) {
    970         mSealed = (parcel.readInt()  == 1);
    971         mAccessibilityViewId = parcel.readInt();
    972         mAccessibilityWindowId = parcel.readInt();
    973         mParentAccessibilityViewId = parcel.readInt();
    974         mConnectionId = parcel.readInt();
    975 
    976         SparseIntArray childIds = mChildAccessibilityIds;
    977         final int childrenSize = parcel.readInt();
    978         for (int i = 0; i < childrenSize; i++) {
    979             final int childId = parcel.readInt();
    980             childIds.put(i, childId);
    981         }
    982 
    983         mBoundsInParent.top = parcel.readInt();
    984         mBoundsInParent.bottom = parcel.readInt();
    985         mBoundsInParent.left = parcel.readInt();
    986         mBoundsInParent.right = parcel.readInt();
    987 
    988         mBoundsInScreen.top = parcel.readInt();
    989         mBoundsInScreen.bottom = parcel.readInt();
    990         mBoundsInScreen.left = parcel.readInt();
    991         mBoundsInScreen.right = parcel.readInt();
    992 
    993         mActions = parcel.readInt();
    994 
    995         mBooleanProperties = parcel.readInt();
    996 
    997         mPackageName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
    998         mClassName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
    999         mText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
   1000         mContentDescription = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
   1001     }
   1002 
   1003     /**
   1004      * Clears the state of this instance.
   1005      */
   1006     private void clear() {
   1007         mSealed = false;
   1008         mAccessibilityViewId = UNDEFINED;
   1009         mParentAccessibilityViewId = UNDEFINED;
   1010         mAccessibilityWindowId = UNDEFINED;
   1011         mConnectionId = UNDEFINED;
   1012         mChildAccessibilityIds.clear();
   1013         mBoundsInParent.set(0, 0, 0, 0);
   1014         mBoundsInScreen.set(0, 0, 0, 0);
   1015         mBooleanProperties = 0;
   1016         mPackageName = null;
   1017         mClassName = null;
   1018         mText = null;
   1019         mContentDescription = null;
   1020         mActions = 0;
   1021     }
   1022 
   1023     /**
   1024      * Gets the human readable action symbolic name.
   1025      *
   1026      * @param action The action.
   1027      * @return The symbolic name.
   1028      */
   1029     private static String getActionSymbolicName(int action) {
   1030         switch (action) {
   1031             case ACTION_FOCUS:
   1032                 return "ACTION_FOCUS";
   1033             case ACTION_CLEAR_FOCUS:
   1034                 return "ACTION_CLEAR_FOCUS";
   1035             case ACTION_SELECT:
   1036                 return "ACTION_SELECT";
   1037             case ACTION_CLEAR_SELECTION:
   1038                 return "ACTION_CLEAR_SELECTION";
   1039             default:
   1040                 throw new IllegalArgumentException("Unknown action: " + action);
   1041         }
   1042     }
   1043 
   1044     private boolean canPerformRequestOverConnection(int accessibilityViewId) {
   1045         return (mConnectionId != UNDEFINED && mAccessibilityWindowId != UNDEFINED
   1046                 && accessibilityViewId != UNDEFINED);
   1047     }
   1048 
   1049     @Override
   1050     public boolean equals(Object object) {
   1051         if (this == object) {
   1052             return true;
   1053         }
   1054         if (object == null) {
   1055             return false;
   1056         }
   1057         if (getClass() != object.getClass()) {
   1058             return false;
   1059         }
   1060         AccessibilityNodeInfo other = (AccessibilityNodeInfo) object;
   1061         if (mAccessibilityViewId != other.mAccessibilityViewId) {
   1062             return false;
   1063         }
   1064         if (mAccessibilityWindowId != other.mAccessibilityWindowId) {
   1065             return false;
   1066         }
   1067         return true;
   1068     }
   1069 
   1070     @Override
   1071     public int hashCode() {
   1072         final int prime = 31;
   1073         int result = 1;
   1074         result = prime * result + mAccessibilityViewId;
   1075         result = prime * result + mAccessibilityWindowId;
   1076         return result;
   1077     }
   1078 
   1079     @Override
   1080     public String toString() {
   1081         StringBuilder builder = new StringBuilder();
   1082         builder.append(super.toString());
   1083 
   1084         if (DEBUG) {
   1085             builder.append("; accessibilityId: " + mAccessibilityViewId);
   1086             builder.append("; parentAccessibilityId: " + mParentAccessibilityViewId);
   1087             SparseIntArray childIds = mChildAccessibilityIds;
   1088             builder.append("; childAccessibilityIds: [");
   1089             for (int i = 0, count = childIds.size(); i < count; i++) {
   1090                 builder.append(childIds.valueAt(i));
   1091                 if (i < count - 1) {
   1092                     builder.append(", ");
   1093                 }
   1094            }
   1095            builder.append("]");
   1096         }
   1097 
   1098         builder.append("; boundsInParent: " + mBoundsInParent);
   1099         builder.append("; boundsInScreen: " + mBoundsInScreen);
   1100 
   1101         builder.append("; packageName: ").append(mPackageName);
   1102         builder.append("; className: ").append(mClassName);
   1103         builder.append("; text: ").append(mText);
   1104         builder.append("; contentDescription: ").append(mContentDescription);
   1105 
   1106         builder.append("; checkable: ").append(isCheckable());
   1107         builder.append("; checked: ").append(isChecked());
   1108         builder.append("; focusable: ").append(isFocusable());
   1109         builder.append("; focused: ").append(isFocused());
   1110         builder.append("; selected: ").append(isSelected());
   1111         builder.append("; clickable: ").append(isClickable());
   1112         builder.append("; longClickable: ").append(isLongClickable());
   1113         builder.append("; enabled: ").append(isEnabled());
   1114         builder.append("; password: ").append(isPassword());
   1115         builder.append("; scrollable: " + isScrollable());
   1116 
   1117         builder.append("; [");
   1118 
   1119         for (int actionBits = mActions; actionBits != 0;) {
   1120             final int action = 1 << Integer.numberOfTrailingZeros(actionBits);
   1121             actionBits &= ~action;
   1122             builder.append(getActionSymbolicName(action));
   1123             if (actionBits != 0) {
   1124                 builder.append(", ");
   1125             }
   1126         }
   1127 
   1128         builder.append("]");
   1129 
   1130         return builder.toString();
   1131     }
   1132 
   1133     /**
   1134      * @see Parcelable.Creator
   1135      */
   1136     public static final Parcelable.Creator<AccessibilityNodeInfo> CREATOR =
   1137             new Parcelable.Creator<AccessibilityNodeInfo>() {
   1138         public AccessibilityNodeInfo createFromParcel(Parcel parcel) {
   1139             AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain();
   1140             info.initFromParcel(parcel);
   1141             return info;
   1142         }
   1143 
   1144         public AccessibilityNodeInfo[] newArray(int size) {
   1145             return new AccessibilityNodeInfo[size];
   1146         }
   1147     };
   1148 }
   1149