/*
 * 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.Instrumentation.DEBUG_START_ACTIVITY;
import static android.app.TaskInfo.SELF_MOVABLE_UNSET;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.app.WindowConfiguration.windowingModeToString;
import static android.window.TaskFragmentOperation.OP_TYPE_CLEAR_ADJACENT_TASK_FRAGMENTS;
import static android.window.TaskFragmentOperation.OP_TYPE_CREATE_TASK_FRAGMENT;
import static android.window.TaskFragmentOperation.OP_TYPE_DELETE_TASK_FRAGMENT;
import static android.window.TaskFragmentOperation.OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT;
import static android.window.TaskFragmentOperation.OP_TYPE_REQUEST_FOCUS_ON_TASK_FRAGMENT;
import static android.window.TaskFragmentOperation.OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS;
import static android.window.TaskFragmentOperation.OP_TYPE_SET_COMPANION_TASK_FRAGMENT;
import static android.window.TaskFragmentOperation.OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT;

import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.annotation.TestApi;
import android.app.Instrumentation;
import android.app.PendingIntent;
import android.app.TaskInfo.SelfMovable;
import android.app.WindowConfiguration;
import android.app.WindowConfiguration.WindowingMode;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.ShortcutInfo;
import android.content.res.Configuration;
import android.graphics.Insets;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.ArrayMap;
import android.util.Log;
import android.view.InsetsFrameProvider;
import android.view.InsetsSource;
import android.view.SurfaceControl;
import android.view.WindowInsets;
import android.view.WindowInsets.Type.InsetsType;

import com.android.window.flags.Flags;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Objects;

/**
 * Represents a collection of operations on some WindowContainers that should be applied all at
 * once.
 *
 * @hide
 */
@TestApi
public final class WindowContainerTransaction implements Parcelable {
    private final ArrayMap<IBinder, Change> mChanges = new ArrayMap<>();

    // Flat list because re-order operations are order-dependent
    private final ArrayList<HierarchyOp> mHierarchyOps = new ArrayList<>();

    @Nullable
    private IBinder mErrorCallbackToken;

    @Nullable
    private ITaskFragmentOrganizer mTaskFragmentOrganizer;

    public WindowContainerTransaction() {}

    private WindowContainerTransaction(@NonNull Parcel in) {
        in.readMap(mChanges, null /* loader */);
        in.readTypedList(mHierarchyOps, HierarchyOp.CREATOR);
        mErrorCallbackToken = in.readStrongBinder();
        mTaskFragmentOrganizer = ITaskFragmentOrganizer.Stub.asInterface(in.readStrongBinder());
    }

    @NonNull
    private Change getOrCreateChange(IBinder token) {
        Change out = mChanges.get(token);
        if (out == null) {
            out = new Change();
            mChanges.put(token, out);
        }
        return out;
    }

    /**
     * Clear the transaction object.
     * This is equivalent to a new empty {@link WindowContainerTransaction} in content.
     *
     * @hide
     */
    public void clear() {
        mChanges.clear();
        mHierarchyOps.clear();
        mErrorCallbackToken = null;
        mTaskFragmentOrganizer = null;
    }

    /*
     * ===========================================================================================
     * Window container properties
     * ===========================================================================================
     */

    /**
     * Resize a container.
     */
    @NonNull
    public WindowContainerTransaction setBounds(
            @NonNull WindowContainerToken container, @NonNull Rect bounds) {
        final Change chg = getOrCreateChange(container.asBinder());
        chg.mConfiguration.windowConfiguration.setBounds(bounds);
        chg.mConfigSetMask |= ActivityInfo.CONFIG_WINDOW_CONFIGURATION;
        chg.mWindowSetMask |= WindowConfiguration.WINDOW_CONFIG_BOUNDS;
        return this;
    }

    /**
     * Resize a container's app bounds. This is the bounds used to report appWidth/Height to an
     * app's DisplayInfo. It is derived by subtracting the overlapping portion of the navbar from
     * the full bounds.
     */
    @NonNull
    public WindowContainerTransaction setAppBounds(
            @NonNull WindowContainerToken container, @Nullable Rect appBounds) {
        final Change chg = getOrCreateChange(container.asBinder());
        chg.mConfiguration.windowConfiguration.setAppBounds(appBounds);
        chg.mConfigSetMask |= ActivityInfo.CONFIG_WINDOW_CONFIGURATION;
        chg.mWindowSetMask |= WindowConfiguration.WINDOW_CONFIG_APP_BOUNDS;
        return this;
    }

    /**
     * Resize a container's configuration size. The configuration size is what gets reported to the
     * app via screenWidth/HeightDp and influences which resources get loaded. This size is
     * derived by subtracting the overlapping portions of both the statusbar and the navbar from
     * the full bounds.
     */
    @NonNull
    public WindowContainerTransaction setScreenSizeDp(
            @NonNull WindowContainerToken container, int w, int h) {
        final Change chg = getOrCreateChange(container.asBinder());
        chg.mConfiguration.screenWidthDp = w;
        chg.mConfiguration.screenHeightDp = h;
        chg.mConfigSetMask |= ActivityInfo.CONFIG_SCREEN_SIZE;
        return this;
    }

    /**
     * Sets the densityDpi value in the configuration for the given container.
     * @hide
     */
    @NonNull
    public WindowContainerTransaction setDensityDpi(@NonNull WindowContainerToken container,
            int densityDpi) {
        final Change chg = getOrCreateChange(container.asBinder());
        chg.mConfiguration.densityDpi = densityDpi;
        chg.mConfigSetMask |= ActivityInfo.CONFIG_DENSITY;
        return this;
    }

    /**
     * Send a SurfaceControl transaction to the server, which the server will apply in sync with
     * the next bounds change. As this uses deferred transaction and not BLAST it is only
     * able to sync with a single window, and the first visible window in this hierarchy of type
     * BASE_APPLICATION to resize will be used. If there are bound changes included in this
     * WindowContainer transaction (from setBounds or scheduleFinishEnterPip), the SurfaceControl
     * transaction will be synced with those bounds. If there are no changes, then
     * the SurfaceControl transaction will be synced with the next bounds change. This means
     * that you can call this, apply the WindowContainer transaction, and then later call
     * dismissPip() to achieve synchronization.
     */
    @NonNull
    public WindowContainerTransaction setBoundsChangeTransaction(
            @NonNull WindowContainerToken container, @NonNull SurfaceControl.Transaction t) {
        final Change chg = getOrCreateChange(container.asBinder());
        chg.mBoundsChangeTransaction = t;
        chg.mChangeMask |= Change.CHANGE_BOUNDS_TRANSACTION;
        return this;
    }

    /**
     * Set the windowing mode of children of a given root task, without changing
     * the windowing mode of the Task itself. This can be used during transitions
     * for example to make the activity render it's fullscreen configuration
     * while the Task is still in PIP, so you can complete the animation.
     *
     * TODO(b/134365562): Can be removed once TaskOrg drives full-screen
     */
    @NonNull
    public WindowContainerTransaction setActivityWindowingMode(
            @NonNull WindowContainerToken container, @WindowingMode int windowingMode) {
        final Change chg = getOrCreateChange(container.asBinder());
        chg.mActivityWindowingMode = windowingMode;
        return this;
    }

    /**
     * Sets the windowing mode of the given container.
     */
    @NonNull
    public WindowContainerTransaction setWindowingMode(
            @NonNull WindowContainerToken container, @WindowingMode int windowingMode) {
        final Change chg = getOrCreateChange(container.asBinder());
        chg.mWindowingMode = windowingMode;
        return this;
    }

    /**
     * Sets whether the container should launch next as Bubble
     * @hide
     */
    @NonNull
    public WindowContainerTransaction setLaunchNextToBubble(
            @NonNull WindowContainerToken container, boolean launchNextToBubble) {
        final Change chg = getOrCreateChange(container.asBinder());
        chg.mLaunchNextToBubble = launchNextToBubble;
        chg.mChangeMask |= Change.CHANGE_LAUNCH_NEXT_TO_BUBBLE;
        return this;
    }

    /**
     * Sets whether a container or any of its children can be focusable. When {@code false}, no
     * child can be focused; however, when {@code true}, it is still possible for children to be
     * non-focusable due to WM policy.
     */
    @NonNull
    public WindowContainerTransaction setFocusable(
            @NonNull WindowContainerToken container, boolean focusable) {
        final Change chg = getOrCreateChange(container.asBinder());
        chg.mFocusable = focusable;
        chg.mChangeMask |= Change.CHANGE_FOCUSABLE;
        return this;
    }

    /**
     * Sets whether the IME insets should be excluded by {@link com.android.server.wm.InsetsPolicy}.
     * @hide
     */
    @SuppressLint("UnflaggedApi")
    @NonNull
    public WindowContainerTransaction setExcludeImeInsets(
            @NonNull WindowContainerToken container, boolean exclude) {
        final HierarchyOp hierarchyOp =
                new HierarchyOp.Builder(HierarchyOp.HIERARCHY_OP_TYPE_SET_EXCLUDE_INSETS_TYPES)
                        .setContainer(container.asBinder())
                        .setExcludeInsetsTypes(exclude ? WindowInsets.Type.ime() : 0)
                        .build();
        mHierarchyOps.add(hierarchyOp);
        return this;
    }

    /**
     * Sets the forcibly showing and hiding types of system bars of the display.
     * @hide
     */
    @NonNull
    public WindowContainerTransaction setSystemBarVisibilityOverride(
            @NonNull WindowContainerToken display,
            @NonNull IBinder caller,
            @InsetsType int forciblyShowingInsetsTypes,
            @InsetsType int forciblyHidingInsetsTypes) {
        final int forciblyShowingAndHidingTypes =
                forciblyShowingInsetsTypes & forciblyHidingInsetsTypes;
        if (forciblyShowingAndHidingTypes != 0) {
            throw new IllegalArgumentException(
                    WindowInsets.Type.toString(forciblyShowingAndHidingTypes)
                            + " cannot be forcibly shown and hidden at the same time.");
        }
        final HierarchyOp hierarchyOp = new HierarchyOp.Builder(
                HierarchyOp.HIERARCHY_OP_TYPE_SET_SYSTEM_BAR_VISIBILITY_OVERRIDE)
                .setContainer(display.asBinder())
                .setCaller(caller)
                .setSystemBarVisibilityOverride(
                        forciblyShowingInsetsTypes, forciblyHidingInsetsTypes)
                .build();
        mHierarchyOps.add(hierarchyOp);
        return this;
    }

    /**
     * Sets whether to allow the child tasks to have override bounds.
     *
     * <p>When {@code true}, the system will ensure the child tasks of the given root task
     * will have no override bounds. That is, the override bounds of the existing child tasks
     * will be cleared, and the override bounds of any newly added child tasks afterward will
     * also be cleared. This mechanism is specifically designed to be applied to a root task
     * created by an organizer only.
     *
     * @param rootTaskContainer The window container of the task that created by organizer.
     * @param disallowOverrideBoundsForChildren {@code true} to avoid the child tasks to have
     *                                                   override bounds, {@code false} otherwise.
     * @hide
     */
    @NonNull
    public WindowContainerTransaction setDisallowOverrideBoundsForChildren(
            @NonNull WindowContainerToken rootTaskContainer,
            boolean disallowOverrideBoundsForChildren) {
        final HierarchyOp hierarchyOp = new HierarchyOp.Builder(
                HierarchyOp.HIERARCHY_OP_TYPE_DISALLOW_OVERRIDE_BOUNDS_FOR_CHILDREN)
                .setContainer(rootTaskContainer.asBinder())
                .setDisallowOverrideBoundsForChildren(disallowOverrideBoundsForChildren)
                .build();
        mHierarchyOps.add(hierarchyOp);
        return this;
    }

    /**
     * Sets whether a container or its children should be hidden. When {@code false}, the existing
     * visibility of the container applies, but when {@code true} the container will be forced
     * to be hidden.
     */
    @NonNull
    public WindowContainerTransaction setHidden(
            @NonNull WindowContainerToken container, boolean hidden) {
        final Change chg = getOrCreateChange(container.asBinder());
        chg.mHidden = hidden;
        chg.mChangeMask |= Change.CHANGE_HIDDEN;
        return this;
    }

    /**
     * Set the smallestScreenWidth of a container.
     */
    @NonNull
    public WindowContainerTransaction setSmallestScreenWidthDp(
            @NonNull WindowContainerToken container, int widthDp) {
        final Change cfg = getOrCreateChange(container.asBinder());
        cfg.mConfiguration.smallestScreenWidthDp = widthDp;
        cfg.mConfigSetMask |= ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE;
        return this;
    }

    /**
     * Sets whether a container should ignore the orientation request from apps and windows below
     * it. It currently only applies to {@link com.android.server.wm.DisplayArea}. When
     * {@code false}, it may rotate based on the orientation request; When {@code true}, it can
     * never specify orientation, but shows the fixed-orientation apps below it in the letterbox.
     * @hide
     */
    @NonNull
    public WindowContainerTransaction setIgnoreOrientationRequest(
            @NonNull WindowContainerToken container, boolean ignoreOrientationRequest) {
        final Change chg = getOrCreateChange(container.asBinder());
        chg.mIgnoreOrientationRequest = ignoreOrientationRequest;
        chg.mChangeMask |= Change.CHANGE_IGNORE_ORIENTATION_REQUEST;
        return this;
    }

    /**
     * Sets whether a task should be translucent. When {@code false}, the existing translucent of
     * the task applies, but when {@code true} the task will be forced to be translucent.
     * @hide
     */
    @NonNull
    public WindowContainerTransaction setForceTranslucent(
            @NonNull WindowContainerToken container, boolean forceTranslucent) {
        final Change chg = getOrCreateChange(container.asBinder());
        chg.mForceTranslucent = forceTranslucent;
        chg.mChangeMask |= Change.CHANGE_FORCE_TRANSLUCENT;
        return this;
    }

    /**
     * Resizes a container by providing a bounds in its parent coordinate.
     * This is only used by {@link TaskFragmentOrganizer}.
     */
    @NonNull
    public WindowContainerTransaction setRelativeBounds(
            @NonNull WindowContainerToken container, @NonNull Rect relBounds) {
        final Change chg = getOrCreateChange(container.asBinder());
        if (chg.mRelativeBounds == null) {
            chg.mRelativeBounds = new Rect();
        }
        chg.mRelativeBounds.set(relBounds);
        chg.mChangeMask |= Change.CHANGE_RELATIVE_BOUNDS;
        // Bounds will be overridden.
        chg.mConfigSetMask |= ActivityInfo.CONFIG_WINDOW_CONFIGURATION;
        chg.mWindowSetMask |= WindowConfiguration.WINDOW_CONFIG_BOUNDS;
        return this;
    }

