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