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