/*
 * Copyright (C) 2020 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.window;

import static android.app.ActivityOptions.ANIM_CLIP_REVEAL;
import static android.app.ActivityOptions.ANIM_CUSTOM;
import static android.app.ActivityOptions.ANIM_FROM_STYLE;
import static android.app.ActivityOptions.ANIM_OPEN_CROSS_PROFILE_APPS;
import static android.app.ActivityOptions.ANIM_SCALE_UP;
import static android.app.ActivityOptions.ANIM_SCENE_TRANSITION;
import static android.app.ActivityOptions.ANIM_THUMBNAIL_SCALE_DOWN;
import static android.app.ActivityOptions.ANIM_THUMBNAIL_SCALE_UP;
import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
import static android.view.Display.INVALID_DISPLAY;
import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_UNSPECIFIED;
import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_APPEARING;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY;
import static android.view.WindowManager.TRANSIT_NONE;
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
import static android.view.WindowManager.TransitionFlags;
import static android.view.WindowManager.TransitionType;
import static android.view.WindowManager.transitTypeToString;

import android.annotation.AnimRes;
import android.annotation.ColorInt;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.content.ComponentName;
import android.graphics.Point;
import android.graphics.Rect;
import android.hardware.HardwareBuffer;
import android.os.Parcel;
import android.os.Parcelable;
import android.view.Surface;
import android.view.SurfaceControl;
import android.view.WindowManager;
import com.android.window.flags.Flags;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

/**
 * Used to communicate information about what is changing during a transition to a TransitionPlayer.
 *
 * @hide
 */
public final class TransitionInfo implements Parcelable {
    private static final String TAG = "TransitionInfo";

    /**
     * Modes are only a sub-set of all the transit-types since they are per-container
     *
     * @hide
     */
    @Retention(RetentionPolicy.SOURCE)
    @IntDef(
            prefix = {"TRANSIT_"},
            value = {
                TRANSIT_NONE,
                TRANSIT_OPEN,
                TRANSIT_CLOSE,
                // Note: to_front/to_back really mean show/hide respectively at the container level.
                TRANSIT_TO_FRONT,
                TRANSIT_TO_BACK,
                TRANSIT_CHANGE
            })
    public @interface TransitionMode {}

    /** No flags */
    public static final int FLAG_NONE = 0;

    /** The container shows the wallpaper behind it. */
    public static final int FLAG_SHOW_WALLPAPER = 1;

    /** The container IS the wallpaper. */
    public static final int FLAG_IS_WALLPAPER = 1 << 1;

    /** The container is translucent. */
    public static final int FLAG_TRANSLUCENT = 1 << 2;

    // TODO: remove when starting-window is moved to Task
    /** The container is the recipient of a transferred starting-window */
    public static final int FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT = 1 << 3;

    /** The container has voice session. */
    public static final int FLAG_IS_VOICE_INTERACTION = 1 << 4;

    /** The container is the display. */
    public static final int FLAG_IS_DISPLAY = 1 << 5;

    // TODO(b/194540864): Once we can include all windows in transition, then replace this with
    // something like FLAG_IS_SYSTEM_ALERT instead. Then we can do mixed rotations.
    /**
     * Only for IS_DISPLAY containers. Is set if the display has system alert windows. This is used
     * to prevent seamless rotation.
     */
    public static final int FLAG_DISPLAY_HAS_ALERT_WINDOWS = 1 << 7;

    /** The container is an input-method window. */
    public static final int FLAG_IS_INPUT_METHOD = 1 << 8;

    /** The container is in a Task with embedded activity. */
    public static final int FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY = 1 << 9;

    /** The container fills its parent Task before and after the transition. */
    public static final int FLAG_FILLS_TASK = 1 << 10;

    /** The container is going to show IME on its task after the transition. */
    public static final int FLAG_WILL_IME_SHOWN = 1 << 11;

    /** The container attaches owner profile thumbnail for cross profile animation. */
    public static final int FLAG_CROSS_PROFILE_OWNER_THUMBNAIL = 1 << 12;

    /** The container attaches work profile thumbnail for cross profile animation. */
    public static final int FLAG_CROSS_PROFILE_WORK_THUMBNAIL = 1 << 13;

    /**
     * Whether the window is covered by an app starting window. This is different from {@link
     * #FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT} which is only set on the Activity window that
     * contains the starting window.
     */
    public static final int FLAG_IS_BEHIND_STARTING_WINDOW = 1 << 14;

    /** This change happened underneath something else. */
    public static final int FLAG_IS_OCCLUDED = 1 << 15;

    /** The container is a system window, excluding wallpaper and input-method. */
    public static final int FLAG_IS_SYSTEM_WINDOW = 1 << 16;

    /** The window was animated by back gesture. */
    public static final int FLAG_BACK_GESTURE_ANIMATED = 1 << 17;

    /** The window should have no animation (by policy). */
    public static final int FLAG_NO_ANIMATION = 1 << 18;

    /** The task is launching behind home. */
    public static final int FLAG_TASK_LAUNCHING_BEHIND = 1 << 19;

    /** The task became the top-most task even if it didn't change visibility. */
    public static final int FLAG_MOVED_TO_TOP = 1 << 20;

    /**
     * This transition must be the only transition when it starts (ie. it must wait for all other
     * transition animations to finish).
     */
    public static final int FLAG_SYNC = 1 << 21;

    /** This change represents its start configuration for the duration of the animation. */
    public static final int FLAG_CONFIG_AT_END = 1 << 22;

    /** The first unused bit. This can be used by remotes to attach custom flags to this change. */
    public static final int FLAG_FIRST_CUSTOM = 1 << 23;

    /** The change belongs to a window that won't contain activities. */
    public static final int FLAGS_IS_NON_APP_WINDOW =
            FLAG_IS_WALLPAPER | FLAG_IS_INPUT_METHOD | FLAG_IS_SYSTEM_WINDOW;

    /** The change will not participate in the animation. */
    public static final int FLAGS_IS_OCCLUDED_NO_ANIMATION = FLAG_IS_OCCLUDED | FLAG_NO_ANIMATION;

