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