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