    /**
     * Sets whether a container is being drag-resized.
     * When {@code true}, the client will reuse a single (larger) surface size to avoid
     * continuous allocations on every size change.
     *
     * @param container WindowContainerToken of the task that changed its drag resizing state
     * @hide
     */
    @NonNull
    public WindowContainerTransaction setDragResizing(@NonNull WindowContainerToken container,
            boolean dragResizing) {
        final Change change = getOrCreateChange(container.asBinder());
        change.mChangeMask |= Change.CHANGE_DRAG_RESIZING;
        change.mDragResizing = dragResizing;
        return this;
    }

    /**
     * Sets/removes the always on top flag for this {@code windowContainer}. See
     * {@link com.android.server.wm.ConfigurationContainer#setAlwaysOnTop(boolean)}.
     * Please note that this method is only intended to be used for a
     * {@link com.android.server.wm.Task} or {@link com.android.server.wm.DisplayArea}.
     *
     * <p>
     *     Setting always on top to {@code True} will also make the {@code windowContainer} to move
     *     to the top.
     * </p>
     * <p>
     *     Setting always on top to {@code False} will make this {@code windowContainer} to move
     *     below the other always on top sibling containers.
     * </p>
     *
     * @param windowContainer the container which the flag need to be updated for.
     * @param alwaysOnTop denotes whether or not always on top flag should be set.
     * @hide
     */
    @NonNull
    public WindowContainerTransaction setAlwaysOnTop(
            @NonNull WindowContainerToken windowContainer, boolean alwaysOnTop) {
        final HierarchyOp hierarchyOp =
                new HierarchyOp.Builder(
                        HierarchyOp.HIERARCHY_OP_TYPE_SET_ALWAYS_ON_TOP)
                        .setContainer(windowContainer.asBinder())
                        .setAlwaysOnTop(alwaysOnTop)
                        .build();
        mHierarchyOps.add(hierarchyOp);
        return this;
    }

    /**
     * Sets/removes the reparent leaf task flag for this {@code windowContainer}.
     * When this is set, the server side will try to reparent the leaf task to task display area
     * if there is an existing activity in history during the activity launch. This operation only
     * support on the organized root task.
     * @hide
     */
    @NonNull
    public WindowContainerTransaction setReparentLeafTaskIfRelaunch(
            @NonNull WindowContainerToken windowContainer, boolean reparentLeafTaskIfRelaunch) {
        final HierarchyOp hierarchyOp =
                new HierarchyOp.Builder(
                        HierarchyOp.HIERARCHY_OP_TYPE_SET_REPARENT_LEAF_TASK_IF_RELAUNCH)
                        .setContainer(windowContainer.asBinder())
                        .setReparentLeafTaskIfRelaunch(reparentLeafTaskIfRelaunch)
                        .build();
        mHierarchyOps.add(hierarchyOp);
        return this;
    }

    /**
     * Defers client-facing configuration changes for activities in `container` until the end of
     * the transition animation. The configuration will still be applied to the WMCore hierarchy
     * at the normal time (beginning); so, special consideration must be made for this in the
     * animation.
     *
     * @param container WindowContainerToken who's children should defer config notification.
     * @hide
     */
    @NonNull
    public WindowContainerTransaction deferConfigToTransitionEnd(
            @NonNull WindowContainerToken container) {
        final Change change = getOrCreateChange(container.asBinder());
        change.mConfigAtTransitionEnd = true;
        return this;
    }

    /**
     * Sets the task as trimmable or not. This can be used to prevent the task from being trimmed by
     * recents. This attribute is set to true on task creation by default.
     *
     * @param isTrimmableFromRecents When {@code true}, task is set as trimmable from recents.
     * @hide
     */
    @NonNull
    public WindowContainerTransaction setTaskTrimmableFromRecents(
            @NonNull WindowContainerToken container,
            boolean isTrimmableFromRecents) {
        mHierarchyOps.add(
                HierarchyOp.createForSetTaskTrimmableFromRecents(container.asBinder(),
                        isTrimmableFromRecents));
        return this;
    }

    /**
     * Sets a given safe region {@code Rect} on the {@code container}. Set {@code null} to reset
     * safe region bounds. When a safe region is set on a WindowContainer, the activities which
     * need to be within a safe region will be letterboxed within the set safe region bounds.
     * <p>Note that if the position of the WindowContainer changes, the caller needs to update the
     * safe region bounds.
     *
     * @param container        The window container that the safe region bounds are set on
     * @param safeRegionBounds The rect for the safe region bounds which are absolute in nature.
     * @hide
     */
    @NonNull
    @FlaggedApi(Flags.FLAG_SAFE_REGION_LETTERBOXING_V1)
    public WindowContainerTransaction setSafeRegionBounds(
            @NonNull WindowContainerToken container,
            @Nullable Rect safeRegionBounds) {
        mHierarchyOps.add(
                HierarchyOp.createForSetSafeRegionBounds(container.asBinder(), safeRegionBounds));
        return this;
    }

    /**
     * Sets whether the task should be forcibly excluded from Recents.
     *
     * @param container The window container of the task that the exclusion state is set on.
     * @param forceExcluded  {@code true} to force exclude the task, {@code false} otherwise.
     * @throws IllegalStateException if the flag {@link Flags#FLAG_EXCLUDE_TASK_FROM_RECENTS} is
     *                               not enabled.
     * @hide
     */
    @NonNull
    public WindowContainerTransaction setTaskForceExcludedFromRecents(
            @NonNull WindowContainerToken container, boolean forceExcluded) {
        final Change chg = getOrCreateChange(container.asBinder());
        chg.mChangeMask |= Change.CHANGE_FORCE_EXCLUDED_FROM_RECENTS;
        chg.mForceExcludedFromRecents = forceExcluded;
        return this;
    }

    /**
     * Sets whether the given container can be repositioned by {@link
     * android.app.ActivityManager.AppTask#moveTaskTo}.
     * Note that there are additional permission checks for the caller of {@link
     * android.app.ActivityManager.AppTask#moveTaskTo}.
     *
     * @param container The window container of the task that the self-movable state is set on.
     * @param selfMovable {@link android.app.TaskInfo#SELF_MOVABLE_ALLOWED} or {@link
     *     android.app.TaskInfo#SELF_MOVABLE_DENIED} to set the task as self-movable or not, {@link
     *     android.app.TaskInfo#SELF_MOVABLE_DEFAULT} to let the WM Core decide.
     * @hide
     */
    @NonNull
    public WindowContainerTransaction setSelfMovable(
            @NonNull WindowContainerToken container, @SelfMovable int selfMovable) {
        final Change change = getOrCreateChange(container.asBinder());
        change.mSelfMovable = selfMovable;
        return this;
    }

    /**
     * Sets whether the given container is able to contain self-movable tasks. A display is
     * considered able to contain self-movable tasks as long as there is one child window container
     * that is able to contain self-movable tasks.
     *
     * <p>Initially after each boot-up no window containers can contain self-movable tasks.
     *
     * <p>The container must be either a TaskDisplayArea or a root Task for this setting to have
     * effect.
     *
     * @param container The window container whose ability to contain self-movable tasks is set on.
     * @param isTaskMoveAllowed {@code true} to allow containing self-movable tasks, {@code
     *     false} otherwise.
     * @hide
     */
    @NonNull
    public WindowContainerTransaction setIsTaskMoveAllowed(
            @NonNull WindowContainerToken container, boolean isTaskMoveAllowed) {
        final Change change = getOrCreateChange(container.asBinder());
        change.mChangeMask |= Change.CHANGE_IS_TASK_MOVE_ALLOWED;
        change.mIsTaskMoveAllowed = isTaskMoveAllowed;
        return this;
    }

    /**
     * Sets whether back press should be intercepted for the root activity of the given root task
     * or its children.
     *
     * <p>When {@code true}, the system will invoke
     * {@link TaskOrganizer#onBackPressedOnTaskRoot(ActivityManager.RunningTaskInfo)}, providing
     * the {@link ActivityManager.RunningTaskInfo} of the task that received the back press.
     * This interception mechanism is specifically designed to be applied to the root task
     * container only.
     *
     * @param rootTaskContainer The window container of the task that the intercept-back state is
     *                          set on. This parameter is expected to refer to the root task of a
     *                          task stack.
     * @param interceptBackPressed {@code true} to allow back to be intercepted for the root
     *                             activity of the task, {@code false} otherwise.
     * @hide
     */
    @NonNull
    public WindowContainerTransaction setInterceptBackPressedOnTaskRoot(
            @NonNull WindowContainerToken rootTaskContainer,
            boolean interceptBackPressed) {
        final Change change = getOrCreateChange(rootTaskContainer.asBinder());
        change.mChangeMask |= Change.CHANGE_INTERCEPT_BACK_PRESSED;
        change.mInterceptBackPressed = interceptBackPressed;
        return this;
    }

    /*
     * ===========================================================================================
     * Hierarchy updates (create/destroy/reorder/reparent containers)
     * ===========================================================================================
     */

    /**
     * Reorders a container within its parent.
     *
     * @param onTop When {@code true}, the child goes to the top of parent; otherwise it goes to
     *              the bottom.
     */
    @NonNull
    public WindowContainerTransaction reorder(@NonNull WindowContainerToken child, boolean onTop) {
        return reorder(child, onTop, false /* includingParents */);
    }

    /**
     * Reorders a container within its parent with an option to reorder all the parents in the
     * hierarchy above among their respective siblings.
     *
     * @param onTop When {@code true}, the child goes to the top of parent; otherwise it goes to
     *              the bottom.
     * @param includingParents When {@code true}, all the parents in the hierarchy above are also
     *                         reordered among their respective siblings.
     * @hide
     */
    @NonNull
    public WindowContainerTransaction reorder(@NonNull WindowContainerToken child, boolean onTop,
            boolean includingParents) {
        mHierarchyOps.add(HierarchyOp.createForReorder(child.asBinder(), onTop, includingParents));
        return this;
    }

    /**
     * Reparents a container into another one. The effect of a {@code null} parent can vary. For
     * example, reparenting a stack to {@code null} will reparent it to its display.
     *
     * @param onTop When {@code true}, the child goes to the top of parent; otherwise it goes to
     *              the bottom.
     */
    @NonNull
    public WindowContainerTransaction reparent(@NonNull WindowContainerToken child,
            @Nullable WindowContainerToken parent, boolean onTop) {
        mHierarchyOps.add(HierarchyOp.createForReparent(child.asBinder(),
                parent == null ? null : parent.asBinder(),
                onTop));
        return this;
    }

    /**
     * Reparent's all children tasks or the top task of {@param currentParent} in the specified
     * overridden {@param windowingMode} and {@param activityType} to {@param newParent} in their
     * current z-order.
     *
     * @param currentParent of the tasks to perform the operation no.
     *                      {@code null} will perform the operation on the display.
     * @param newParent for the tasks. {@code null} will perform the operation on the display.
     * @param windowingModes of the tasks to reparent.
     * @param activityTypes of the tasks to reparent.
     * @param onTop When {@code true}, the child goes to the top of parent; otherwise it goes to
     *              the bottom.
     * @param reparentTopOnly When {@code true}, only reparent the top task which fit windowingModes
     *                        and activityTypes.
     * @hide
     */
    @NonNull
    public WindowContainerTransaction reparentTasks(@Nullable WindowContainerToken currentParent,
            @Nullable WindowContainerToken newParent, @Nullable int[] windowingModes,
            @Nullable int[] activityTypes, boolean onTop, boolean reparentTopOnly) {
        mHierarchyOps.add(HierarchyOp.createForChildrenTasksReparent(
                currentParent != null ? currentParent.asBinder() : null,
                newParent != null ? newParent.asBinder() : null,
                windowingModes,
                activityTypes,
                onTop,
                reparentTopOnly));
        return this;
    }

    /**
     * Reparent's all children tasks of {@param currentParent} in the specified
     * {@param windowingMode} and {@param activityType} to {@param newParent} in their current
     * z-order.
     *
     * @param currentParent of the tasks to perform the operation no.
     *                      {@code null} will perform the operation on the display.
     * @param newParent for the tasks. {@code null} will perform the operation on the display.
     * @param windowingModes of the tasks to reparent. {@code null} ignore this attribute when
     *                       perform the operation.
     * @param activityTypes of the tasks to reparent.  {@code null} ignore this attribute when
     *                      perform the operation.
     * @param onTop When {@code true}, the child goes to the top of parent; otherwise it goes to
     *              the bottom.
     */
    @NonNull
    public WindowContainerTransaction reparentTasks(@Nullable WindowContainerToken currentParent,
            @Nullable WindowContainerToken newParent, @Nullable int[] windowingModes,
            @Nullable int[] activityTypes, boolean onTop) {
        return reparentTasks(currentParent, newParent, windowingModes, activityTypes, onTop,
                false /* reparentTopOnly */);
    }

    /**
     * Finds and removes a task and its children using its container token. The task is removed
     * from recents.
     *
     * <p>If the task is a root task, its leaves are removed but the root task is not. Use
     * {@link #removeRootTask(WindowContainerToken)} to remove the root task.
     *
     * @param containerToken ContainerToken of Task to be removed
     */
    @NonNull
    public WindowContainerTransaction removeTask(@NonNull WindowContainerToken containerToken) {
        mHierarchyOps.add(HierarchyOp.createForRemoveTask(containerToken.asBinder()));
        return this;
    }

    /**
     * Finds and removes a root task created by an organizer and its leaves using its container
     * token.
     *
     * @param containerToken ContainerToken of the root task to be removed
     * @hide
     */
    @NonNull
    public WindowContainerTransaction removeRootTask(@NonNull WindowContainerToken containerToken) {
        mHierarchyOps.add(HierarchyOp.createForRemoveRootTask(containerToken.asBinder()));
        return this;
    }

    /**
     * If `container` was brought to front as a transient-launch (eg. recents), this will reorder
     * the container back to where it was prior to the transient-launch. This way if a transient
     * launch is "aborted", the z-ordering of containers in WM should be restored to before the
     * launch.
     * @hide
     */
    @NonNull
    public WindowContainerTransaction restoreTransientOrder(
            @NonNull WindowContainerToken container) {
        final HierarchyOp hierarchyOp =
                new HierarchyOp.Builder(HierarchyOp.HIERARCHY_OP_TYPE_RESTORE_TRANSIENT_ORDER)
                        .setContainer(container.asBinder())
                        .build();
        mHierarchyOps.add(hierarchyOp);
        return this;
    }

    /**
     * Restore the back navigation target from visible to invisible for canceling gesture animation.
     * @hide
     */
    @NonNull
    public WindowContainerTransaction restoreBackNavi() {
        final HierarchyOp hierarchyOp =
                new HierarchyOp.Builder(HierarchyOp.HIERARCHY_OP_TYPE_RESTORE_BACK_NAVIGATION)
                        .build();
        mHierarchyOps.add(hierarchyOp);
        return this;
    }

