• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
AccessibilityWindowInfo()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
getTitle()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      */
setTitle(CharSequence title)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      */
getType()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      */
setType(int type)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      */
getLayer()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      */
setLayer(int layer)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      */
getRoot()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      */
setAnchorId(int anchorId)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      */
getAnchor()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 */
setPictureInPicture(boolean pictureInPicture)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      */
inPictureInPicture()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      */
isInPictureInPictureMode()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      */
getParent()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      */
setParentId(int parentId)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      */
getId()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      */
setId(int id)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      */
setConnectionId(int connectionId)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      */
getBoundsInScreen(Rect outBounds)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      */
setBoundsInScreen(Rect bounds)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      */
isActive()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      */
setActive(boolean active)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      */
isFocused()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      */
setFocused(boolean focused)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      */
isAccessibilityFocused()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      */
setAccessibilityFocused(boolean focused)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      */
getChildCount()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      */
getChild(int index)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      */
addChild(int childId)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      */
obtain()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      */
obtain(AccessibilityWindowInfo info)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
setNumInstancesInUseCounter(AtomicInteger counter)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      */
recycle()501     public void recycle() {
502         clear();
503         sPool.release(this);
504         if (sNumInstancesInUse != null) {
505             sNumInstancesInUse.decrementAndGet();
506         }
507     }
508 
509     @Override
describeContents()510     public int describeContents() {
511         return 0;
512     }
513 
514     @Override
writeToParcel(Parcel parcel, int flags)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 
initFromParcel(Parcel parcel)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
hashCode()566     public int hashCode() {
567         return mId;
568     }
569 
570     @Override
equals(Object obj)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
toString()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      */
clear()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      */
getBooleanProperty(int property)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      */
setBooleanProperty(int property, boolean value)659     private void setBooleanProperty(int property, boolean value) {
660         if (value) {
661             mBooleanProperties |= property;
662         } else {
663             mBooleanProperties &= ~property;
664         }
665     }
666 
typeToString(int type)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      */
changed(AccessibilityWindowInfo other)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