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.NonNull;
20 import android.annotation.Nullable;
21 import android.annotation.TestApi;
22 import android.app.ActivityTaskManager;
23 import android.graphics.Rect;
24 import android.graphics.Region;
25 import android.os.Parcel;
26 import android.os.Parcelable;
27 import android.text.TextUtils;
28 import android.util.LongArray;
29 import android.util.Pools.SynchronizedPool;
30 import android.util.SparseArray;
31 import android.view.Display;
32 import android.view.accessibility.AccessibilityEvent.WindowsChangeTypes;
33 
34 import java.util.ArrayList;
35 import java.util.List;
36 import java.util.Objects;
37 import java.util.concurrent.atomic.AtomicInteger;
38 
39 /**
40  * This class represents a state snapshot of a window for accessibility
41  * purposes. The screen content contains one or more windows where some
42  * windows can be descendants of other windows, which is the windows are
43  * hierarchically ordered. Note that there is no root window. Hence, the
44  * screen content can be seen as a collection of window trees.
45  */
46 public final class AccessibilityWindowInfo implements Parcelable {
47 
48     private static final boolean DEBUG = false;
49 
50     /**
51      * Window type: This is an application window. Such a window shows UI for
52      * interacting with an application.
53      */
54     public static final int TYPE_APPLICATION = 1;
55 
56     /**
57      * Window type: This is an input method window. Such a window shows UI for
58      * inputting text such as keyboard, suggestions, etc.
59      */
60     public static final int TYPE_INPUT_METHOD = 2;
61 
62     /**
63      * Window type: This is a system window. Such a window shows UI for
64      * interacting with the system.
65      */
66     public static final int TYPE_SYSTEM = 3;
67 
68     /**
69      * Window type: Windows that are overlaid <em>only</em> by an {@link
70      * android.accessibilityservice.AccessibilityService} for interception of
71      * user interactions without changing the windows an accessibility service
72      * can introspect. In particular, an accessibility service can introspect
73      * only windows that a sighted user can interact with which they can touch
74      * these windows or can type into these windows. For example, if there
75      * is a full screen accessibility overlay that is touchable, the windows
76      * below it will be introspectable by an accessibility service regardless
77      * they are covered by a touchable window.
78      */
79     public static final int TYPE_ACCESSIBILITY_OVERLAY = 4;
80 
81     /**
82      * Window type: A system window used to divide the screen in split-screen mode.
83      * This type of window is present only in split-screen mode.
84      */
85     public static final int TYPE_SPLIT_SCREEN_DIVIDER = 5;
86 
87     /* Special values for window IDs */
88     /** @hide */
89     public static final int ACTIVE_WINDOW_ID = Integer.MAX_VALUE;
90     /** @hide */
91     public static final int UNDEFINED_CONNECTION_ID = -1;
92     /** @hide */
93     public static final int UNDEFINED_WINDOW_ID = -1;
94     /** @hide */
95     public static final int ANY_WINDOW_ID = -2;
96     /** @hide */
97     public static final int PICTURE_IN_PICTURE_ACTION_REPLACER_WINDOW_ID = -3;
98 
99     private static final int BOOLEAN_PROPERTY_ACTIVE = 1 << 0;
100     private static final int BOOLEAN_PROPERTY_FOCUSED = 1 << 1;
101     private static final int BOOLEAN_PROPERTY_ACCESSIBILITY_FOCUSED = 1 << 2;
102     private static final int BOOLEAN_PROPERTY_PICTURE_IN_PICTURE = 1 << 3;
103 
104     // Housekeeping.
105     private static final int MAX_POOL_SIZE = 10;
106     private static final SynchronizedPool<AccessibilityWindowInfo> sPool =
107             new SynchronizedPool<AccessibilityWindowInfo>(MAX_POOL_SIZE);
108     // TODO(b/129300068): Remove sNumInstancesInUse.
109     private static AtomicInteger sNumInstancesInUse;
110 
111     // Data.
112     private int mDisplayId = Display.INVALID_DISPLAY;
113     private int mType = UNDEFINED_WINDOW_ID;
114     private int mLayer = UNDEFINED_WINDOW_ID;
115     private int mBooleanProperties;
116     private int mId = UNDEFINED_WINDOW_ID;
117     private int mParentId = UNDEFINED_WINDOW_ID;
118     private int mTaskId = ActivityTaskManager.INVALID_TASK_ID;
119     private Region mRegionInScreen = new Region();
120     private LongArray mChildIds;
121     private CharSequence mTitle;
122     private long mAnchorId = AccessibilityNodeInfo.UNDEFINED_NODE_ID;
123 
124     private int mConnectionId = UNDEFINED_CONNECTION_ID;
125 
126     /**
127      * Creates a new {@link AccessibilityWindowInfo}.
128      */
AccessibilityWindowInfo()129     public AccessibilityWindowInfo() {
130     }
131 
132     /**
133      * Copy constructor. Creates a new {@link AccessibilityWindowInfo}, and this new instance is
134      * initialized from given <code>info</code>.
135      *
136      * @param info The other info.
137      */
AccessibilityWindowInfo(@onNull AccessibilityWindowInfo info)138     public AccessibilityWindowInfo(@NonNull AccessibilityWindowInfo info) {
139         init(info);
140     }
141 
142     /**
143      * Gets the title of the window.
144      *
145      * @return The title of the window, or {@code null} if none is available.
146      */
147     @Nullable
getTitle()148     public CharSequence getTitle() {
149         return mTitle;
150     }
151 
152     /**
153      * Sets the title of the window.
154      *
155      * @param title The title.
156      *
157      * @hide
158      */
setTitle(CharSequence title)159     public void setTitle(CharSequence title) {
160         mTitle = title;
161     }
162 
163     /**
164      * Gets the type of the window.
165      *
166      * @return The type.
167      *
168      * @see #TYPE_APPLICATION
169      * @see #TYPE_INPUT_METHOD
170      * @see #TYPE_SYSTEM
171      * @see #TYPE_ACCESSIBILITY_OVERLAY
172      */
getType()173     public int getType() {
174         return mType;
175     }
176 
177     /**
178      * Sets the type of the window.
179      *
180      * @param type The type
181      *
182      * @hide
183      */
setType(int type)184     public void setType(int type) {
185         mType = type;
186     }
187 
188     /**
189      * Gets the layer which determines the Z-order of the window. Windows
190      * with greater layer appear on top of windows with lesser layer.
191      *
192      * @return The window layer.
193      */
getLayer()194     public int getLayer() {
195         return mLayer;
196     }
197 
198     /**
199      * Sets the layer which determines the Z-order of the window. Windows
200      * with greater layer appear on top of windows with lesser layer.
201      *
202      * @param layer The window layer.
203      *
204      * @hide
205      */
setLayer(int layer)206     public void setLayer(int layer) {
207         mLayer = layer;
208     }
209 
210     /**
211      * Gets the root node in the window's hierarchy.
212      *
213      * @return The root node.
214      */
getRoot()215     public AccessibilityNodeInfo getRoot() {
216         if (mConnectionId == UNDEFINED_WINDOW_ID) {
217             return null;
218         }
219         AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
220         return client.findAccessibilityNodeInfoByAccessibilityId(mConnectionId,
221                 mId, AccessibilityNodeInfo.ROOT_NODE_ID,
222                 true, AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS, null);
223     }
224 
225     /**
226      * Sets the anchor node's ID.
227      *
228      * @param anchorId The anchor's accessibility id in its window.
229      *
230      * @hide
231      */
setAnchorId(long anchorId)232     public void setAnchorId(long anchorId) {
233         mAnchorId = anchorId;
234     }
235 
236     /**
237      * Gets the node that anchors this window to another.
238      *
239      * @return The anchor node, or {@code null} if none exists.
240      */
getAnchor()241     public AccessibilityNodeInfo getAnchor() {
242         if ((mConnectionId == UNDEFINED_WINDOW_ID)
243                 || (mAnchorId == AccessibilityNodeInfo.UNDEFINED_NODE_ID)
244                 || (mParentId == UNDEFINED_WINDOW_ID)) {
245             return null;
246         }
247 
248         AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
249         return client.findAccessibilityNodeInfoByAccessibilityId(mConnectionId,
250                 mParentId, mAnchorId, true, 0, null);
251     }
252 
253     /** @hide */
setPictureInPicture(boolean pictureInPicture)254     public void setPictureInPicture(boolean pictureInPicture) {
255         setBooleanProperty(BOOLEAN_PROPERTY_PICTURE_IN_PICTURE, pictureInPicture);
256     }
257 
258     /**
259      * Check if the window is in picture-in-picture mode.
260      *
261      * @return {@code true} if the window is in picture-in-picture mode, {@code false} otherwise.
262      */
isInPictureInPictureMode()263     public boolean isInPictureInPictureMode() {
264         return getBooleanProperty(BOOLEAN_PROPERTY_PICTURE_IN_PICTURE);
265     }
266 
267     /**
268      * Gets the parent window.
269      *
270      * @return The parent window, or {@code null} if none exists.
271      */
getParent()272     public AccessibilityWindowInfo getParent() {
273         if (mConnectionId == UNDEFINED_WINDOW_ID || mParentId == UNDEFINED_WINDOW_ID) {
274             return null;
275         }
276         AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
277         return client.getWindow(mConnectionId, mParentId);
278     }
279 
280     /**
281      * Sets the parent window id.
282      *
283      * @param parentId The parent id.
284      *
285      * @hide
286      */
setParentId(int parentId)287     public void setParentId(int parentId) {
288         mParentId = parentId;
289     }
290 
291     /**
292      * Gets the unique window id.
293      *
294      * @return windowId The window id.
295      */
getId()296     public int getId() {
297         return mId;
298     }
299 
300     /**
301      * Sets the unique window id.
302      *
303      * @param id The window id.
304      *
305      * @hide
306      */
setId(int id)307     public void setId(int id) {
308         mId = id;
309     }
310 
311     /**
312      * Gets the task ID.
313      *
314      * @return The task ID.
315      *
316      * @hide
317      */
getTaskId()318     public int getTaskId() {
319         return mTaskId;
320     }
321 
322     /**
323      * Sets the task ID.
324      *
325      * @param taskId The task ID.
326      *
327      * @hide
328      */
setTaskId(int taskId)329     public void setTaskId(int taskId) {
330         mTaskId = taskId;
331     }
332 
333     /**
334      * Sets the unique id of the IAccessibilityServiceConnection over which
335      * this instance can send requests to the system.
336      *
337      * @param connectionId The connection id.
338      *
339      * @hide
340      */
setConnectionId(int connectionId)341     public void setConnectionId(int connectionId) {
342         mConnectionId = connectionId;
343     }
344 
345     /**
346      * Gets the touchable region of this window in the screen.
347      *
348      * @param outRegion The out window region.
349      */
getRegionInScreen(@onNull Region outRegion)350     public void getRegionInScreen(@NonNull Region outRegion) {
351         outRegion.set(mRegionInScreen);
352     }
353 
354     /**
355      * Sets the touchable region of this window in the screen.
356      *
357      * @param region The window region.
358      *
359      * @hide
360      */
setRegionInScreen(Region region)361     public void setRegionInScreen(Region region) {
362         mRegionInScreen.set(region);
363     }
364 
365     /**
366      * Gets the bounds of this window in the screen. This is equivalent to get the bounds of the
367      * Region from {@link #getRegionInScreen(Region)}.
368      *
369      * @param outBounds The out window bounds.
370      */
getBoundsInScreen(Rect outBounds)371     public void getBoundsInScreen(Rect outBounds) {
372         outBounds.set(mRegionInScreen.getBounds());
373     }
374 
375     /**
376      * Gets if this window is active. An active window is the one
377      * the user is currently touching or the window has input focus
378      * and the user is not touching any window.
379      *
380      * @return Whether this is the active window.
381      */
isActive()382     public boolean isActive() {
383         return getBooleanProperty(BOOLEAN_PROPERTY_ACTIVE);
384     }
385 
386     /**
387      * Sets if this window is active, which is this is the window
388      * the user is currently touching or the window has input focus
389      * and the user is not touching any window.
390      *
391      * @param active Whether this is the active window.
392      *
393      * @hide
394      */
setActive(boolean active)395     public void setActive(boolean active) {
396         setBooleanProperty(BOOLEAN_PROPERTY_ACTIVE, active);
397     }
398 
399     /**
400      * Gets if this window has input focus.
401      *
402      * @return Whether has input focus.
403      */
isFocused()404     public boolean isFocused() {
405         return getBooleanProperty(BOOLEAN_PROPERTY_FOCUSED);
406     }
407 
408     /**
409      * Sets if this window has input focus.
410      *
411      * @param focused Whether has input focus.
412      *
413      * @hide
414      */
setFocused(boolean focused)415     public void setFocused(boolean focused) {
416         setBooleanProperty(BOOLEAN_PROPERTY_FOCUSED, focused);
417     }
418 
419     /**
420      * Gets if this window has accessibility focus.
421      *
422      * @return Whether has accessibility focus.
423      */
isAccessibilityFocused()424     public boolean isAccessibilityFocused() {
425         return getBooleanProperty(BOOLEAN_PROPERTY_ACCESSIBILITY_FOCUSED);
426     }
427 
428     /**
429      * Sets if this window has accessibility focus.
430      *
431      * @param focused Whether has accessibility focus.
432      *
433      * @hide
434      */
setAccessibilityFocused(boolean focused)435     public void setAccessibilityFocused(boolean focused) {
436         setBooleanProperty(BOOLEAN_PROPERTY_ACCESSIBILITY_FOCUSED, focused);
437     }
438 
439     /**
440      * Gets the number of child windows.
441      *
442      * @return The child count.
443      */
getChildCount()444     public int getChildCount() {
445         return (mChildIds != null) ? mChildIds.size() : 0;
446     }
447 
448     /**
449      * Gets the child window at a given index.
450      *
451      * @param index The index.
452      * @return The child.
453      */
getChild(int index)454     public AccessibilityWindowInfo getChild(int index) {
455         if (mChildIds == null) {
456             throw new IndexOutOfBoundsException();
457         }
458         if (mConnectionId == UNDEFINED_WINDOW_ID) {
459             return null;
460         }
461         final int childId = (int) mChildIds.get(index);
462         AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
463         return client.getWindow(mConnectionId, childId);
464     }
465 
466     /**
467      * Adds a child window.
468      *
469      * @param childId The child window id.
470      *
471      * @hide
472      */
addChild(int childId)473     public void addChild(int childId) {
474         if (mChildIds == null) {
475             mChildIds = new LongArray();
476         }
477         mChildIds.add(childId);
478     }
479 
480     /**
481      * Sets the display Id.
482      *
483      * @param displayId The display id.
484      *
485      * @hide
486      */
setDisplayId(int displayId)487     public void setDisplayId(int displayId) {
488         mDisplayId = displayId;
489     }
490 
491     /**
492      * Returns the ID of the display this window is on, for use with
493      * {@link android.hardware.display.DisplayManager#getDisplay(int)}.
494      *
495      * @return The logical display id.
496      */
getDisplayId()497     public int getDisplayId() {
498         return mDisplayId;
499     }
500 
501     /**
502      * Returns a cached instance if such is available or a new one is
503      * created.
504      *
505      * <p>In most situations object pooling is not beneficial. Create a new instance using the
506      * constructor {@link #AccessibilityWindowInfo()} instead.
507      *
508      * @return An instance.
509      */
obtain()510     public static AccessibilityWindowInfo obtain() {
511         AccessibilityWindowInfo info = sPool.acquire();
512         if (info == null) {
513             info = new AccessibilityWindowInfo();
514         }
515         if (sNumInstancesInUse != null) {
516             sNumInstancesInUse.incrementAndGet();
517         }
518         return info;
519     }
520 
521     /**
522      * Returns a cached instance if such is available or a new one is
523      * created. The returned instance is initialized from the given
524      * <code>info</code>.
525      *
526      * <p>In most situations object pooling is not beneficial. Create a new instance using the
527      * constructor {@link #AccessibilityWindowInfo(AccessibilityWindowInfo)} instead.
528      *
529      * @param info The other info.
530      * @return An instance.
531      */
obtain(AccessibilityWindowInfo info)532     public static AccessibilityWindowInfo obtain(AccessibilityWindowInfo info) {
533         AccessibilityWindowInfo infoClone = obtain();
534         infoClone.init(info);
535         return infoClone;
536     }
537 
538     /**
539      * Specify a counter that will be incremented on obtain() and decremented on recycle()
540      *
541      * @hide
542      */
543     @TestApi
setNumInstancesInUseCounter(AtomicInteger counter)544     public static void setNumInstancesInUseCounter(AtomicInteger counter) {
545         if (sNumInstancesInUse != null) {
546             sNumInstancesInUse = counter;
547         }
548     }
549 
550     /**
551      * Return an instance back to be reused.
552      * <p>
553      * <strong>Note:</strong> You must not touch the object after calling this function.
554      * </p>
555      *
556      * <p>In most situations object pooling is not beneficial, and recycling is not necessary.
557      *
558      * @throws IllegalStateException If the info is already recycled.
559      */
recycle()560     public void recycle() {
561         clear();
562         sPool.release(this);
563         if (sNumInstancesInUse != null) {
564             sNumInstancesInUse.decrementAndGet();
565         }
566     }
567 
568     /**
569      * Refreshes this window with the latest state of the window it represents.
570      * <p>
571      * <strong>Note:</strong> If this method returns false this info is obsolete
572      * since it represents a window that is no longer exist.
573      * </p>
574      *
575      * @hide
576      */
refresh()577     public boolean refresh() {
578         if (mConnectionId == UNDEFINED_CONNECTION_ID || mId == UNDEFINED_WINDOW_ID) {
579             return false;
580         }
581         final AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
582         final AccessibilityWindowInfo refreshedInfo = client.getWindow(mConnectionId,
583                 mId, /* bypassCache */true);
584         if (refreshedInfo == null) {
585             return false;
586         }
587         init(refreshedInfo);
588         refreshedInfo.recycle();
589         return true;
590     }
591 
592     @Override
describeContents()593     public int describeContents() {
594         return 0;
595     }
596 
597     @Override
writeToParcel(Parcel parcel, int flags)598     public void writeToParcel(Parcel parcel, int flags) {
599         parcel.writeInt(mDisplayId);
600         parcel.writeInt(mType);
601         parcel.writeInt(mLayer);
602         parcel.writeInt(mBooleanProperties);
603         parcel.writeInt(mId);
604         parcel.writeInt(mParentId);
605         parcel.writeInt(mTaskId);
606         mRegionInScreen.writeToParcel(parcel, flags);
607         parcel.writeCharSequence(mTitle);
608         parcel.writeLong(mAnchorId);
609 
610         final LongArray childIds = mChildIds;
611         if (childIds == null) {
612             parcel.writeInt(0);
613         } else {
614             final int childCount = childIds.size();
615             parcel.writeInt(childCount);
616             for (int i = 0; i < childCount; i++) {
617                 parcel.writeInt((int) childIds.get(i));
618             }
619         }
620 
621         parcel.writeInt(mConnectionId);
622     }
623 
624     /**
625      * Initializes this instance from another one.
626      *
627      * @param other The other instance.
628      */
init(AccessibilityWindowInfo other)629     private void init(AccessibilityWindowInfo other) {
630         mDisplayId = other.mDisplayId;
631         mType = other.mType;
632         mLayer = other.mLayer;
633         mBooleanProperties = other.mBooleanProperties;
634         mId = other.mId;
635         mParentId = other.mParentId;
636         mTaskId = other.mTaskId;
637         mRegionInScreen.set(other.mRegionInScreen);
638         mTitle = other.mTitle;
639         mAnchorId = other.mAnchorId;
640 
641         if (mChildIds != null) mChildIds.clear();
642         if (other.mChildIds != null && other.mChildIds.size() > 0) {
643             if (mChildIds == null) {
644                 mChildIds = other.mChildIds.clone();
645             } else {
646                 mChildIds.addAll(other.mChildIds);
647             }
648         }
649 
650         mConnectionId = other.mConnectionId;
651     }
652 
initFromParcel(Parcel parcel)653     private void initFromParcel(Parcel parcel) {
654         mDisplayId = parcel.readInt();
655         mType = parcel.readInt();
656         mLayer = parcel.readInt();
657         mBooleanProperties = parcel.readInt();
658         mId = parcel.readInt();
659         mParentId = parcel.readInt();
660         mTaskId = parcel.readInt();
661         mRegionInScreen = Region.CREATOR.createFromParcel(parcel);
662         mTitle = parcel.readCharSequence();
663         mAnchorId = parcel.readLong();
664 
665         final int childCount = parcel.readInt();
666         if (childCount > 0) {
667             if (mChildIds == null) {
668                 mChildIds = new LongArray(childCount);
669             }
670             for (int i = 0; i < childCount; i++) {
671                 final int childId = parcel.readInt();
672                 mChildIds.add(childId);
673             }
674         }
675 
676         mConnectionId = parcel.readInt();
677     }
678 
679     @Override
hashCode()680     public int hashCode() {
681         return mId;
682     }
683 
684     @Override
equals(@ullable Object obj)685     public boolean equals(@Nullable Object obj) {
686         if (this == obj) {
687             return true;
688         }
689         if (obj == null) {
690             return false;
691         }
692         if (getClass() != obj.getClass()) {
693             return false;
694         }
695         AccessibilityWindowInfo other = (AccessibilityWindowInfo) obj;
696         return (mId == other.mId);
697     }
698 
699     @Override
toString()700     public String toString() {
701         StringBuilder builder = new StringBuilder();
702         builder.append("AccessibilityWindowInfo[");
703         builder.append("title=").append(mTitle);
704         builder.append(", displayId=").append(mDisplayId);
705         builder.append(", id=").append(mId);
706         builder.append(", taskId=").append(mTaskId);
707         builder.append(", type=").append(typeToString(mType));
708         builder.append(", layer=").append(mLayer);
709         builder.append(", region=").append(mRegionInScreen);
710         builder.append(", bounds=").append(mRegionInScreen.getBounds());
711         builder.append(", focused=").append(isFocused());
712         builder.append(", active=").append(isActive());
713         builder.append(", pictureInPicture=").append(isInPictureInPictureMode());
714         if (DEBUG) {
715             builder.append(", parent=").append(mParentId);
716             builder.append(", children=[");
717             if (mChildIds != null) {
718                 final int childCount = mChildIds.size();
719                 for (int i = 0; i < childCount; i++) {
720                     builder.append(mChildIds.get(i));
721                     if (i < childCount - 1) {
722                         builder.append(',');
723                     }
724                 }
725             } else {
726                 builder.append("null");
727             }
728             builder.append(']');
729         } else {
730             builder.append(", hasParent=").append(mParentId != UNDEFINED_WINDOW_ID);
731             builder.append(", isAnchored=")
732                     .append(mAnchorId != AccessibilityNodeInfo.UNDEFINED_NODE_ID);
733             builder.append(", hasChildren=").append(mChildIds != null
734                     && mChildIds.size() > 0);
735         }
736         builder.append(']');
737         return builder.toString();
738     }
739 
740     /**
741      * Clears the internal state.
742      */
clear()743     private void clear() {
744         mDisplayId = Display.INVALID_DISPLAY;
745         mType = UNDEFINED_WINDOW_ID;
746         mLayer = UNDEFINED_WINDOW_ID;
747         mBooleanProperties = 0;
748         mId = UNDEFINED_WINDOW_ID;
749         mParentId = UNDEFINED_WINDOW_ID;
750         mTaskId = ActivityTaskManager.INVALID_TASK_ID;
751         mRegionInScreen.setEmpty();
752         mChildIds = null;
753         mConnectionId = UNDEFINED_WINDOW_ID;
754         mAnchorId = AccessibilityNodeInfo.UNDEFINED_NODE_ID;
755         mTitle = null;
756     }
757 
758     /**
759      * Gets the value of a boolean property.
760      *
761      * @param property The property.
762      * @return The value.
763      */
getBooleanProperty(int property)764     private boolean getBooleanProperty(int property) {
765         return (mBooleanProperties & property) != 0;
766     }
767 
768     /**
769      * Sets a boolean property.
770      *
771      * @param property The property.
772      * @param value The value.
773      *
774      * @throws IllegalStateException If called from an AccessibilityService.
775      */
setBooleanProperty(int property, boolean value)776     private void setBooleanProperty(int property, boolean value) {
777         if (value) {
778             mBooleanProperties |= property;
779         } else {
780             mBooleanProperties &= ~property;
781         }
782     }
783 
784     /**
785      * @hide
786      */
typeToString(int type)787     public static String typeToString(int type) {
788         switch (type) {
789             case TYPE_APPLICATION: {
790                 return "TYPE_APPLICATION";
791             }
792             case TYPE_INPUT_METHOD: {
793                 return "TYPE_INPUT_METHOD";
794             }
795             case TYPE_SYSTEM: {
796                 return "TYPE_SYSTEM";
797             }
798             case TYPE_ACCESSIBILITY_OVERLAY: {
799                 return "TYPE_ACCESSIBILITY_OVERLAY";
800             }
801             case TYPE_SPLIT_SCREEN_DIVIDER: {
802                 return "TYPE_SPLIT_SCREEN_DIVIDER";
803             }
804             default:
805                 return "<UNKNOWN:" + type + ">";
806         }
807     }
808 
809     /**
810      * Reports how this window differs from a possibly different state of the same window. The
811      * argument must have the same id and type as neither of those properties may change.
812      *
813      * @param other The new state.
814      * @return A set of flags showing how the window has changes, or 0 if the two states are the
815      * same.
816      *
817      * @hide
818      */
819     @WindowsChangeTypes
differenceFrom(AccessibilityWindowInfo other)820     public int differenceFrom(AccessibilityWindowInfo other) {
821         if (other.mId != mId) {
822             throw new IllegalArgumentException("Not same window.");
823         }
824         if (other.mType != mType) {
825             throw new IllegalArgumentException("Not same type.");
826         }
827         int changes = 0;
828         if (!TextUtils.equals(mTitle, other.mTitle)) {
829             changes |= AccessibilityEvent.WINDOWS_CHANGE_TITLE;
830         }
831         if (!mRegionInScreen.equals(other.mRegionInScreen)) {
832             changes |= AccessibilityEvent.WINDOWS_CHANGE_BOUNDS;
833         }
834         if (mLayer != other.mLayer) {
835             changes |= AccessibilityEvent.WINDOWS_CHANGE_LAYER;
836         }
837         if (getBooleanProperty(BOOLEAN_PROPERTY_ACTIVE)
838                 != other.getBooleanProperty(BOOLEAN_PROPERTY_ACTIVE)) {
839             changes |= AccessibilityEvent.WINDOWS_CHANGE_ACTIVE;
840         }
841         if (getBooleanProperty(BOOLEAN_PROPERTY_FOCUSED)
842                 != other.getBooleanProperty(BOOLEAN_PROPERTY_FOCUSED)) {
843             changes |= AccessibilityEvent.WINDOWS_CHANGE_FOCUSED;
844         }
845         if (getBooleanProperty(BOOLEAN_PROPERTY_ACCESSIBILITY_FOCUSED)
846                 != other.getBooleanProperty(BOOLEAN_PROPERTY_ACCESSIBILITY_FOCUSED)) {
847             changes |= AccessibilityEvent.WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED;
848         }
849         if (getBooleanProperty(BOOLEAN_PROPERTY_PICTURE_IN_PICTURE)
850                 != other.getBooleanProperty(BOOLEAN_PROPERTY_PICTURE_IN_PICTURE)) {
851             changes |= AccessibilityEvent.WINDOWS_CHANGE_PIP;
852         }
853         if (mParentId != other.mParentId) {
854             changes |= AccessibilityEvent.WINDOWS_CHANGE_PARENT;
855         }
856         if (!Objects.equals(mChildIds, other.mChildIds)) {
857             changes |= AccessibilityEvent.WINDOWS_CHANGE_CHILDREN;
858         }
859         //TODO(b/1338122): Add DISPLAY_CHANGED type for multi-display
860         return changes;
861     }
862 
863     public static final @android.annotation.NonNull Parcelable.Creator<AccessibilityWindowInfo> CREATOR =
864             new Creator<AccessibilityWindowInfo>() {
865         @Override
866         public AccessibilityWindowInfo createFromParcel(Parcel parcel) {
867             AccessibilityWindowInfo info = obtain();
868             info.initFromParcel(parcel);
869             return info;
870         }
871 
872         @Override
873         public AccessibilityWindowInfo[] newArray(int size) {
874             return new AccessibilityWindowInfo[size];
875         }
876     };
877 
878     /**
879      * Transfers a sparsearray with lists having {@link AccessibilityWindowInfo}s across an IPC.
880      * The key of this sparsearray is display Id.
881      *
882      * @hide
883      */
884     public static final class WindowListSparseArray
885             extends SparseArray<List<AccessibilityWindowInfo>> implements Parcelable {
886 
887         @Override
describeContents()888         public int describeContents() {
889             return 0;
890         }
891 
892         @Override
writeToParcel(Parcel dest, int flags)893         public void writeToParcel(Parcel dest, int flags) {
894             final int count = size();
895             dest.writeInt(count);
896             for (int i = 0; i < count; i++) {
897                 dest.writeParcelableList(valueAt(i), 0);
898                 dest.writeInt(keyAt(i));
899             }
900         }
901 
902         public static final Parcelable.Creator<WindowListSparseArray> CREATOR =
903                 new Parcelable.Creator<WindowListSparseArray>() {
904             public WindowListSparseArray createFromParcel(
905                     Parcel source) {
906                 final WindowListSparseArray array = new WindowListSparseArray();
907                 final ClassLoader loader = array.getClass().getClassLoader();
908                 final int count = source.readInt();
909                 for (int i = 0; i < count; i++) {
910                     List<AccessibilityWindowInfo> windows = new ArrayList<>();
911                     source.readParcelableList(windows, loader);
912                     array.put(source.readInt(), windows);
913                 }
914                 return array;
915             }
916 
917             public WindowListSparseArray[] newArray(int size) {
918                 return new WindowListSparseArray[size];
919             }
920         };
921     }
922 }
923