    /*
     * ===========================================================================================
     * Activity launch
     * ===========================================================================================
     */

    /**
     * Starts a task by id. The task is expected to already exist (eg. as a recent task).
     * @param taskId Id of task to start.
     * @param options bundle containing ActivityOptions for the task's top activity.
     * @hide
     */
    @NonNull
    public WindowContainerTransaction startTask(int taskId, @Nullable Bundle options) {
        if (DEBUG_START_ACTIVITY) {
            Log.d(Instrumentation.TAG, "WCT.startTask: taskId=" + taskId
                    + " options=" + options, new Throwable());
        }
        mHierarchyOps.add(HierarchyOp.createForTaskLaunch(taskId, options));
        return this;
    }

    /**
     * Sends a pending intent in sync.
     * @param sender The PendingIntent sender.
     * @param fillInIntent The fillIn intent to patch over the sender's base intent.
     * @param options bundle containing ActivityOptions for the task's top activity.
     * @hide
     */
    @NonNull
    public WindowContainerTransaction sendPendingIntent(@Nullable PendingIntent sender,
            @Nullable Intent fillInIntent, @Nullable Bundle options) {
        if (DEBUG_START_ACTIVITY) {
            Log.d(Instrumentation.TAG, "WCT.sendPendingIntent: sender="
                    + (sender != null ? sender.getIntent() : "null")
                    + " fillInIntent=" + fillInIntent + " options=" + options, new Throwable());
        }
        mHierarchyOps.add(new HierarchyOp.Builder(HierarchyOp.HIERARCHY_OP_TYPE_PENDING_INTENT)
                .setLaunchOptions(options)
                .setPendingIntent(sender)
                .setActivityIntent(fillInIntent)
                .build());
        return this;
    }

    /**
     * Starts activity(s) from a shortcut.
     * @param callingPackage The package launching the shortcut.
     * @param shortcutInfo Information about the shortcut to start
     * @param options bundle containing ActivityOptions for the task's top activity.
     * @hide
     */
    @NonNull
    public WindowContainerTransaction startShortcut(@NonNull String callingPackage,
            @NonNull ShortcutInfo shortcutInfo, @Nullable Bundle options) {
        if (DEBUG_START_ACTIVITY) {
            Log.d(Instrumentation.TAG, "WCT.startShortcut: shortcutInfo=" + shortcutInfo
                    + " options=" + options, new Throwable());
        }
        mHierarchyOps.add(HierarchyOp.createForStartShortcut(
                callingPackage, shortcutInfo, options));
        return this;
    }

    /**
     * Sets whether a container should be the launch root for the specified windowing mode and
     * activity type. This currently only applies to Task containers created by organizer.
     */
    @NonNull
    public WindowContainerTransaction setLaunchRoot(@NonNull WindowContainerToken container,
            @Nullable int[] windowingModes, @Nullable int[] activityTypes) {
        mHierarchyOps.add(HierarchyOp.createForSetLaunchRoot(
                container.asBinder(),
                windowingModes,
                activityTypes));
        return this;
    }

    /*
     * ===========================================================================================
     * Multitasking
     * ===========================================================================================
     */

    /**
     * Sets multiple containers adjacent to each other. Containers below the visible adjacent roots
     * will be made invisible. This currently only applies to Task containers created by organizer.
     *
     * <p>To remove one container from the adjacent roots, one can call {@link #clearAdjacentRoots}
     * with the target container.
     * To remove all containers from the adjacent roots, one much call {@link #clearAdjacentRoots}
     * on each container if there were more than two containers in the set.
     *
     * <p>For non-Task TaskFragment, use {@link #setAdjacentTaskFragments} instead.
     *
     * @param roots the Tasks that should be adjacent to each other.
     * @throws IllegalArgumentException if roots have size < 2.
     */
    @SuppressWarnings("UnflaggedApi") // @TestApi without associated feature.
    @NonNull
    public WindowContainerTransaction setAdjacentRoots(@NonNull WindowContainerToken... roots) {
        if (roots.length < 2) {
            throw new IllegalArgumentException("setAdjacentRoots must have size >= 2");
        }
        final IBinder[] rootTokens = new IBinder[roots.length];
        for (int i = 0; i < roots.length; i++) {
            rootTokens[i] = roots[i].asBinder();
        }
        mHierarchyOps.add(
                new HierarchyOp.Builder(HierarchyOp.HIERARCHY_OP_TYPE_SET_ADJACENT_ROOTS)
                        .setContainers(rootTokens)
                        .build());
        return this;
    }

    /**
     * Clears container adjacent.
     * If {@link #setAdjacentRoots} is called with more than 2 roots, calling this will only
     * remove the given root from the adjacent set. The rest of roots will stay adjacent to each
     * other.
     *
     * @param root the root container to clear the adjacent roots for.
     * @hide
     */
    @NonNull
    public WindowContainerTransaction clearAdjacentRoots(@NonNull WindowContainerToken root) {
        mHierarchyOps.add(HierarchyOp.createForClearAdjacentRoots(root.asBinder()));
        return this;
    }

    /**
     * Sets the container as launch adjacent flag root. Task starting with
     * {@link Intent#FLAG_ACTIVITY_LAUNCH_ADJACENT} will be launching to.
     */
    @NonNull
    public WindowContainerTransaction setLaunchAdjacentFlagRoot(
            @NonNull WindowContainerToken container) {
        mHierarchyOps.add(HierarchyOp.createForSetLaunchAdjacentFlagRoot(container.asBinder(),
                false /* clearRoot */));
        return this;
    }

    /**
     * Clears launch adjacent flag root for the display area of passing container.
     */
    @NonNull
    public WindowContainerTransaction clearLaunchAdjacentFlagRoot(
            @NonNull WindowContainerToken container) {
        mHierarchyOps.add(HierarchyOp.createForSetLaunchAdjacentFlagRoot(container.asBinder(),
                true /* clearRoot */));
        return this;
    }

    /**
     * Disables or enables activities to be started in adjacent tasks (see
     * {@link Intent#FLAG_ACTIVITY_LAUNCH_ADJACENT}) for the specified root of any child tasks.
     * This differs from {@link #setLaunchAdjacentFlagRoot(WindowContainerToken)} which controls the
     * preferred launch-adjacent target and allows for selectively setting which root tasks can
     * support launch-adjacent.
     * @hide
     */
    @NonNull
    public WindowContainerTransaction setDisableLaunchAdjacent(
            @NonNull WindowContainerToken container, boolean disabled) {
        final Change chg = getOrCreateChange(container.asBinder());
        chg.mChangeMask |= Change.CHANGE_DISABLE_LAUNCH_ADJACENT;
        chg.mDisableLaunchAdjacent = disabled;
        return this;
    }

    /*
     * ===========================================================================================
     * PIP
     * ===========================================================================================
     */

    /**
     * Moves the PiP activity of a parent task to a pinned root task.
     * @param parentToken the parent task of the PiP activity
     * @param bounds the entry bounds
     * @hide
     */
    @NonNull
    public WindowContainerTransaction movePipActivityToPinnedRootTask(
            @NonNull WindowContainerToken parentToken, @NonNull Rect bounds) {
        mHierarchyOps.add(new HierarchyOp
                .Builder(HierarchyOp.HIERARCHY_OP_TYPE_MOVE_PIP_ACTIVITY_TO_PINNED_TASK)
                .setContainer(parentToken.asBinder())
                .setBounds(bounds)
                .build());
        return this;
    }

    // TODO(b/365884835): Remove this method and the assertion in
    //  TaskFragmentOrganizerPolicyTest#testApplyChange_unsupportedChangeMask_throwException.
    /**
     * Notify {@link com.android.server.wm.PinnedTaskController} that the picture-in-picture task
     * has finished the enter animation with the given bounds.
     */
    @NonNull
    public WindowContainerTransaction scheduleFinishEnterPip(
            @NonNull WindowContainerToken container, @NonNull Rect bounds) {
        final Change chg = getOrCreateChange(container.asBinder());
        chg.mChangeMask |= Change.CHANGE_PIP_CALLBACK;
        return this;
    }

    /**
     * Used in conjunction with a shell-transition call (usually finishTransition). This is
     * basically a message to the transition system that a particular task should NOT go into
     * PIP even though it normally would. This is to deal with some edge-case situations where
     * Recents will "commit" the transition to go home, but then not actually go-home.
     * @hide
     */
    @NonNull
    public WindowContainerTransaction setDoNotPip(@NonNull WindowContainerToken container) {
        final Change chg = getOrCreateChange(container.asBinder());
        chg.mChangeMask |= Change.CHANGE_FORCE_NO_PIP;
        return this;
    }

    /**
     * Sets whether a Task or any of its children can enter picture-in-picture.
     * When {@code false}, the container and its children won't be able to enter PiP.
     *
     * <p>Note: this is different from {@link #setDoNotPip}, which is to temporarily disable PiP
     * during finishTransition.
     * @hide
     */
    @NonNull
    public WindowContainerTransaction setDisablePip(
            @NonNull WindowContainerToken container, boolean disablePip) {
        final Change chg = getOrCreateChange(container.asBinder());
        chg.mChangeMask |= Change.CHANGE_DISABLE_PIP;
        chg.mDisablePip = disablePip;
        return this;
    }

    /*
     * ===========================================================================================
     * Insets
     * ===========================================================================================
     */

    /**
     * Adds a given {@code Rect} as an insets source frame on the {@code receiver}.
     *
     * @param receiver The window container that the insets source is added to.
     * @param owner    The owner of the insets source. An insets source can only be modified by its
     *                 owner.
     * @param index    An owner might add multiple insets sources with the same type.
     *                 This identifies them.
     * @param type     The {@link InsetsType} of the insets source.
     * @param frame    The rectangle area of the insets source.
     * @param boundingRects The bounding rects within this inset, relative to the |frame|.
     * @hide
     */
    @NonNull
    public WindowContainerTransaction addInsetsSource(
            @NonNull WindowContainerToken receiver,
            @Nullable IBinder owner, int index, @InsetsType int type, @Nullable Rect frame,
            @Nullable Rect[] boundingRects, @InsetsSource.Flags int flags) {
        return addInsetsSource(receiver, owner, new InsetsFrameProvider(owner, index, type)
                .setSource(InsetsFrameProvider.SOURCE_ARBITRARY_RECTANGLE)
                .setArbitraryRectangle(frame)
                .setBoundingRects(boundingRects)
                .setFlags(flags));
    }

    /**
     * Adds a given {@code Insets} attached to the {@code receiver}'s bounds.
     *
     * @param receiver      The window container that the insets source is attached to.
     * @param owner         The owner of the insets source. An insets source can only be modified by
     *                      its owner.
     * @param index         An owner might add multiple insets sources with the same type.
     *                      This identifies them.
     * @param type          The {@link InsetsType} of the insets source.
     * @param insets        The size of the insets on each side of the edges.
     * @param boundingRects The bounding rects within this inset, relative to the |frame|.
     * @hide
     */
    @NonNull
    public WindowContainerTransaction addInsetsSource(
            @NonNull WindowContainerToken receiver,
            @Nullable IBinder owner, int index, @InsetsType int type, @NonNull Insets insets,
            @Nullable Rect[] boundingRects, @InsetsSource.Flags int flags) {
        return addInsetsSource(receiver, owner, new InsetsFrameProvider(owner, index, type)
                .setSource(InsetsFrameProvider.SOURCE_ATTACHED_CONTAINER_BOUNDS)
                .setInsetsSize(insets)
                .setBoundingRects(boundingRects)
                .setFlags(flags));
    }

    @NonNull
    private WindowContainerTransaction addInsetsSource(
            @NonNull WindowContainerToken receiver, IBinder owner, InsetsFrameProvider provider) {
        final HierarchyOp hierarchyOp =
                new HierarchyOp.Builder(HierarchyOp.HIERARCHY_OP_TYPE_ADD_INSETS_FRAME_PROVIDER)
                        .setContainer(receiver.asBinder())
                        .setInsetsFrameProvider(provider)
                        .setCaller(owner)
                        .build();
        mHierarchyOps.add(hierarchyOp);
        return this;
    }

    /**
     * Removes the insets source from the {@code receiver}.
     *
     * @param receiver The window container that the insets source was added to.
     * @param owner    The owner of the insets source. An insets source can only be modified by its
     *                 owner.
     * @param index    An owner might add multiple insets sources with the same type.
     *                 This identifies them.
     * @param type     The {@link InsetsType} of the insets source.
     * @hide
     */
    @NonNull
    public WindowContainerTransaction removeInsetsSource(@NonNull WindowContainerToken receiver,
            @Nullable IBinder owner, int index, @InsetsType int type) {
        final HierarchyOp hierarchyOp =
                new HierarchyOp.Builder(HierarchyOp.HIERARCHY_OP_TYPE_REMOVE_INSETS_FRAME_PROVIDER)
                        .setContainer(receiver.asBinder())
                        .setInsetsFrameProvider(new InsetsFrameProvider(owner, index, type))
                        .setCaller(owner)
                        .build();
        mHierarchyOps.add(hierarchyOp);
        return this;
    }

    /*
     * ===========================================================================================
     * Keyguard
     * ===========================================================================================
     */

    /**
     * Adds a {@link KeyguardState} to apply to the given displays.
     *
     * @hide
     */
    @NonNull
    public WindowContainerTransaction addKeyguardState(@NonNull KeyguardState keyguardState) {
        Objects.requireNonNull(keyguardState);
        final HierarchyOp hierarchyOp =
                new HierarchyOp.Builder(
                        HierarchyOp.HIERARCHY_OP_TYPE_SET_KEYGUARD_STATE)
                        .setKeyguardState(keyguardState)
                        .build();
        mHierarchyOps.add(hierarchyOp);
        return this;
    }

    /*
     * ===========================================================================================
     * Task fragments
     * ===========================================================================================
     */

    /**
     * Sets the {@link TaskFragmentOrganizer} that applies this {@link WindowContainerTransaction}.
     * When this is set, the server side will not check for the permission of
     * {@link android.Manifest.permission#MANAGE_ACTIVITY_TASKS}, but will ensure this WCT only
     * contains operations that are allowed for this organizer, such as modifying TaskFragments that
     * are organized by this organizer.
     * @hide
     */
    @NonNull
    public WindowContainerTransaction setTaskFragmentOrganizer(
            @NonNull ITaskFragmentOrganizer organizer) {
        mTaskFragmentOrganizer = organizer;
        return this;
    }

