1 /*
2  * Copyright (C) 2020 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.window;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.annotation.TestApi;
22 import android.app.WindowConfiguration;
23 import android.content.pm.ActivityInfo;
24 import android.content.res.Configuration;
25 import android.graphics.Rect;
26 import android.os.IBinder;
27 import android.os.Parcel;
28 import android.os.Parcelable;
29 import android.util.ArrayMap;
30 import android.view.SurfaceControl;
31 
32 import java.util.ArrayList;
33 import java.util.List;
34 import java.util.Map;
35 
36 /**
37  * Represents a collection of operations on some WindowContainers that should be applied all at
38  * once.
39  *
40  * @hide
41  */
42 @TestApi
43 public final class WindowContainerTransaction implements Parcelable {
44     private final ArrayMap<IBinder, Change> mChanges = new ArrayMap<>();
45 
46     // Flat list because re-order operations are order-dependent
47     private final ArrayList<HierarchyOp> mHierarchyOps = new ArrayList<>();
48 
WindowContainerTransaction()49     public WindowContainerTransaction() {}
50 
WindowContainerTransaction(Parcel in)51     private WindowContainerTransaction(Parcel in) {
52         in.readMap(mChanges, null /* loader */);
53         in.readList(mHierarchyOps, null /* loader */);
54     }
55 
getOrCreateChange(IBinder token)56     private Change getOrCreateChange(IBinder token) {
57         Change out = mChanges.get(token);
58         if (out == null) {
59             out = new Change();
60             mChanges.put(token, out);
61         }
62         return out;
63     }
64 
65     /**
66      * Resize a container.
67      */
68     @NonNull
setBounds( @onNull WindowContainerToken container,@NonNull Rect bounds)69     public WindowContainerTransaction setBounds(
70             @NonNull WindowContainerToken container,@NonNull Rect bounds) {
71         Change chg = getOrCreateChange(container.asBinder());
72         chg.mConfiguration.windowConfiguration.setBounds(bounds);
73         chg.mConfigSetMask |= ActivityInfo.CONFIG_WINDOW_CONFIGURATION;
74         chg.mWindowSetMask |= WindowConfiguration.WINDOW_CONFIG_BOUNDS;
75         return this;
76     }
77 
78     /**
79      * Resize a container's app bounds. This is the bounds used to report appWidth/Height to an
80      * app's DisplayInfo. It is derived by subtracting the overlapping portion of the navbar from
81      * the full bounds.
82      */
83     @NonNull
setAppBounds( @onNull WindowContainerToken container,@NonNull Rect appBounds)84     public WindowContainerTransaction setAppBounds(
85             @NonNull WindowContainerToken container,@NonNull Rect appBounds) {
86         Change chg = getOrCreateChange(container.asBinder());
87         chg.mConfiguration.windowConfiguration.setAppBounds(appBounds);
88         chg.mConfigSetMask |= ActivityInfo.CONFIG_WINDOW_CONFIGURATION;
89         chg.mWindowSetMask |= WindowConfiguration.WINDOW_CONFIG_APP_BOUNDS;
90         return this;
91     }
92 
93     /**
94      * Resize a container's configuration size. The configuration size is what gets reported to the
95      * app via screenWidth/HeightDp and influences which resources get loaded. This size is
96      * derived by subtracting the overlapping portions of both the statusbar and the navbar from
97      * the full bounds.
98      */
99     @NonNull
setScreenSizeDp( @onNull WindowContainerToken container, int w, int h)100     public WindowContainerTransaction setScreenSizeDp(
101             @NonNull WindowContainerToken container, int w, int h) {
102         Change chg = getOrCreateChange(container.asBinder());
103         chg.mConfiguration.screenWidthDp = w;
104         chg.mConfiguration.screenHeightDp = h;
105         chg.mConfigSetMask |= ActivityInfo.CONFIG_SCREEN_SIZE;
106         return this;
107     }
108 
109     /**
110      * Notify activities within the hierarchy of a container that they have entered picture-in-picture
111      * mode with the given bounds.
112      */
113     @NonNull
scheduleFinishEnterPip( @onNull WindowContainerToken container,@NonNull Rect bounds)114     public WindowContainerTransaction scheduleFinishEnterPip(
115             @NonNull WindowContainerToken container,@NonNull Rect bounds) {
116         Change chg = getOrCreateChange(container.asBinder());
117         chg.mPinnedBounds = new Rect(bounds);
118         chg.mChangeMask |= Change.CHANGE_PIP_CALLBACK;
119 
120         return this;
121     }
122 
123     /**
124      * Send a SurfaceControl transaction to the server, which the server will apply in sync with
125      * the next bounds change. As this uses deferred transaction and not BLAST it is only
126      * able to sync with a single window, and the first visible window in this hierarchy of type
127      * BASE_APPLICATION to resize will be used. If there are bound changes included in this
128      * WindowContainer transaction (from setBounds or scheduleFinishEnterPip), the SurfaceControl
129      * transaction will be synced with those bounds. If there are no changes, then
130      * the SurfaceControl transaction will be synced with the next bounds change. This means
131      * that you can call this, apply the WindowContainer transaction, and then later call
132      * dismissPip() to achieve synchronization.
133      */
134     @NonNull
setBoundsChangeTransaction( @onNull WindowContainerToken container,@NonNull SurfaceControl.Transaction t)135     public WindowContainerTransaction setBoundsChangeTransaction(
136             @NonNull WindowContainerToken container,@NonNull SurfaceControl.Transaction t) {
137         Change chg = getOrCreateChange(container.asBinder());
138         chg.mBoundsChangeTransaction = t;
139         chg.mChangeMask |= Change.CHANGE_BOUNDS_TRANSACTION;
140         return this;
141     }
142 
143     /**
144      * Like {@link #setBoundsChangeTransaction} but instead queues up a setPosition/WindowCrop
145      * on a container's surface control. This is useful when a boundsChangeTransaction needs to be
146      * queued up on a Task that won't be organized until the end of this window-container
147      * transaction.
148      *
149      * This requires that, at the end of this transaction, `task` will be organized; otherwise
150      * the server will throw an IllegalArgumentException.
151      *
152      * WARNING: Use this carefully. Whatever is set here should match the expected bounds after
153      *          the transaction completes since it will likely be replaced by it. This call is
154      *          intended to pre-emptively set bounds on a surface in sync with a buffer when
155      *          otherwise the new bounds and the new buffer would update on different frames.
156      *
157      * TODO(b/134365562): remove once TaskOrg drives full-screen or BLAST is enabled.
158      *
159      * @hide
160      */
161     @NonNull
setBoundsChangeTransaction( @onNull WindowContainerToken task, @NonNull Rect surfaceBounds)162     public WindowContainerTransaction setBoundsChangeTransaction(
163             @NonNull WindowContainerToken task, @NonNull Rect surfaceBounds) {
164         Change chg = getOrCreateChange(task.asBinder());
165         if (chg.mBoundsChangeSurfaceBounds == null) {
166             chg.mBoundsChangeSurfaceBounds = new Rect();
167         }
168         chg.mBoundsChangeSurfaceBounds.set(surfaceBounds);
169         chg.mChangeMask |= Change.CHANGE_BOUNDS_TRANSACTION_RECT;
170         return this;
171     }
172 
173     /**
174      * Set the windowing mode of children of a given root task, without changing
175      * the windowing mode of the Task itself. This can be used during transitions
176      * for example to make the activity render it's fullscreen configuration
177      * while the Task is still in PIP, so you can complete the animation.
178      *
179      * TODO(b/134365562): Can be removed once TaskOrg drives full-screen
180      */
181     @NonNull
setActivityWindowingMode( @onNull WindowContainerToken container, int windowingMode)182     public WindowContainerTransaction setActivityWindowingMode(
183             @NonNull WindowContainerToken container, int windowingMode) {
184         Change chg = getOrCreateChange(container.asBinder());
185         chg.mActivityWindowingMode = windowingMode;
186         return this;
187     }
188 
189     /**
190      * Sets the windowing mode of the given container.
191      */
192     @NonNull
setWindowingMode( @onNull WindowContainerToken container, int windowingMode)193     public WindowContainerTransaction setWindowingMode(
194             @NonNull WindowContainerToken container, int windowingMode) {
195         Change chg = getOrCreateChange(container.asBinder());
196         chg.mWindowingMode = windowingMode;
197         return this;
198     }
199 
200     /**
201      * Sets whether a container or any of its children can be focusable. When {@code false}, no
202      * child can be focused; however, when {@code true}, it is still possible for children to be
203      * non-focusable due to WM policy.
204      */
205     @NonNull
setFocusable( @onNull WindowContainerToken container, boolean focusable)206     public WindowContainerTransaction setFocusable(
207             @NonNull WindowContainerToken container, boolean focusable) {
208         Change chg = getOrCreateChange(container.asBinder());
209         chg.mFocusable = focusable;
210         chg.mChangeMask |= Change.CHANGE_FOCUSABLE;
211         return this;
212     }
213 
214     /**
215      * Sets whether a container or its children should be hidden. When {@code false}, the existing
216      * visibility of the container applies, but when {@code true} the container will be forced
217      * to be hidden.
218      */
219     @NonNull
setHidden( @onNull WindowContainerToken container, boolean hidden)220     public WindowContainerTransaction setHidden(
221             @NonNull WindowContainerToken container, boolean hidden) {
222         Change chg = getOrCreateChange(container.asBinder());
223         chg.mHidden = hidden;
224         chg.mChangeMask |= Change.CHANGE_HIDDEN;
225         return this;
226     }
227 
228     /**
229      * Set the smallestScreenWidth of a container.
230      */
231     @NonNull
setSmallestScreenWidthDp( @onNull WindowContainerToken container, int widthDp)232     public WindowContainerTransaction setSmallestScreenWidthDp(
233             @NonNull WindowContainerToken container, int widthDp) {
234         Change cfg = getOrCreateChange(container.asBinder());
235         cfg.mConfiguration.smallestScreenWidthDp = widthDp;
236         cfg.mConfigSetMask |= ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE;
237         return this;
238     }
239 
240     /**
241      * Reparents a container into another one. The effect of a {@code null} parent can vary. For
242      * example, reparenting a stack to {@code null} will reparent it to its display.
243      *
244      * @param onTop When {@code true}, the child goes to the top of parent; otherwise it goes to
245      *              the bottom.
246      */
247     @NonNull
reparent(@onNull WindowContainerToken child, @Nullable WindowContainerToken parent, boolean onTop)248     public WindowContainerTransaction reparent(@NonNull WindowContainerToken child,
249             @Nullable WindowContainerToken parent, boolean onTop) {
250         mHierarchyOps.add(new HierarchyOp(child.asBinder(),
251                 parent == null ? null : parent.asBinder(), onTop));
252         return this;
253     }
254 
255     /**
256      * Reorders a container within its parent.
257      *
258      * @param onTop When {@code true}, the child goes to the top of parent; otherwise it goes to
259      *              the bottom.
260      */
261     @NonNull
reorder(@onNull WindowContainerToken child, boolean onTop)262     public WindowContainerTransaction reorder(@NonNull WindowContainerToken child, boolean onTop) {
263         mHierarchyOps.add(new HierarchyOp(child.asBinder(), onTop));
264         return this;
265     }
266 
267     /**
268      * Merges another WCT into this one.
269      * @param transfer When true, this will transfer everything from other potentially leaving
270      *                 other in an unusable state. When false, other is left alone, but
271      *                 SurfaceFlinger Transactions will not be merged.
272      * @hide
273      */
merge(WindowContainerTransaction other, boolean transfer)274     public void merge(WindowContainerTransaction other, boolean transfer) {
275         for (int i = 0, n = other.mChanges.size(); i < n; ++i) {
276             final IBinder key = other.mChanges.keyAt(i);
277             Change existing = mChanges.get(key);
278             if (existing == null) {
279                 existing = new Change();
280                 mChanges.put(key, existing);
281             }
282             existing.merge(other.mChanges.valueAt(i), transfer);
283         }
284         for (int i = 0, n = other.mHierarchyOps.size(); i < n; ++i) {
285             mHierarchyOps.add(transfer ? other.mHierarchyOps.get(i)
286                     : new HierarchyOp(other.mHierarchyOps.get(i)));
287         }
288     }
289 
290     /** @hide */
getChanges()291     public Map<IBinder, Change> getChanges() {
292         return mChanges;
293     }
294 
295     /** @hide */
getHierarchyOps()296     public List<HierarchyOp> getHierarchyOps() {
297         return mHierarchyOps;
298     }
299 
300     @Override
301     @NonNull
toString()302     public String toString() {
303         return "WindowContainerTransaction { changes = " + mChanges + " hops = " + mHierarchyOps
304                 + " }";
305     }
306 
307     @Override
308     /** @hide */
writeToParcel(@onNull Parcel dest, int flags)309     public void writeToParcel(@NonNull Parcel dest, int flags) {
310         dest.writeMap(mChanges);
311         dest.writeList(mHierarchyOps);
312     }
313 
314     @Override
315     /** @hide */
describeContents()316     public int describeContents() {
317         return 0;
318     }
319 
320     @NonNull
321     public static final Creator<WindowContainerTransaction> CREATOR =
322             new Creator<WindowContainerTransaction>() {
323                 @Override
324                 public WindowContainerTransaction createFromParcel(Parcel in) {
325                     return new WindowContainerTransaction(in);
326                 }
327 
328                 @Override
329                 public WindowContainerTransaction[] newArray(int size) {
330                     return new WindowContainerTransaction[size];
331                 }
332             };
333 
334     /**
335      * Holds changes on a single WindowContainer including Configuration changes.
336      * @hide
337      */
338     public static class Change implements Parcelable {
339         public static final int CHANGE_FOCUSABLE = 1;
340         public static final int CHANGE_BOUNDS_TRANSACTION = 1 << 1;
341         public static final int CHANGE_PIP_CALLBACK = 1 << 2;
342         public static final int CHANGE_HIDDEN = 1 << 3;
343         public static final int CHANGE_BOUNDS_TRANSACTION_RECT = 1 << 4;
344 
345         private final Configuration mConfiguration = new Configuration();
346         private boolean mFocusable = true;
347         private boolean mHidden = false;
348         private int mChangeMask = 0;
349         private @ActivityInfo.Config int mConfigSetMask = 0;
350         private @WindowConfiguration.WindowConfig int mWindowSetMask = 0;
351 
352         private Rect mPinnedBounds = null;
353         private SurfaceControl.Transaction mBoundsChangeTransaction = null;
354         private Rect mBoundsChangeSurfaceBounds = null;
355 
356         private int mActivityWindowingMode = -1;
357         private int mWindowingMode = -1;
358 
Change()359         public Change() {}
360 
Change(Parcel in)361         protected Change(Parcel in) {
362             mConfiguration.readFromParcel(in);
363             mFocusable = in.readBoolean();
364             mHidden = in.readBoolean();
365             mChangeMask = in.readInt();
366             mConfigSetMask = in.readInt();
367             mWindowSetMask = in.readInt();
368             if ((mChangeMask & Change.CHANGE_PIP_CALLBACK) != 0) {
369                 mPinnedBounds = new Rect();
370                 mPinnedBounds.readFromParcel(in);
371             }
372             if ((mChangeMask & Change.CHANGE_BOUNDS_TRANSACTION) != 0) {
373                 mBoundsChangeTransaction =
374                     SurfaceControl.Transaction.CREATOR.createFromParcel(in);
375             }
376             if ((mChangeMask & Change.CHANGE_BOUNDS_TRANSACTION_RECT) != 0) {
377                 mBoundsChangeSurfaceBounds = new Rect();
378                 mBoundsChangeSurfaceBounds.readFromParcel(in);
379             }
380 
381             mWindowingMode = in.readInt();
382             mActivityWindowingMode = in.readInt();
383         }
384 
385         /**
386          * @param transfer When true, this will transfer other into this leaving other in an
387          *                 undefined state. Use this if you don't intend to use other. When false,
388          *                 SurfaceFlinger Transactions will not merge.
389          */
merge(Change other, boolean transfer)390         public void merge(Change other, boolean transfer) {
391             mConfiguration.setTo(other.mConfiguration, other.mConfigSetMask, other.mWindowSetMask);
392             mConfigSetMask |= other.mConfigSetMask;
393             mWindowSetMask |= other.mWindowSetMask;
394             if ((other.mChangeMask & CHANGE_FOCUSABLE) != 0) {
395                 mFocusable = other.mFocusable;
396             }
397             if (transfer && (other.mChangeMask & CHANGE_BOUNDS_TRANSACTION) != 0) {
398                 mBoundsChangeTransaction = other.mBoundsChangeTransaction;
399                 other.mBoundsChangeTransaction = null;
400             }
401             if ((other.mChangeMask & CHANGE_PIP_CALLBACK) != 0) {
402                 mPinnedBounds = transfer ? other.mPinnedBounds : new Rect(other.mPinnedBounds);
403             }
404             if ((other.mChangeMask & CHANGE_HIDDEN) != 0) {
405                 mHidden = other.mHidden;
406             }
407             mChangeMask |= other.mChangeMask;
408             if (other.mActivityWindowingMode >= 0) {
409                 mActivityWindowingMode = other.mActivityWindowingMode;
410             }
411             if (other.mWindowingMode >= 0) {
412                 mWindowingMode = other.mWindowingMode;
413             }
414             if (other.mBoundsChangeSurfaceBounds != null) {
415                 mBoundsChangeSurfaceBounds = transfer ? other.mBoundsChangeSurfaceBounds
416                         : new Rect(other.mBoundsChangeSurfaceBounds);
417             }
418         }
419 
getWindowingMode()420         public int getWindowingMode() {
421             return mWindowingMode;
422         }
423 
getActivityWindowingMode()424         public int getActivityWindowingMode() {
425             return mActivityWindowingMode;
426         }
427 
getConfiguration()428         public Configuration getConfiguration() {
429             return mConfiguration;
430         }
431 
432         /** Gets the requested focusable state */
getFocusable()433         public boolean getFocusable() {
434             if ((mChangeMask & CHANGE_FOCUSABLE) == 0) {
435                 throw new RuntimeException("Focusable not set. check CHANGE_FOCUSABLE first");
436             }
437             return mFocusable;
438         }
439 
440         /** Gets the requested hidden state */
getHidden()441         public boolean getHidden() {
442             if ((mChangeMask & CHANGE_HIDDEN) == 0) {
443                 throw new RuntimeException("Hidden not set. check CHANGE_HIDDEN first");
444             }
445             return mHidden;
446         }
447 
getChangeMask()448         public int getChangeMask() {
449             return mChangeMask;
450         }
451 
452         @ActivityInfo.Config
getConfigSetMask()453         public int getConfigSetMask() {
454             return mConfigSetMask;
455         }
456 
457         @WindowConfiguration.WindowConfig
getWindowSetMask()458         public int getWindowSetMask() {
459             return mWindowSetMask;
460         }
461 
462         /**
463          * Returns the bounds to be used for scheduling the enter pip callback
464          * or null if no callback is to be scheduled.
465          */
getEnterPipBounds()466         public Rect getEnterPipBounds() {
467             return mPinnedBounds;
468         }
469 
getBoundsChangeTransaction()470         public SurfaceControl.Transaction getBoundsChangeTransaction() {
471             return mBoundsChangeTransaction;
472         }
473 
getBoundsChangeSurfaceBounds()474         public Rect getBoundsChangeSurfaceBounds() {
475             return mBoundsChangeSurfaceBounds;
476         }
477 
478         @Override
toString()479         public String toString() {
480             final boolean changesBounds =
481                     (mConfigSetMask & ActivityInfo.CONFIG_WINDOW_CONFIGURATION) != 0
482                             && ((mWindowSetMask & WindowConfiguration.WINDOW_CONFIG_BOUNDS)
483                                     != 0);
484             final boolean changesAppBounds =
485                     (mConfigSetMask & ActivityInfo.CONFIG_WINDOW_CONFIGURATION) != 0
486                             && ((mWindowSetMask & WindowConfiguration.WINDOW_CONFIG_APP_BOUNDS)
487                                     != 0);
488             final boolean changesSs = (mConfigSetMask & ActivityInfo.CONFIG_SCREEN_SIZE) != 0;
489             final boolean changesSss =
490                     (mConfigSetMask & ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE) != 0;
491             StringBuilder sb = new StringBuilder();
492             sb.append('{');
493             if (changesBounds) {
494                 sb.append("bounds:" + mConfiguration.windowConfiguration.getBounds() + ",");
495             }
496             if (changesAppBounds) {
497                 sb.append("appbounds:" + mConfiguration.windowConfiguration.getAppBounds() + ",");
498             }
499             if (changesSss) {
500                 sb.append("ssw:" + mConfiguration.smallestScreenWidthDp + ",");
501             }
502             if (changesSs) {
503                 sb.append("sw/h:" + mConfiguration.screenWidthDp + "x"
504                         + mConfiguration.screenHeightDp + ",");
505             }
506             if ((mChangeMask & CHANGE_FOCUSABLE) != 0) {
507                 sb.append("focusable:" + mFocusable + ",");
508             }
509             if (mBoundsChangeTransaction != null) {
510                 sb.append("hasBoundsTransaction,");
511             }
512             sb.append("}");
513             return sb.toString();
514         }
515 
516         @Override
writeToParcel(Parcel dest, int flags)517         public void writeToParcel(Parcel dest, int flags) {
518             mConfiguration.writeToParcel(dest, flags);
519             dest.writeBoolean(mFocusable);
520             dest.writeBoolean(mHidden);
521             dest.writeInt(mChangeMask);
522             dest.writeInt(mConfigSetMask);
523             dest.writeInt(mWindowSetMask);
524 
525             if (mPinnedBounds != null) {
526                 mPinnedBounds.writeToParcel(dest, flags);
527             }
528             if (mBoundsChangeTransaction != null) {
529                 mBoundsChangeTransaction.writeToParcel(dest, flags);
530             }
531             if (mBoundsChangeSurfaceBounds != null) {
532                 mBoundsChangeSurfaceBounds.writeToParcel(dest, flags);
533             }
534 
535             dest.writeInt(mWindowingMode);
536             dest.writeInt(mActivityWindowingMode);
537         }
538 
539         @Override
describeContents()540         public int describeContents() {
541             return 0;
542         }
543 
544         public static final Creator<Change> CREATOR = new Creator<Change>() {
545             @Override
546             public Change createFromParcel(Parcel in) {
547                 return new Change(in);
548             }
549 
550             @Override
551             public Change[] newArray(int size) {
552                 return new Change[size];
553             }
554         };
555     }
556 
557     /**
558      * Holds information about a reparent/reorder operation in the hierarchy. This is separate from
559      * Changes because they must be executed in the same order that they are added.
560      * @hide
561      */
562     public static class HierarchyOp implements Parcelable {
563         private final IBinder mContainer;
564 
565         // If this is same as mContainer, then only change position, don't reparent.
566         private final IBinder mReparent;
567 
568         // Moves/reparents to top of parent when {@code true}, otherwise moves/reparents to bottom.
569         private final boolean mToTop;
570 
HierarchyOp(@onNull IBinder container, @Nullable IBinder reparent, boolean toTop)571         public HierarchyOp(@NonNull IBinder container, @Nullable IBinder reparent, boolean toTop) {
572             mContainer = container;
573             mReparent = reparent;
574             mToTop = toTop;
575         }
576 
HierarchyOp(@onNull IBinder container, boolean toTop)577         public HierarchyOp(@NonNull IBinder container, boolean toTop) {
578             mContainer = container;
579             mReparent = container;
580             mToTop = toTop;
581         }
582 
HierarchyOp(@onNull HierarchyOp copy)583         public HierarchyOp(@NonNull HierarchyOp copy) {
584             mContainer = copy.mContainer;
585             mReparent = copy.mReparent;
586             mToTop = copy.mToTop;
587         }
588 
HierarchyOp(Parcel in)589         protected HierarchyOp(Parcel in) {
590             mContainer = in.readStrongBinder();
591             mReparent = in.readStrongBinder();
592             mToTop = in.readBoolean();
593         }
594 
isReparent()595         public boolean isReparent() {
596             return mContainer != mReparent;
597         }
598 
599         @Nullable
getNewParent()600         public IBinder getNewParent() {
601             return mReparent;
602         }
603 
604         @NonNull
getContainer()605         public IBinder getContainer() {
606             return mContainer;
607         }
608 
getToTop()609         public boolean getToTop() {
610             return mToTop;
611         }
612 
613         @Override
toString()614         public String toString() {
615             if (isReparent()) {
616                 return "{reparent: " + mContainer + " to " + (mToTop ? "top of " : "bottom of ")
617                         + mReparent + "}";
618             } else {
619                 return "{reorder: " + mContainer + " to " + (mToTop ? "top" : "bottom") + "}";
620             }
621         }
622 
623         @Override
writeToParcel(Parcel dest, int flags)624         public void writeToParcel(Parcel dest, int flags) {
625             dest.writeStrongBinder(mContainer);
626             dest.writeStrongBinder(mReparent);
627             dest.writeBoolean(mToTop);
628         }
629 
630         @Override
describeContents()631         public int describeContents() {
632             return 0;
633         }
634 
635         public static final Creator<HierarchyOp> CREATOR = new Creator<HierarchyOp>() {
636             @Override
637             public HierarchyOp createFromParcel(Parcel in) {
638                 return new HierarchyOp(in);
639             }
640 
641             @Override
642             public HierarchyOp[] newArray(int size) {
643                 return new HierarchyOp[size];
644             }
645         };
646     }
647 }
648