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
17package android.view.accessibility;
18
19import android.annotation.Nullable;
20import android.graphics.Rect;
21import android.os.Parcel;
22import android.os.Parcelable;
23import android.util.LongArray;
24import 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 */
33public 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