    /**
     * When this {@link WindowContainerTransaction} failed to finish on the server side, it will
     * trigger callback with this {@param errorCallbackToken}.
     * @param errorCallbackToken    client provided token that will be passed back as parameter in
     *                              the callback if there is an error on the server side.
     * @see com.android.server.wm.TaskFragmentOrganizerController#onTaskFragmentError
     */
    @NonNull
    public WindowContainerTransaction setErrorCallbackToken(@NonNull IBinder errorCallbackToken) {
        if (mErrorCallbackToken != null) {
            throw new IllegalStateException("Can't set multiple error token for one transaction.");
        }
        mErrorCallbackToken = errorCallbackToken;
        return this;
    }

    /**
     * Creates a new TaskFragment with the given options.
     * @param taskFragmentCreationParams the options used to create the TaskFragment.
     */
    @NonNull
    public WindowContainerTransaction createTaskFragment(
            @NonNull TaskFragmentCreationParams taskFragmentCreationParams) {
        final TaskFragmentOperation operation = new TaskFragmentOperation.Builder(
                OP_TYPE_CREATE_TASK_FRAGMENT)
                .setTaskFragmentCreationParams(taskFragmentCreationParams)
                .build();
        return addTaskFragmentOperation(taskFragmentCreationParams.getFragmentToken(), operation);
    }

    /**
     * Deletes an existing TaskFragment. Any remaining activities below it will be destroyed.
     * @param fragmentToken client assigned unique token to create TaskFragment with specified in
     *                      {@link TaskFragmentCreationParams#getFragmentToken()}.
     */
    @NonNull
    public WindowContainerTransaction deleteTaskFragment(@NonNull IBinder fragmentToken) {
        final TaskFragmentOperation operation = new TaskFragmentOperation.Builder(
                OP_TYPE_DELETE_TASK_FRAGMENT)
                .build();
        return addTaskFragmentOperation(fragmentToken, operation);
    }

    /**
     * Starts an activity in the TaskFragment.
     * @param fragmentToken client assigned unique token to create TaskFragment with specified in
     *                      {@link TaskFragmentCreationParams#getFragmentToken()}.
     * @param callerToken  the activity token that initialized the activity launch.
     * @param activityIntent    intent to start the activity.
     * @param activityOptions    ActivityOptions to start the activity with.
     * @see android.content.Context#startActivity(Intent, Bundle).
     */
    @NonNull
    public WindowContainerTransaction startActivityInTaskFragment(
            @NonNull IBinder fragmentToken, @NonNull IBinder callerToken,
            @NonNull Intent activityIntent, @Nullable Bundle activityOptions) {
        final TaskFragmentOperation operation = new TaskFragmentOperation.Builder(
                OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT)
                .setActivityToken(callerToken)
                .setActivityIntent(activityIntent)
                .setBundle(activityOptions)
                .build();
        return addTaskFragmentOperation(fragmentToken, operation);
    }

    /**
     * Moves an activity into the TaskFragment.
     * @param fragmentToken client assigned unique token to create TaskFragment with specified in
     *                      {@link TaskFragmentCreationParams#getFragmentToken()}.
     * @param activityToken activity to be reparented.
     */
    @NonNull
    public WindowContainerTransaction reparentActivityToTaskFragment(
            @NonNull IBinder fragmentToken, @NonNull IBinder activityToken) {
        final TaskFragmentOperation operation = new TaskFragmentOperation.Builder(
                OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT)
                .setActivityToken(activityToken)
                .build();
        return addTaskFragmentOperation(fragmentToken, operation);
    }

    /**
     * Sets to TaskFragments adjacent to each other. Containers below two visible adjacent
     * TaskFragments will be made invisible. This is similar to
     * {@link #setAdjacentRoots(WindowContainerToken...)}, but can be used with
     * fragmentTokens when that TaskFragments haven't been created (but will be created in the same
     * {@link WindowContainerTransaction}).
     * @param fragmentToken1    client assigned unique token to create TaskFragment with specified
     *                          in {@link TaskFragmentCreationParams#getFragmentToken()}.
     * @param fragmentToken2    client assigned unique token to create TaskFragment with specified
     *                          in {@link TaskFragmentCreationParams#getFragmentToken()}.
     */
    @NonNull
    public WindowContainerTransaction setAdjacentTaskFragments(
            @NonNull IBinder fragmentToken1, @NonNull IBinder fragmentToken2,
            @Nullable TaskFragmentAdjacentParams params) {
        final TaskFragmentOperation operation = new TaskFragmentOperation.Builder(
                OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS)
                .setSecondaryFragmentToken(fragmentToken2)
                .setBundle(params != null ? params.toBundle() : null)
                .build();
        return addTaskFragmentOperation(fragmentToken1, operation);
    }

    /**
     * Clears the adjacent TaskFragments relationship that is previously set through
     * {@link #setAdjacentTaskFragments}. Clear operation on one TaskFragment will also clear its
     * current adjacent TaskFragment's.
     * @param fragmentToken     client assigned unique token to create TaskFragment with specified
     *                          in {@link TaskFragmentCreationParams#getFragmentToken()}.
     */
    @NonNull
    public WindowContainerTransaction clearAdjacentTaskFragments(@NonNull IBinder fragmentToken) {
        final TaskFragmentOperation operation = new TaskFragmentOperation.Builder(
                OP_TYPE_CLEAR_ADJACENT_TASK_FRAGMENTS)
                .build();
        return addTaskFragmentOperation(fragmentToken, operation);
    }

    /**
     * Requests focus on the top running Activity in the given TaskFragment. This will only take
     * effect if there is no focus, or if the current focus is in the same Task as the requested
     * TaskFragment.
     * @param fragmentToken client assigned unique token to create TaskFragment with specified in
     *                      {@link TaskFragmentCreationParams#getFragmentToken()}.
     */
    @NonNull
    public WindowContainerTransaction requestFocusOnTaskFragment(@NonNull IBinder fragmentToken) {
        final TaskFragmentOperation operation = new TaskFragmentOperation.Builder(
                OP_TYPE_REQUEST_FOCUS_ON_TASK_FRAGMENT)
                .build();
        return addTaskFragmentOperation(fragmentToken, operation);
    }

    /**
     * Finishes the Activity.
     * Comparing to directly calling {@link android.app.Activity#finish()}, calling this can make
     * sure the finishing happens in the same transaction with other operations.
     * @param activityToken activity to be finished.
     */
    @NonNull
    public WindowContainerTransaction finishActivity(@NonNull IBinder activityToken) {
        final HierarchyOp hierarchyOp =
                new HierarchyOp.Builder(
                        HierarchyOp.HIERARCHY_OP_TYPE_FINISH_ACTIVITY)
                        .setContainer(activityToken)
                        .build();
        mHierarchyOps.add(hierarchyOp);
        return this;
    }

    /**
     * Sets the TaskFragment {@code fragmentToken} to have a companion TaskFragment
     * {@code companionFragmentToken}.
     *
     * If {@code toBeFinishedActivity} is {@code null}, this indicates that the organizer will
     * remove the TaskFragment when the companion TaskFragment is removed; otherwise, the organizer
     * will finish the {@code toBeFinishedActivity} when the companion TaskFragment is removed
     * unless it is the last activity in the TaskFragment.
     *
     * @param fragmentToken client assigned unique token to create TaskFragment with specified
     *                      in {@link TaskFragmentCreationParams#getFragmentToken()}.
     * @param companionFragmentToken client assigned unique token to create TaskFragment with
     *                               specified in
     *                               {@link TaskFragmentCreationParams#getFragmentToken()}.
     *                               If it is {@code null}, the transaction will reset the companion
     *                               TaskFragment.
     * @param toBeFinishedActivity   Activity token. If non-{@code null}, it indicates that the
     *                               organizer will only remove this activity when the companion
     *                               TaskFragment is removed. The request TaskFragment will only be
     *                               removed when this activity is the last running activity in it.
     * @hide
     */
    @NonNull
    public WindowContainerTransaction setCompanionTaskFragment(@NonNull IBinder fragmentToken,
            @Nullable IBinder companionFragmentToken, @Nullable IBinder toBeFinishedActivity) {
        final TaskFragmentOperation operation = new TaskFragmentOperation.Builder(
                OP_TYPE_SET_COMPANION_TASK_FRAGMENT)
                .setSecondaryFragmentToken(companionFragmentToken)
                .setActivityToken(toBeFinishedActivity)
                .build();
        return addTaskFragmentOperation(fragmentToken, operation);
    }

    /**
     * Adds a {@link TaskFragmentOperation} to apply to the given TaskFragment.
     *
     * @param fragmentToken client assigned unique token to create TaskFragment with specified in
     *                      {@link TaskFragmentCreationParams#getFragmentToken()}.
     * @param taskFragmentOperation the {@link TaskFragmentOperation} to apply to the given
     *                              TaskFragment.
     * @hide
     */
    @NonNull
    public WindowContainerTransaction addTaskFragmentOperation(@NonNull IBinder fragmentToken,
            @NonNull TaskFragmentOperation taskFragmentOperation) {
        Objects.requireNonNull(fragmentToken);
        Objects.requireNonNull(taskFragmentOperation);
        final HierarchyOp hierarchyOp =
                new HierarchyOp.Builder(
                        HierarchyOp.HIERARCHY_OP_TYPE_ADD_TASK_FRAGMENT_OPERATION)
                        .setContainer(fragmentToken)
                        .setTaskFragmentOperation(taskFragmentOperation)
                        .build();
        mHierarchyOps.add(hierarchyOp);
        return this;
    }

    /**
     * Adds a hierarchy op for app compat reachability.
     *
     * @param container The token for the container Task
     * @param taskId    The id of the current task
     * @hide
     */
    public WindowContainerTransaction setReachabilityOffset(
            @NonNull WindowContainerToken container, int taskId, int x, int y) {
        mHierarchyOps.add(HierarchyOp.createForReachability(container.asBinder(), taskId, x, y));
        return this;
    }

    /**
     * Merges another WCT into this one.
     * @param transfer When true, this will transfer everything from other potentially leaving
     *                 other in an unusable state. When false, other is left alone, but
     *                 SurfaceFlinger Transactions will not be merged.
     * @hide
     */
    public void merge(@NonNull WindowContainerTransaction other, boolean transfer) {
        for (int i = 0, n = other.mChanges.size(); i < n; ++i) {
            final IBinder key = other.mChanges.keyAt(i);
            Change existing = mChanges.get(key);
            if (existing == null) {
                existing = new Change();
                mChanges.put(key, existing);
            }
            existing.merge(other.mChanges.valueAt(i), transfer);
        }
        for (int i = 0, n = other.mHierarchyOps.size(); i < n; ++i) {
            final HierarchyOp otherHierarchyOp = other.mHierarchyOps.get(i);
            mHierarchyOps.add(transfer ? otherHierarchyOp : new HierarchyOp(otherHierarchyOp));
        }
        if (mErrorCallbackToken != null && other.mErrorCallbackToken != null && mErrorCallbackToken
                != other.mErrorCallbackToken) {
            throw new IllegalArgumentException("Can't merge two WCTs with different error token");
        }
        final IBinder taskFragmentOrganizerAsBinder = mTaskFragmentOrganizer != null
                ? mTaskFragmentOrganizer.asBinder()
                : null;
        final IBinder otherTaskFragmentOrganizerAsBinder = other.mTaskFragmentOrganizer != null
                ? other.mTaskFragmentOrganizer.asBinder()
                : null;
        if (!Objects.equals(taskFragmentOrganizerAsBinder, otherTaskFragmentOrganizerAsBinder)) {
            throw new IllegalArgumentException(
                    "Can't merge two WCTs from different TaskFragmentOrganizers");
        }
        mErrorCallbackToken = mErrorCallbackToken != null
                ? mErrorCallbackToken
                : other.mErrorCallbackToken;
    }

    /** @hide */
    public boolean isEmpty() {
        return mChanges.isEmpty() && mHierarchyOps.isEmpty();
    }

    /** @hide */
    @NonNull
    public Map<IBinder, Change> getChanges() {
        return mChanges;
    }

    /** @hide */
    @NonNull
    public List<HierarchyOp> getHierarchyOps() {
        return mHierarchyOps;
    }

    /** @hide */
    @Nullable
    public IBinder getErrorCallbackToken() {
        return mErrorCallbackToken;
    }

    /** @hide */
    @Nullable
    public ITaskFragmentOrganizer getTaskFragmentOrganizer() {
        return mTaskFragmentOrganizer;
    }

    @Override
    @NonNull
    public String toString() {
        return "WindowContainerTransaction {"
                + " changes= " + mChanges
                + " hops= " + mHierarchyOps
                + " errorCallbackToken=" + mErrorCallbackToken
                + " taskFragmentOrganizer=" + mTaskFragmentOrganizer
                + " }";
    }

    @Override
    public void writeToParcel(@NonNull Parcel dest, int flags) {
        dest.writeMap(mChanges);
        dest.writeTypedList(mHierarchyOps);
        dest.writeStrongBinder(mErrorCallbackToken);
        dest.writeStrongInterface(mTaskFragmentOrganizer);
    }

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

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

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

    /**
     * Holds changes on a single WindowContainer including Configuration changes.
     * @hide
     */
    public static class Change implements Parcelable {
        public static final int CHANGE_FOCUSABLE = 1;
        public static final int CHANGE_BOUNDS_TRANSACTION = 1 << 1;
        public static final int CHANGE_PIP_CALLBACK = 1 << 2;
        public static final int CHANGE_HIDDEN = 1 << 3;
        public static final int CHANGE_IGNORE_ORIENTATION_REQUEST = 1 << 4;
        public static final int CHANGE_FORCE_NO_PIP = 1 << 5;
        public static final int CHANGE_FORCE_TRANSLUCENT = 1 << 6;
        public static final int CHANGE_DRAG_RESIZING = 1 << 7;
        public static final int CHANGE_RELATIVE_BOUNDS = 1 << 8;
        public static final int CHANGE_FORCE_EXCLUDED_FROM_RECENTS = 1 << 9;
        public static final int CHANGE_LAUNCH_NEXT_TO_BUBBLE = 1 << 10;
        public static final int CHANGE_DISABLE_PIP = 1 << 11;
        public static final int CHANGE_DISABLE_LAUNCH_ADJACENT = 1 << 12;
        public static final int CHANGE_IS_TASK_MOVE_ALLOWED = 1 << 13;
        public static final int CHANGE_INTERCEPT_BACK_PRESSED = 1 << 14;