    /** @hide */
    @Retention(RetentionPolicy.SOURCE)
    @IntDef(
            prefix = {"FLAG_"},
            value = {
                FLAG_NONE,
                FLAG_SHOW_WALLPAPER,
                FLAG_IS_WALLPAPER,
                FLAG_TRANSLUCENT,
                FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT,
                FLAG_IS_VOICE_INTERACTION,
                FLAG_IS_DISPLAY,
                FLAG_DISPLAY_HAS_ALERT_WINDOWS,
                FLAG_IS_INPUT_METHOD,
                FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY,
                FLAG_FILLS_TASK,
                FLAG_WILL_IME_SHOWN,
                FLAG_CROSS_PROFILE_OWNER_THUMBNAIL,
                FLAG_CROSS_PROFILE_WORK_THUMBNAIL,
                FLAG_IS_BEHIND_STARTING_WINDOW,
                FLAG_IS_OCCLUDED,
                FLAG_IS_SYSTEM_WINDOW,
                FLAG_BACK_GESTURE_ANIMATED,
                FLAG_NO_ANIMATION,
                FLAG_TASK_LAUNCHING_BEHIND,
                FLAG_MOVED_TO_TOP,
                FLAG_SYNC,
                FLAG_CONFIG_AT_END,
                FLAG_FIRST_CUSTOM
            },
            flag = true)
    public @interface ChangeFlags {}

    private final @TransitionType int mType;
    private @TransitionFlags int mFlags;
    private int mTrack = 0;
    private final ArrayList<Change> mChanges = new ArrayList<>();
    private final ArrayList<Root> mRoots = new ArrayList<>();

    // TODO(b/327332488): Clean-up usages after the flag is fully enabled.
    @Deprecated private AnimationOptions mOptions;

    /** This is only a BEST-EFFORT id used for log correlation. DO NOT USE for any real work! */
    private int mDebugId = -1;

    /** @hide */
    public TransitionInfo(@TransitionType int type, @TransitionFlags int flags) {
        mType = type;
        mFlags = flags;
    }

    private TransitionInfo(Parcel in) {
        mType = in.readInt();
        mFlags = in.readInt();
        in.readTypedList(mChanges, Change.CREATOR);
        in.readTypedList(mRoots, Root.CREATOR);
        mOptions = in.readTypedObject(AnimationOptions.CREATOR);
        mDebugId = in.readInt();
        mTrack = in.readInt();
    }

    @Override
    /** @hide */
    public void writeToParcel(@NonNull Parcel dest, int flags) {
        dest.writeInt(mType);
        dest.writeInt(mFlags);
        dest.writeTypedList(mChanges);
        dest.writeTypedList(mRoots, flags);
        dest.writeTypedObject(mOptions, flags);
        dest.writeInt(mDebugId);
        dest.writeInt(mTrack);
    }

    @NonNull
    public static final Creator<TransitionInfo> CREATOR =
            new Creator<TransitionInfo>() {
                @Override
                public TransitionInfo createFromParcel(Parcel in) {
                    return new TransitionInfo(in);
                }

                @Override
                public TransitionInfo[] newArray(int size) {
                    return new TransitionInfo[size];
                }
            };

    @Override
    /** @hide */
    public int describeContents() {
        return 0;
    }

    /**
     * @see #getRoot
     */
    public void addRootLeash(
            int displayId, @NonNull SurfaceControl leash, int offsetLeft, int offsetTop) {
        mRoots.add(new Root(displayId, leash, offsetLeft, offsetTop));
    }

    /**
     * @see #getRoot
     */
    public void addRoot(@NonNull Root other) {
        mRoots.add(other);
    }

    /**
     * @deprecated Set {@link AnimationOptions} to change. This method is only used if {@link
     *     Flags#FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE} is disabled.
     */
    @Deprecated
    public void setAnimationOptions(@Nullable AnimationOptions options) {
        if (Flags.moveAnimationOptionsToChange()) {
            return;
        }
        mOptions = options;
    }

    public @TransitionType int getType() {
        return mType;
    }

    public void setFlags(int flags) {
        mFlags = flags;
    }

    public int getFlags() {
        return mFlags;
    }

    /**
     * @return The number of animation roots. Most transitions should have 1, but there may be more
     *     in some cases (such as a transition spanning multiple displays).
     */
    public int getRootCount() {
        return mRoots.size();
    }

    /**
     * @return the transition-root at a specific index.
     */
    @NonNull
    public Root getRoot(int idx) {
        return mRoots.get(idx);
    }

    /**
     * @return the index of the transition-root associated with `displayId` or -1 if not found.
     */
    public int findRootIndex(int displayId) {
        for (int i = 0; i < mRoots.size(); ++i) {
            if (mRoots.get(i).mDisplayId == displayId) {
                return i;
            }
        }
        return -1;
    }

    /**
     * @return a surfacecontrol that can serve as a parent surfacecontrol for all the changing
     *     participants to animate within. This will generally be placed at the highest-z-order
     *     shared ancestor of all participants. While this is non-null, it's possible for the
     *     rootleash to be invalid if the transition is a no-op.
     * @deprecated Use {@link #getRoot} instead. This call assumes there is only one root.
     */
    @Deprecated
    @NonNull
    public SurfaceControl getRootLeash() {
        if (mRoots.isEmpty()) {
            throw new IllegalStateException("Trying to get a root leash from a no-op transition.");
        }
        if (mRoots.size() > 1) {
            android.util.Log.e(
                    TAG, "Assuming one animation root when there are more.", new Throwable());
        }
        return mRoots.get(0).mLeash;
    }

    /**
     * @deprecated Use {@link Change#getAnimationOptions()} instead. This method is called only if
     *     {@link Flags#FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE} is disabled.
     */
    @Deprecated
    @Nullable
    public AnimationOptions getAnimationOptions() {
        return mOptions;
    }

    /**
     * @return the list of {@link Change}s in this transition. The list is sorted top-to-bottom in Z
     *     (meaning index 0 is the top-most container).
     */
    @NonNull
    public List<Change> getChanges() {
        return mChanges;
    }

    /**
     * @return the Change that a window is undergoing or {@code null} if not directly represented.
     */
    @Nullable
    public Change getChange(@NonNull WindowContainerToken token) {
        for (int i = mChanges.size() - 1; i >= 0; --i) {
            if (token.equals(mChanges.get(i).mContainer)) {
                return mChanges.get(i);
            }
        }
        return null;
    }

    /** Add a {@link Change} to this transition. */
    public void addChange(@NonNull Change change) {
        mChanges.add(change);
    }

    /**
     * Whether this transition contains any changes to the window hierarchy, including keyguard
     * visibility.
     */
    public boolean hasChangesOrSideEffects() {
        return !mChanges.isEmpty()
                || isKeyguardGoingAway()
                || (mFlags & TRANSIT_FLAG_KEYGUARD_APPEARING) != 0;
    }

    /** Whether this transition includes keyguard going away. */
    public boolean isKeyguardGoingAway() {
        return (mFlags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY) != 0;
    }

    /** Gets which animation track this transition should run on. */
    public int getTrack() {
        return mTrack;
    }

    /** Sets which animation track this transition should run on. */
    public void setTrack(int track) {
        mTrack = track;
    }

