Home | History | Annotate | Download | only in accessibility
      1 /*
      2  * Copyright (C) 2014 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.util.LongArray;
     23 import android.util.Pools.SynchronizedPool;
     24 
     25 /**
     26  * This class represents a state snapshot of a window for accessibility
     27  * purposes. The screen content contains one or more windows where some
     28  * windows can be descendants of other windows, which is the windows are
     29  * hierarchically ordered. Note that there is no root window. Hence, the
     30  * screen content can be seen as a collection of window trees.
     31  */
     32 public final class AccessibilityWindowInfo implements Parcelable {
     33 
     34     private static final boolean DEBUG = false;
     35 
     36     /**
     37      * Window type: This is an application window. Such a window shows UI for
     38      * interacting with an application.
     39      */
     40     public static final int TYPE_APPLICATION = 1;
     41 
     42     /**
     43      * Window type: This is an input method window. Such a window shows UI for
     44      * inputting text such as keyboard, suggestions, etc.
     45      */
     46     public static final int TYPE_INPUT_METHOD = 2;
     47 
     48     /**
     49      * Window type: This is an system window. Such a window shows UI for
     50      * interacting with the system.
     51      */
     52     public static final int TYPE_SYSTEM = 3;
     53 
     54     /**
     55      * Window type: Windows that are overlaid <em>only</em> by an {@link
     56      * android.accessibilityservice.AccessibilityService} for interception of
     57      * user interactions without changing the windows an accessibility service
     58      * can introspect. In particular, an accessibility service can introspect
     59      * only windows that a sighted user can interact with which they can touch
     60      * these windows or can type into these windows. For example, if there
     61      * is a full screen accessibility overlay that is touchable, the windows
     62      * below it will be introspectable by an accessibility service regardless
     63      * they are covered by a touchable window.
     64      */
     65     public static final int TYPE_ACCESSIBILITY_OVERLAY = 4;
     66 
     67     private static final int UNDEFINED = -1;
     68 
     69     private static final int BOOLEAN_PROPERTY_ACTIVE = 1 << 0;
     70     private static final int BOOLEAN_PROPERTY_FOCUSED = 1 << 1;
     71     private static final int BOOLEAN_PROPERTY_ACCESSIBILITY_FOCUSED = 1 << 2;
     72 
     73     // Housekeeping.
     74     private static final int MAX_POOL_SIZE = 10;
     75     private static final SynchronizedPool<AccessibilityWindowInfo> sPool =
     76             new SynchronizedPool<AccessibilityWindowInfo>(MAX_POOL_SIZE);
     77 
     78     // Data.
     79     private int mType = UNDEFINED;
     80     private int mLayer = UNDEFINED;
     81     private int mBooleanProperties;
     82     private int mId = UNDEFINED;
     83     private int mParentId = UNDEFINED;
     84     private final Rect mBoundsInScreen = new Rect();
     85     private LongArray mChildIds;
     86 
     87     private int mConnectionId = UNDEFINED;
     88 
     89     private AccessibilityWindowInfo() {
     90         /* do nothing - hide constructor */
     91     }
     92 
     93     /**
     94      * Gets the type of the window.
     95      *
     96      * @return The type.
     97      *
     98      * @see #TYPE_APPLICATION
     99      * @see #TYPE_INPUT_METHOD
    100      * @see #TYPE_SYSTEM
    101      * @see #TYPE_ACCESSIBILITY_OVERLAY
    102      */
    103     public int getType() {
    104         return mType;
    105     }
    106 
    107     /**
    108      * Sets the type of the window.
    109      *
    110      * @param type The type
    111      *
    112      * @hide
    113      */
    114     public void setType(int type) {
    115         mType = type;
    116     }
    117 
    118     /**
    119      * Gets the layer which determines the Z-order of the window. Windows
    120      * with greater layer appear on top of windows with lesser layer.
    121      *
    122      * @return The window layer.
    123      */
    124     public int getLayer() {
    125         return mLayer;
    126     }
    127 
    128     /**
    129      * Sets the layer which determines the Z-order of the window. Windows
    130      * with greater layer appear on top of windows with lesser layer.
    131      *
    132      * @param layer The window layer.
    133      *
    134      * @hide
    135      */
    136     public void setLayer(int layer) {
    137         mLayer = layer;
    138     }
    139 
    140     /**
    141      * Gets the root node in the window's hierarchy.
    142      *
    143      * @return The root node.
    144      */
    145     public AccessibilityNodeInfo getRoot() {
    146         if (mConnectionId == UNDEFINED) {
    147             return null;
    148         }
    149         AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
    150         return client.findAccessibilityNodeInfoByAccessibilityId(mConnectionId,
    151                 mId, AccessibilityNodeInfo.ROOT_NODE_ID,
    152                 true, AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS);
    153     }
    154 
    155     /**
    156      * Gets the parent window if such.
    157      *
    158      * @return The parent window.
    159      */
    160     public AccessibilityWindowInfo getParent() {
    161         if (mConnectionId == UNDEFINED || mParentId == UNDEFINED) {
    162             return null;
    163         }
    164         AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
    165         return client.getWindow(mConnectionId, mParentId);
    166     }
    167 
    168     /**
    169      * Sets the parent window id.
    170      *
    171      * @param parentId The parent id.
    172      *
    173      * @hide
    174      */
    175     public void setParentId(int parentId) {
    176         mParentId = parentId;
    177     }
    178 
    179     /**
    180      * Gets the unique window id.
    181      *
    182      * @return windowId The window id.
    183      */
    184     public int getId() {
    185         return mId;
    186     }
    187 
    188     /**
    189      * Sets the unique window id.
    190      *
    191      * @param id The window id.
    192      *
    193      * @hide
    194      */
    195     public void setId(int id) {
    196         mId = id;
    197     }
    198 
    199     /**
    200      * Sets the unique id of the IAccessibilityServiceConnection over which
    201      * this instance can send requests to the system.
    202      *
    203      * @param connectionId The connection id.
    204      *
    205      * @hide
    206      */
    207     public void setConnectionId(int connectionId) {
    208         mConnectionId = connectionId;
    209     }
    210 
    211     /**
    212      * Gets the bounds of this window in the screen.
    213      *
    214      * @param outBounds The out window bounds.
    215      */
    216     public void getBoundsInScreen(Rect outBounds) {
    217         outBounds.set(mBoundsInScreen);
    218     }
    219 
    220     /**
    221      * Sets the bounds of this window in the screen.
    222      *
    223      * @param bounds The out window bounds.
    224      *
    225      * @hide
    226      */
    227     public void setBoundsInScreen(Rect bounds) {
    228         mBoundsInScreen.set(bounds);
    229     }
    230 
    231     /**
    232      * Gets if this window is active. An active window is the one
    233      * the user is currently touching or the window has input focus
    234      * and the user is not touching any window.
    235      *
    236      * @return Whether this is the active window.
    237      */
    238     public boolean isActive() {
    239         return getBooleanProperty(BOOLEAN_PROPERTY_ACTIVE);
    240     }
    241 
    242     /**
    243      * Sets if this window is active, which is this is the window
    244      * the user is currently touching or the window has input focus
    245      * and the user is not touching any window.
    246      *
    247      * @param active Whether this is the active window.
    248      *
    249      * @hide
    250      */
    251     public void setActive(boolean active) {
    252         setBooleanProperty(BOOLEAN_PROPERTY_ACTIVE, active);
    253     }
    254 
    255     /**
    256      * Gets if this window has input focus.
    257      *
    258      * @return Whether has input focus.
    259      */
    260     public boolean isFocused() {
    261         return getBooleanProperty(BOOLEAN_PROPERTY_FOCUSED);
    262     }
    263 
    264     /**
    265      * Sets if this window has input focus.
    266      *
    267      * @param focused Whether has input focus.
    268      *
    269      * @hide
    270      */
    271     public void setFocused(boolean focused) {
    272         setBooleanProperty(BOOLEAN_PROPERTY_FOCUSED, focused);
    273     }
    274 
    275     /**
    276      * Gets if this window has accessibility focus.
    277      *
    278      * @return Whether has accessibility focus.
    279      */
    280     public boolean isAccessibilityFocused() {
    281         return getBooleanProperty(BOOLEAN_PROPERTY_ACCESSIBILITY_FOCUSED);
    282     }
    283 
    284     /**
    285      * Sets if this window has accessibility focus.
    286      *
    287      * @param focused Whether has accessibility focus.
    288      *
    289      * @hide
    290      */
    291     public void setAccessibilityFocused(boolean focused) {
    292         setBooleanProperty(BOOLEAN_PROPERTY_ACCESSIBILITY_FOCUSED, focused);
    293     }
    294 
    295     /**
    296      * Gets the number of child windows.
    297      *
    298      * @return The child count.
    299      */
    300     public int getChildCount() {
    301         return (mChildIds != null) ? mChildIds.size() : 0;
    302     }
    303 
    304     /**
    305      * Gets the child window at a given index.
    306      *
    307      * @param index The index.
    308      * @return The child.
    309      */
    310     public AccessibilityWindowInfo getChild(int index) {
    311         if (mChildIds == null) {
    312             throw new IndexOutOfBoundsException();
    313         }
    314         if (mConnectionId == UNDEFINED) {
    315             return null;
    316         }
    317         final int childId = (int) mChildIds.get(index);
    318         AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
    319         return client.getWindow(mConnectionId, childId);
    320     }
    321 
    322     /**
    323      * Adds a child window.
    324      *
    325      * @param childId The child window id.
    326      *
    327      * @hide
    328      */
    329     public void addChild(int childId) {
    330         if (mChildIds == null) {
    331             mChildIds = new LongArray();
    332         }
    333         mChildIds.add(childId);
    334     }
    335 
    336     /**
    337      * Returns a cached instance if such is available or a new one is
    338      * created.
    339      *
    340      * @return An instance.
    341      */
    342     public static AccessibilityWindowInfo obtain() {
    343         AccessibilityWindowInfo info = sPool.acquire();
    344         if (info == null) {
    345             info = new AccessibilityWindowInfo();
    346         }
    347         return info;
    348     }
    349 
    350     /**
    351      * Returns a cached instance if such is available or a new one is
    352      * created. The returned instance is initialized from the given
    353      * <code>info</code>.
    354      *
    355      * @param info The other info.
    356      * @return An instance.
    357      */
    358     public static AccessibilityWindowInfo obtain(AccessibilityWindowInfo info) {
    359         AccessibilityWindowInfo infoClone = obtain();
    360 
    361         infoClone.mType = info.mType;
    362         infoClone.mLayer = info.mLayer;
    363         infoClone.mBooleanProperties = info.mBooleanProperties;
    364         infoClone.mId = info.mId;
    365         infoClone.mParentId = info.mParentId;
    366         infoClone.mBoundsInScreen.set(info.mBoundsInScreen);
    367 
    368         if (info.mChildIds != null && info.mChildIds.size() > 0) {
    369             if (infoClone.mChildIds == null) {
    370                 infoClone.mChildIds = info.mChildIds.clone();
    371             } else {
    372                 infoClone.mChildIds.addAll(info.mChildIds);
    373             }
    374         }
    375 
    376         infoClone.mConnectionId = info.mConnectionId;
    377 
    378         return infoClone;
    379     }
    380 
    381     /**
    382      * Return an instance back to be reused.
    383      * <p>
    384      * <strong>Note:</strong> You must not touch the object after calling this function.
    385      * </p>
    386      *
    387      * @throws IllegalStateException If the info is already recycled.
    388      */
    389     public void recycle() {
    390         clear();
    391         sPool.release(this);
    392     }
    393 
    394     @Override
    395     public int describeContents() {
    396         return 0;
    397     }
    398 
    399     @Override
    400     public void writeToParcel(Parcel parcel, int flags) {
    401         parcel.writeInt(mType);
    402         parcel.writeInt(mLayer);
    403         parcel.writeInt(mBooleanProperties);
    404         parcel.writeInt(mId);
    405         parcel.writeInt(mParentId);
    406         mBoundsInScreen.writeToParcel(parcel, flags);
    407 
    408         final LongArray childIds = mChildIds;
    409         if (childIds == null) {
    410             parcel.writeInt(0);
    411         } else {
    412             final int childCount = childIds.size();
    413             parcel.writeInt(childCount);
    414             for (int i = 0; i < childCount; i++) {
    415                 parcel.writeInt((int) childIds.get(i));
    416             }
    417         }
    418 
    419         parcel.writeInt(mConnectionId);
    420     }
    421 
    422     private void initFromParcel(Parcel parcel) {
    423         mType = parcel.readInt();
    424         mLayer = parcel.readInt();
    425         mBooleanProperties = parcel.readInt();
    426         mId = parcel.readInt();
    427         mParentId = parcel.readInt();
    428         mBoundsInScreen.readFromParcel(parcel);
    429 
    430         final int childCount = parcel.readInt();
    431         if (childCount > 0) {
    432             if (mChildIds == null) {
    433                 mChildIds = new LongArray(childCount);
    434             }
    435             for (int i = 0; i < childCount; i++) {
    436                 final int childId = parcel.readInt();
    437                 mChildIds.add(childId);
    438             }
    439         }
    440 
    441         mConnectionId = parcel.readInt();
    442     }
    443 
    444     @Override
    445     public int hashCode() {
    446         return mId;
    447     }
    448 
    449     @Override
    450     public boolean equals(Object obj) {
    451         if (this == obj) {
    452             return true;
    453         }
    454         if (obj == null) {
    455             return false;
    456         }
    457         if (getClass() != obj.getClass()) {
    458             return false;
    459         }
    460         AccessibilityWindowInfo other = (AccessibilityWindowInfo) obj;
    461         return (mId == other.mId);
    462     }
    463 
    464     @Override
    465     public String toString() {
    466         StringBuilder builder = new StringBuilder();
    467         builder.append("AccessibilityWindowInfo[");
    468         builder.append("id=").append(mId);
    469         builder.append(", type=").append(typeToString(mType));
    470         builder.append(", layer=").append(mLayer);
    471         builder.append(", bounds=").append(mBoundsInScreen);
    472         builder.append(", focused=").append(isFocused());
    473         builder.append(", active=").append(isActive());
    474         if (DEBUG) {
    475             builder.append(", parent=").append(mParentId);
    476             builder.append(", children=[");
    477             if (mChildIds != null) {
    478                 final int childCount = mChildIds.size();
    479                 for (int i = 0; i < childCount; i++) {
    480                     builder.append(mChildIds.get(i));
    481                     if (i < childCount - 1) {
    482                         builder.append(',');
    483                     }
    484                 }
    485             } else {
    486                 builder.append("null");
    487             }
    488             builder.append(']');
    489         } else {
    490             builder.append(", hasParent=").append(mParentId != UNDEFINED);
    491             builder.append(", hasChildren=").append(mChildIds != null
    492                     && mChildIds.size() > 0);
    493         }
    494         builder.append(']');
    495         return builder.toString();
    496     }
    497 
    498     /**
    499      * Clears the internal state.
    500      */
    501     private void clear() {
    502         mType = UNDEFINED;
    503         mLayer = UNDEFINED;
    504         mBooleanProperties = 0;
    505         mId = UNDEFINED;
    506         mParentId = UNDEFINED;
    507         mBoundsInScreen.setEmpty();
    508         if (mChildIds != null) {
    509             mChildIds.clear();
    510         }
    511         mConnectionId = UNDEFINED;
    512     }
    513 
    514     /**
    515      * Gets the value of a boolean property.
    516      *
    517      * @param property The property.
    518      * @return The value.
    519      */
    520     private boolean getBooleanProperty(int property) {
    521         return (mBooleanProperties & property) != 0;
    522     }
    523 
    524     /**
    525      * Sets a boolean property.
    526      *
    527      * @param property The property.
    528      * @param value The value.
    529      *
    530      * @throws IllegalStateException If called from an AccessibilityService.
    531      */
    532     private void setBooleanProperty(int property, boolean value) {
    533         if (value) {
    534             mBooleanProperties |= property;
    535         } else {
    536             mBooleanProperties &= ~property;
    537         }
    538     }
    539 
    540     private static String typeToString(int type) {
    541         switch (type) {
    542             case TYPE_APPLICATION: {
    543                 return "TYPE_APPLICATION";
    544             }
    545             case TYPE_INPUT_METHOD: {
    546                 return "TYPE_INPUT_METHOD";
    547             }
    548             case TYPE_SYSTEM: {
    549                 return "TYPE_SYSTEM";
    550             }
    551             case TYPE_ACCESSIBILITY_OVERLAY: {
    552                 return "TYPE_ACCESSIBILITY_OVERLAY";
    553             }
    554             default:
    555                 return "<UNKNOWN>";
    556         }
    557     }
    558 
    559     /**
    560      * Checks whether this window changed. The argument should be
    561      * another state of the same window, which is have the same id
    562      * and type as they never change.
    563      *
    564      * @param other The new state.
    565      * @return Whether something changed.
    566      *
    567      * @hide
    568      */
    569     public boolean changed(AccessibilityWindowInfo other) {
    570         if (other.mId != mId) {
    571             throw new IllegalArgumentException("Not same window.");
    572         }
    573         if (other.mType != mType) {
    574             throw new IllegalArgumentException("Not same type.");
    575         }
    576         if (!mBoundsInScreen.equals(mBoundsInScreen)) {
    577             return true;
    578         }
    579         if (mLayer != other.mLayer) {
    580             return true;
    581         }
    582         if (mBooleanProperties != other.mBooleanProperties) {
    583             return true;
    584         }
    585         if (mParentId != other.mParentId) {
    586             return true;
    587         }
    588         if (mChildIds == null) {
    589             if (other.mChildIds != null) {
    590                 return true;
    591             }
    592         } else if (!mChildIds.equals(other.mChildIds)) {
    593             return true;
    594         }
    595         return false;
    596     }
    597 
    598     public static final Parcelable.Creator<AccessibilityWindowInfo> CREATOR =
    599             new Creator<AccessibilityWindowInfo>() {
    600         @Override
    601         public AccessibilityWindowInfo createFromParcel(Parcel parcel) {
    602             AccessibilityWindowInfo info = obtain();
    603             info.initFromParcel(parcel);
    604             return info;
    605         }
    606 
    607         @Override
    608         public AccessibilityWindowInfo[] newArray(int size) {
    609             return new AccessibilityWindowInfo[size];
    610         }
    611     };
    612 }
    613