        @IntDef(flag = true, prefix = { "CHANGE_" }, value = {
                CHANGE_FOCUSABLE,
                CHANGE_BOUNDS_TRANSACTION,
                CHANGE_PIP_CALLBACK,
                CHANGE_HIDDEN,
                CHANGE_IGNORE_ORIENTATION_REQUEST,
                CHANGE_FORCE_NO_PIP,
                CHANGE_FORCE_TRANSLUCENT,
                CHANGE_DRAG_RESIZING,
                CHANGE_RELATIVE_BOUNDS,
                CHANGE_FORCE_EXCLUDED_FROM_RECENTS,
                CHANGE_LAUNCH_NEXT_TO_BUBBLE,
                CHANGE_DISABLE_PIP,
                CHANGE_DISABLE_LAUNCH_ADJACENT,
                CHANGE_IS_TASK_MOVE_ALLOWED,
                CHANGE_INTERCEPT_BACK_PRESSED
        })
        @Retention(RetentionPolicy.SOURCE)
        public @interface ChangeMask {}

        private final Configuration mConfiguration = new Configuration();
        private boolean mFocusable = true;
        private boolean mHidden = false;
        private boolean mIgnoreOrientationRequest = false;
        private boolean mForceTranslucent = false;
        private boolean mDragResizing = false;
        private boolean mForceExcludedFromRecents = false;
        private boolean mDisablePip = false;
        private boolean mDisableLaunchAdjacent = false;
        private boolean mIsTaskMoveAllowed = false;
        private boolean mInterceptBackPressed = false;

        private @ChangeMask int mChangeMask = 0;
        private @ActivityInfo.Config int mConfigSetMask = 0;
        private @WindowConfiguration.WindowConfig int mWindowSetMask = 0;

        private SurfaceControl.Transaction mBoundsChangeTransaction = null;
        @Nullable
        private Rect mRelativeBounds = null;
        private boolean mConfigAtTransitionEnd = false;

        private int mActivityWindowingMode = -1;
        private int mWindowingMode = -1;
        private @SelfMovable int mSelfMovable = SELF_MOVABLE_UNSET;

        private boolean mLaunchNextToBubble = false;

        private Change() {}

        private Change(@NonNull Parcel in) {
            mConfiguration.readFromParcel(in);
            mFocusable = in.readBoolean();
            mHidden = in.readBoolean();
            mIgnoreOrientationRequest = in.readBoolean();
            mForceTranslucent = in.readBoolean();
            mDragResizing = in.readBoolean();
            mForceExcludedFromRecents = in.readBoolean();
            mLaunchNextToBubble = in.readBoolean();
            mDisablePip = in.readBoolean();
            mDisableLaunchAdjacent = in.readBoolean();
            mIsTaskMoveAllowed = in.readBoolean();
            mInterceptBackPressed = in.readBoolean();
            mChangeMask = in.readInt();
            mConfigSetMask = in.readInt();
            mWindowSetMask = in.readInt();
            if ((mChangeMask & Change.CHANGE_BOUNDS_TRANSACTION) != 0) {
                mBoundsChangeTransaction =
                    SurfaceControl.Transaction.CREATOR.createFromParcel(in);
            }
            if ((mChangeMask & Change.CHANGE_RELATIVE_BOUNDS) != 0) {
                mRelativeBounds = new Rect();
                mRelativeBounds.readFromParcel(in);
            }
            mConfigAtTransitionEnd = in.readBoolean();

            mWindowingMode = in.readInt();
            mActivityWindowingMode = in.readInt();
            mSelfMovable = in.readInt();
        }

        /**
         * @param transfer When true, this will transfer other into this leaving other in an
         *                 undefined state. Use this if you don't intend to use other. When false,
         *                 SurfaceFlinger Transactions will not merge.
         */
        public void merge(@NonNull Change other, boolean transfer) {
            mConfiguration.setTo(other.mConfiguration, other.mConfigSetMask, other.mWindowSetMask);
            mConfigSetMask |= other.mConfigSetMask;
            mWindowSetMask |= other.mWindowSetMask;
            if ((other.mChangeMask & CHANGE_FOCUSABLE) != 0) {
                mFocusable = other.mFocusable;
            }
            if (transfer && (other.mChangeMask & CHANGE_BOUNDS_TRANSACTION) != 0) {
                mBoundsChangeTransaction = other.mBoundsChangeTransaction;
                other.mBoundsChangeTransaction = null;
            }
            if ((other.mChangeMask & CHANGE_HIDDEN) != 0) {
                mHidden = other.mHidden;
            }
            if ((other.mChangeMask & CHANGE_IGNORE_ORIENTATION_REQUEST) != 0) {
                mIgnoreOrientationRequest = other.mIgnoreOrientationRequest;
            }
            if ((other.mChangeMask & CHANGE_FORCE_TRANSLUCENT) != 0) {
                mForceTranslucent = other.mForceTranslucent;
            }
            if ((other.mChangeMask & CHANGE_DRAG_RESIZING) != 0) {
                mDragResizing = other.mDragResizing;
            }
            if ((other.mChangeMask & CHANGE_FORCE_EXCLUDED_FROM_RECENTS) != 0) {
                mForceExcludedFromRecents = other.mForceExcludedFromRecents;
            }
            if ((other.mChangeMask & CHANGE_LAUNCH_NEXT_TO_BUBBLE) != 0) {
                mLaunchNextToBubble = other.mLaunchNextToBubble;
            }
            if ((other.mChangeMask & CHANGE_DISABLE_PIP) != 0) {
                mDisablePip = other.mDisablePip;
            }
            if ((other.mChangeMask & CHANGE_DISABLE_LAUNCH_ADJACENT) != 0) {
                mDisableLaunchAdjacent = other.mDisableLaunchAdjacent;
            }
            if ((other.mChangeMask & CHANGE_IS_TASK_MOVE_ALLOWED) != 0) {
                mIsTaskMoveAllowed = other.mIsTaskMoveAllowed;
            }
            if ((other.mChangeMask & CHANGE_INTERCEPT_BACK_PRESSED) != 0) {
                mInterceptBackPressed = other.mInterceptBackPressed;
            }
            mChangeMask |= other.mChangeMask;
            if (other.mActivityWindowingMode >= WINDOWING_MODE_UNDEFINED) {
                mActivityWindowingMode = other.mActivityWindowingMode;
            }
            if (other.mWindowingMode >= WINDOWING_MODE_UNDEFINED) {
                mWindowingMode = other.mWindowingMode;
            }
            if (other.mSelfMovable != SELF_MOVABLE_UNSET) {
                mSelfMovable = other.mSelfMovable;
            }
            if (other.mRelativeBounds != null) {
                mRelativeBounds = transfer
                        ? other.mRelativeBounds
                        : new Rect(other.mRelativeBounds);
            }
            mConfigAtTransitionEnd = mConfigAtTransitionEnd
                    || other.mConfigAtTransitionEnd;
        }

        public int getWindowingMode() {
            return mWindowingMode;
        }

        public int getActivityWindowingMode() {
            return mActivityWindowingMode;
        }

        @NonNull
        public Configuration getConfiguration() {
            return mConfiguration;
        }

        /** Gets the requested mLaunchNextToBubble state */
        public boolean getLaunchNextToBubble() {
            if ((mChangeMask & CHANGE_LAUNCH_NEXT_TO_BUBBLE) == 0) {
                throw new RuntimeException(
                        "mLaunchNextToBubble not set. check CHANGE_LAUNCH_NEXT_TO_BUBBLE first");
            }
            return mLaunchNextToBubble;
        }

        /** Gets the requested focusable state */
        public boolean getFocusable() {
            if ((mChangeMask & CHANGE_FOCUSABLE) == 0) {
                throw new RuntimeException("Focusable not set. check CHANGE_FOCUSABLE first");
            }
            return mFocusable;
        }

        /** Gets the requested hidden state */
        public boolean getHidden() {
            if ((mChangeMask & CHANGE_HIDDEN) == 0) {
                throw new RuntimeException("Hidden not set. check CHANGE_HIDDEN first");
            }
            return mHidden;
        }

        /** Gets the requested state of whether to ignore orientation request. */
        public boolean getIgnoreOrientationRequest() {
            if ((mChangeMask & CHANGE_IGNORE_ORIENTATION_REQUEST) == 0) {
                throw new RuntimeException("IgnoreOrientationRequest not set. "
                        + "Check CHANGE_IGNORE_ORIENTATION_REQUEST first");
            }
            return mIgnoreOrientationRequest;
        }

        /** Gets the requested force translucent state. */
        public boolean getForceTranslucent() {
            if ((mChangeMask & CHANGE_FORCE_TRANSLUCENT) == 0) {
                throw new RuntimeException("Force translucent not set. "
                        + "Check CHANGE_FORCE_TRANSLUCENT first");
            }
            return mForceTranslucent;
        }

        /** Gets the requested drag resizing state. */
        public boolean getDragResizing() {
            if ((mChangeMask & CHANGE_DRAG_RESIZING) == 0) {
                throw new RuntimeException("Drag resizing not set. "
                        + "Check CHANGE_DRAG_RESIZING first");
            }
            return mDragResizing;
        }

        /** Gets whether the task is force excluded from recents. */
        public boolean getForceExcludedFromRecents() {
            return mForceExcludedFromRecents;
        }

        /** Gets whether the task is disabled to enter picture-in-picture. */
        public boolean getDisablePip() {
            return mDisablePip;
        }

        /**
         * Gets whether activities are disabled to be started in adjacent tasks for the specified
         * root of any child tasks .
         */
        public boolean getDisableLaunchAdjacent() {
            return mDisableLaunchAdjacent;
        }

        /** Gets the intercept-back-pressed state. */
        public boolean getInterceptBackPressed() {
            if ((mChangeMask & CHANGE_INTERCEPT_BACK_PRESSED) == 0) {
                throw new RuntimeException("Intercept back pressed not set. "
                        + "Check CHANGE_INTERCEPT_BACK_PRESSED first");
            }
            return mInterceptBackPressed;
        }

        /** Gets whether the config should be sent to the client at the end of the transition. */
        public boolean getConfigAtTransitionEnd() {
            return mConfigAtTransitionEnd;
        }

        /**
         * Gets whether the given container can be repositioned by {@link
         * android.app.ActivityManager.AppTask#moveTaskTo}.
         */
        public @SelfMovable int getSelfMovable() {
            return mSelfMovable;
        }

        /**
         * Gets whether the given container is able to contain self-movable tasks. A display
         * is considered able to contain self-movable tasks as long as there is one child window
         * container that is able to contain self-movable tasks.
         */
        public boolean getIsTaskMoveAllowed() {
            return mIsTaskMoveAllowed;
        }

        @ChangeMask
        public int getChangeMask() {
            return mChangeMask;
        }

        @ActivityInfo.Config
        public int getConfigSetMask() {
            return mConfigSetMask;
        }

        @WindowConfiguration.WindowConfig
        public int getWindowSetMask() {
            return mWindowSetMask;
        }

        @Nullable
        public SurfaceControl.Transaction getBoundsChangeTransaction() {
            return mBoundsChangeTransaction;
        }

        @Nullable
        public Rect getRelativeBounds() {
            return mRelativeBounds;
        }

        @Override
        public String toString() {
            final boolean changesBounds =
                    (mConfigSetMask & ActivityInfo.CONFIG_WINDOW_CONFIGURATION) != 0
                            && ((mWindowSetMask & WindowConfiguration.WINDOW_CONFIG_BOUNDS)
                                    != 0);
            final boolean changesAppBounds =
                    (mConfigSetMask & ActivityInfo.CONFIG_WINDOW_CONFIGURATION) != 0
                            && ((mWindowSetMask & WindowConfiguration.WINDOW_CONFIG_APP_BOUNDS)
                                    != 0);
            final boolean changesSs = (mConfigSetMask & ActivityInfo.CONFIG_SCREEN_SIZE) != 0;
            final boolean changesSss =
                    (mConfigSetMask & ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE) != 0;
            final var sb = new StringBuilder();
            sb.append('{');
            if (changesBounds) {
                sb.append("bounds:").append(mConfiguration.windowConfiguration.getBounds())
                        .append(",");
            }
            if (changesAppBounds) {
                sb.append("appbounds:").append(mConfiguration.windowConfiguration.getAppBounds())
                        .append(",");
            }
            if (changesSss) {
                sb.append("ssw:").append(mConfiguration.smallestScreenWidthDp).append(",");
            }
            if (changesSs) {
                sb.append("sw/h:").append(mConfiguration.screenWidthDp).append("x")
                        .append(mConfiguration.screenHeightDp).append(",");
            }
            if (mWindowingMode >= WINDOWING_MODE_UNDEFINED) {
                sb.append("windowingMode:").append(windowingModeToString(mWindowingMode))
                        .append(",");
            }
            if ((mChangeMask & CHANGE_FOCUSABLE) != 0) {
                sb.append("focusable:").append(mFocusable).append(",");
            }
            if ((mChangeMask & CHANGE_FORCE_TRANSLUCENT) != 0) {
                sb.append("forceTranslucent:").append(mForceTranslucent).append(",");
            }
            if ((mChangeMask & CHANGE_HIDDEN) != 0) {
                sb.append("hidden:").append(mHidden).append(",");
            }
            if ((mChangeMask & CHANGE_DRAG_RESIZING) != 0) {
                sb.append("dragResizing:").append(mDragResizing).append(",");
            }
            if ((mChangeMask & CHANGE_FORCE_EXCLUDED_FROM_RECENTS) != 0) {
                sb.append("forceExcludedFromRecents:").append(mForceExcludedFromRecents)
                        .append(",");
            }
            if ((mChangeMask & CHANGE_DISABLE_PIP) != 0) {
                sb.append("disablePip:").append(mDisablePip).append(",");
            }
            if ((mChangeMask & CHANGE_DISABLE_LAUNCH_ADJACENT) != 0) {
                sb.append("disableLaunchAdjacent:").append(mDisableLaunchAdjacent).append(",");
            }
            if ((mChangeMask & CHANGE_IS_TASK_MOVE_ALLOWED) != 0) {
                sb.append("isTaskMoveAllowed:").append(mIsTaskMoveAllowed).append(",");
            }
            if ((mChangeMask & CHANGE_INTERCEPT_BACK_PRESSED) != 0) {
                sb.append("interceptBack:" + mInterceptBackPressed + ",");
            }
            if (mBoundsChangeTransaction != null) {
                sb.append("hasBoundsTransaction,");
            }
            if ((mChangeMask & CHANGE_IGNORE_ORIENTATION_REQUEST) != 0) {
                sb.append("ignoreOrientationRequest:").append(mIgnoreOrientationRequest)
                        .append(",");
            }
            if ((mChangeMask & CHANGE_RELATIVE_BOUNDS) != 0) {
                sb.append("relativeBounds:").append(mRelativeBounds).append(",");
            }
            if ((mChangeMask & CHANGE_LAUNCH_NEXT_TO_BUBBLE) != 0) {
                sb.append("launchNextToBubble:").append(mLaunchNextToBubble).append(",");
            }
            if (mConfigAtTransitionEnd) {
                sb.append("configAtTransitionEnd").append(",");
            }
            sb.append("}");
            return sb.toString();
        }