    /**
     * Set an arbitrary "debug" id for this info. This id will not be used for any "real work", it
     * is just for debugging and logging.
     */
    public void setDebugId(int id) {
        mDebugId = id;
    }

    /** Get the "debug" id of this info. Do NOT use this for real work, only use for debugging. */
    public int getDebugId() {
        return mDebugId;
    }

    @Override
    public String toString() {
        return toString("");
    }

    /**
     * Returns a string representation of this transition info.
     *
     * @hide
     */
    public String toString(@NonNull String prefix) {
        final boolean shouldPrettyPrint = !prefix.isEmpty() && !mChanges.isEmpty();
        final String innerPrefix = shouldPrettyPrint ? prefix + "    " : "";
        final String changesLineStart = shouldPrettyPrint ? "\n" + prefix : "";
        final String perChangeLineStart = shouldPrettyPrint ? "\n" + innerPrefix : "";
        StringBuilder sb = new StringBuilder();
        sb.append("{id=")
                .append(mDebugId)
                .append(" t=")
                .append(transitTypeToString(mType))
                .append(" f=0x")
                .append(Integer.toHexString(mFlags))
                .append(" trk=")
                .append(mTrack);
        if (mOptions != null) {
            sb.append(" opt=").append(mOptions);
        }
        sb.append(" r=[");
        for (int i = 0; i < mRoots.size(); ++i) {
            if (i > 0) {
                sb.append(',');
            }
            sb.append(mRoots.get(i).mDisplayId).append("@").append(mRoots.get(i).mOffset);
        }
        sb.append("] c=[");
        sb.append(perChangeLineStart);
        for (int i = 0; i < mChanges.size(); ++i) {
            if (i > 0) {
                sb.append(',');
                sb.append(perChangeLineStart);
            }
            sb.append(mChanges.get(i));
        }
        sb.append(changesLineStart);
        sb.append("]}");
        return sb.toString();
    }

    /** Converts a transition mode/action to its string representation. */
    @NonNull
    public static String modeToString(@TransitionMode int mode) {
        switch (mode) {
            case TRANSIT_NONE:
                return "NONE";
            case TRANSIT_OPEN:
                return "OPEN";
            case TRANSIT_CLOSE:
                return "CLOSE";
            case TRANSIT_TO_FRONT:
                return "TO_FRONT";
            case TRANSIT_TO_BACK:
                return "TO_BACK";
            case TRANSIT_CHANGE:
                return "CHANGE";
            default:
                return "<unknown:" + mode + ">";
        }
    }

    /** Converts change flags into a string representation. */
    @NonNull
    public static String flagsToString(@ChangeFlags int flags) {
        if (flags == 0) return "NONE";
        final StringBuilder sb = new StringBuilder();
        if ((flags & FLAG_SHOW_WALLPAPER) != 0) {
            sb.append("SHOW_WALLPAPER");
        }
        if ((flags & FLAG_IS_WALLPAPER) != 0) {
            sb.append("IS_WALLPAPER");
        }
        if ((flags & FLAG_IS_INPUT_METHOD) != 0) {
            sb.append("IS_INPUT_METHOD");
        }
        if ((flags & FLAG_TRANSLUCENT) != 0) {
            sb.append(sb.length() == 0 ? "" : "|").append("TRANSLUCENT");
        }
        if ((flags & FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT) != 0) {
            sb.append(sb.length() == 0 ? "" : "|").append("STARTING_WINDOW_TRANSFER");
        }
        if ((flags & FLAG_IS_VOICE_INTERACTION) != 0) {
            sb.append(sb.length() == 0 ? "" : "|").append("IS_VOICE_INTERACTION");
        }
        if ((flags & FLAG_IS_DISPLAY) != 0) {
            sb.append(sb.length() == 0 ? "" : "|").append("IS_DISPLAY");
        }
        if ((flags & FLAG_DISPLAY_HAS_ALERT_WINDOWS) != 0) {
            sb.append(sb.length() == 0 ? "" : "|").append("DISPLAY_HAS_ALERT_WINDOWS");
        }
        if ((flags & FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY) != 0) {
            sb.append(sb.length() == 0 ? "" : "|").append("IN_TASK_WITH_EMBEDDED_ACTIVITY");
        }
        if ((flags & FLAG_FILLS_TASK) != 0) {
            sb.append(sb.length() == 0 ? "" : "|").append("FILLS_TASK");
        }
        if ((flags & FLAG_IS_BEHIND_STARTING_WINDOW) != 0) {
            sb.append(sb.length() == 0 ? "" : "|").append("IS_BEHIND_STARTING_WINDOW");
        }
        if ((flags & FLAG_IS_OCCLUDED) != 0) {
            sb.append(sb.length() == 0 ? "" : "|").append("IS_OCCLUDED");
        }
        if ((flags & FLAG_IS_SYSTEM_WINDOW) != 0) {
            sb.append(sb.length() == 0 ? "" : "|").append("FLAG_IS_SYSTEM_WINDOW");
        }
        if ((flags & FLAG_BACK_GESTURE_ANIMATED) != 0) {
            sb.append(sb.length() == 0 ? "" : "|").append("FLAG_BACK_GESTURE_ANIMATED");
        }
        if ((flags & FLAG_NO_ANIMATION) != 0) {
            sb.append(sb.length() == 0 ? "" : "|").append("NO_ANIMATION");
        }
        if ((flags & FLAG_TASK_LAUNCHING_BEHIND) != 0) {
            sb.append((sb.length() == 0 ? "" : "|") + "TASK_LAUNCHING_BEHIND");
        }
        if ((flags & FLAG_SYNC) != 0) {
            sb.append((sb.length() == 0 ? "" : "|") + "SYNC");
        }
        if ((flags & FLAG_FIRST_CUSTOM) != 0) {
            sb.append(sb.length() == 0 ? "" : "|").append("FIRST_CUSTOM");
        }
        if ((flags & FLAG_CONFIG_AT_END) != 0) {
            sb.append(sb.length() == 0 ? "" : "|").append("CONFIG_AT_END");
        }
        if ((flags & FLAG_MOVED_TO_TOP) != 0) {
            sb.append(sb.length() == 0 ? "" : "|").append("MOVE_TO_TOP");
        }
        return sb.toString();
    }

    /**
     * Indication that `change` is independent of parents (ie. it has a different type of transition
     * vs. "going along for the ride")
     */
    public static boolean isIndependent(
            @NonNull TransitionInfo.Change change, @NonNull TransitionInfo info) {
        // If the change has no parent (it is root), then it is independent
        if (change.getParent() == null) return true;

        if (change.getLastParent() != null && !change.getLastParent().equals(change.getParent())) {
            // If the change has been reparented, then it's independent.
            return true;
        }

        // non-visibility changes will just be folded into the parent change, so they aren't
        // independent either.
        if (change.getMode() == TRANSIT_CHANGE) return false;

        // Always fold the activity embedding change into the parent change.
        if (change.hasFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY)) return false;

