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