        @Override
        public void writeToParcel(@NonNull Parcel dest, int flags) {
            mConfiguration.writeToParcel(dest, flags);
            dest.writeBoolean(mFocusable);
            dest.writeBoolean(mHidden);
            dest.writeBoolean(mIgnoreOrientationRequest);
            dest.writeBoolean(mForceTranslucent);
            dest.writeBoolean(mDragResizing);
            dest.writeBoolean(mForceExcludedFromRecents);
            dest.writeBoolean(mLaunchNextToBubble);
            dest.writeBoolean(mDisablePip);
            dest.writeBoolean(mDisableLaunchAdjacent);
            dest.writeBoolean(mIsTaskMoveAllowed);
            dest.writeBoolean(mInterceptBackPressed);
            dest.writeInt(mChangeMask);
            dest.writeInt(mConfigSetMask);
            dest.writeInt(mWindowSetMask);

            if (mBoundsChangeTransaction != null) {
                mBoundsChangeTransaction.writeToParcel(dest, flags);
            }
            if (mRelativeBounds != null) {
                mRelativeBounds.writeToParcel(dest, flags);
            }
            dest.writeBoolean(mConfigAtTransitionEnd);

            dest.writeInt(mWindowingMode);
            dest.writeInt(mActivityWindowingMode);
            dest.writeInt(mSelfMovable);
        }

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

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

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

    /**
     * Holds information about a reparent/reorder operation in the hierarchy. This is separate from
     * Changes because they must be executed in the same order that they are added.
     * @see com.android.server.wm.WindowOrganizerController#applyHierarchyOp
     * @hide
     */
    public static final class HierarchyOp implements Parcelable {
        public static final int HIERARCHY_OP_TYPE_REPARENT = 0;
        public static final int HIERARCHY_OP_TYPE_REORDER = 1;
        public static final int HIERARCHY_OP_TYPE_CHILDREN_TASKS_REPARENT = 2;
        public static final int HIERARCHY_OP_TYPE_SET_LAUNCH_ROOT = 3;
        public static final int HIERARCHY_OP_TYPE_SET_ADJACENT_ROOTS = 4;
        public static final int HIERARCHY_OP_TYPE_LAUNCH_TASK = 5;
        public static final int HIERARCHY_OP_TYPE_SET_LAUNCH_ADJACENT_FLAG_ROOT = 6;
        public static final int HIERARCHY_OP_TYPE_PENDING_INTENT = 7;
        public static final int HIERARCHY_OP_TYPE_START_SHORTCUT = 8;
        public static final int HIERARCHY_OP_TYPE_RESTORE_TRANSIENT_ORDER = 9;
        public static final int HIERARCHY_OP_TYPE_ADD_INSETS_FRAME_PROVIDER = 10;
        public static final int HIERARCHY_OP_TYPE_REMOVE_INSETS_FRAME_PROVIDER = 11;
        public static final int HIERARCHY_OP_TYPE_SET_ALWAYS_ON_TOP = 12;
        public static final int HIERARCHY_OP_TYPE_REMOVE_TASK = 13;
        public static final int HIERARCHY_OP_TYPE_FINISH_ACTIVITY = 14;
        public static final int HIERARCHY_OP_TYPE_CLEAR_ADJACENT_ROOTS = 15;
        public static final int HIERARCHY_OP_TYPE_SET_REPARENT_LEAF_TASK_IF_RELAUNCH = 16;
        public static final int HIERARCHY_OP_TYPE_ADD_TASK_FRAGMENT_OPERATION = 17;
        public static final int HIERARCHY_OP_TYPE_MOVE_PIP_ACTIVITY_TO_PINNED_TASK = 18;
        public static final int HIERARCHY_OP_TYPE_SET_IS_TRIMMABLE = 19;
        public static final int HIERARCHY_OP_TYPE_RESTORE_BACK_NAVIGATION = 20;
        public static final int HIERARCHY_OP_TYPE_SET_EXCLUDE_INSETS_TYPES = 21;
        public static final int HIERARCHY_OP_TYPE_SET_KEYGUARD_STATE = 22;
        public static final int HIERARCHY_OP_TYPE_REMOVE_ROOT_TASK = 23;
        public static final int HIERARCHY_OP_TYPE_APP_COMPAT_REACHABILITY = 24;
        public static final int HIERARCHY_OP_TYPE_SET_SAFE_REGION_BOUNDS = 25;
        public static final int HIERARCHY_OP_TYPE_SET_SYSTEM_BAR_VISIBILITY_OVERRIDE = 26;
        public static final int HIERARCHY_OP_TYPE_DISALLOW_OVERRIDE_BOUNDS_FOR_CHILDREN = 27;

        @IntDef(prefix = {"HIERARCHY_OP_TYPE_"}, value = {
                HIERARCHY_OP_TYPE_REPARENT,
                HIERARCHY_OP_TYPE_REORDER,
                HIERARCHY_OP_TYPE_CHILDREN_TASKS_REPARENT,
                HIERARCHY_OP_TYPE_SET_LAUNCH_ROOT,
                HIERARCHY_OP_TYPE_SET_ADJACENT_ROOTS,
                HIERARCHY_OP_TYPE_LAUNCH_TASK,
                HIERARCHY_OP_TYPE_SET_LAUNCH_ADJACENT_FLAG_ROOT,
                HIERARCHY_OP_TYPE_PENDING_INTENT,
                HIERARCHY_OP_TYPE_START_SHORTCUT,
                HIERARCHY_OP_TYPE_RESTORE_TRANSIENT_ORDER,
                HIERARCHY_OP_TYPE_ADD_INSETS_FRAME_PROVIDER,
                HIERARCHY_OP_TYPE_REMOVE_INSETS_FRAME_PROVIDER,
                HIERARCHY_OP_TYPE_SET_ALWAYS_ON_TOP,
                HIERARCHY_OP_TYPE_REMOVE_TASK,
                HIERARCHY_OP_TYPE_FINISH_ACTIVITY,
                HIERARCHY_OP_TYPE_CLEAR_ADJACENT_ROOTS,
                HIERARCHY_OP_TYPE_SET_REPARENT_LEAF_TASK_IF_RELAUNCH,
                HIERARCHY_OP_TYPE_ADD_TASK_FRAGMENT_OPERATION,
                HIERARCHY_OP_TYPE_MOVE_PIP_ACTIVITY_TO_PINNED_TASK,
                HIERARCHY_OP_TYPE_SET_IS_TRIMMABLE,
                HIERARCHY_OP_TYPE_RESTORE_BACK_NAVIGATION,
                HIERARCHY_OP_TYPE_SET_EXCLUDE_INSETS_TYPES,
                HIERARCHY_OP_TYPE_SET_KEYGUARD_STATE,
                HIERARCHY_OP_TYPE_REMOVE_ROOT_TASK,
                HIERARCHY_OP_TYPE_APP_COMPAT_REACHABILITY,
                HIERARCHY_OP_TYPE_SET_SAFE_REGION_BOUNDS,
                HIERARCHY_OP_TYPE_SET_SYSTEM_BAR_VISIBILITY_OVERRIDE,
                HIERARCHY_OP_TYPE_DISALLOW_OVERRIDE_BOUNDS_FOR_CHILDREN,
        })
        @Retention(RetentionPolicy.SOURCE)
        public @interface HierarchyOpType {
        }

        // The following key(s) are for use with mLaunchOptions:
        // When launching a task (eg. from recents), this is the taskId to be launched.
        public static final String LAUNCH_KEY_TASK_ID = "android:transaction.hop.taskId";

        // When starting from a shortcut, this contains the calling package.
        public static final String LAUNCH_KEY_SHORTCUT_CALLING_PACKAGE =
                "android:transaction.hop.shortcut_calling_package";

        // The following keys are used to define the reachability direction after a double tap.
        public static final String REACHABILITY_EVENT_X = "android:transaction.reachability_x";
        public static final String REACHABILITY_EVENT_Y = "android:transaction.reachability_y";

        @HierarchyOpType
        private final int mType;

        // Container we are performing the operation on.
        @Nullable
        private IBinder mContainer;

        @Nullable
        private IBinder[] mContainers;

        // If this is same as mContainer, then only change position, don't reparent.
        @Nullable
        private IBinder mReparent;

        @Nullable
        private InsetsFrameProvider mInsetsFrameProvider;

        @Nullable
        private IBinder mCaller;

        // Moves/reparents to top of parent when {@code true}, otherwise moves/reparents to bottom.
        private boolean mToTop;

        private boolean mReparentTopOnly;

        @Nullable
        private int[]  mWindowingModes;

        @Nullable
        private int[] mActivityTypes;

        @Nullable
        private Bundle mLaunchOptions;

        @Nullable
        private Bundle mAppCompatOptions;

        @Nullable
        private Intent mActivityIntent;

        /** Used as options for {@link #addTaskFragmentOperation}. */
        @Nullable
        private TaskFragmentOperation mTaskFragmentOperation;

        @Nullable
        private KeyguardState mKeyguardState;

        @Nullable
        private PendingIntent mPendingIntent;

        @Nullable
        private ShortcutInfo mShortcutInfo;

        @Nullable
        private Rect mBounds;

        private boolean mIncludingParents;

        private boolean mAlwaysOnTop;

        private boolean mReparentLeafTaskIfRelaunch;

        private boolean mIsTrimmableFromRecents;

        private @InsetsType int mExcludeInsetsTypes;

        private @InsetsType int mForciblyShowingInsetsTypes;
        private @InsetsType int mForciblyHidingInsetsTypes;

        @Nullable
        private Rect mSafeRegionBounds;

        private boolean mDisallowOverrideBoundsForChildren;

        /** Creates a hierarchy operation for reparenting a container within the hierarchy. */
        @NonNull
        public static HierarchyOp createForReparent(
                @NonNull IBinder container, @Nullable IBinder reparent, boolean toTop) {
            return new HierarchyOp.Builder(HIERARCHY_OP_TYPE_REPARENT)
                    .setContainer(container)
                    .setReparentContainer(reparent)
                    .setToTop(toTop)
                    .build();
        }

        /**
         * Creates a a hierarchy op for the reorder operation.
         *
         * @param container which needs to be reordered
         * @param toTop if true, the container reorders
         * @param includingParents if true, all the parents in the hierarchy above are also
         *                         reordered among their respective siblings
         * @return
         */
        @NonNull
        public static HierarchyOp createForReorder(@NonNull IBinder container, boolean toTop,
                boolean includingParents) {
            return new HierarchyOp.Builder(HIERARCHY_OP_TYPE_REORDER)
                    .setContainer(container)
                    .setReparentContainer(container)
                    .setToTop(toTop)
                    .setIncludingParents(includingParents)
                    .build();
        }

        /** Creates a hierarchy op for reparenting child tasks from one container to another. */
        @NonNull
        public static HierarchyOp createForChildrenTasksReparent(@Nullable IBinder currentParent,
                @Nullable IBinder newParent, @Nullable int[] windowingModes,
                @Nullable int[] activityTypes, boolean onTop, boolean reparentTopOnly) {
            return new HierarchyOp.Builder(HIERARCHY_OP_TYPE_CHILDREN_TASKS_REPARENT)
                    .setContainer(currentParent)
                    .setReparentContainer(newParent)
                    .setWindowingModes(windowingModes)
                    .setActivityTypes(activityTypes)
                    .setToTop(onTop)
                    .setReparentTopOnly(reparentTopOnly)
                    .build();
        }

        /** Creates a hierarchy op for setting the launch root for tasks. */
        @NonNull
        public static HierarchyOp createForSetLaunchRoot(@Nullable IBinder container,
                @Nullable int[] windowingModes, @Nullable int[] activityTypes) {
            return new HierarchyOp.Builder(HIERARCHY_OP_TYPE_SET_LAUNCH_ROOT)
                    .setContainer(container)
                    .setWindowingModes(windowingModes)
                    .setActivityTypes(activityTypes)
                    .build();
        }

        /** Creates a hierarchy op for setting adjacent root tasks. */
        @NonNull
        public static HierarchyOp createForAdjacentRoots(
                @Nullable IBinder root1, @Nullable IBinder root2) {
            return new HierarchyOp.Builder(HIERARCHY_OP_TYPE_SET_ADJACENT_ROOTS)
                    .setContainer(root1)
                    .setReparentContainer(root2)
                    .build();
        }

        /** Creates a hierarchy op for launching a task. */
        @NonNull
        public static HierarchyOp createForTaskLaunch(int taskId, @Nullable Bundle options) {
            final Bundle fullOptions = options == null ? new Bundle() : options;
            fullOptions.putInt(LAUNCH_KEY_TASK_ID, taskId);
            return new HierarchyOp.Builder(HIERARCHY_OP_TYPE_LAUNCH_TASK)
                    .setToTop(true)
                    .setLaunchOptions(fullOptions)
                    .build();
        }

        /** Creates a hierarchy op for starting a shortcut. */
        @NonNull
        public static HierarchyOp createForStartShortcut(@NonNull String callingPackage,
                @NonNull ShortcutInfo shortcutInfo, @Nullable Bundle options) {
            final Bundle fullOptions = options == null ? new Bundle() : options;
            fullOptions.putString(LAUNCH_KEY_SHORTCUT_CALLING_PACKAGE, callingPackage);
            return new HierarchyOp.Builder(HIERARCHY_OP_TYPE_START_SHORTCUT)
                    .setShortcutInfo(shortcutInfo)
                    .setLaunchOptions(fullOptions)
                    .build();
        }

        /** Creates a hierarchy op for setting launch adjacent flag root. */
        @NonNull
        public static HierarchyOp createForSetLaunchAdjacentFlagRoot(@Nullable IBinder container,
                boolean clearRoot) {
            return new HierarchyOp.Builder(HIERARCHY_OP_TYPE_SET_LAUNCH_ADJACENT_FLAG_ROOT)
                    .setContainer(container)
                    .setToTop(clearRoot)
                    .build();
        }

        /** Creates a hierarchy op for deleting a task **/
        @NonNull
        public static HierarchyOp createForRemoveTask(@NonNull IBinder container) {
            return new HierarchyOp.Builder(HIERARCHY_OP_TYPE_REMOVE_TASK)
                    .setContainer(container)
                    .build();
        }

        /**
         * Creates a hierarchy op for deleting a root task
         *
         * @hide
         **/
        @NonNull
        public static HierarchyOp createForRemoveRootTask(@NonNull IBinder container) {
            return new HierarchyOp.Builder(HIERARCHY_OP_TYPE_REMOVE_ROOT_TASK)
                    .setContainer(container)
                    .build();
        }