        TransitionInfo.Change parentChg = info.getChange(change.getParent());
        while (parentChg != null) {
            // If the parent is a visibility change, it will include the results of all child
            // changes into itself, so none of its children can be independent.
            if (parentChg.getMode() != TRANSIT_CHANGE) return false;

            // If there are no more parents left, then all the parents, so far, have not been
            // visibility changes which means this change is independent.
            if (parentChg.getParent() == null) return true;

            parentChg = info.getChange(parentChg.getParent());
        }
        return false;
    }

    /**
     * Releases temporary-for-animation surfaces referenced by this to potentially free up memory.
     * This includes root-leash and snapshots.
     */
    public void releaseAnimSurfaces() {
        for (int i = mChanges.size() - 1; i >= 0; --i) {
            final Change c = mChanges.get(i);
            if (c.mSnapshot != null) {
                c.mSnapshot.release();
                c.mSnapshot = null;
            }
        }
        for (int i = 0; i < mRoots.size(); ++i) {
            mRoots.get(i).mLeash.release();
        }
    }

    /**
     * Releases ALL the surfaces referenced by this to potentially free up memory. Do NOT use this
     * if the surface-controls get stored and used elsewhere in the process. To just release
     * temporary-for-animation surfaces, use {@link #releaseAnimSurfaces}.
     */
    public void releaseAllSurfaces() {
        releaseAnimSurfaces();
        for (int i = mChanges.size() - 1; i >= 0; --i) {
            mChanges.get(i).getLeash().release();
        }
    }

    /**
     * Updates the callsites of all the surfaces in this transition, which aids in the debugging of
     * lingering surfaces.
     */
    public void setUnreleasedWarningCallSiteForAllSurfaces(@Nullable String callsite) {
        for (int i = mChanges.size() - 1; i >= 0; --i) {
            mChanges.get(i).getLeash().setUnreleasedWarningCallSite(callsite);
        }
    }

    /**
     * Makes a copy of this as if it were parcel'd and unparcel'd. This implies that surfacecontrol
     * refcounts are incremented which allows the "remote" receiver to release them without breaking
     * the caller's references. Use this only if you need to "send" this to a local function which
     * assumes it is being called from a remote caller.
     */
    @NonNull
    public TransitionInfo localRemoteCopy() {
        final TransitionInfo out = new TransitionInfo(mType, mFlags);
        out.mTrack = mTrack;
        out.mDebugId = mDebugId;
        for (int i = 0; i < mChanges.size(); ++i) {
            out.mChanges.add(mChanges.get(i).localRemoteCopy());
        }
        for (int i = 0; i < mRoots.size(); ++i) {
            out.mRoots.add(mRoots.get(i).localRemoteCopy());
        }
        // Doesn't have any native stuff, so no need for actual copy
        out.mOptions = mOptions;
        return out;
    }

    /** Represents the change a WindowContainer undergoes during a transition */
    public static final class Change implements Parcelable {
        private final WindowContainerToken mContainer;
        private WindowContainerToken mParent;
        private WindowContainerToken mLastParent;
        private SurfaceControl mLeash;
        private @TransitionMode int mMode = TRANSIT_NONE;
        private @ChangeFlags int mFlags = FLAG_NONE;
        private final Rect mStartAbsBounds = new Rect();
        private final Rect mEndAbsBounds = new Rect();
        private final Point mEndRelOffset = new Point();
        private ActivityManager.RunningTaskInfo mTaskInfo = null;
        private boolean mAllowEnterPip;
        private int mStartDisplayId = INVALID_DISPLAY;
        private int mEndDisplayId = INVALID_DISPLAY;
        private @Surface.Rotation int mStartRotation = ROTATION_UNDEFINED;
        private @Surface.Rotation int mEndRotation = ROTATION_UNDEFINED;

        /**
         * The end rotation of the top activity after fixed rotation is finished. If the top
         * activity is not in fixed rotation, it will be {@link ROTATION_UNDEFINED}.
         */
        private @Surface.Rotation int mEndFixedRotation = ROTATION_UNDEFINED;

        private int mRotationAnimation = ROTATION_ANIMATION_UNSPECIFIED;
        private @ColorInt int mBackgroundColor;
        private SurfaceControl mSnapshot = null;
        private float mSnapshotLuma;
        private ComponentName mActivityComponent = null;
        private AnimationOptions mAnimationOptions = null;

        public Change(@Nullable WindowContainerToken container, @NonNull SurfaceControl leash) {
            mContainer = container;
            mLeash = leash;
        }

        private Change(Parcel in) {
            mContainer = in.readTypedObject(WindowContainerToken.CREATOR);
            mParent = in.readTypedObject(WindowContainerToken.CREATOR);
            mLastParent = in.readTypedObject(WindowContainerToken.CREATOR);
            mLeash = new SurfaceControl();
            mLeash.readFromParcel(in);
            mMode = in.readInt();
            mFlags = in.readInt();
            mStartAbsBounds.readFromParcel(in);
            mEndAbsBounds.readFromParcel(in);
            mEndRelOffset.readFromParcel(in);
            mTaskInfo = in.readTypedObject(ActivityManager.RunningTaskInfo.CREATOR);
            mAllowEnterPip = in.readBoolean();
            mStartDisplayId = in.readInt();
            mEndDisplayId = in.readInt();
            mStartRotation = in.readInt();
            mEndRotation = in.readInt();
            mEndFixedRotation = in.readInt();
            mRotationAnimation = in.readInt();
            mBackgroundColor = in.readInt();
            mSnapshot = in.readTypedObject(SurfaceControl.CREATOR);
            mSnapshotLuma = in.readFloat();
            mActivityComponent = in.readTypedObject(ComponentName.CREATOR);
            mAnimationOptions = in.readTypedObject(AnimationOptions.CREATOR);
        }

        private Change localRemoteCopy() {
            final Change out = new Change(mContainer, new SurfaceControl(mLeash, "localRemote"));
            out.mParent = mParent;
            out.mLastParent = mLastParent;
            out.mMode = mMode;
            out.mFlags = mFlags;
            out.mStartAbsBounds.set(mStartAbsBounds);
            out.mEndAbsBounds.set(mEndAbsBounds);
            out.mEndRelOffset.set(mEndRelOffset);
            out.mTaskInfo = mTaskInfo;
            out.mAllowEnterPip = mAllowEnterPip;
            out.mStartDisplayId = mStartDisplayId;
            out.mEndDisplayId = mEndDisplayId;
            out.mStartRotation = mStartRotation;
            out.mEndRotation = mEndRotation;
            out.mEndFixedRotation = mEndFixedRotation;
            out.mRotationAnimation = mRotationAnimation;
            out.mBackgroundColor = mBackgroundColor;
            out.mSnapshot = mSnapshot != null ? new SurfaceControl(mSnapshot, "localRemote") : null;
            out.mSnapshotLuma = mSnapshotLuma;
            out.mActivityComponent = mActivityComponent;
            out.mAnimationOptions = mAnimationOptions;
            return out;
        }

        /** Sets the parent of this change's container. The parent must be a participant or null. */
        public void setParent(@Nullable WindowContainerToken parent) {
            mParent = parent;
        }

        /**
         * Sets the parent of this change's container before the transition if this change's
         * container is reparented in the transition.
         */
        public void setLastParent(@Nullable WindowContainerToken lastParent) {
            mLastParent = lastParent;
        }

        /** Sets the animation leash for controlling this change's container */
        public void setLeash(@NonNull SurfaceControl leash) {
            mLeash = Objects.requireNonNull(leash);
        }

        /** Sets the transition mode for this change */
        public void setMode(@TransitionMode int mode) {
            mMode = mode;
        }

        /** Sets the flags for this change */
        public void setFlags(@ChangeFlags int flags) {
            mFlags = flags;
        }

        /** Sets the bounds this container occupied before the change in screen space */
        public void setStartAbsBounds(@Nullable Rect rect) {
            mStartAbsBounds.set(rect);
        }

        /** Sets the bounds this container will occupy after the change in screen space */
        public void setEndAbsBounds(@Nullable Rect rect) {
            mEndAbsBounds.set(rect);
        }

        /** Sets the offset of this container from its parent surface */
        public void setEndRelOffset(int left, int top) {
            mEndRelOffset.set(left, top);
        }

        /**
         * Sets the taskinfo of this container if this is a task. WARNING: this takes the reference,
         * so don't modify it afterwards.
         */
        public void setTaskInfo(@Nullable ActivityManager.RunningTaskInfo taskInfo) {
            mTaskInfo = taskInfo;
        }

        /** Sets the allowEnterPip flag which represents AppOpsManager check on PiP permission */
        public void setAllowEnterPip(boolean allowEnterPip) {
            mAllowEnterPip = allowEnterPip;
        }

        /** Sets the start and end rotation of this container. */
        public void setDisplayId(int start, int end) {
            mStartDisplayId = start;
            mEndDisplayId = end;
        }

        /** Sets the start and end rotation of this container. */
        public void setRotation(@Surface.Rotation int start, @Surface.Rotation int end) {
            mStartRotation = start;
            mEndRotation = end;
        }

        /** Sets end rotation that top activity will be launched to after fixed rotation. */
        public void setEndFixedRotation(@Surface.Rotation int endFixedRotation) {
            mEndFixedRotation = endFixedRotation;
        }

        /**
         * Sets the app-requested animation type for rotation. Will be one of the
         * ROTATION_ANIMATION_ values in {@link android.view.WindowManager.LayoutParams};
         */
        public void setRotationAnimation(int anim) {
            mRotationAnimation = anim;
        }

        /** Sets the background color of this change's container. */
        public void setBackgroundColor(@ColorInt int backgroundColor) {
            mBackgroundColor = backgroundColor;
        }

        /** Sets a snapshot surface for the "start" state of the container. */
        public void setSnapshot(@Nullable SurfaceControl snapshot, float luma) {
            mSnapshot = snapshot;
            mSnapshotLuma = luma;
        }

        /** Sets the component-name of the container. Container must be an Activity. */
        public void setActivityComponent(@Nullable ComponentName component) {
            mActivityComponent = component;
        }

        /** Sets {@link AnimationOptions} to override animation. */
        public void setAnimationOptions(@Nullable AnimationOptions options) {
            if (!Flags.moveAnimationOptionsToChange()) {
                return;
            }
            mAnimationOptions = options;
        }

        /**
         * @return the container that is changing. May be null if non-remotable (eg. activity)
         */
        @Nullable
        public WindowContainerToken getContainer() {
            return mContainer;
        }

        /**
         * @return the parent of the changing container. This is the parent within the participants,
         *     not necessarily the actual parent.
         */
        @Nullable
        public WindowContainerToken getParent() {
            return mParent;
        }

        /**
         * @return the parent of the changing container before the transition if it is reparented in
         *     the transition. The parent window may not be collected in the transition as a
         *     participant, and it may have been detached from the display. {@code null} if the
         *     changing container has not been reparented in the transition, or if the parent is not
         *     organizable.
         */
        @Nullable
        public WindowContainerToken getLastParent() {
            return mLastParent;
        }

        /**
         * @return which action this change represents.
         */
        public @TransitionMode int getMode() {
            return mMode;
        }

        /**
         * @return the flags for this change.
         */
        public @ChangeFlags int getFlags() {
            return mFlags;
        }

        /** Whether this change contains any of the given change flags. */
        public boolean hasFlags(@ChangeFlags int flags) {
            return (mFlags & flags) != 0;
        }

        /** Whether this change contains all of the given change flags. */
        public boolean hasAllFlags(@ChangeFlags int flags) {
            return (mFlags & flags) == flags;
        }

        /**
         * @return the bounds of the container before the change. It may be empty if the container
         *     is coming into existence.
         */
        @NonNull
        public Rect getStartAbsBounds() {
            return mStartAbsBounds;
        }

        /**
         * @return the bounds of the container after the change. It may be empty if the container is
         *     disappearing.
         */
        @NonNull
        public Rect getEndAbsBounds() {
            return mEndAbsBounds;
        }

        /**
         * @return the offset of the container's surface from its parent surface after the change.
         */
        @NonNull
        public Point getEndRelOffset() {
            return mEndRelOffset;
        }

        /**
         * @return the leash or surface to animate for this container
         */
        @NonNull
        public SurfaceControl getLeash() {
            return mLeash;
        }

        /**
         * @return the task info or null if this isn't a task
         */
        @Nullable
        public ActivityManager.RunningTaskInfo getTaskInfo() {
            return mTaskInfo;
        }

        public boolean isAllowEnterPip() {
            return mAllowEnterPip;
        }

        public int getStartDisplayId() {
            return mStartDisplayId;
        }

        public int getEndDisplayId() {
            return mEndDisplayId;
        }

        @Surface.Rotation
        public int getStartRotation() {
            return mStartRotation;
        }

        @Surface.Rotation
        public int getEndRotation() {
            return mEndRotation;
        }

        @Surface.Rotation
        public int getEndFixedRotation() {
            return mEndFixedRotation;
        }

        /**
         * @return the rotation animation.
         */
        public int getRotationAnimation() {
            return mRotationAnimation;
        }

        /**
         * @return get the background color of this change's container.
         */
        @ColorInt
        public int getBackgroundColor() {
            return mBackgroundColor;
        }

        /**
         * @return a snapshot surface (if applicable).
         */
        @Nullable
        public SurfaceControl getSnapshot() {
            return mSnapshot;
        }

        /**
         * @return the luma calculated for the snapshot surface (if applicable).
         */
        public float getSnapshotLuma() {
            return mSnapshotLuma;
        }

        /**
         * @return the component-name of this container (if it is an activity).
         */
        @Nullable
        public ComponentName getActivityComponent() {
            return mActivityComponent;
        }

        /** Returns the {@link AnimationOptions}. */
        @Nullable
        public AnimationOptions getAnimationOptions() {
            return mAnimationOptions;
        }

        /** @hide */
        @Override
        public void writeToParcel(@NonNull Parcel dest, int flags) {
            dest.writeTypedObject(mContainer, flags);
            dest.writeTypedObject(mParent, flags);
            dest.writeTypedObject(mLastParent, flags);
            mLeash.writeToParcel(dest, flags);
            dest.writeInt(mMode);
            dest.writeInt(mFlags);
            mStartAbsBounds.writeToParcel(dest, flags);
            mEndAbsBounds.writeToParcel(dest, flags);
            mEndRelOffset.writeToParcel(dest, flags);
            dest.writeTypedObject(mTaskInfo, flags);
            dest.writeBoolean(mAllowEnterPip);
            dest.writeInt(mStartDisplayId);
            dest.writeInt(mEndDisplayId);
            dest.writeInt(mStartRotation);
            dest.writeInt(mEndRotation);
            dest.writeInt(mEndFixedRotation);
            dest.writeInt(mRotationAnimation);
            dest.writeInt(mBackgroundColor);
            dest.writeTypedObject(mSnapshot, flags);
            dest.writeFloat(mSnapshotLuma);
            dest.writeTypedObject(mActivityComponent, flags);
            dest.writeTypedObject(mAnimationOptions, flags);
        }

        @NonNull
        public static final Creator<Change> CREATOR =
                new Creator<Change>() {
                    @Override
                    public Change createFromParcel(Parcel in) {
                        return new Change(in);
                    }

                    @Override
                    public Change[] newArray(int size) {
                        return new Change[size];
                    }
                };

        /** @hide */
        @Override
        public int describeContents() {
            return 0;
        }

        @Override
        public String toString() {
            final StringBuilder sb = new StringBuilder();
            sb.append('{');
            sb.append(mContainer);
            sb.append(" m=");
            sb.append(modeToString(mMode));
            sb.append(" f=");
            sb.append(flagsToString(mFlags));
            if (mParent != null) {
                sb.append(" p=");
                sb.append(mParent);
            }
            if (mLeash != null) {
                sb.append(" leash=");
                sb.append(mLeash);
            }
            sb.append(" sb=");
            sb.append(mStartAbsBounds);
            sb.append(" eb=");
            sb.append(mEndAbsBounds);
            if (mEndRelOffset.x != 0 || mEndRelOffset.y != 0) {
                sb.append(" eo=");
                sb.append(mEndRelOffset);
            }
            sb.append(" d=");
            if (mStartDisplayId != mEndDisplayId) {
                sb.append(mStartDisplayId).append("->");
            }
            sb.append(mEndDisplayId);
            if (mStartRotation != mEndRotation) {
                sb.append(" r=");
                sb.append(mStartRotation);
                sb.append("->");
                sb.append(mEndRotation);
                sb.append(':');
                sb.append(mRotationAnimation);
            }
            if (mEndFixedRotation != ROTATION_UNDEFINED) {
                sb.append(" endFixedRotation=");
                sb.append(mEndFixedRotation);
            }
            if (mBackgroundColor != 0) {
                sb.append(" bc=").append(Integer.toHexString(mBackgroundColor));
            }
            if (mSnapshot != null) {
                sb.append(" snapshot=");
                sb.append(mSnapshot);
            }
            if (mLastParent != null) {
                sb.append(" lastParent=");
                sb.append(mLastParent);
            }
            if (mActivityComponent != null) {
                sb.append(" component=");
                sb.append(mActivityComponent.flattenToShortString());
            }
            if (mTaskInfo != null) {
                sb.append(" taskParent=");
                sb.append(mTaskInfo.parentTaskId);
            }
            if (mAnimationOptions != null) {
                sb.append(" opt=").append(mAnimationOptions);
            }
            sb.append('}');
            return sb.toString();
        }
    }

    /** Represents animation options during a transition */
    @SuppressWarnings("UserHandleName")
    public static final class AnimationOptions implements Parcelable {

        /**
         * The default value for animation resources ID, which means to use the system default
         * animation.
         */
        @SuppressWarnings("ResourceType") // Use as a hint to use the system default animation.
        @AnimRes
        public static final int DEFAULT_ANIMATION_RESOURCES_ID = 0xFFFFFFFF;

        private int mType;
        private @AnimRes int mEnterResId = DEFAULT_ANIMATION_RESOURCES_ID;
        private @AnimRes int mChangeResId = DEFAULT_ANIMATION_RESOURCES_ID;
        private @AnimRes int mExitResId = DEFAULT_ANIMATION_RESOURCES_ID;
        private boolean mOverrideTaskTransition;
        private String mPackageName;
        private final Rect mTransitionBounds = new Rect();
        private HardwareBuffer mThumbnail;
        private int mAnimations;
        // TODO(b/295805497): Extract mBackgroundColor from AnimationOptions
        private @ColorInt int mBackgroundColor;
        // Customize activity transition animation
        private CustomActivityTransition mCustomActivityOpenTransition;
        private CustomActivityTransition mCustomActivityCloseTransition;

        private AnimationOptions(int type) {
            mType = type;
        }

        private AnimationOptions(Parcel in) {
            mType = in.readInt();
            mEnterResId = in.readInt();
            mChangeResId = in.readInt();
            mExitResId = in.readInt();
            mBackgroundColor = in.readInt();
            mOverrideTaskTransition = in.readBoolean();
            mPackageName = in.readString();
            mTransitionBounds.readFromParcel(in);
            mThumbnail = in.readTypedObject(HardwareBuffer.CREATOR);
            mAnimations = in.readInt();
            mCustomActivityOpenTransition = in.readTypedObject(CustomActivityTransition.CREATOR);
            mCustomActivityCloseTransition = in.readTypedObject(CustomActivityTransition.CREATOR);
        }

        /** Make basic customized animation for a package */
        @NonNull
        public static AnimationOptions makeCommonAnimOptions(@NonNull String packageName) {
            AnimationOptions options = new AnimationOptions(ANIM_FROM_STYLE);
            options.mPackageName = packageName;
            return options;
        }

        /** Make custom animation from the content of LayoutParams */
        @NonNull
        public static AnimationOptions makeAnimOptionsFromLayoutParameters(
                @NonNull WindowManager.LayoutParams lp) {
            AnimationOptions options = new AnimationOptions(ANIM_FROM_STYLE);
            options.mPackageName = lp.packageName;
            options.mAnimations = lp.windowAnimations;
            return options;
        }

        /** Add customized window animations */
        public void addOptionsFromLayoutParameters(@NonNull WindowManager.LayoutParams lp) {
            mAnimations = lp.windowAnimations;
        }

        /** Add customized activity animation attributes */
        public void addCustomActivityTransition(
                boolean isOpen, int enterResId, int exitResId, int backgroundColor) {
            CustomActivityTransition customTransition =
                    isOpen ? mCustomActivityOpenTransition : mCustomActivityCloseTransition;
            if (customTransition == null) {
                customTransition = new CustomActivityTransition();
                if (isOpen) {
                    mCustomActivityOpenTransition = customTransition;
                } else {
                    mCustomActivityCloseTransition = customTransition;
                }
            }
            customTransition.addCustomActivityTransition(enterResId, exitResId, backgroundColor);
        }

        /**
         * Make options for a custom animation based on anim resources.
         *
         * @param packageName the package name to find the animation resources
         * @param enterResId the open animation resources ID
         * @param exitResId the close animation resources ID
         * @param backgroundColor the background color
         * @param overrideTaskTransition whether to override the task transition
         */
        @NonNull
        public static AnimationOptions makeCustomAnimOptions(
                @NonNull String packageName,
                @AnimRes int enterResId,
                @AnimRes int exitResId,
                @ColorInt int backgroundColor,
                boolean overrideTaskTransition) {
            return makeCustomAnimOptions(
                    packageName,
                    enterResId,
                    DEFAULT_ANIMATION_RESOURCES_ID,
                    exitResId,
                    backgroundColor,
                    overrideTaskTransition);
        }

        /**
         * Creates a {@link android.app.ActivityOptions#ANIM_CUSTOM} {@link AnimationOptions}.
         *
         * @param packageName the package name that includes the animation resources.
         * @param enterResId the resources ID of open animation.
         * @param changeResId the resources ID of change animation.
         * @param exitResId the resources ID of close animation.
         * @param overrideTaskTransition indicates whether to override task transition.
         */
        @NonNull
        public static AnimationOptions makeCustomAnimOptions(
                @NonNull String packageName,
                @AnimRes int enterResId,
                @AnimRes int changeResId,
                @AnimRes int exitResId,
                @ColorInt int backgroundColor,
                boolean overrideTaskTransition) {
            AnimationOptions options = new AnimationOptions(ANIM_CUSTOM);
            options.mPackageName = packageName;
            options.mEnterResId = enterResId;
            options.mChangeResId = changeResId;
            options.mExitResId = exitResId;
            options.mBackgroundColor = backgroundColor;
            options.mOverrideTaskTransition = overrideTaskTransition;
            return options;
        }

        /** Make options for a clip-reveal animation. */
        @NonNull
        public static AnimationOptions makeClipRevealAnimOptions(
                int startX, int startY, int width, int height) {
            AnimationOptions options = new AnimationOptions(ANIM_CLIP_REVEAL);
            options.mTransitionBounds.set(startX, startY, startX + width, startY + height);
            return options;
        }

        /** Make options for a scale-up animation. */
        @NonNull
        public static AnimationOptions makeScaleUpAnimOptions(
                int startX, int startY, int width, int height) {
            AnimationOptions options = new AnimationOptions(ANIM_SCALE_UP);
            options.mTransitionBounds.set(startX, startY, startX + width, startY + height);
            return options;
        }

        /** Make options for a thumbnail-scaling animation. */
        @NonNull
        public static AnimationOptions makeThumbnailAnimOptions(
                @NonNull HardwareBuffer srcThumb, int startX, int startY, boolean scaleUp) {
            AnimationOptions options =
                    new AnimationOptions(
                            scaleUp ? ANIM_THUMBNAIL_SCALE_UP : ANIM_THUMBNAIL_SCALE_DOWN);
            options.mTransitionBounds.set(startX, startY, startX, startY);
            options.mThumbnail = srcThumb;
            return options;
        }

        /** Make options for an animation that spans activities of different profiles. */
        @NonNull
        public static AnimationOptions makeCrossProfileAnimOptions() {
            AnimationOptions options = new AnimationOptions(ANIM_OPEN_CROSS_PROFILE_APPS);
            return options;
        }

        /** Make options designating this as a scene-transition animation. */
        @NonNull
        public static AnimationOptions makeSceneTransitionAnimOptions() {
            AnimationOptions options = new AnimationOptions(ANIM_SCENE_TRANSITION);
            return options;
        }

        public int getType() {
            return mType;
        }

        @AnimRes
        public int getEnterResId() {
            return mEnterResId;
        }

        @AnimRes
        public int getChangeResId() {
            return mChangeResId;
        }

        @AnimRes
        public int getExitResId() {
            return mExitResId;
        }

        public @ColorInt int getBackgroundColor() {
            return mBackgroundColor;
        }

        public boolean getOverrideTaskTransition() {
            return mOverrideTaskTransition;
        }

        @Nullable
        public String getPackageName() {
            return mPackageName;
        }

        @NonNull
        public Rect getTransitionBounds() {
            return mTransitionBounds;
        }

        @Nullable
        public HardwareBuffer getThumbnail() {
            return mThumbnail;
        }

        public int getAnimations() {
            return mAnimations;
        }

        /** Return customized activity transition if existed. */
        @Nullable
        public CustomActivityTransition getCustomActivityTransition(boolean open) {
            return open ? mCustomActivityOpenTransition : mCustomActivityCloseTransition;
        }

        @Override
        public void writeToParcel(@NonNull Parcel dest, int flags) {
            dest.writeInt(mType);
            dest.writeInt(mEnterResId);
            dest.writeInt(mChangeResId);
            dest.writeInt(mExitResId);
            dest.writeInt(mBackgroundColor);
            dest.writeBoolean(mOverrideTaskTransition);
            dest.writeString(mPackageName);
            mTransitionBounds.writeToParcel(dest, flags);
            dest.writeTypedObject(mThumbnail, flags);
            dest.writeInt(mAnimations);
            dest.writeTypedObject(mCustomActivityOpenTransition, flags);
            dest.writeTypedObject(mCustomActivityCloseTransition, flags);
        }

        @NonNull
        public static final Creator<AnimationOptions> CREATOR =
                new Creator<AnimationOptions>() {
                    @Override
                    public AnimationOptions createFromParcel(Parcel in) {
                        return new AnimationOptions(in);
                    }

                    @Override
                    public AnimationOptions[] newArray(int size) {
                        return new AnimationOptions[size];
                    }
                };

        /** @hide */
        @Override
        public int describeContents() {
            return 0;
        }

        @NonNull
        private static String typeToString(int mode) {
            return switch (mode) {
                case ANIM_CUSTOM -> "CUSTOM";
                case ANIM_SCALE_UP -> "SCALE_UP";
                case ANIM_THUMBNAIL_SCALE_UP -> "THUMBNAIL_SCALE_UP";
                case ANIM_THUMBNAIL_SCALE_DOWN -> "THUMBNAIL_SCALE_DOWN";
                case ANIM_SCENE_TRANSITION -> "SCENE_TRANSITION";
                case ANIM_CLIP_REVEAL -> "CLIP_REVEAL";
                case ANIM_OPEN_CROSS_PROFILE_APPS -> "OPEN_CROSS_PROFILE_APPS";
                case ANIM_FROM_STYLE -> "FROM_STYLE";
                default -> "<" + mode + ">";
            };
        }

        @Override
        @NonNull
        public String toString() {
            final StringBuilder sb = new StringBuilder(32);
            sb.append("{t=").append(typeToString(mType));
            if (mOverrideTaskTransition) {
                sb.append(" overrideTask=true");
            }
            if (!mTransitionBounds.isEmpty()) {
                sb.append(" bounds=").append(mTransitionBounds);
            }
            if (mEnterResId != DEFAULT_ANIMATION_RESOURCES_ID) {
                sb.append(" enterResId=").append(mEnterResId);
            }
            if (mChangeResId != DEFAULT_ANIMATION_RESOURCES_ID) {
                sb.append(" changeResId=").append(mChangeResId);
            }
            if (mExitResId != DEFAULT_ANIMATION_RESOURCES_ID) {
                sb.append(" exitResId=").append(mExitResId);
            }
            sb.append('}');
            return sb.toString();
        }

        /** Customized activity transition. */
        public static final class CustomActivityTransition implements Parcelable {
            private int mCustomEnterResId;
            private int mCustomExitResId;
            private int mCustomBackgroundColor;

            /** Returns customize activity animation enter resource id */
            public int getCustomEnterResId() {
                return mCustomEnterResId;
            }

            /** Returns customize activity animation exit resource id */
            public int getCustomExitResId() {
                return mCustomExitResId;
            }

            /** Returns customize activity animation background color */
            public int getCustomBackgroundColor() {
                return mCustomBackgroundColor;
            }

            CustomActivityTransition() {}

            CustomActivityTransition(Parcel in) {
                mCustomEnterResId = in.readInt();
                mCustomExitResId = in.readInt();
                mCustomBackgroundColor = in.readInt();
            }

            /** Add customized activity animation attributes */
            public void addCustomActivityTransition(
                    int enterResId, int exitResId, int backgroundColor) {
                mCustomEnterResId = enterResId;
                mCustomExitResId = exitResId;
                mCustomBackgroundColor = backgroundColor;
            }

            @Override
            public int describeContents() {
                return 0;
            }

            @Override
            public void writeToParcel(@NonNull Parcel dest, int flags) {
                dest.writeInt(mCustomEnterResId);
                dest.writeInt(mCustomExitResId);
                dest.writeInt(mCustomBackgroundColor);
            }

            @NonNull
            public static final Creator<CustomActivityTransition> CREATOR =
                    new Creator<CustomActivityTransition>() {
                        @Override
                        public CustomActivityTransition createFromParcel(Parcel in) {
                            return new CustomActivityTransition(in);
                        }

                        @Override
                        public CustomActivityTransition[] newArray(int size) {
                            return new CustomActivityTransition[size];
                        }
                    };
        }
    }

    /**
     * An animation root in a transition. There is one of these for each display that contains
     * participants. It will be placed, in z-order, right above the top-most participant and at the
     * same position in the hierarchy. As a result, if all participants are animating within a part
     * of the screen, the root-leash will only be in that part of the screen. In these cases, it's
     * relative position (from the screen) is stored in {@link Root#getOffset}.
     */
    public static final class Root implements Parcelable {
        private final int mDisplayId;
        private final SurfaceControl mLeash;
        private final Point mOffset = new Point();

        public Root(int displayId, @NonNull SurfaceControl leash, int offsetLeft, int offsetTop) {
            mDisplayId = displayId;
            mLeash = leash;
            mOffset.set(offsetLeft, offsetTop);
        }

        private Root(Parcel in) {
            mDisplayId = in.readInt();
            mLeash = new SurfaceControl();
            mLeash.readFromParcel(in);
            mLeash.setUnreleasedWarningCallSite("TransitionInfo.Root");
            mOffset.readFromParcel(in);
        }

        private Root localRemoteCopy() {
            return new Root(
                    mDisplayId, new SurfaceControl(mLeash, "localRemote"), mOffset.x, mOffset.y);
        }

        /**
         * @return the id of the display this root is on.
         */
        public int getDisplayId() {
            return mDisplayId;
        }

        /**
         * @return the root's leash. Surfaces should be parented to this while animating.
         */
        @NonNull
        public SurfaceControl getLeash() {
            return mLeash;
        }

        /**
         * @return the offset (relative to its screen) of the root leash.
         */
        @NonNull
        public Point getOffset() {
            return mOffset;
        }

        /** @hide */
        @Override
        public void writeToParcel(@NonNull Parcel dest, int flags) {
            dest.writeInt(mDisplayId);
            mLeash.writeToParcel(dest, flags);
            mOffset.writeToParcel(dest, flags);
        }

        @NonNull
        public static final Creator<Root> CREATOR =
                new Creator<Root>() {
                    @Override
                    public Root createFromParcel(Parcel in) {
                        return new Root(in);
                    }

                    @Override
                    public Root[] newArray(int size) {
                        return new Root[size];
                    }
                };

        /** @hide */
        @Override
        public int describeContents() {
            return 0;
        }

        @Override
        public String toString() {
            return mDisplayId + "@" + mOffset + ":" + mLeash;
        }
    }
}
