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 android.support.v4.view.accessibility;
18
19import android.graphics.Rect;
20import android.os.Build;
21
22/**
23 * Helper for accessing {@link android.view.accessibility.AccessibilityWindowInfo}
24 * introduced after API level 4 in a backwards compatible fashion.
25 */
26public class AccessibilityWindowInfoCompat {
27
28    private static interface AccessibilityWindowInfoImpl {
29        public Object obtain();
30        public Object obtain(Object info);
31        public int getType(Object info);
32        public int getLayer(Object info);
33        public Object getRoot(Object info);
34        public Object getParent(Object info);
35        public int getId(Object info);
36        public void getBoundsInScreen(Object info, Rect outBounds);
37        public boolean isActive(Object info);
38        public boolean isFocused(Object info);
39        public boolean isAccessibilityFocused(Object info);
40        public int getChildCount(Object info);
41        public Object getChild(Object info, int index);
42        public CharSequence getTitle(Object info);
43        public Object getAnchor(Object info);
44        public void recycle(Object info);
45    }
46
47    private static class AccessibilityWindowInfoStubImpl implements AccessibilityWindowInfoImpl {
48
49        @Override
50        public Object obtain() {
51            return null;
52        }
53
54        @Override
55        public Object obtain(Object info) {
56            return null;
57        }
58
59        @Override
60        public int getType(Object info) {
61            return UNDEFINED;
62        }
63
64        @Override
65        public int getLayer(Object info) {
66            return UNDEFINED;
67        }
68
69        @Override
70        public Object getRoot(Object info) {
71            return null;
72        }
73
74        @Override
75        public Object getParent(Object info) {
76            return null;
77        }
78
79        @Override
80        public int getId(Object info) {
81            return UNDEFINED;
82        }
83
84        @Override
85        public void getBoundsInScreen(Object info, Rect outBounds) {
86        }
87
88        @Override
89        public boolean isActive(Object info) {
90            return true;
91        }
92
93        @Override
94        public boolean isFocused(Object info) {
95            return true;
96        }
97
98        @Override
99        public boolean isAccessibilityFocused(Object info) {
100            return true;
101        }
102
103        @Override
104        public int getChildCount(Object info) {
105            return 0;
106        }
107
108        @Override
109        public Object getChild(Object info, int index) {
110            return null;
111        }
112
113        @Override
114        public void recycle(Object info) {
115        }
116
117        @Override
118        public CharSequence getTitle(Object info) {
119            return null;
120        }
121
122        @Override
123        public Object getAnchor(Object info) {
124            return null;
125        }
126    }
127
128    private static class AccessibilityWindowInfoApi21Impl extends AccessibilityWindowInfoStubImpl {
129        @Override
130        public Object obtain() {
131            return AccessibilityWindowInfoCompatApi21.obtain();
132        }
133
134        @Override
135        public Object obtain(Object info) {
136            return AccessibilityWindowInfoCompatApi21.obtain(info);
137        }
138
139        @Override
140        public int getType(Object info) {
141            return AccessibilityWindowInfoCompatApi21.getType(info);
142        }
143
144        @Override
145        public int getLayer(Object info) {
146            return AccessibilityWindowInfoCompatApi21.getLayer(info);
147        }
148
149        @Override
150        public Object getRoot(Object info) {
151            return AccessibilityWindowInfoCompatApi21.getRoot(info);
152        }
153
154        @Override
155        public Object getParent(Object info) {
156            return AccessibilityWindowInfoCompatApi21.getParent(info);
157        }
158
159        @Override
160        public int getId(Object info) {
161            return AccessibilityWindowInfoCompatApi21.getId(info);
162        }
163
164        @Override
165        public void getBoundsInScreen(Object info, Rect outBounds) {
166            AccessibilityWindowInfoCompatApi21.getBoundsInScreen(info, outBounds);
167        }
168
169        @Override
170        public boolean isActive(Object info) {
171            return AccessibilityWindowInfoCompatApi21.isActive(info);
172        }
173
174        @Override
175        public boolean isFocused(Object info) {
176            return AccessibilityWindowInfoCompatApi21.isFocused(info);
177        }
178
179        @Override
180        public boolean isAccessibilityFocused(Object info) {
181            return AccessibilityWindowInfoCompatApi21.isAccessibilityFocused(info);
182        }
183
184        @Override
185        public int getChildCount(Object info) {
186            return AccessibilityWindowInfoCompatApi21.getChildCount(info);
187        }
188
189        @Override
190        public Object getChild(Object info, int index) {
191            return AccessibilityWindowInfoCompatApi21.getChild(info, index);
192        }
193
194        @Override
195        public void recycle(Object info) {
196            AccessibilityWindowInfoCompatApi21.recycle(info);
197        }
198    }
199
200    private static class AccessibilityWindowInfoApi24Impl extends AccessibilityWindowInfoApi21Impl {
201        @Override
202        public CharSequence getTitle(Object info) {
203            return AccessibilityWindowInfoCompatApi24.getTitle(info);
204        }
205
206        @Override
207        public Object getAnchor(Object info) {
208            return AccessibilityWindowInfoCompatApi24.getAnchor(info);
209        }
210    }
211
212    static {
213        if (Build.VERSION.SDK_INT >= 24) {
214            IMPL = new AccessibilityWindowInfoApi24Impl();
215        } else  if (Build.VERSION.SDK_INT >= 21) {
216            IMPL = new AccessibilityWindowInfoApi21Impl();
217        } else {
218            IMPL = new AccessibilityWindowInfoStubImpl();
219        }
220    }
221
222    private static final AccessibilityWindowInfoImpl IMPL;
223    private Object mInfo;
224
225    private static final int UNDEFINED = -1;
226
227    /**
228     * Window type: This is an application window. Such a window shows UI for
229     * interacting with an application.
230     */
231    public static final int TYPE_APPLICATION = 1;
232
233    /**
234     * Window type: This is an input method window. Such a window shows UI for
235     * inputting text such as keyboard, suggestions, etc.
236     */
237    public static final int TYPE_INPUT_METHOD = 2;
238
239    /**
240     * Window type: This is an system window. Such a window shows UI for
241     * interacting with the system.
242     */
243    public static final int TYPE_SYSTEM = 3;
244
245    /**
246     * Window type: Windows that are overlaid <em>only</em> by an {@link
247     * android.accessibilityservice.AccessibilityService} for interception of
248     * user interactions without changing the windows an accessibility service
249     * can introspect. In particular, an accessibility service can introspect
250     * only windows that a sighted user can interact with which they can touch
251     * these windows or can type into these windows. For example, if there
252     * is a full screen accessibility overlay that is touchable, the windows
253     * below it will be introspectable by an accessibility service regardless
254     * they are covered by a touchable window.
255     */
256    public static final int TYPE_ACCESSIBILITY_OVERLAY = 4;
257
258    /**
259     * Creates a wrapper for info implementation.
260     *
261     * @param object The info to wrap.
262     * @return A wrapper for if the object is not null, null otherwise.
263     */
264    static AccessibilityWindowInfoCompat wrapNonNullInstance(Object object) {
265        if (object != null) {
266            return new AccessibilityWindowInfoCompat(object);
267        }
268        return null;
269    }
270
271    private AccessibilityWindowInfoCompat(Object info) {
272        mInfo = info;
273    }
274
275    /**
276     * Gets the type of the window.
277     *
278     * @return The type.
279     *
280     * @see #TYPE_APPLICATION
281     * @see #TYPE_INPUT_METHOD
282     * @see #TYPE_SYSTEM
283     * @see #TYPE_ACCESSIBILITY_OVERLAY
284     */
285    public int getType() {
286        return IMPL.getType(mInfo);
287    }
288
289    /**
290     * Gets the layer which determines the Z-order of the window. Windows
291     * with greater layer appear on top of windows with lesser layer.
292     *
293     * @return The window layer.
294     */
295    public int getLayer() {
296        return IMPL.getLayer(mInfo);
297    }
298
299    /**
300     * Gets the root node in the window's hierarchy.
301     *
302     * @return The root node.
303     */
304    public AccessibilityNodeInfoCompat getRoot() {
305        return AccessibilityNodeInfoCompat.wrapNonNullInstance(IMPL.getRoot(mInfo));
306    }
307
308    /**
309     * Gets the parent window if such.
310     *
311     * @return The parent window.
312     */
313    public AccessibilityWindowInfoCompat getParent() {
314        return wrapNonNullInstance(IMPL.getParent(mInfo));
315    }
316
317    /**
318     * Gets the unique window id.
319     *
320     * @return windowId The window id.
321     */
322    public int getId() {
323        return IMPL.getId(mInfo);
324    }
325
326    /**
327     * Gets the bounds of this window in the screen.
328     *
329     * @param outBounds The out window bounds.
330     */
331    public void getBoundsInScreen(Rect outBounds) {
332        IMPL.getBoundsInScreen(mInfo, outBounds);
333    }
334
335    /**
336     * Gets if this window is active. An active window is the one
337     * the user is currently touching or the window has input focus
338     * and the user is not touching any window.
339     *
340     * @return Whether this is the active window.
341     */
342    public boolean isActive() {
343        return IMPL.isActive(mInfo);
344    }
345
346    /**
347     * Gets if this window has input focus.
348     *
349     * @return Whether has input focus.
350     */
351    public boolean isFocused() {
352        return IMPL.isFocused(mInfo);
353    }
354
355    /**
356     * Gets if this window has accessibility focus.
357     *
358     * @return Whether has accessibility focus.
359     */
360    public boolean isAccessibilityFocused() {
361        return IMPL.isAccessibilityFocused(mInfo);
362    }
363
364    /**
365     * Gets the number of child windows.
366     *
367     * @return The child count.
368     */
369    public int getChildCount() {
370        return IMPL.getChildCount(mInfo);
371    }
372
373    /**
374     * Gets the child window at a given index.
375     *
376     * @param index The index.
377     * @return The child.
378     */
379    public AccessibilityWindowInfoCompat getChild(int index) {
380        return wrapNonNullInstance(IMPL.getChild(mInfo, index));
381    }
382
383    /**
384     * Gets the title of the window.
385     *
386     * @return The title of the window, or the application label for the window if no title was
387     * explicitly set, or {@code null} if neither is available.
388     */
389    public CharSequence getTitle() {
390        return IMPL.getTitle(mInfo);
391    }
392
393    /**
394     * Gets the node that anchors this window to another.
395     *
396     * @return The anchor node, or {@code null} if none exists.
397     */
398    public AccessibilityNodeInfoCompat getAnchor() {
399        return AccessibilityNodeInfoCompat.wrapNonNullInstance(IMPL.getAnchor(mInfo));
400    }
401
402    /**
403     * Returns a cached instance if such is available or a new one is
404     * created.
405     *
406     * @return An instance.
407     */
408    public static AccessibilityWindowInfoCompat obtain() {
409        return wrapNonNullInstance(IMPL.obtain());
410    }
411
412    /**
413     * Returns a cached instance if such is available or a new one is
414     * created. The returned instance is initialized from the given
415     * <code>info</code>.
416     *
417     * @param info The other info.
418     * @return An instance.
419     */
420    public static AccessibilityWindowInfoCompat obtain(AccessibilityWindowInfoCompat info) {
421        return wrapNonNullInstance(IMPL.obtain(info.mInfo));
422    }
423
424    /**
425     * Return an instance back to be reused.
426     * <p>
427     * <strong>Note:</strong> You must not touch the object after calling this function.
428     * </p>
429     *
430     * @throws IllegalStateException If the info is already recycled.
431     */
432    public void recycle() {
433        IMPL.recycle(mInfo);
434    }
435
436    @Override
437    public int hashCode() {
438        return (mInfo == null) ? 0 : mInfo.hashCode();
439    }
440
441    @Override
442    public boolean equals(Object obj) {
443        if (this == obj) {
444            return true;
445        }
446        if (obj == null) {
447            return false;
448        }
449        if (getClass() != obj.getClass()) {
450            return false;
451        }
452        AccessibilityWindowInfoCompat other = (AccessibilityWindowInfoCompat) obj;
453        if (mInfo == null) {
454            if (other.mInfo != null) {
455                return false;
456            }
457        } else if (!mInfo.equals(other.mInfo)) {
458            return false;
459        }
460        return true;
461    }
462
463    @Override
464    public String toString() {
465        StringBuilder builder = new StringBuilder();
466        Rect bounds = new Rect();
467        getBoundsInScreen(bounds);
468        builder.append("AccessibilityWindowInfo[");
469        builder.append("id=").append(getId());
470        builder.append(", type=").append(typeToString(getType()));
471        builder.append(", layer=").append(getLayer());
472        builder.append(", bounds=").append(bounds);
473        builder.append(", focused=").append(isFocused());
474        builder.append(", active=").append(isActive());
475        builder.append(", hasParent=").append(getParent() != null);
476        builder.append(", hasChildren=").append(getChildCount() > 0);
477        builder.append(']');
478        return builder.toString();
479    }
480
481    private static String typeToString(int type) {
482        switch (type) {
483            case TYPE_APPLICATION: {
484                return "TYPE_APPLICATION";
485            }
486            case TYPE_INPUT_METHOD: {
487                return "TYPE_INPUT_METHOD";
488            }
489            case TYPE_SYSTEM: {
490                return "TYPE_SYSTEM";
491            }
492            case TYPE_ACCESSIBILITY_OVERLAY: {
493                return "TYPE_ACCESSIBILITY_OVERLAY";
494            }
495            default:
496                return "<UNKNOWN>";
497        }
498    }
499}
500