        /** Creates a hierarchy op for clearing adjacent root tasks. */
        @NonNull
        public static HierarchyOp createForClearAdjacentRoots(@NonNull IBinder root) {
            return new HierarchyOp.Builder(HIERARCHY_OP_TYPE_CLEAR_ADJACENT_ROOTS)
                    .setContainer(root)
                    .build();
        }

        /** Create a hierarchy op for app compat reachability. */
        @NonNull
        public static HierarchyOp createForReachability(IBinder container, int taskId, int x,
                int y) {
            final Bundle appCompatOptions = new Bundle();
            appCompatOptions.putInt(LAUNCH_KEY_TASK_ID, taskId);
            appCompatOptions.putInt(REACHABILITY_EVENT_X, x);
            appCompatOptions.putInt(REACHABILITY_EVENT_Y, y);
            return new HierarchyOp.Builder(HIERARCHY_OP_TYPE_APP_COMPAT_REACHABILITY)
                    .setAppCompatOptions(appCompatOptions)
                    .setContainer(container)
                    .build();
        }

        /** Create a hierarchy op for setting a task non-trimmable by recents. */
        @NonNull
        @FlaggedApi(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
        public static HierarchyOp createForSetTaskTrimmableFromRecents(@NonNull IBinder container,
                boolean isTrimmableFromRecents) {
            return new HierarchyOp.Builder(HIERARCHY_OP_TYPE_SET_IS_TRIMMABLE)
                    .setContainer(container)
                    .setIsTrimmableFromRecents(isTrimmableFromRecents)
                    .build();
        }

        /** Creates a hierarchy op for setting the safe region bounds. */
        @NonNull
        @FlaggedApi(Flags.FLAG_SAFE_REGION_LETTERBOXING_V1)
        public static HierarchyOp createForSetSafeRegionBounds(@NonNull IBinder container,
                @Nullable Rect safeRegionBounds) {
            return new Builder(HIERARCHY_OP_TYPE_SET_SAFE_REGION_BOUNDS)
                    .setContainer(container)
                    .setSafeRegionBounds(safeRegionBounds)
                    .build();
        }

        /** Only creates through {@link Builder}. */
        private HierarchyOp(@HierarchyOpType int type) {
            mType = type;
        }

        public HierarchyOp(@NonNull HierarchyOp copy) {
            mType = copy.mType;
            mContainer = copy.mContainer;
            mContainers = copy.mContainers;
            mBounds = copy.mBounds;
            mIncludingParents = copy.mIncludingParents;
            mReparent = copy.mReparent;
            mInsetsFrameProvider = copy.mInsetsFrameProvider;
            mCaller = copy.mCaller;
            mToTop = copy.mToTop;
            mReparentTopOnly = copy.mReparentTopOnly;
            mWindowingModes = copy.mWindowingModes;
            mActivityTypes = copy.mActivityTypes;
            mLaunchOptions = copy.mLaunchOptions;
            mAppCompatOptions = copy.mAppCompatOptions;
            mActivityIntent = copy.mActivityIntent;
            mTaskFragmentOperation = copy.mTaskFragmentOperation;
            mKeyguardState = copy.mKeyguardState;
            mPendingIntent = copy.mPendingIntent;
            mShortcutInfo = copy.mShortcutInfo;
            mAlwaysOnTop = copy.mAlwaysOnTop;
            mReparentLeafTaskIfRelaunch = copy.mReparentLeafTaskIfRelaunch;
            mIsTrimmableFromRecents = copy.mIsTrimmableFromRecents;
            mExcludeInsetsTypes = copy.mExcludeInsetsTypes;
            mForciblyShowingInsetsTypes = copy.mForciblyShowingInsetsTypes;
            mForciblyHidingInsetsTypes = copy.mForciblyHidingInsetsTypes;
            mSafeRegionBounds = copy.mSafeRegionBounds;
            mDisallowOverrideBoundsForChildren = copy.mDisallowOverrideBoundsForChildren;
        }

        private HierarchyOp(@NonNull Parcel in) {
            mType = in.readInt();
            mContainer = in.readStrongBinder();
            mContainers = in.createBinderArray();
            mBounds = in.readTypedObject(Rect.CREATOR);
            mIncludingParents = in.readBoolean();
            mReparent = in.readStrongBinder();
            mInsetsFrameProvider = in.readTypedObject(InsetsFrameProvider.CREATOR);
            mCaller = in.readStrongBinder();
            mToTop = in.readBoolean();
            mReparentTopOnly = in.readBoolean();
            mWindowingModes = in.createIntArray();
            mActivityTypes = in.createIntArray();
            mLaunchOptions = in.readBundle();
            mAppCompatOptions = in.readBundle(getClass().getClassLoader());
            mActivityIntent = in.readTypedObject(Intent.CREATOR);
            mTaskFragmentOperation = in.readTypedObject(TaskFragmentOperation.CREATOR);
            mKeyguardState = in.readTypedObject(KeyguardState.CREATOR);
            mPendingIntent = in.readTypedObject(PendingIntent.CREATOR);
            mShortcutInfo = in.readTypedObject(ShortcutInfo.CREATOR);
            mAlwaysOnTop = in.readBoolean();
            mReparentLeafTaskIfRelaunch = in.readBoolean();
            mIsTrimmableFromRecents = in.readBoolean();
            mExcludeInsetsTypes = in.readInt();
            mForciblyShowingInsetsTypes = in.readInt();
            mForciblyHidingInsetsTypes = in.readInt();
            mSafeRegionBounds = in.readTypedObject(Rect.CREATOR);
            mDisallowOverrideBoundsForChildren = in.readBoolean();
        }

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

        public boolean isReparent() {
            return mType == HIERARCHY_OP_TYPE_REPARENT;
        }

        @Nullable
        public IBinder getNewParent() {
            return mReparent;
        }

        @Nullable
        public InsetsFrameProvider getInsetsFrameProvider() {
            return mInsetsFrameProvider;
        }

        @Nullable
        public IBinder getCaller() {
            return mCaller;
        }

        @NonNull
        public IBinder getContainer() {
            return mContainer;
        }

        @NonNull
        public IBinder[] getContainers() {
            return mContainers;
        }

        public boolean getToTop() {
            return mToTop;
        }

        public boolean getReparentTopOnly() {
            return mReparentTopOnly;
        }

        @Nullable
        public int[] getWindowingModes() {
            return mWindowingModes;
        }

        @Nullable
        public int[] getActivityTypes() {
            return mActivityTypes;
        }

        @Nullable
        public Bundle getLaunchOptions() {
            return mLaunchOptions;
        }

        @Nullable
        public Bundle getAppCompatOptions() {
            return mAppCompatOptions;
        }

        @Nullable
        public Intent getActivityIntent() {
            return mActivityIntent;
        }

        public boolean isAlwaysOnTop() {
            return mAlwaysOnTop;
        }

        public boolean isReparentLeafTaskIfRelaunch() {
            return mReparentLeafTaskIfRelaunch;
        }

        @Nullable
        public TaskFragmentOperation getTaskFragmentOperation() {
            return mTaskFragmentOperation;
        }

        @Nullable
        public KeyguardState getKeyguardState() {
            return mKeyguardState;
        }

        @Nullable
        public PendingIntent getPendingIntent() {
            return mPendingIntent;
        }

        @Nullable
        public ShortcutInfo getShortcutInfo() {
            return mShortcutInfo;
        }

        @NonNull
        public Rect getBounds() {
            return mBounds;
        }

        /** Denotes whether the parents should also be included in the op. */
        public boolean includingParents() {
            return mIncludingParents;
        }

        /** Denotes whether the task can be trimmable from recents */
        public boolean isTrimmableFromRecents() {
            return mIsTrimmableFromRecents;
        }

        public @InsetsType int getExcludeInsetsTypes() {
            return mExcludeInsetsTypes;
        }

        public @InsetsType int getForciblyShowingInsetsTypes() {
            return mForciblyShowingInsetsTypes;
        }

        public @InsetsType int getForciblyHidingInsetsTypes() {
            return mForciblyHidingInsetsTypes;
        }

        /** Denotes the safe region bounds */
        @Nullable
        public Rect getSafeRegionBounds() {
            return mSafeRegionBounds;
        }

        public boolean getDisallowOverrideBoundsForChildren() {
            return mDisallowOverrideBoundsForChildren;
        }

        /** Gets a string representation of a hierarchy-op type. */
        public static String hopToString(@HierarchyOpType int type) {
            switch (type) {
                case HIERARCHY_OP_TYPE_REPARENT: return "reparent";
                case HIERARCHY_OP_TYPE_REORDER: return "reorder";
                case HIERARCHY_OP_TYPE_CHILDREN_TASKS_REPARENT: return "childrenTasksReparent";
                case HIERARCHY_OP_TYPE_SET_LAUNCH_ROOT: return "setLaunchRoot";
                case HIERARCHY_OP_TYPE_SET_ADJACENT_ROOTS: return "setAdjacentRoots";
                case HIERARCHY_OP_TYPE_LAUNCH_TASK: return "launchTask";
                case HIERARCHY_OP_TYPE_SET_LAUNCH_ADJACENT_FLAG_ROOT: return "setAdjacentFlagRoot";
                case HIERARCHY_OP_TYPE_PENDING_INTENT: return "pendingIntent";
                case HIERARCHY_OP_TYPE_START_SHORTCUT: return "startShortcut";
                case HIERARCHY_OP_TYPE_RESTORE_TRANSIENT_ORDER: return "restoreTransientOrder";
                case HIERARCHY_OP_TYPE_ADD_INSETS_FRAME_PROVIDER: return "addInsetsFrameProvider";
                case HIERARCHY_OP_TYPE_REMOVE_INSETS_FRAME_PROVIDER:
                    return "removeInsetsFrameProvider";
                case HIERARCHY_OP_TYPE_SET_ALWAYS_ON_TOP: return "setAlwaysOnTop";
                case HIERARCHY_OP_TYPE_REMOVE_TASK: return "removeTask";
                case HIERARCHY_OP_TYPE_REMOVE_ROOT_TASK: return "removeRootTask";
                case HIERARCHY_OP_TYPE_FINISH_ACTIVITY: return "finishActivity";
                case HIERARCHY_OP_TYPE_CLEAR_ADJACENT_ROOTS: return "clearAdjacentRoots";
                case HIERARCHY_OP_TYPE_SET_REPARENT_LEAF_TASK_IF_RELAUNCH:
                    return "setReparentLeafTaskIfRelaunch";
                case HIERARCHY_OP_TYPE_ADD_TASK_FRAGMENT_OPERATION:
                    return "addTaskFragmentOperation";
                case HIERARCHY_OP_TYPE_MOVE_PIP_ACTIVITY_TO_PINNED_TASK:
                    return "movePipActivityToPinnedTask";
                case HIERARCHY_OP_TYPE_SET_IS_TRIMMABLE: return "setIsTrimmable";
                case HIERARCHY_OP_TYPE_RESTORE_BACK_NAVIGATION: return "restoreBackNav";
                case HIERARCHY_OP_TYPE_SET_EXCLUDE_INSETS_TYPES: return "setExcludeInsetsTypes";
                case HIERARCHY_OP_TYPE_SET_KEYGUARD_STATE: return "setKeyguardState";
                case HIERARCHY_OP_TYPE_SET_SAFE_REGION_BOUNDS: return "setSafeRegionBounds";
                case HIERARCHY_OP_TYPE_SET_SYSTEM_BAR_VISIBILITY_OVERRIDE:
                    return "setSystemBarVisibilityOverride";
                case HIERARCHY_OP_TYPE_DISALLOW_OVERRIDE_BOUNDS_FOR_CHILDREN:
                    return "disallowOverrideBoundsForChildren";
                default: return "HOP(" + type + ")";
            }
        }

        @Override
        public String toString() {
            StringBuilder sb = new StringBuilder();
            sb.append("{").append(hopToString(mType)).append(": ");
            switch (mType) {
                case HIERARCHY_OP_TYPE_CHILDREN_TASKS_REPARENT:
                    sb.append("from=").append(mContainer).append(" to=").append(mReparent)
                            .append(" mToTop=").append(mToTop)
                            .append(" mReparentTopOnly=").append(mReparentTopOnly)
                            .append(" mWindowingMode=").append(Arrays.toString(mWindowingModes))
                            .append(" mActivityType=").append(Arrays.toString(mActivityTypes));
                    break;
                case HIERARCHY_OP_TYPE_SET_LAUNCH_ROOT:
                    sb.append("container=").append(mContainer)
                            .append(" mWindowingMode=").append(Arrays.toString(mWindowingModes))
                            .append(" mActivityType=").append(Arrays.toString(mActivityTypes));
                    break;
                case HIERARCHY_OP_TYPE_REPARENT:
                    sb.append(mContainer).append(" to ").append(mToTop ? "top of " : "bottom of ")
                            .append(mReparent);
                    break;
                case HIERARCHY_OP_TYPE_REORDER:
                    sb.append(mContainer).append(" to ").append(mToTop ? "top" : "bottom");
                    break;
                case HIERARCHY_OP_TYPE_SET_ADJACENT_ROOTS:
                    for (IBinder container : mContainers) {
                        if (container == mContainers[0]) {
                            sb.append("adjacentRoots=").append(container);
                        } else {
                            sb.append(", ").append(container);
                        }
                    }
                    break;
                case HIERARCHY_OP_TYPE_LAUNCH_TASK:
                    sb.append(mLaunchOptions);
                    break;
                case HIERARCHY_OP_TYPE_APP_COMPAT_REACHABILITY:
                    sb.append(mAppCompatOptions);
                    break;
                case HIERARCHY_OP_TYPE_SET_LAUNCH_ADJACENT_FLAG_ROOT:
                    sb.append("container=").append(mContainer).append(" clearRoot=").append(mToTop);
                    break;
                case HIERARCHY_OP_TYPE_START_SHORTCUT:
                    sb.append("options=").append(mLaunchOptions)
                            .append(" info=").append(mShortcutInfo);
                    break;
                case HIERARCHY_OP_TYPE_PENDING_INTENT:
                    sb.append("options=").append(mLaunchOptions);
                    break;
                case HIERARCHY_OP_TYPE_ADD_INSETS_FRAME_PROVIDER:
                case HIERARCHY_OP_TYPE_REMOVE_INSETS_FRAME_PROVIDER:
                    sb.append("container=").append(mContainer)
                            .append(" provider=").append(mInsetsFrameProvider)
                            .append(" caller=").append(mCaller);
                    break;
                case HIERARCHY_OP_TYPE_SET_ALWAYS_ON_TOP:
                    sb.append("container=").append(mContainer)
                            .append(" alwaysOnTop=").append(mAlwaysOnTop);
                    break;
                case HIERARCHY_OP_TYPE_REMOVE_TASK:
                    sb.append("task=").append(mContainer);
                    break;
                case HIERARCHY_OP_TYPE_REMOVE_ROOT_TASK:
                    sb.append("rootTask=").append(mContainer);
                    break;
                case HIERARCHY_OP_TYPE_FINISH_ACTIVITY:
                    sb.append("activity=").append(mContainer);
                    break;
                case HIERARCHY_OP_TYPE_CLEAR_ADJACENT_ROOTS:
                    sb.append("container=").append(mContainer);
                    break;
                case HIERARCHY_OP_TYPE_SET_REPARENT_LEAF_TASK_IF_RELAUNCH:
                    sb.append("container= ").append(mContainer)
                            .append(" reparentLeafTaskIfRelaunch= ")
                            .append(mReparentLeafTaskIfRelaunch);
                    break;
                case HIERARCHY_OP_TYPE_ADD_TASK_FRAGMENT_OPERATION:
                    sb.append("fragmentToken= ").append(mContainer)
                            .append(" operation= ").append(mTaskFragmentOperation);
                    break;
                case HIERARCHY_OP_TYPE_SET_EXCLUDE_INSETS_TYPES:
                    sb.append("container= ").append(mContainer)
                            .append(" mExcludeInsetsTypes= ")
                            .append(WindowInsets.Type.toString(mExcludeInsetsTypes));
                    break;
                case HIERARCHY_OP_TYPE_SET_KEYGUARD_STATE:
                    sb.append("KeyguardState= ").append(mKeyguardState);
                    break;
                case HIERARCHY_OP_TYPE_SET_IS_TRIMMABLE:
                    sb.append("container= ").append(mContainer)
                            .append(" isTrimmable= ")
                            .append(mIsTrimmableFromRecents);
                    break;
                case HIERARCHY_OP_TYPE_SET_SAFE_REGION_BOUNDS:
                    sb.append("container= ").append(mContainer)
                            .append(" safeRegionBounds= ")
                            .append(mSafeRegionBounds);
                    break;
                case HIERARCHY_OP_TYPE_SET_SYSTEM_BAR_VISIBILITY_OVERRIDE:
                    sb.append(" container=").append(mContainer)
                            .append(" caller=").append(mCaller)
                            .append(" mForciblyShowingInsetsTypes=")
                            .append(WindowInsets.Type.toString(mForciblyShowingInsetsTypes))
                            .append(" mForciblyHidingInsetsTypes=")
                            .append(WindowInsets.Type.toString(mForciblyHidingInsetsTypes));
                    break;
                case HIERARCHY_OP_TYPE_DISALLOW_OVERRIDE_BOUNDS_FOR_CHILDREN:
                    sb.append(" container=").append(mContainer)
                            .append(" mDisallowOverrideBoundsForChildren=")
                            .append(mDisallowOverrideBoundsForChildren);
                    break;
                default:
                    sb.append("container=").append(mContainer)
                            .append(" reparent=").append(mReparent)
                            .append(" mToTop=").append(mToTop)
                            .append(" mWindowingMode=").append(Arrays.toString(mWindowingModes))
                            .append(" mActivityType=").append(Arrays.toString(mActivityTypes));
            }
            return sb.append("}").toString();
        }

        @Override
        public void writeToParcel(@NonNull Parcel dest, int flags) {
            dest.writeInt(mType);
            dest.writeStrongBinder(mContainer);
            dest.writeBinderArray(mContainers);
            dest.writeTypedObject(mBounds, flags);
            dest.writeBoolean(mIncludingParents);
            dest.writeStrongBinder(mReparent);
            dest.writeTypedObject(mInsetsFrameProvider, flags);
            dest.writeStrongBinder(mCaller);
            dest.writeBoolean(mToTop);
            dest.writeBoolean(mReparentTopOnly);
            dest.writeIntArray(mWindowingModes);
            dest.writeIntArray(mActivityTypes);
            dest.writeBundle(mLaunchOptions);
            dest.writeBundle(mAppCompatOptions);
            dest.writeTypedObject(mActivityIntent, flags);
            dest.writeTypedObject(mTaskFragmentOperation, flags);
            dest.writeTypedObject(mKeyguardState, flags);
            dest.writeTypedObject(mPendingIntent, flags);
            dest.writeTypedObject(mShortcutInfo, flags);
            dest.writeBoolean(mAlwaysOnTop);
            dest.writeBoolean(mReparentLeafTaskIfRelaunch);
            dest.writeBoolean(mIsTrimmableFromRecents);
            dest.writeInt(mExcludeInsetsTypes);
            dest.writeInt(mForciblyShowingInsetsTypes);
            dest.writeInt(mForciblyHidingInsetsTypes);
            dest.writeTypedObject(mSafeRegionBounds, flags);
            dest.writeBoolean(mDisallowOverrideBoundsForChildren);
        }

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

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

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

        private static class Builder {

            @HierarchyOpType
            private final int mType;

            @Nullable
            private IBinder mContainer;

            @Nullable
            private IBinder[] mContainers;

            @Nullable
            private IBinder mReparent;

            @Nullable
            private InsetsFrameProvider mInsetsFrameProvider;

            @Nullable
            private IBinder mCaller;

            private boolean mToTop;

            private boolean mReparentTopOnly;

            @Nullable
            private int[] mWindowingModes;

            @Nullable
            private int[] mActivityTypes;

            @Nullable
            private Bundle mLaunchOptions;

            @Nullable
            private Bundle mAppCompatOptions;

            @Nullable
            private Intent mActivityIntent;

            @Nullable
            private TaskFragmentOperation mTaskFragmentOperation;

            @Nullable
            private KeyguardState mKeyguardState;

            @Nullable
            private PendingIntent mPendingIntent;

            @Nullable
            private ShortcutInfo mShortcutInfo;

            @Nullable
            private Rect mBounds;

            private boolean mIncludingParents;

            private boolean mAlwaysOnTop;

            private boolean mReparentLeafTaskIfRelaunch;

            private boolean mIsTrimmableFromRecents;

            private @InsetsType int mExcludeInsetsTypes;

            private @InsetsType int mForciblyShowingInsetsTypes;
            private @InsetsType int mForciblyHidingInsetsTypes;

            @Nullable
            private Rect mSafeRegionBounds;

            private boolean mDisallowOverrideBoundsForChildren;

            Builder(@HierarchyOpType int type) {
                mType = type;
            }

            Builder setContainer(@Nullable IBinder container) {
                mContainer = container;
                return this;
            }

            Builder setContainers(@Nullable IBinder[] containers) {
                mContainers = containers;
                return this;
            }

            Builder setReparentContainer(@Nullable IBinder reparentContainer) {
                mReparent = reparentContainer;
                return this;
            }

            Builder setInsetsFrameProvider(InsetsFrameProvider provider) {
                mInsetsFrameProvider = provider;
                return this;
            }

            Builder setCaller(@Nullable IBinder caller) {
                mCaller = caller;
                return this;
            }

            Builder setToTop(boolean toTop) {
                mToTop = toTop;
                return this;
            }

            Builder setReparentTopOnly(boolean reparentTopOnly) {
                mReparentTopOnly = reparentTopOnly;
                return this;
            }

            Builder setWindowingModes(@Nullable int[] windowingModes) {
                mWindowingModes = windowingModes;
                return this;
            }

            Builder setActivityTypes(@Nullable int[] activityTypes) {
                mActivityTypes = activityTypes;
                return this;
            }

            Builder setLaunchOptions(@Nullable Bundle launchOptions) {
                mLaunchOptions = launchOptions;
                return this;
            }

            Builder setAppCompatOptions(@Nullable Bundle appCompatOptions) {
                mAppCompatOptions = appCompatOptions;
                return this;
            }

            Builder setActivityIntent(@Nullable Intent activityIntent) {
                mActivityIntent = activityIntent;
                return this;
            }

            Builder setPendingIntent(@Nullable PendingIntent sender) {
                mPendingIntent = sender;
                return this;
            }

            Builder setAlwaysOnTop(boolean alwaysOnTop) {
                mAlwaysOnTop = alwaysOnTop;
                return this;
            }

            Builder setTaskFragmentOperation(
                    @Nullable TaskFragmentOperation taskFragmentOperation) {
                mTaskFragmentOperation = taskFragmentOperation;
                return this;
            }

            Builder setKeyguardState(
                    @Nullable KeyguardState keyguardState) {
                mKeyguardState = keyguardState;
                return this;
            }

            Builder setReparentLeafTaskIfRelaunch(boolean reparentLeafTaskIfRelaunch) {
                mReparentLeafTaskIfRelaunch = reparentLeafTaskIfRelaunch;
                return this;
            }

            Builder setShortcutInfo(@Nullable ShortcutInfo shortcutInfo) {
                mShortcutInfo = shortcutInfo;
                return this;
            }

            Builder setBounds(@NonNull Rect bounds) {
                mBounds = bounds;
                return this;
            }

            Builder setIncludingParents(boolean value) {
                mIncludingParents = value;
                return this;
            }

            Builder setIsTrimmableFromRecents(boolean isTrimmableFromRecents) {
                mIsTrimmableFromRecents = isTrimmableFromRecents;
                return this;
            }

            Builder setExcludeInsetsTypes(@InsetsType int excludeInsetsTypes) {
                mExcludeInsetsTypes = excludeInsetsTypes;
                return this;
            }

            Builder setSystemBarVisibilityOverride(
                    @InsetsType int forciblyShowingInsetsTypes,
                    @InsetsType int forciblyHidingInsetsTypes) {
                mForciblyShowingInsetsTypes = forciblyShowingInsetsTypes;
                mForciblyHidingInsetsTypes = forciblyHidingInsetsTypes;
                return this;
            }

            Builder setSafeRegionBounds(Rect safeRegionBounds) {
                mSafeRegionBounds = safeRegionBounds;
                return this;
            }

            Builder setDisallowOverrideBoundsForChildren(
                    boolean disallowOverrideBoundsForChildren) {
                mDisallowOverrideBoundsForChildren = disallowOverrideBoundsForChildren;
                return this;
            }

            @NonNull
            HierarchyOp build() {
                final HierarchyOp hierarchyOp = new HierarchyOp(mType);
                hierarchyOp.mContainer = mContainer;
                hierarchyOp.mContainers = mContainers;
                hierarchyOp.mReparent = mReparent;
                hierarchyOp.mWindowingModes = mWindowingModes != null
                        ? Arrays.copyOf(mWindowingModes, mWindowingModes.length)
                        : null;
                hierarchyOp.mActivityTypes = mActivityTypes != null
                        ? Arrays.copyOf(mActivityTypes, mActivityTypes.length)
                        : null;
                hierarchyOp.mInsetsFrameProvider = mInsetsFrameProvider;
                hierarchyOp.mCaller = mCaller;
                hierarchyOp.mToTop = mToTop;
                hierarchyOp.mReparentTopOnly = mReparentTopOnly;
                hierarchyOp.mLaunchOptions = mLaunchOptions;
                hierarchyOp.mAppCompatOptions = mAppCompatOptions;
                hierarchyOp.mActivityIntent = mActivityIntent;
                hierarchyOp.mPendingIntent = mPendingIntent;
                hierarchyOp.mAlwaysOnTop = mAlwaysOnTop;
                hierarchyOp.mTaskFragmentOperation = mTaskFragmentOperation;
                hierarchyOp.mKeyguardState = mKeyguardState;
                hierarchyOp.mShortcutInfo = mShortcutInfo;
                hierarchyOp.mBounds = mBounds;
                hierarchyOp.mIncludingParents = mIncludingParents;
                hierarchyOp.mReparentLeafTaskIfRelaunch = mReparentLeafTaskIfRelaunch;
                hierarchyOp.mIsTrimmableFromRecents = mIsTrimmableFromRecents;
                hierarchyOp.mExcludeInsetsTypes = mExcludeInsetsTypes;
                hierarchyOp.mForciblyShowingInsetsTypes = mForciblyShowingInsetsTypes;
                hierarchyOp.mForciblyHidingInsetsTypes = mForciblyHidingInsetsTypes;
                hierarchyOp.mSafeRegionBounds = mSafeRegionBounds;
                hierarchyOp.mDisallowOverrideBoundsForChildren = mDisallowOverrideBoundsForChildren;
                return hierarchyOp;
            }
        }
    }

