AccessibilityWindowInfoCompat.java revision b31c3281d870e9abb673db239234d580dcc4feff
1/*
2 * Copyright (C) 2015 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 androidx.core.view.accessibility;
18
19import static android.os.Build.VERSION.SDK_INT;
20
21import android.graphics.Rect;
22import android.view.accessibility.AccessibilityWindowInfo;
23
24/**
25 * Helper for accessing {@link android.view.accessibility.AccessibilityWindowInfo}.
26 */
27public class AccessibilityWindowInfoCompat {
28    private Object mInfo;
29
30    private static final int UNDEFINED = -1;
31
32    /**
33     * Window type: This is an application window. Such a window shows UI for
34     * interacting with an application.
35     */
36    public static final int TYPE_APPLICATION = 1;
37
38    /**
39     * Window type: This is an input method window. Such a window shows UI for
40     * inputting text such as keyboard, suggestions, etc.
41     */
42    public static final int TYPE_INPUT_METHOD = 2;
43
44    /**
45     * Window type: This is an system window. Such a window shows UI for
46     * interacting with the system.
47     */
48    public static final int TYPE_SYSTEM = 3;
49
50    /**
51     * Window type: Windows that are overlaid <em>only</em> by an {@link
52     * android.accessibilityservice.AccessibilityService} for interception of
53     * user interactions without changing the windows an accessibility service
54     * can introspect. In particular, an accessibility service can introspect
55     * only windows that a sighted user can interact with which they can touch
56     * these windows or can type into these windows. For example, if there
57     * is a full screen accessibility overlay that is touchable, the windows
58     * below it will be introspectable by an accessibility service regardless
59     * they are covered by a touchable window.
60     */
61    public static final int TYPE_ACCESSIBILITY_OVERLAY = 4;
62
63    /**
64     * Window type: A system window used to divide the screen in split-screen mode.
65     * This type of window is present only in split-screen mode.
66     */
67    public static final int TYPE_SPLIT_SCREEN_DIVIDER = 5;
68
69    /**
70     * Creates a wrapper for info implementation.
71     *
72     * @param object The info to wrap.
73     * @return A wrapper for if the object is not null, null otherwise.
74     */
75    static AccessibilityWindowInfoCompat wrapNonNullInstance(Object object) {
76        if (object != null) {
77            return new AccessibilityWindowInfoCompat(object);
78        }
79        return null;
80    }
81
82    private AccessibilityWindowInfoCompat(Object info) {
83        mInfo = info;
84    }
85
86    /**
87     * Gets the type of the window.
88     *
89     * @return The type.
90     *
91     * @see #TYPE_APPLICATION
92     * @see #TYPE_INPUT_METHOD
93     * @see #TYPE_SYSTEM
94     * @see #TYPE_ACCESSIBILITY_OVERLAY
95     */
96    public int getType() {
97        if (SDK_INT >= 21) {
98            return ((AccessibilityWindowInfo) mInfo).getType();
99        } else {
100            return UNDEFINED;
101        }
102    }
103
104    /**
105     * Gets the layer which determines the Z-order of the window. Windows
106     * with greater layer appear on top of windows with lesser layer.
107     *
108     * @return The window layer.
109     */
110    public int getLayer() {
111        if (SDK_INT >= 21) {
112            return ((AccessibilityWindowInfo) mInfo).getLayer();
113        } else {
114            return UNDEFINED;
115        }
116    }
117
118    /**
119     * Gets the root node in the window's hierarchy.
120     *
121     * @return The root node.
122     */
123    public AccessibilityNodeInfoCompat getRoot() {
124        if (SDK_INT >= 21) {
125            return AccessibilityNodeInfoCompat.wrapNonNullInstance(
126                    ((AccessibilityWindowInfo) mInfo).getRoot());
127        } else {
128            return null;
129        }
130    }
131
132    /**
133     * Gets the parent window if such.
134     *
135     * @return The parent window.
136     */
137    public AccessibilityWindowInfoCompat getParent() {
138        if (SDK_INT >= 21) {
139            return wrapNonNullInstance(((AccessibilityWindowInfo) mInfo).getParent());
140        } else {
141            return null;
142        }
143    }
144
145    /**
146     * Gets the unique window id.
147     *
148     * @return windowId The window id.
149     */
150    public int getId() {
151        if (SDK_INT >= 21) {
152            return ((AccessibilityWindowInfo) mInfo).getId();
153        } else {
154            return UNDEFINED;
155        }
156    }
157
158    /**
159     * Gets the bounds of this window in the screen.
160     *
161     * @param outBounds The out window bounds.
162     */
163    public void getBoundsInScreen(Rect outBounds) {
164        if (SDK_INT >= 21) {
165            ((AccessibilityWindowInfo) mInfo).getBoundsInScreen(outBounds);
166        }
167    }
168
169    /**
170     * Gets if this window is active. An active window is the one
171     * the user is currently touching or the window has input focus
172     * and the user is not touching any window.
173     *
174     * @return Whether this is the active window.
175     */
176    public boolean isActive() {
177        if (SDK_INT >= 21) {
178            return ((AccessibilityWindowInfo) mInfo).isActive();
179        } else {
180            return true;
181        }
182    }
183
184    /**
185     * Gets if this window has input focus.
186     *
187     * @return Whether has input focus.
188     */
189    public boolean isFocused() {
190        if (SDK_INT >= 21) {
191            return ((AccessibilityWindowInfo) mInfo).isFocused();
192        } else {
193            return true;
194        }
195    }
196
197    /**
198     * Gets if this window has accessibility focus.
199     *
200     * @return Whether has accessibility focus.
201     */
202    public boolean isAccessibilityFocused() {
203        if (SDK_INT >= 21) {
204            return ((AccessibilityWindowInfo) mInfo).isAccessibilityFocused();
205        } else {
206            return true;
207        }
208    }
209
210    /**
211     * Gets the number of child windows.
212     *
213     * @return The child count.
214     */
215    public int getChildCount() {
216        if (SDK_INT >= 21) {
217            return ((AccessibilityWindowInfo) mInfo).getChildCount();
218        } else {
219            return 0;
220        }
221    }
222
223    /**
224     * Gets the child window at a given index.
225     *
226     * @param index The index.
227     * @return The child.
228     */
229    public AccessibilityWindowInfoCompat getChild(int index) {
230        if (SDK_INT >= 21) {
231            return wrapNonNullInstance(((AccessibilityWindowInfo) mInfo).getChild(index));
232        } else {
233            return null;
234        }
235    }
236
237    /**
238     * Gets the title of the window.
239     *
240     * @return The title of the window, or the application label for the window if no title was
241     * explicitly set, or {@code null} if neither is available.
242     */
243    public CharSequence getTitle() {
244        if (SDK_INT >= 24) {
245            return ((AccessibilityWindowInfo) mInfo).getTitle();
246        } else {
247            return null;
248        }
249    }
250
251    /**
252     * Gets the node that anchors this window to another.
253     *
254     * @return The anchor node, or {@code null} if none exists.
255     */
256    public AccessibilityNodeInfoCompat getAnchor() {
257        if (SDK_INT >= 24) {
258            return AccessibilityNodeInfoCompat.wrapNonNullInstance(
259                    ((AccessibilityWindowInfo) mInfo).getAnchor());
260        } else {
261            return null;
262        }
263    }
264
265    /**
266     * Returns a cached instance if such is available or a new one is
267     * created.
268     *
269     * @return An instance.
270     */
271    public static AccessibilityWindowInfoCompat obtain() {
272        if (SDK_INT >= 21) {
273            return wrapNonNullInstance(AccessibilityWindowInfo.obtain());
274        } else {
275            return null;
276        }
277    }
278
279    /**
280     * Returns a cached instance if such is available or a new one is
281     * created. The returned instance is initialized from the given
282     * <code>info</code>.
283     *
284     * @param info The other info.
285     * @return An instance.
286     */
287    public static AccessibilityWindowInfoCompat obtain(AccessibilityWindowInfoCompat info) {
288        if (SDK_INT >= 21) {
289            return info == null
290                    ? null
291                    : wrapNonNullInstance(
292                            AccessibilityWindowInfo.obtain((AccessibilityWindowInfo) info.mInfo));
293        } else {
294            return null;
295        }
296    }
297
298    /**
299     * Return an instance back to be reused.
300     * <p>
301     * <strong>Note:</strong> You must not touch the object after calling this function.
302     * </p>
303     *
304     * @throws IllegalStateException If the info is already recycled.
305     */
306    public void recycle() {
307        if (SDK_INT >= 21) {
308            ((AccessibilityWindowInfo) mInfo).recycle();
309        }
310    }
311
312    @Override
313    public int hashCode() {
314        return (mInfo == null) ? 0 : mInfo.hashCode();
315    }
316
317    @Override
318    public boolean equals(Object obj) {
319        if (this == obj) {
320            return true;
321        }
322        if (obj == null) {
323            return false;
324        }
325        if (getClass() != obj.getClass()) {
326            return false;
327        }
328        AccessibilityWindowInfoCompat other = (AccessibilityWindowInfoCompat) obj;
329        if (mInfo == null) {
330            if (other.mInfo != null) {
331                return false;
332            }
333        } else if (!mInfo.equals(other.mInfo)) {
334            return false;
335        }
336        return true;
337    }
338
339    @Override
340    public String toString() {
341        StringBuilder builder = new StringBuilder();
342        Rect bounds = new Rect();
343        getBoundsInScreen(bounds);
344        builder.append("AccessibilityWindowInfo[");
345        builder.append("id=").append(getId());
346        builder.append(", type=").append(typeToString(getType()));
347        builder.append(", layer=").append(getLayer());
348        builder.append(", bounds=").append(bounds);
349        builder.append(", focused=").append(isFocused());
350        builder.append(", active=").append(isActive());
351        builder.append(", hasParent=").append(getParent() != null);
352        builder.append(", hasChildren=").append(getChildCount() > 0);
353        builder.append(']');
354        return builder.toString();
355    }
356
357    private static String typeToString(int type) {
358        switch (type) {
359            case TYPE_APPLICATION: {
360                return "TYPE_APPLICATION";
361            }
362            case TYPE_INPUT_METHOD: {
363                return "TYPE_INPUT_METHOD";
364            }
365            case TYPE_SYSTEM: {
366                return "TYPE_SYSTEM";
367            }
368            case TYPE_ACCESSIBILITY_OVERLAY: {
369                return "TYPE_ACCESSIBILITY_OVERLAY";
370            }
371            default:
372                return "<UNKNOWN>";
373        }
374    }
375}
376