    /**
     * Helper class for building an options Bundle that can be used to set adjacent rules of
     * TaskFragments.
     */
    public static class TaskFragmentAdjacentParams {
        private static final String DELAY_PRIMARY_LAST_ACTIVITY_REMOVAL =
                "android:transaction.adjacent.option.delay_primary_removal";
        private static final String DELAY_SECONDARY_LAST_ACTIVITY_REMOVAL =
                "android:transaction.adjacent.option.delay_secondary_removal";

        private boolean mDelayPrimaryLastActivityRemoval;
        private boolean mDelaySecondaryLastActivityRemoval;

        public TaskFragmentAdjacentParams() {
        }

        public TaskFragmentAdjacentParams(@NonNull Bundle bundle) {
            mDelayPrimaryLastActivityRemoval = bundle.getBoolean(
                    DELAY_PRIMARY_LAST_ACTIVITY_REMOVAL);
            mDelaySecondaryLastActivityRemoval = bundle.getBoolean(
                    DELAY_SECONDARY_LAST_ACTIVITY_REMOVAL);
        }

        /** @see #shouldDelayPrimaryLastActivityRemoval() */
        public void setShouldDelayPrimaryLastActivityRemoval(boolean delay) {
            mDelayPrimaryLastActivityRemoval = delay;
        }

        /** @see #shouldDelaySecondaryLastActivityRemoval() */
        public void setShouldDelaySecondaryLastActivityRemoval(boolean delay) {
            mDelaySecondaryLastActivityRemoval = delay;
        }

        /**
         * Whether to delay the last activity of the primary adjacent TaskFragment being immediately
         * removed while finishing.
         * <p>
         * It is usually set to {@code true} to give organizer an opportunity to perform other
         * actions or animations. An example is to finish together with the adjacent TaskFragment.
         * </p>
         */
        public boolean shouldDelayPrimaryLastActivityRemoval() {
            return mDelayPrimaryLastActivityRemoval;
        }

        /**
         * Similar to {@link #shouldDelayPrimaryLastActivityRemoval()}, but for the secondary
         * TaskFragment.
         */
        public boolean shouldDelaySecondaryLastActivityRemoval() {
            return mDelaySecondaryLastActivityRemoval;
        }

        @NonNull
        Bundle toBundle() {
            final Bundle b = new Bundle();
            b.putBoolean(DELAY_PRIMARY_LAST_ACTIVITY_REMOVAL, mDelayPrimaryLastActivityRemoval);
            b.putBoolean(DELAY_SECONDARY_LAST_ACTIVITY_REMOVAL, mDelaySecondaryLastActivityRemoval);
            return b;
        }
    }
}
