/*
 * Copyright (C) 2021 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 com.android.server.wm;

import static android.Manifest.permission.EMBED_ANY_APP_IN_UNTRUSTED_MODE;
import static android.Manifest.permission.MANAGE_ACTIVITY_TASKS;
import static android.app.ActivityManager.LOCK_TASK_MODE_LOCKED;
import static android.app.ActivityManager.LOCK_TASK_MODE_PINNED;
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.content.pm.ActivityInfo.FLAG_ALLOW_UNTRUSTED_ACTIVITY_EMBEDDING;
import static android.content.pm.ActivityInfo.FLAG_RESUME_WHILE_PAUSING;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static android.content.res.Configuration.ORIENTATION_UNDEFINED;
import static android.internal.perfetto.protos.Windowmanagerservice.IdentifierProto.HASH_CODE;
import static android.internal.perfetto.protos.Windowmanagerservice.IdentifierProto.TITLE;
import static android.internal.perfetto.protos.Windowmanagerservice.IdentifierProto.USER_ID;
import static android.internal.perfetto.protos.Windowmanagerservice.TaskFragmentProto.ACTIVITY_TYPE;
import static android.internal.perfetto.protos.Windowmanagerservice.TaskFragmentProto.DISPLAY_ID;
import static android.internal.perfetto.protos.Windowmanagerservice.TaskFragmentProto.MIN_HEIGHT;
import static android.internal.perfetto.protos.Windowmanagerservice.TaskFragmentProto.MIN_WIDTH;
import static android.internal.perfetto.protos.Windowmanagerservice.TaskFragmentProto.WINDOW_CONTAINER;
import static android.internal.perfetto.protos.Windowmanagerservice.WindowContainerChildProto.TASK_FRAGMENT;
import static android.os.Process.INVALID_UID;
import static android.os.Process.SYSTEM_UID;
import static android.os.UserHandle.USER_NULL;
import static android.view.Display.INVALID_DISPLAY;
import static android.view.Surface.ROTATION_270;
import static android.view.Surface.ROTATION_90;
import static android.view.WindowManager.LayoutParams.FLAG_DIM_BEHIND;
import static android.window.DesktopExperienceFlags.ENABLE_DESKTOP_WINDOWING_ENTERPRISE_BUGFIX;

import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_STATES;
import static com.android.server.wm.ActivityRecord.State.PAUSED;
import static com.android.server.wm.ActivityRecord.State.PAUSING;
import static com.android.server.wm.ActivityRecord.State.RESUMED;
import static com.android.server.wm.ActivityRecord.State.STOPPING;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_RESULTS;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_SWITCH;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_RESULTS;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_SWITCH;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_TRANSITION;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.ActivityTaskManagerService.checkPermission;
import static com.android.server.wm.ActivityTaskSupervisor.printThisActivity;

import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityOptions;
import android.app.IApplicationThread;
import android.app.ResultInfo;
import android.app.WindowConfiguration;
import android.app.servertransaction.ActivityResultItem;
import android.app.servertransaction.NewIntentItem;
import android.app.servertransaction.PauseActivityItem;
import android.app.servertransaction.ResumeActivityItem;
import android.content.PermissionChecker;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.graphics.Insets;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.IBinder;
import android.os.UserHandle;
import android.util.ArraySet;
import android.util.DisplayMetrics;
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
import android.view.DisplayInfo;
import android.view.SurfaceControl;
import android.window.DesktopExperienceFlags;
import android.window.ITaskFragmentOrganizer;
import android.window.TaskFragmentAnimationParams;
import android.window.TaskFragmentInfo;
import android.window.TaskFragmentOrganizerToken;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.ProtoLog;
import com.android.internal.util.ToBooleanFunction;
import com.android.server.am.HostingRecord;
import com.android.server.pm.pkg.AndroidPackage;
import com.android.window.flags.Flags;

import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Predicate;

/**
 * A basic container that can be used to contain activities or other {@link TaskFragment}, which
 * also able to manage the activity lifecycle and updates the visibilities of the activities in it.
 */
class TaskFragment extends WindowContainer<WindowContainer> {
    @IntDef(prefix = {"TASK_FRAGMENT_VISIBILITY"}, value = {
            TASK_FRAGMENT_VISIBILITY_VISIBLE,
            TASK_FRAGMENT_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT,
            TASK_FRAGMENT_VISIBILITY_INVISIBLE,
    })
    @interface TaskFragmentVisibility {}

    /**
     * TaskFragment is visible. No other TaskFragment(s) on top that fully or partially occlude it.
     */
    static final int TASK_FRAGMENT_VISIBILITY_VISIBLE = 0;

    /** TaskFragment is partially occluded by other translucent TaskFragment(s) on top of it. */
    static final int TASK_FRAGMENT_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT = 1;

    /** TaskFragment is completely invisible. */
    static final int TASK_FRAGMENT_VISIBILITY_INVISIBLE = 2;

    private static final String TAG = TAG_WITH_CLASS_NAME ? "TaskFragment" : TAG_ATM;
    private static final String TAG_SWITCH = TAG + POSTFIX_SWITCH;
    private static final String TAG_RESULTS = TAG + POSTFIX_RESULTS;
    private static final String TAG_TRANSITION = TAG + POSTFIX_TRANSITION;

    /** Set to false to disable the preview that is shown while a new activity is being started. */
    static final boolean SHOW_APP_STARTING_PREVIEW = true;

    /**
     * An embedding check result of {@link #isAllowedToEmbedActivity(ActivityRecord)} or
     * {@link ActivityStarter#canEmbedActivity(TaskFragment, ActivityRecord, Task)}:
     * indicate that an Activity can be embedded successfully.
     */
    static final int EMBEDDING_ALLOWED = 0;
    /**
     * An embedding check result of {@link #isAllowedToEmbedActivity(ActivityRecord)} or
     * {@link ActivityStarter#canEmbedActivity(TaskFragment, ActivityRecord, Task)}:
     * indicate that an Activity can't be embedded because either the Activity does not allow
     * untrusted embedding, and the embedding host app is not trusted.
     */
    static final int EMBEDDING_DISALLOWED_UNTRUSTED_HOST = 1;
    /**
     * An embedding check result of {@link #isAllowedToEmbedActivity(ActivityRecord)} or
     * {@link ActivityStarter#canEmbedActivity(TaskFragment, ActivityRecord, Task)}:
     * indicate that an Activity can't be embedded because this taskFragment's bounds are
     * {@link #smallerThanMinDimension(ActivityRecord)}.
     */
    static final int EMBEDDING_DISALLOWED_MIN_DIMENSION_VIOLATION = 2;
    /**
     * An embedding check result of
     * {@link ActivityStarter#canEmbedActivity(TaskFragment, ActivityRecord, Task)}:
     * indicate that an Activity can't be embedded because the Activity is started on a new task.
     */
    static final int EMBEDDING_DISALLOWED_NEW_TASK = 3;

    /**
     * Embedding check results of {@link #isAllowedToEmbedActivity(ActivityRecord)} or
     * {@link ActivityStarter#canEmbedActivity(TaskFragment, ActivityRecord, Task)}.
     */
    @IntDef(prefix = {"EMBEDDING_"}, value = {
            EMBEDDING_ALLOWED,
            EMBEDDING_DISALLOWED_UNTRUSTED_HOST,
            EMBEDDING_DISALLOWED_MIN_DIMENSION_VIOLATION,
            EMBEDDING_DISALLOWED_NEW_TASK,
    })
    @interface EmbeddingCheckResult {}

    /**
     * Indicate that the minimal width/height should use the default value.
     *
     * @see #mMinWidth
     * @see #mMinHeight
     */
    static final int INVALID_MIN_SIZE = -1;

    final ActivityTaskManagerService mAtmService;
    final ActivityTaskSupervisor mTaskSupervisor;
    final RootWindowContainer mRootWindowContainer;
    private final TaskFragmentOrganizerController mTaskFragmentOrganizerController;
    private final LockTaskController mLockTaskController;

    // TODO(b/233177466): Move mMinWidth and mMinHeight to Task and remove usages in TaskFragment
    /**
     * Minimal width of this task fragment when it's resizeable. {@link #INVALID_MIN_SIZE} means it
     * should use the default minimal width.
     */
    int mMinWidth;

    /**
     * Minimal height of this task fragment when it's resizeable. {@link #INVALID_MIN_SIZE} means it
     * should use the default minimal height.
     */
    int mMinHeight;

    final Dimmer mDimmer = new Dimmer(this);

    /** Apply the dim layer on the embedded TaskFragment. */
    static final int EMBEDDED_DIM_AREA_TASK_FRAGMENT = 0;

    /** Apply the dim layer on the parent Task for an embedded TaskFragment. */
    static final int EMBEDDED_DIM_AREA_PARENT_TASK = 1;

    /**
     * The type of dim layer area for an embedded TaskFragment.
     */
    @IntDef(prefix = {"EMBEDDED_DIM_AREA_"}, value = {
            EMBEDDED_DIM_AREA_TASK_FRAGMENT,
            EMBEDDED_DIM_AREA_PARENT_TASK,
    })
    @interface EmbeddedDimArea {}

    @EmbeddedDimArea
    private int mEmbeddedDimArea = EMBEDDED_DIM_AREA_TASK_FRAGMENT;

    /** This task fragment will be removed when the cleanup of its children are done. */
    private boolean mIsRemovalRequested;

    /**
     * The TaskFragments that are adjacent to each other, including this TaskFragment.
     * All TaskFragments in this set share the same set instance.
     */
    @Nullable
    private AdjacentSet mAdjacentTaskFragments;

    /**
     * Unlike the {@link #mAdjacentTaskFragments}, the companion TaskFragment is not always visually
     * adjacent to this one, but this TaskFragment will be removed by the organizer if the
     * companion TaskFragment is removed.
     *
     * Note: if {@link #mCompanionTaskFragment} is non-{@code null}, the organizer will only remove
     * that Activity instead unless that Activity is the last Activity in this TaskFragment.
     */
    @Nullable
    private TaskFragment mCompanionTaskFragment;

    /**
     * When this is non-{@code null} while {@link #mCompanionTaskFragment} is set, only this
     * Activity will be removed by the organizer if the companion TaskFragment is removed, unless
     * this is the last Activity in this TaskFragment, in which case this TaskFragment will also be
     * removed.
     */
    @Nullable
    private IBinder mCompanionToBeFinishedActivity;

    /**
     * Prevents duplicate calls to onTaskFragmentAppeared.
     */
    boolean mTaskFragmentAppearedSent;

    /**
     * Prevents unnecessary callbacks after onTaskFragmentVanished.
     */
    boolean mTaskFragmentVanishedSent;

    /**
     * The last running activity of the TaskFragment was finished due to clear task while launching
     * an activity in the Task.
     */
    boolean mClearedTaskForReuse;

    /**
     * The last running activity of the TaskFragment was reparented to a different Task because it
     * is entering PiP.
     */
    boolean mClearedTaskFragmentForPip;

    /**
     * The last running activity of the TaskFragment was removed and added to the top-most of the
     * Task because it was launched with FLAG_ACTIVITY_REORDER_TO_FRONT.
     */
    boolean mClearedForReorderActivityToFront;

    /**
     * When we are in the process of pausing an activity, before starting the
     * next one, this variable holds the activity that is currently being paused.
     *
     * Only set at leaf task fragments.
     */
    @Nullable
    private ActivityRecord mPausingActivity = null;

    /**
     * This is the last activity that we put into the paused state.  This is
     * used to determine if we need to do an activity transition while sleeping,
     * when we normally hold the top activity paused.
     */
    ActivityRecord mLastPausedActivity = null;

    /**
     * Current activity that is resumed, or null if there is none.
     * Only set at leaf task fragments.
     */
    @Nullable
    private ActivityRecord mResumedActivity = null;

    /**
     * This TaskFragment was created by an organizer which has the following implementations.
     * <ul>
     *     <li>The TaskFragment won't be removed when it is empty. Removal has to be an explicit
     *     request from the organizer.</li>
     *     <li>If this fragment is a Task object then unlike other non-root tasks, it's direct
     *     children are visible to the organizer for ordering purposes.</li>
     *     <li>A TaskFragment can be created by {@link android.window.TaskFragmentOrganizer}, and
     *     a Task can be created by {@link android.window.TaskOrganizer}.</li>
     * </ul>
     */
    @VisibleForTesting
    boolean mCreatedByOrganizer;

    /** Whether this TaskFragment is embedded in a task. */
    private final boolean mIsEmbedded;

    /** Organizer that organizing this TaskFragment. */
    @Nullable
    private ITaskFragmentOrganizer mTaskFragmentOrganizer;

    int mTaskFragmentOrganizerUid = INVALID_UID;
    @Nullable String mTaskFragmentOrganizerProcessName;

    /** Client assigned unique token for this TaskFragment if this is created by an organizer. */
    @Nullable
    private final IBinder mFragmentToken;

    /** The animation override params for animation running on this TaskFragment. */
    @NonNull
    private TaskFragmentAnimationParams mAnimationParams = TaskFragmentAnimationParams.DEFAULT;

    /**
     * The organizer requested bounds of the embedded TaskFragment relative to the parent Task.
     * {@code null} if it is not {@link #mIsEmbedded}
     */
    @Nullable
    private final Rect mRelativeEmbeddedBounds;

    /**
     * Whether to delay the call to {@link #updateOrganizedTaskFragmentSurface()} when there is a
     * configuration change.
     */
    private boolean mDelayOrganizedTaskFragmentSurfaceUpdate;

    /**
     * Whether to delay the last activity of TaskFragment being immediately removed while finishing.
     * This should only be set on a embedded TaskFragment, where the organizer can have the
     * opportunity to perform animations and finishing the adjacent TaskFragment.
     */
    private boolean mDelayLastActivityRemoval;

    /**
     * Whether the activity navigation should be isolated. That is, Activities cannot be launched
     * on an isolated TaskFragment unless explicitly requested to be launched to.
     */
    private boolean mIsolatedNav;

    /**
     * Whether the TaskFragment to be pinned.
     * <p>
     * If a TaskFragment is pinned, the TaskFragment should be the top-most TaskFragment among other
     * sibling TaskFragments. Any newly launched and embeddable activity should not be placed in the
     * pinned TaskFragment, unless the activity is launched from the pinned TaskFragment or
     * explicitly requested to. Non-embeddable activities are not restricted to.
     */
    private boolean mPinned;

    /**
     * Whether the TaskFragment should move to bottom of task when any activity below it is
     * launched in clear top mode.
     */
    private boolean mMoveToBottomIfClearWhenLaunch;

    /**
     * If {@code true}, transitions are allowed even if this TaskFragment is empty. If
     * {@code false}, transitions will wait until this TaskFragment becomes non-empty or other
     * conditions are met. Default to {@code false}.
     *
     * @see #isReadyToTransit
     */
    private boolean mAllowTransitionWhenEmpty;

    /**
     * Specifies which configuration changes should trigger TaskFragment info changed callbacks.
     * Only system TaskFragment organizers are allowed to set this value.
     */
    private @ActivityInfo.Config int mConfigurationChangeMaskForOrganizer;

    /** When set, will force the task to report as invisible. */
    static final int FLAG_FORCE_HIDDEN_FOR_PINNED_TASK = 1;
    static final int FLAG_FORCE_HIDDEN_FOR_TASK_ORG = 1 << 1;
    static final int FLAG_FORCE_HIDDEN_FOR_TASK_FRAGMENT_ORG = 1 << 2;

    @IntDef(prefix = {"FLAG_FORCE_HIDDEN_"}, value = {
            FLAG_FORCE_HIDDEN_FOR_PINNED_TASK,
            FLAG_FORCE_HIDDEN_FOR_TASK_ORG,
            FLAG_FORCE_HIDDEN_FOR_TASK_FRAGMENT_ORG,
    }, flag = true)
    @interface FlagForceHidden {}
    protected int mForceHiddenFlags = 0;

    private boolean mForceTranslucent = false;

    /** @see #setCanAffectSystemUiFlags */
    private boolean mCanAffectSystemUiFlags = true;

    final Point mLastSurfaceSize = new Point();

    private final Rect mTmpBounds = new Rect();
    private final Rect mTmpAbsBounds = new Rect();
    private final Rect mTmpFullBounds = new Rect();
    /** For calculating screenWidthDp and screenWidthDp, i.e. the area without the system bars. */
    private final Rect mTmpStableBounds = new Rect();
    /** For calculating app bounds, i.e. the area without the nav bar and display cutout. */
    private final Rect mTmpNonDecorBounds = new Rect();

    private final EnsureActivitiesVisibleHelper mEnsureActivitiesVisibleHelper =
            new EnsureActivitiesVisibleHelper(this);

    private final boolean mEnableSeeThroughTaskFragments =
            DesktopExperienceFlags.ENABLE_SEE_THROUGH_TASK_FRAGMENTS.isTrue();

    /** Creates an embedded task fragment. */
    TaskFragment(ActivityTaskManagerService atmService, IBinder fragmentToken,
            boolean createdByOrganizer) {
        this(atmService, fragmentToken, createdByOrganizer, true /* isEmbedded */);
    }

    TaskFragment(ActivityTaskManagerService atmService, IBinder fragmentToken,
            boolean createdByOrganizer, boolean isEmbedded) {
        super(atmService.mWindowManager);

        mAtmService = atmService;
        mTaskSupervisor = mAtmService.mTaskSupervisor;
        mRootWindowContainer = mAtmService.mRootWindowContainer;
        mCreatedByOrganizer = createdByOrganizer;
        mIsEmbedded = isEmbedded;
        mRelativeEmbeddedBounds = isEmbedded ? new Rect() : null;
        mTaskFragmentOrganizerController =
                mAtmService.mWindowOrganizerController.mTaskFragmentOrganizerController;
        mLockTaskController = mAtmService.getLockTaskController();
        mFragmentToken = fragmentToken;
        mRemoteToken = new RemoteToken(this);
    }

    @Nullable
    static TaskFragment fromTaskFragmentToken(@Nullable IBinder token,
            @NonNull ActivityTaskManagerService service) {
        if (token == null) return null;
        return service.mWindowOrganizerController.getTaskFragment(token);
    }

    void setAdjacentTaskFragments(@NonNull AdjacentSet adjacentTaskFragments) {
        adjacentTaskFragments.setAsAdjacent();
    }

    void clearCompanionTaskFragment() {
        mCompanionTaskFragment = null;
        mCompanionToBeFinishedActivity = null;
    }

    void setCompanionTaskFragment(@Nullable TaskFragment companionTaskFragment,
            @Nullable IBinder toBeFinishedActivity) {
        mCompanionTaskFragment = companionTaskFragment;
        if (Flags.taskFragmentCompanionActivity()) {
            mCompanionToBeFinishedActivity = toBeFinishedActivity;
        }
    }

    @Nullable
    TaskFragment getCompanionTaskFragment() {
        return mCompanionTaskFragment;
    }

    @Nullable
    IBinder getCompanionToBeFinishedActivity() {
        return mCompanionToBeFinishedActivity;
    }

    boolean shouldBeFinishedWithCompanionTaskFragment() {
        if (getCompanionTaskFragment() == null) {
            return false;
        }
        if (getCompanionToBeFinishedActivity() == null) {
            return true;
        }
        // Only the mCompanionToBeFinishedActivity activity will be finished with the companion TF,
        // unless the mCompanionToBeFinishedActivity is the only activity in this TF.
        return getNonFinishingActivityCount() == 1
                && getTopNonFinishingActivity().token.equals(getCompanionToBeFinishedActivity());
    }

    void clearAdjacentTaskFragments() {
        if (mAdjacentTaskFragments != null) {
            mAdjacentTaskFragments.clear();
        }
    }

    void removeFromAdjacentTaskFragments() {
        if (mAdjacentTaskFragments != null) {
            mAdjacentTaskFragments.remove(this);
        }
    }

    @Nullable
    AdjacentSet getAdjacentTaskFragments() {
        return mAdjacentTaskFragments;
    }

    /**
     * Runs callback on all TaskFragments that are adjacent to this. The invoke order is not
     * guaranteed.
     */
    void forOtherAdjacentTaskFragments(@NonNull Consumer<TaskFragment> callback) {
        if (mAdjacentTaskFragments == null) {
            return;
        }
        mAdjacentTaskFragments.forAllTaskFragments(callback, this /* exclude */);
    }

    /**
     * Runs callback on all TaskFragments that are adjacent to this. Returns early if callback
     * returns true on any of them. The invoke order is not guaranteed.
     */
    boolean forOtherAdjacentTaskFragments(@NonNull Predicate<TaskFragment> callback) {
        if (mAdjacentTaskFragments == null) {
            return false;
        }
        return mAdjacentTaskFragments.forAllTaskFragments(callback, this /* exclude */);
    }

    boolean hasAdjacentTaskFragment() {
        return mAdjacentTaskFragments != null;
    }

    boolean isAdjacentTo(@NonNull TaskFragment other) {
        return other != this
                && mAdjacentTaskFragments != null
                && mAdjacentTaskFragments.contains(other);
    }

    void setTaskFragmentOrganizer(@NonNull TaskFragmentOrganizerToken organizer, int uid,
            @NonNull String processName) {
        mTaskFragmentOrganizer = ITaskFragmentOrganizer.Stub.asInterface(organizer.asBinder());
        mTaskFragmentOrganizerUid = uid;
        mTaskFragmentOrganizerProcessName = processName;
    }

    void onTaskFragmentOrganizerRestarted(@NonNull ITaskFragmentOrganizer organizer) {
        mTaskFragmentOrganizer = organizer;
    }

    void onTaskFragmentOrganizerRemoved() {
        mTaskFragmentOrganizer = null;
    }

    /** Whether this TaskFragment is organized by the given {@code organizer}. */
    boolean hasTaskFragmentOrganizer(ITaskFragmentOrganizer organizer) {
        return organizer != null && mTaskFragmentOrganizer != null
                && organizer.asBinder().equals(mTaskFragmentOrganizer.asBinder());
    }

    /**
     * Returns the process of organizer if this TaskFragment is organized and the activity lives in
     * a different process than the organizer.
     */
    @Nullable
    private WindowProcessController getOrganizerProcessIfDifferent(@Nullable ActivityRecord r) {
        final Task task = getTask();
        if (r == null || task == null || task.mTaskFragmentHostProcessName == null) {
            return null;
        }
        if (task.mTaskFragmentHostProcessName.equals(r.processName)
                && task.mTaskFragmentHostUid == r.getUid()) {
            return null;
        }
        return mAtmService.getProcessController(task.mTaskFragmentHostProcessName,
                task.mTaskFragmentHostUid);
    }

    void setAnimationParams(@NonNull TaskFragmentAnimationParams animationParams) {
        mAnimationParams = animationParams;
    }

    @NonNull
    TaskFragmentAnimationParams getAnimationParams() {
        return mAnimationParams;
    }

    /** @see #mIsolatedNav */
    void setIsolatedNav(boolean isolatedNav) {
        if (!isEmbedded()) {
            return;
        }
        mIsolatedNav = isolatedNav;
    }

    /**
     * Sets whether this TaskFragment {@link #isPinned()}.
     * <p>
     * Note that this is no-op if the TaskFragment is not {@link #isEmbedded() embedded}.
     */
    void setPinned(boolean pinned) {
        if (!isEmbedded()) {
            return;
        }
        mPinned = pinned;
    }

    /**
     * Sets whether transitions are allowed when the TaskFragment is empty. If {@code true},
     * transitions are allowed when the TaskFragment is empty. If {@code false}, transitions
     * will wait until the TaskFragment becomes non-empty or other conditions are met. Default
     * to {@code false}.
     */
    void setAllowTransitionWhenEmpty(boolean allowTransitionWhenEmpty) {
        if (!isEmbedded()) {
            return;
        }
        mAllowTransitionWhenEmpty = allowTransitionWhenEmpty;
    }

    void setConfigurationChangeMaskForOrganizer(@ActivityInfo.Config int mask) {
        // Only system organizers are allowed to set configuration change mask.
        if (mTaskFragmentOrganizerController.isSystemOrganizer(mTaskFragmentOrganizer.asBinder())) {
            mConfigurationChangeMaskForOrganizer = mask;
        }
    }

    @ActivityInfo.Config int getConfigurationChangeMaskForOrganizer() {
        return mConfigurationChangeMaskForOrganizer;
    }

    /** @see #mIsolatedNav */
    boolean isIsolatedNav() {
        return isEmbedded() && mIsolatedNav;
    }

    /**
     * Indicates whether this TaskFragment is pinned.
     *
     * @see android.window.TaskFragmentOperation#OP_TYPE_SET_PINNED
     */
    boolean isPinned() {
        return isEmbedded() && mPinned;
    }

    /** Returns the currently topmost resumed activity. */
    @Nullable
    ActivityRecord getTopResumedActivity() {
        final ActivityRecord taskFragResumedActivity = getResumedActivity();
        for (int i = getChildCount() - 1; i >= 0; --i) {
            WindowContainer<?> child = getChildAt(i);
            ActivityRecord topResumedActivity = null;
            if (taskFragResumedActivity != null && child == taskFragResumedActivity) {
                topResumedActivity = child.asActivityRecord();
            } else if (child.asTaskFragment() != null) {
                topResumedActivity = child.asTaskFragment().getTopResumedActivity();
            }
            if (topResumedActivity != null) {
                return topResumedActivity;
            }
        }
        return null;
    }

    /**
     * Returns the currently resumed activity in this TaskFragment's
     * {@link #mChildren direct children}
     */
    ActivityRecord getResumedActivity() {
        return mResumedActivity;
    }

    void setResumedActivity(ActivityRecord r, String reason) {
        if (mResumedActivity == r) {
            return;
        }

        if (ActivityTaskManagerDebugConfig.DEBUG_ROOT_TASK) {
            Slog.d(TAG, "setResumedActivity taskFrag:" + this + " + from: "
                    + mResumedActivity + " to:" + r + " reason:" + reason);
        }

        if (r != null && mResumedActivity == null) {
            // Task is becoming active.
            getTask().touchActiveTime();
        }

        final ActivityRecord prevR = mResumedActivity;
        mResumedActivity = r;
        final ActivityRecord topResumed = mTaskSupervisor.updateTopResumedActivityIfNeeded(reason);
        if (mResumedActivity != null && topResumed != null && topResumed.isEmbedded()
                && topResumed.getTaskFragment().isAdjacentTo(this)
                && topResumed.getTaskFragment().isPinned()) {
            // Explicitly updates the last resumed Activity if the resumed activity is
            // adjacent to the top-resumed embedded activity and the top-resumed TaskFragment is
            // pinned.
            mAtmService.setLastResumedActivityUncheckLocked(mResumedActivity, reason);
        }
        if (r == null && prevR.mDisplayContent != null
                && prevR.mDisplayContent.getFocusedRootTask() == null) {
            // Only need to notify DWPC when no activity will resume.
            prevR.mDisplayContent.onRunningActivityChanged();
        } else if (r != null) {
            r.mDisplayContent.onRunningActivityChanged();
        }
    }

    @VisibleForTesting
    void setPausingActivity(ActivityRecord pausing) {
        mPausingActivity = pausing;
    }

    /** Returns the currently topmost pausing activity. */
    @Nullable
    ActivityRecord getTopPausingActivity() {
        final ActivityRecord taskFragPausingActivity = getPausingActivity();
        for (int i = getChildCount() - 1; i >= 0; --i) {
            WindowContainer<?> child = getChildAt(i);
            ActivityRecord topPausingActivity = null;
            if (taskFragPausingActivity != null && child == taskFragPausingActivity) {
                topPausingActivity = child.asActivityRecord();
            } else if (child.asTaskFragment() != null) {
                topPausingActivity = child.asTaskFragment().getTopPausingActivity();
            }
            if (topPausingActivity != null) {
                return topPausingActivity;
            }
        }
        return null;
    }

    ActivityRecord getPausingActivity() {
        return mPausingActivity;
    }

    int getDisplayId() {
        final DisplayContent dc = getDisplayContent();
        return dc != null ? dc.mDisplayId : INVALID_DISPLAY;
    }

    @Nullable
    Task getTask() {
        if (asTask() != null) {
            return asTask();
        }

        TaskFragment parent = getParent() != null ? getParent().asTaskFragment() : null;
        return parent != null ? parent.getTask() : null;
    }

    @Override
    @Nullable
    TaskDisplayArea getDisplayArea() {
        return (TaskDisplayArea) super.getDisplayArea();
    }

    @Override
    public boolean isAttached() {
        final TaskDisplayArea taskDisplayArea = getDisplayArea();
        return taskDisplayArea != null && !taskDisplayArea.isRemoved();
    }

    /**
     * Returns the root {@link TaskFragment}, which is usually also a {@link Task}.
     */
    @NonNull
    TaskFragment getRootTaskFragment() {
        final WindowContainer parent = getParent();
        if (parent == null) return this;

        final TaskFragment parentTaskFragment = parent.asTaskFragment();
        return parentTaskFragment == null ? this : parentTaskFragment.getRootTaskFragment();
    }

    @Nullable
    Task getRootTask() {
        return getRootTaskFragment().asTask();
    }

    @Override
    TaskFragment asTaskFragment() {
        return this;
    }

    @Override
    boolean isEmbedded() {
        return mIsEmbedded;
    }


    /**
     * Returns true if this container fills its parent by policy or bounds. Similar to
     * {@link ActivityRecord}, this returns {@code true} if it has override bounds which equals
     * to its parent bounds
     */
    @Override
    boolean fillsParentBounds() {
        if (com.android.window.flags.Flags.rootTaskForBubble()) {
            final int windowingMode = getWindowingMode();
            if (windowingMode == WINDOWING_MODE_PINNED) {
                return false;
            }
            if (windowingMode == WINDOWING_MODE_FULLSCREEN) {
                return true;
            }

            final Rect overrideBounds = getResolvedOverrideBounds();
            if (overrideBounds.isEmpty()) {
                return true;
            }
            final WindowContainer parent = getParent();
            return parent == null || parent.getBounds().equals(overrideBounds);
        }

        return super.fillsParentBounds();
    }

    @EmbeddingCheckResult
    int isAllowedToEmbedActivity(@NonNull ActivityRecord a) {
        return isAllowedToEmbedActivity(a, mTaskFragmentOrganizerUid);
    }

    /**
     * Checks if the organized task fragment is allowed to have the specified activity, which is
     * allowed if an activity allows embedding in untrusted mode, if the trusted mode can be
     * enabled, or if the organized task fragment bounds are not
     * {@link #smallerThanMinDimension(ActivityRecord)}.
     *
     * @param uid   uid of the TaskFragment organizer.
     * @see #isAllowedToEmbedActivityInTrustedMode(ActivityRecord)
     */
    @EmbeddingCheckResult
    int isAllowedToEmbedActivity(@NonNull ActivityRecord a, int uid) {
        if (!isAllowedToEmbedActivityInUntrustedMode(a)
                && !isAllowedToEmbedActivityInTrustedMode(a, uid)) {
            return EMBEDDING_DISALLOWED_UNTRUSTED_HOST;
        }

        if (smallerThanMinDimension(a)) {
            return EMBEDDING_DISALLOWED_MIN_DIMENSION_VIOLATION;
        }

        return EMBEDDING_ALLOWED;
    }

    boolean smallerThanMinDimension(@NonNull ActivityRecord activity) {
        final Rect taskFragBounds = getBounds();
        final Task task = getTask();
        // Don't need to check if the bounds match parent Task bounds because the fallback mechanism
        // is to reparent the Activity to parent if minimum dimensions are not satisfied.
        if (task == null || taskFragBounds.equals(task.getBounds())) {
            return false;
        }
        final Point minDimensions = activity.getMinDimensions();
        if (minDimensions == null) {
            return false;
        }
        final int minWidth = minDimensions.x;
        final int minHeight = minDimensions.y;
        return taskFragBounds.width() < minWidth
                || taskFragBounds.height() < minHeight;
    }

    /**
     * Checks if the organized task fragment is allowed to embed activity in untrusted mode.
     */
    boolean isAllowedToEmbedActivityInUntrustedMode(@NonNull ActivityRecord a) {
        final WindowContainer parent = getParent();
        if (parent == null || !parent.getBounds().contains(getBounds())) {
            // Without full trust between the host and the embedded activity, we don't allow
            // TaskFragment to have bounds outside of the parent bounds.
            return false;
        }
        if (hasEmbedAnyAppInUntrustedModePermission(mTaskFragmentOrganizerUid)) {
            return true;
        }
        return (a.info.flags & FLAG_ALLOW_UNTRUSTED_ACTIVITY_EMBEDDING)
                == FLAG_ALLOW_UNTRUSTED_ACTIVITY_EMBEDDING;
    }

    boolean isAllowedToEmbedActivityInTrustedMode(@NonNull ActivityRecord a) {
        return isAllowedToEmbedActivityInTrustedMode(a, mTaskFragmentOrganizerUid);
    }

    /**
     * Checks if the organized task fragment is allowed to embed activity in fully trusted mode,
     * which means that all transactions are allowed. This is supported in the following cases:
     * <li>the activity belongs to the same app as the organizer host;</li>
     * <li>the activity has declared the organizer host as trusted explicitly via known
     * certificate.</li>
     * @param uid   uid of the TaskFragment organizer.
     */
    boolean isAllowedToEmbedActivityInTrustedMode(@NonNull ActivityRecord a, int uid) {
        if (isFullyTrustedEmbedding(a, uid)) {
            return true;
        }

        final AndroidPackage hostPackage = mAtmService.getPackageManagerInternalLocked()
                .getPackage(uid);

        return hostPackage != null
                && isAllowedToEmbedActivityInTrustedModeByHostPackage(a, hostPackage);
    }

    @VisibleForTesting
    boolean isAllowedToEmbedActivityInTrustedModeByHostPackage(
            @NonNull ActivityRecord a, @NonNull AndroidPackage hostPackage) {

        // Known certs declared in the <activity> tag
        Set<String> knownActivityEmbeddingCerts = a.info.getKnownActivityEmbeddingCerts();

        // If the activity-level value is specified, it takes precedence. Otherwise, we read the
        // application-level value.
        if (knownActivityEmbeddingCerts.isEmpty()) {
            // Known certs declared in the <application> tag
            knownActivityEmbeddingCerts = a.info.applicationInfo.getKnownActivityEmbeddingCerts();
        }

        // An application must specify a set of app certificates that are allowed to embed it in
        // trusted mode.
        return hostPackage.getSigningDetails().hasAncestorOrSelfWithDigest(
                knownActivityEmbeddingCerts);
    }

    /**
     * It is fully trusted for embedding in the system app or embedding in the same app. This is
     * different from {@link #isAllowedToBeEmbeddedInTrustedMode()} since there may be a small
     * chance for a previous trusted app to start doing something bad.
     */
    private static boolean isFullyTrustedEmbedding(@NonNull ActivityRecord a, int uid) {
        // The system is trusted to embed other apps securely and for all users.
        return UserHandle.getAppId(uid) == SYSTEM_UID
                // Activities from the same UID can be embedded freely by the host.
                || a.isUid(uid)
                // Apps which have the signature MANAGE_ACTIVITY_TASK permission are trusted.
                || hasManageTaskPermission(uid);
    }

    /**
     * Checks if a particular app uid has the {@link MANAGE_ACTIVITY_TASKS} permission.
     */
    private static boolean hasManageTaskPermission(int uid) {
        return checkPermission(MANAGE_ACTIVITY_TASKS, PermissionChecker.PID_UNKNOWN, uid)
                == PackageManager.PERMISSION_GRANTED;
    }

    /**
     * Checks if a particular app uid has the {@link EMBED_ANY_APP_IN_UNTRUSTED_MODE} permission.
     */
    private static boolean hasEmbedAnyAppInUntrustedModePermission(int uid) {
        return Flags.untrustedEmbeddingAnyAppPermission()
                && checkPermission(EMBED_ANY_APP_IN_UNTRUSTED_MODE,
                PermissionChecker.PID_UNKNOWN, uid) == PackageManager.PERMISSION_GRANTED;
    }

    /**
     * Checks if all activities in the task fragment are allowed to be embedded in trusted mode.
     * @see #isAllowedToEmbedActivityInTrustedMode(ActivityRecord)
     */
    boolean isAllowedToBeEmbeddedInTrustedMode() {
        // Traverse all activities to see if any of them are not in the trusted mode.
        return !forAllActivities(r -> !isAllowedToEmbedActivityInTrustedMode(r));
    }

    /**
     * @param canAffectSystemUiFlags If false, all windows in this taskfragment can not affect
     *                               SystemUI flags. See
     *                               {@link WindowState#canAffectSystemUiFlags()}.
     */
    void setCanAffectSystemUiFlags(boolean canAffectSystemUiFlags) {
        mCanAffectSystemUiFlags = canAffectSystemUiFlags;
    }

    /**
     * @see #setCanAffectSystemUiFlags
     */
    boolean canAffectSystemUiFlags() {
        if (!mCanAffectSystemUiFlags) {
            return false;
        }
        final TaskFragment parentTaskFragment =
                getParent() != null ? getParent().asTaskFragment() : null;
        return parentTaskFragment == null || parentTaskFragment.canAffectSystemUiFlags();
    }

    /**
     * Returns the TaskFragment that is being organized, which could be this or the ascendant
     * TaskFragment.
     */
    @Nullable
    TaskFragment getOrganizedTaskFragment() {
        if (mTaskFragmentOrganizer != null) {
            return this;
        }

        TaskFragment parentTaskFragment = getParent() != null ? getParent().asTaskFragment() : null;
        return parentTaskFragment != null ? parentTaskFragment.getOrganizedTaskFragment() : null;
    }

    boolean hasDirectChildActivities() {
        for (int i = mChildren.size() - 1; i >= 0; --i) {
            if (mChildren.get(i).asActivityRecord() != null) {
                return true;
            }
        }
        return false;
    }

    void cleanUpActivityReferences(@NonNull ActivityRecord r) {
        if (mPausingActivity != null && mPausingActivity == r) {
            mPausingActivity = null;
        }

        if (mResumedActivity != null && mResumedActivity == r) {
            setResumedActivity(null, "cleanUpActivityReferences");
        }
        r.removeTimeouts();
    }

    /**
     * Returns whether this TaskFragment is currently forced to be hidden for any reason.
     */
    protected boolean isForceHidden() {
        return mForceHiddenFlags != 0;
    }

    /**
     * Sets/unsets the forced-hidden state flag for this task depending on {@param set}.
     * @return Whether the force hidden state changed
     */
    boolean setForceHidden(@FlagForceHidden int flags, boolean set) {
        int newFlags = mForceHiddenFlags;
        if (set) {
            newFlags |= flags;
        } else {
            newFlags &= ~flags;
        }
        if (mForceHiddenFlags == newFlags) {
            return false;
        }
        mForceHiddenFlags = newFlags;
        return true;
    }

    boolean isForceTranslucent() {
        return mForceTranslucent;
    }

    boolean setForceTranslucent(boolean set) {
        if (mForceTranslucent == set) {
            return false;
        }
        mForceTranslucent = set;
        return true;
    }

    boolean isLeafTaskFragment() {
        for (int i = mChildren.size() - 1; i >= 0; --i) {
            if (mChildren.get(i).asTaskFragment() != null) {
                return false;
            }
        }
        return true;
    }

    /**
     * This should be called when an child activity changes state. This should only
     * be called from
     * {@link ActivityRecord#setState(ActivityRecord.State, String)} .
     * @param record The {@link ActivityRecord} whose state has changed.
     * @param state The new state.
     * @param reason The reason for the change.
     */
    void onActivityStateChanged(ActivityRecord record, ActivityRecord.State state,
            String reason) {
        if (record == mResumedActivity && state != RESUMED) {
            setResumedActivity(null, reason + " - onActivityStateChanged");
        }

        if (state == RESUMED) {
            if (ActivityTaskManagerDebugConfig.DEBUG_ROOT_TASK) {
                Slog.v(TAG, "set resumed activity to:" + record + " reason:" + reason);
            }
            setResumedActivity(record, reason + " - onActivityStateChanged");
            mTaskSupervisor.mRecentTasks.add(record.getTask());
        }

        // Update the process state for the organizer process if the activity is in a different
        // process in case the organizer process may not have activity state change in its process.
        final WindowProcessController hostProcess = getOrganizerProcessIfDifferent(record);
        if (hostProcess != null) {
            mTaskSupervisor.onProcessActivityStateChanged(hostProcess, false /* forceBatch */);
            hostProcess.updateProcessInfo(false /* updateServiceConnectionActivities */,
                    true /* activityChange */, true /* updateOomAdj */,
                    false /* addPendingTopUid */);
        }
    }

    /**
     * Resets local parameters because an app's activity died.
     * @param app The app of the activity that died.
     * @return {@code true} if the process of the pausing activity is died.
     */
    boolean handleAppDied(WindowProcessController app) {
        boolean isPausingDied = false;
        if (mPausingActivity != null && mPausingActivity.app == app) {
            ProtoLog.v(WM_DEBUG_STATES, "App died while pausing: %s",
                    mPausingActivity);
            mPausingActivity = null;
            isPausingDied = true;
        }
        if (mLastPausedActivity != null && mLastPausedActivity.app == app) {
            mLastPausedActivity = null;
        }
        return isPausingDied;
    }

    void awakeFromSleeping() {
        if (mPausingActivity != null) {
            Slog.d(TAG, "awakeFromSleeping: previously pausing activity didn't pause");
            mPausingActivity.activityPaused(true);
        }
    }

    /**
     * Tries to put the activities in the task fragment to sleep.
     *
     * If the task fragment is not in a state where its activities can be put to sleep, this
     * function will start any necessary actions to move the task fragment into such a state.
     * It is expected that this function get called again when those actions complete.
     *
     * @param shuttingDown {@code true} when the called because the device is shutting down.
     * @return true if the root task finished going to sleep, false if the root task only started
     * the process of going to sleep (checkReadyForSleep will be called when that process finishes).
     */
    boolean sleepIfPossible(boolean shuttingDown) {
        boolean shouldSleep = true;
        if (mResumedActivity != null) {
            if (!shuttingDown && mResumedActivity.canTurnScreenOn()) {
                ProtoLog.v(WM_DEBUG_STATES, "Waiting for screen on due to %s", mResumedActivity);
            } else {
                // Still have something resumed; can't sleep until it is paused.
                ProtoLog.v(WM_DEBUG_STATES, "Sleep needs to pause %s", mResumedActivity);
                startPausing(true /* uiSleeping */, null /* resuming */, "sleep");
            }
            shouldSleep = false;
        } else if (mPausingActivity != null) {
            // Still waiting for something to pause; can't sleep yet.
            ProtoLog.v(WM_DEBUG_STATES, "Sleep still waiting to pause %s", mPausingActivity);
            shouldSleep = false;
        }

        if (!shuttingDown) {
            if (containsStoppingActivity()) {
                // Still need to tell some activities to stop; can't sleep yet.
                ProtoLog.v(WM_DEBUG_STATES, "Sleep still need to stop %d activities",
                        mTaskSupervisor.mStoppingActivities.size());

                mTaskSupervisor.scheduleIdle();
                shouldSleep = false;
            }
        }

        if (shouldSleep) {
            updateActivityVisibilities(null /* starting */, true /* notifyClients */);
        }

        return shouldSleep;
    }

    private boolean containsStoppingActivity() {
        for (int i = mTaskSupervisor.mStoppingActivities.size() - 1; i >= 0; --i) {
            ActivityRecord r = mTaskSupervisor.mStoppingActivities.get(i);
            if (r.getTaskFragment() == this) {
                return true;
            }
        }
        return false;
    }

    /**
     * Returns true if the TaskFragment is translucent and can have other contents visible behind
     * it if needed. A TaskFragment is considered translucent if it don't contain a visible or
     * starting (about to be visible) activity that is fullscreen (opaque).
     * @param starting The currently starting activity or null if there is none.
     */
    boolean isTranslucent(@Nullable ActivityRecord starting) {
        if (!isAttached() || isForceHidden() || isForceTranslucent()) {
            return true;
        }
        return !mTaskSupervisor.mOpaqueContainerHelper.isOpaque(
                this, starting, true /* ignoringKeyguard */, true /* ignoringInvisibleActivity */);
    }

    /**
     * Whether the TaskFragment should be treated as translucent for the current transition.
     * This is different from {@link #isTranslucent(ActivityRecord)} as this function also checks
     * finishing activities when the TaskFragment itself is becoming invisible.
     */
    boolean isTranslucentForTransition() {
        if (!isAttached() || isForceHidden() || isForceTranslucent()) {
            return true;
        }
        // Including finishing Activity if the TaskFragment is becoming invisible in the transition.
        return !mTaskSupervisor.mOpaqueContainerHelper.isOpaque(this);
    }

    /**
     * Like {@link  #isTranslucent(ActivityRecord)} but evaluating the actual visibility of the
     * windows rather than their visibility ignoring keyguard.
     */
    boolean isTranslucentAndVisible() {
        if (!isAttached() || isForceHidden() || isForceTranslucent()) {
            return true;
        }
        return !mTaskSupervisor.mOpaqueContainerHelper.isOpaque(this, /* starting */ null,
                false /* ignoringKeyguard */, true /* ignoringInvisibleActivity */);
    }

    @Override
    ActivityRecord getTopNonFinishingActivity() {
        return getTopNonFinishingActivity(
                true /* includeOverlays */, true /* includeLaunchedFromBubble */);
    }

    /**
     * Returns the top-most non-finishing activity, even if the activity is NOT ok to show to
     * the current user.
     * @param includeOverlays whether the task overlay activity should be included.
     * @param includeLaunchedFromBubble whether activities that were launched from a bubble should
     *                                  be included.
     * @see #topRunningActivity(boolean)
     */
    ActivityRecord getTopNonFinishingActivity(boolean includeOverlays,
            boolean includeLaunchedFromBubble) {
        // Split to avoid object creation due to variable capture.
        if (includeOverlays) {
            if (includeLaunchedFromBubble) {
                return getActivity(r -> !r.finishing);
            } else {
                return getActivity(r -> !r.finishing && !r.getLaunchedFromBubble());
            }
        }
        if (includeLaunchedFromBubble) {
            return getActivity(r -> !r.finishing && !r.isTaskOverlay());
        } else {
            return getActivity(
                    r -> !r.finishing && !r.isTaskOverlay() && !r.getLaunchedFromBubble());
        }
    }

    ActivityRecord topRunningActivity() {
        return topRunningActivity(false /* focusableOnly */);
    }

    /**
     * Returns the top-most running activity, which the activity is non-finishing and ok to show
     * to the current user.
     *
     * @see ActivityRecord#canBeTopRunning()
     */
    ActivityRecord topRunningActivity(boolean focusableOnly) {
        // Split into 2 to avoid object creation due to variable capture.
        if (focusableOnly) {
            return getActivity((r) -> r.canBeTopRunning() && r.isFocusable());
        }
        return getActivity(ActivityRecord::canBeTopRunning);
    }

    /**
     * Reports non-finishing activity count including this TaskFragment's child embedded
     * TaskFragments' children activities.
     */
    int getNonFinishingActivityCount() {
        final int[] runningActivityCount = new int[1];
        forAllActivities(a -> {
            if (!a.finishing) {
                runningActivityCount[0]++;
            }
        });
        return runningActivityCount[0];
    }

    /**
     * Returns {@code true} if there's any non-finishing direct children activity, which is not
     * embedded in TaskFragments
     */
    boolean hasNonFinishingDirectActivity() {
        for (int i = getChildCount() - 1; i >= 0; --i) {
            final ActivityRecord activity = getChildAt(i).asActivityRecord();
            if (activity != null && !activity.finishing) {
                return true;
            }
        }
        return false;
    }

    boolean isTopActivityFocusable() {
        final ActivityRecord r = topRunningActivity();
        return r != null ? r.isFocusable()
                : (isFocusable() && getWindowConfiguration().canReceiveKeys());
    }

    /**
     * Returns whether the activity launch by the source activity in this TaskFragment should be
     * aborted. Currently only activity launches from a cross-uid embedded source within a finishing
     * TaskFragment are aborted.
     *
     * @param source an activity in this TaskFragment that launches another activity.
     */
    boolean shouldAbortActivityLaunchOnFinishingTf(@NonNull ActivityRecord source) {
        if (!Flags.activityEmbeddingAbortCrossUidLaunchInFinishingTaskFragment()) {
            return false;
        }
        // If the source activity is a cross-uid embedded activity, the newly launched activity is
        // always expected to be in the same TaskFragment. If this TaskFragment is being removed, we
        // should not allow a new activity to be launched by the source, because it may be placed
        // into a wrong TaskFragment due to current limitations in cross-uid activity launch
        // tracking.
        // TODO(b/293800510) Improve cross-uid activity launch tracking.
        boolean abort = isEmbedded() && isRemovalRequested()
                && mTaskFragmentOrganizerUid != INVALID_UID
                && source.getUid() != mTaskFragmentOrganizerUid;
        if (abort) {
            Slog.w(TAG, "Activity launch aborted for cross-uid launch from " + source
                    + " in a finishing TaskFragment");
        }
        return abort;
    }

    /**
     * Returns the visibility state of this TaskFragment.
     *
     * @param starting The currently starting activity or null if there is none.
     */
    @TaskFragmentVisibility
    int getVisibility(ActivityRecord starting) {
        if (!isAttached() || isForceHidden()) {
            return TASK_FRAGMENT_VISIBILITY_INVISIBLE;
        }

        if (isTopActivityLaunchedBehind()) {
            return TASK_FRAGMENT_VISIBILITY_VISIBLE;
        }
        final WindowContainer<?> parent = getParent();
        final Task thisTask = asTask();
        if (thisTask != null && parent.asTask() == null
                && mTransitionController.isTransientVisible(thisTask)) {
            // Keep transient-hide root tasks visible. Non-root tasks still follow standard rule.
            return TASK_FRAGMENT_VISIBILITY_VISIBLE;
        }

        if (thisTask != null && !isPermittedInLockTask(thisTask)) {
            return TASK_FRAGMENT_VISIBILITY_INVISIBLE;
        }

        boolean gotTranslucentFullscreen = false;
        boolean gotTranslucentAdjacent = false;
        boolean shouldBeVisible = true;

        // This TaskFragment is only considered visible if all its parent TaskFragments are
        // considered visible, so check the visibility of all ancestor TaskFragment first.
        if (parent.asTaskFragment() != null) {
            final int parentVisibility = parent.asTaskFragment().getVisibility(starting);
            if (parentVisibility == TASK_FRAGMENT_VISIBILITY_INVISIBLE) {
                // Can't be visible if parent isn't visible
                return TASK_FRAGMENT_VISIBILITY_INVISIBLE;
            } else if (parentVisibility == TASK_FRAGMENT_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT) {
                // Parent is behind a translucent container so the highest visibility this container
                // can get is that.
                gotTranslucentFullscreen = true;
            }
        }

        final List<TaskFragment> adjacentTaskFragments = new ArrayList<>();
        for (int i = parent.getChildCount() - 1; i >= 0; --i) {
            final WindowContainer other = parent.getChildAt(i);
            if (other == null) continue;

            final boolean hasRunningActivities = hasRunningActivity(other);
            if (other == this) {
                if (!adjacentTaskFragments.isEmpty() && !gotTranslucentAdjacent) {
                    // The z-order of this TaskFragment is in middle of two adjacent TaskFragments
                    // and it cannot be visible if the TaskFragment on top is not translucent and
                    // is occluding this one.
                    mTmpRect.set(getBounds());
                    for (int j = adjacentTaskFragments.size() - 1; j >= 0; --j) {
                        final TaskFragment taskFragment = adjacentTaskFragments.get(j);
                        if (taskFragment.isAdjacentTo(this)) {
                            continue;
                        }
                        final boolean isOccluding = mTmpRect.intersect(taskFragment.getBounds())
                                || taskFragment.forOtherAdjacentTaskFragments(adjacentTf -> {
                                    return mTmpRect.intersect(adjacentTf.getBounds());
                                });
                        if (isOccluding) {
                            return TASK_FRAGMENT_VISIBILITY_INVISIBLE;
                        }
                    }
                }
                // Should be visible if there is no other fragment occluding it, unless it doesn't
                // have any running activities, not starting one and not home stack.
                shouldBeVisible = hasRunningActivities
                        || (starting != null && starting.isDescendantOf(this))
                        || (isActivityTypeHome() && !isEmbedded());
                break;
            }

            if (!hasRunningActivities) {
                continue;
            }

            // Must fill the parent to affect visibility.
            boolean affectsSiblingVisibility = other.fillsParentBounds();
            if (mEnableSeeThroughTaskFragments) {
                // It also must have filling content itself, to prevent empty or only partially
                // occluding containers from affecting visibility.
                affectsSiblingVisibility &= other.hasFillingContent();
            }
            if (affectsSiblingVisibility) {
                // This task fragment is fully covered by |other|.
                if (isTranslucent(other, starting)) {
                    // Can be visible behind a translucent TaskFragment.
                    gotTranslucentFullscreen = true;
                    continue;
                }
                return TASK_FRAGMENT_VISIBILITY_INVISIBLE;
            }

            final TaskFragment otherTaskFrag = other.asTaskFragment();
            if (otherTaskFrag != null && otherTaskFrag.hasAdjacentTaskFragment()) {
                // For adjacent TaskFragments, we have assumptions that:
                // 1. A set of adjacent TaskFragments always cover the entire Task window, so that
                // if this TaskFragment is behind a set of opaque TaskFragments, then this
                // TaskFragment is invisible.
                // 2. Adjacent TaskFragments do not overlap, so that if this TaskFragment is behind
                // any translucent TaskFragment in the adjacent set, then this TaskFragment is
                // visible behind translucent.
                final boolean hasTraversedAdj = otherTaskFrag.forOtherAdjacentTaskFragments(
                        adjacentTaskFragments::contains);
                if (hasTraversedAdj) {
                    final boolean isTranslucent =
                            isBehindTransparentTaskFragment(otherTaskFrag, starting)
                                    || otherTaskFrag.forOtherAdjacentTaskFragments(
                                    (Predicate<TaskFragment>) tf ->
                                            isBehindTransparentTaskFragment(tf, starting));
                    if (isTranslucent) {
                        // Can be visible behind a translucent adjacent TaskFragments.
                        gotTranslucentFullscreen = true;
                        gotTranslucentAdjacent = true;
                        continue;
                    }
                    // Can not be visible behind adjacent TaskFragments.
                    return TASK_FRAGMENT_VISIBILITY_INVISIBLE;
                }
                adjacentTaskFragments.add(otherTaskFrag);
            }
        }

        if (!shouldBeVisible) {
            return TASK_FRAGMENT_VISIBILITY_INVISIBLE;
        }

        // Lastly - check if there is a translucent fullscreen TaskFragment on top.
        return gotTranslucentFullscreen
                ? TASK_FRAGMENT_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT
                : TASK_FRAGMENT_VISIBILITY_VISIBLE;
    }

    private boolean isBehindTransparentTaskFragment(
            @NonNull TaskFragment otherTf, @Nullable ActivityRecord starting) {
        return otherTf.isTranslucent(starting) && getBounds().intersect(otherTf.getBounds());
    }

    private static boolean hasRunningActivity(WindowContainer wc) {
        if (wc.asTaskFragment() != null) {
            return wc.asTaskFragment().topRunningActivity() != null;
        }
        return wc.asActivityRecord() != null && !wc.asActivityRecord().finishing;
    }

    private static boolean isTranslucent(WindowContainer wc, ActivityRecord starting) {
        if (wc.asTaskFragment() != null) {
            return wc.asTaskFragment().isTranslucent(starting);
        } else if (wc.asActivityRecord() != null) {
            return !wc.asActivityRecord().occludesParent();
        }
        return false;
    }


    private boolean isTopActivityLaunchedBehind() {
        final ActivityRecord top = topRunningActivity();
        return top != null && top.mLaunchTaskBehind;
    }

    /**
     * Checks if a task is allowed to run in the lock task mode.
     *
     * <p>Returns {@code true} if {@link ENABLE_DESKTOP_WINDOWING_ENTERPRISE_BUGFIX} flag is not
     * enabled.
     *
     * <p>Returns {@code true} if the device is not currently in lock task.
     *
     * <p>A task is permitted if it's a leaf task that is allowed by the lock task admin policy, or
     * if any of its descendant leaf tasks are permitted by the policy.
     *
     * @param task The task to evaluate.
     * @return {@code true} if the task is allowed to run, {@code false} otherwise.
     */
    private boolean isPermittedInLockTask(@NonNull Task task) {
        if (!ENABLE_DESKTOP_WINDOWING_ENTERPRISE_BUGFIX.isTrue()) {
            return true;
        }
        final int lockTaskState = mLockTaskController.getLockTaskModeState();
        final boolean isInLockTask =
                lockTaskState == LOCK_TASK_MODE_LOCKED || lockTaskState == LOCK_TASK_MODE_PINNED;
        if (!isInLockTask) {
            return true;
        }
        return task.forAllTasks(
                leafTask -> !mLockTaskController.isLockTaskModeViolation(leafTask));
    }

    final void updateActivityVisibilities(@Nullable ActivityRecord starting,
            boolean notifyClients) {
        mTaskSupervisor.beginActivityVisibilityUpdate();
        try {
            mEnsureActivitiesVisibleHelper.process(starting, notifyClients);
        } finally {
            mTaskSupervisor.endActivityVisibilityUpdate();
        }
    }

    final boolean resumeTopActivity(ActivityRecord prev, ActivityOptions options,
            boolean skipPause) {
        ActivityRecord next = topRunningActivity(true /* focusableOnly */);
        if (next == null || !next.canResumeByCompat()) {
            return false;
        }

        next.delayedResume = false;

        if (!skipPause && !mRootWindowContainer.allPausedActivitiesComplete()) {
            // If we aren't skipping pause, then we have to wait for currently pausing activities.
            ProtoLog.v(WM_DEBUG_STATES, "resumeTopActivity: Skip resume: some activity pausing.");
            return false;
        }

        final TaskDisplayArea taskDisplayArea = getDisplayArea();
        // If the top activity is the resumed one, nothing to do.
        if (mResumedActivity == next && next.isState(RESUMED)
                && taskDisplayArea.allResumedActivitiesComplete()) {
            // Ensure the visibility gets updated before execute app transition.
            taskDisplayArea.ensureActivitiesVisible(null /* starting */, true /* notifyClients */);
            // Make sure we have executed any pending transitions, since there
            // should be nothing left to do at this point.
            executeAppTransition(options);

            // In a multi-resumed environment, like in a freeform device, the top
            // activity can be resumed, but it might not be the focused app.
            // Set focused app when top activity is resumed. However, we shouldn't do it for a
            // same task because it can break focused state. (e.g. activity embedding)
            if (taskDisplayArea.inMultiWindowMode() && taskDisplayArea.mDisplayContent != null) {
                final ActivityRecord focusedApp = taskDisplayArea.mDisplayContent.mFocusedApp;
                if (focusedApp == null || focusedApp.getTask() != next.getTask()) {
                    taskDisplayArea.mDisplayContent.setFocusedApp(next);
                }
            }
            ProtoLog.d(WM_DEBUG_STATES, "resumeTopActivity: Top activity "
                    + "resumed %s", next);
            return false;
        }

        // If we are sleeping, and there is no resumed activity, and the top activity is paused,
        // well that is the state we want.
        if (mLastPausedActivity == next && shouldSleepOrShutDownActivities()) {
            // Make sure we have executed any pending transitions, since there
            // should be nothing left to do at this point.
            executeAppTransition(options);
            ProtoLog.d(WM_DEBUG_STATES, "resumeTopActivity: Going to sleep and"
                    + " all paused");
            return false;
        }

        // Make sure that the user who owns this activity is started.  If not,
        // we will just leave it as is because someone should be bringing
        // another user's activities to the top of the stack.
        if (!mAtmService.mAmInternal.hasStartedUserState(next.mUserId)) {
            Slog.w(TAG, "Skipping resume of top activity " + next
                    + ": user " + next.mUserId + " is stopped");
            return false;
        }

        // The activity may be waiting for stop, but that is no longer
        // appropriate for it.
        mTaskSupervisor.mStoppingActivities.remove(next);

        if (DEBUG_SWITCH) Slog.v(TAG_SWITCH, "Resuming " + next);

        mTaskSupervisor.setLaunchSource(next.info.applicationInfo.uid);

        ActivityRecord lastResumed = null;
        final Task lastFocusedRootTask = taskDisplayArea.getLastFocusedRootTask();
        if (lastFocusedRootTask != null && lastFocusedRootTask != getRootTaskFragment().asTask()) {
            // So, why aren't we using prev here??? See the param comment on the method. prev
            // doesn't represent the last resumed activity. However, the last focus stack does if
            // it isn't null.
            lastResumed = lastFocusedRootTask.getTopResumedActivity();
        }

        boolean pausing = !skipPause && taskDisplayArea.pauseBackTasks(next);
        if (mResumedActivity != null) {
            ProtoLog.d(WM_DEBUG_STATES, "resumeTopActivity: Pausing %s", mResumedActivity);
            pausing |= startPausing(mTaskSupervisor.mUserLeaving, false /* uiSleeping */,
                    next, "resumeTopActivity");
        }
        if (pausing) {
            ProtoLog.v(WM_DEBUG_STATES, "resumeTopActivity: Skip resume: need to"
                    + " start pausing");
            // At this point we want to put the upcoming activity's process
            // at the top of the LRU list, since we know we will be needing it
            // very soon and it would be a waste to let it get killed if it
            // happens to be sitting towards the end.
            if (next.attachedToProcess()) {
                next.app.updateProcessInfo(false /* updateServiceConnectionActivities */,
                        true /* activityChange */, false /* updateOomAdj */,
                        false /* addPendingTopUid */);
            } else if (!next.isProcessRunning()) {
                // Since the start-process is asynchronous, if we already know the process of next
                // activity isn't running, we can start the process earlier to save the time to wait
                // for the current activity to be paused.
                final boolean isTop = this == taskDisplayArea.getFocusedRootTask();
                mAtmService.startProcessAsync(next, false /* knownToBeDead */, isTop,
                        isTop ? HostingRecord.HOSTING_TYPE_NEXT_TOP_ACTIVITY
                                : HostingRecord.HOSTING_TYPE_NEXT_ACTIVITY);
            }
            if (lastResumed != null) {
                lastResumed.setWillCloseOrEnterPip(true);
            }
            return true;
        } else if (mResumedActivity == next && next.isState(RESUMED)
                && taskDisplayArea.allResumedActivitiesComplete()) {
            // It is possible for the activity to be resumed when we paused back stacks above if the
            // next activity doesn't have to wait for pause to complete.
            // So, nothing else to-do except:
            // Make sure we have executed any pending transitions, since there
            // should be nothing left to do at this point.
            executeAppTransition(options);
            ProtoLog.d(WM_DEBUG_STATES, "resumeTopActivity: Top activity resumed "
                    + "(dontWaitForPause) %s", next);
            return true;
        }

        // If the most recent activity was noHistory but was only stopped rather
        // than stopped+finished because the device went to sleep, we need to make
        // sure to finish it as we're making a new activity topmost.
        if (shouldSleepActivities()) {
            mTaskSupervisor.finishNoHistoryActivitiesIfNeeded(next);
        }

        if (prev != null && prev != next && next.nowVisible) {
            // The next activity is already visible, so hide the previous
            // activity's windows right now so we can show the new one ASAP.
            // We only do this if the previous is finishing, which should mean
            // it is on top of the one being resumed so hiding it quickly
            // is good.  Otherwise, we want to do the normal route of allowing
            // the resumed activity to be shown so we can decide if the
            // previous should actually be hidden depending on whether the
            // new one is found to be full-screen or not.
            if (prev.finishing) {
                prev.setVisibility(false);
                if (DEBUG_SWITCH) {
                    Slog.v(TAG_SWITCH, "Not waiting for visible to hide: " + prev
                            + ", nowVisible=" + next.nowVisible);
                }
            } else {
                if (DEBUG_SWITCH) {
                    Slog.v(TAG_SWITCH, "Previous already visible but still waiting to hide: " + prev
                            + ", nowVisible=" + next.nowVisible);
                }
            }
        }

        try {
            mTaskSupervisor.getActivityMetricsLogger()
                    .notifyBeforePackageUnstopped(next.packageName);
            mAtmService.getPackageManagerInternalLocked().notifyComponentUsed(
                    next.packageName, next.mUserId,
                    next.packageName, next.toString()); /* TODO: Verify if correct userid */
        } catch (IllegalArgumentException e) {
            Slog.w(TAG, "Failed trying to unstop package "
                    + next.packageName + ": " + e);
        }

        // We are starting up the next activity, so tell the window manager
        // that the previous one will be hidden soon.  This way it can know
        // to ignore it when computing the desired screen orientation.
        boolean anim = true;
        final DisplayContent dc = taskDisplayArea.mDisplayContent;
        if (prev != null) {
            if (prev.finishing) {
                if (mTaskSupervisor.mNoAnimActivities.contains(prev)) {
                    anim = false;
                }
                prev.setVisibility(false);
            } else if (mTaskSupervisor.mNoAnimActivities.contains(next)) {
                anim = false;
            }
        } else if (mTaskSupervisor.mNoAnimActivities.contains(next)) {
            anim = false;
        }

        if (anim) {
            next.applyOptionsAnimation();
        } else {
            next.abortAndClearOptionsAnimation();
        }

        mTaskSupervisor.mNoAnimActivities.clear();

        if (next.attachedToProcess()) {
            if (DEBUG_SWITCH) {
                Slog.v(TAG_SWITCH, "Resume running: " + next + " stopped=" + next.mAppStopped
                        + " visibleRequested=" + next.isVisibleRequested());
            }

            // If the previous activity is translucent, force a visibility update of
            // the next activity, so that it's added to WM's opening app list, and
            // transition animation can be set up properly.
            // For example, pressing Home button with a translucent activity in focus.
            // Launcher is already visible in this case. If we don't add it to opening
            // apps, maybeUpdateTransitToWallpaper() will fail to identify this as a
            // TRANSIT_WALLPAPER_OPEN animation, and run some funny animation.
            final boolean lastActivityTranslucent = inMultiWindowMode()
                    || mLastPausedActivity != null && !mLastPausedActivity.occludesParent();

            // This activity is now becoming visible.
            if (!next.isVisibleRequested() || next.mAppStopped || lastActivityTranslucent) {
                next.app.addToPendingTop();
                next.setVisibility(true);
            }

            ActivityRecord lastResumedActivity =
                    lastFocusedRootTask == null ? null
                            : lastFocusedRootTask.getTopResumedActivity();
            final ActivityRecord.State lastState = next.getState();

            mAtmService.updateCpuStats();

            ProtoLog.v(WM_DEBUG_STATES, "Moving to RESUMED: %s (in existing)", next);

            next.setState(RESUMED, "resumeTopActivity");

            // Activity should also be visible if set mLaunchTaskBehind to true (see
            // ActivityRecord#shouldBeVisibleIgnoringKeyguard()).
            if (shouldBeVisible(next)) {
                // We have special rotation behavior when here is some active activity that
                // requests specific orientation or Keyguard is locked. Make sure all activity
                // visibilities are set correctly as well as the transition is updated if needed
                // to get the correct rotation behavior. Otherwise the following call to update
                // the orientation may cause incorrect configurations delivered to client as a
                // result of invisible window resize.
                // TODO: Remove this once visibilities are set correctly immediately when
                // starting an activity.
                final int originalRelaunchingCount = next.mPendingRelaunchCount;
                mRootWindowContainer.ensureVisibilityAndConfig(next, mDisplayContent,
                        false /* deferResume */);
                if (next.mPendingRelaunchCount > originalRelaunchingCount) {
                    // The activity is scheduled to relaunch, then ResumeActivityItem will be also
                    // included (see ActivityRecord#relaunchActivityLocked) if it should resume.
                    next.completeResumeLocked();
                    return true;
                }
            }

            final IApplicationThread appThread = next.app.getThread();
            // Deliver all pending results.
            final ArrayList<ResultInfo> a = next.results;
            if (a != null) {
                final int size = a.size();
                if (!next.finishing && size > 0) {
                    if (DEBUG_RESULTS) {
                        Slog.v(TAG_RESULTS, "Delivering results to " + next + ": " + a);
                    }
                    final ActivityResultItem item = new ActivityResultItem(next.token, a);
                    final boolean isSuccessful = mAtmService.getLifecycleManager()
                            .scheduleTransactionItem(appThread, item);
                    if (!isSuccessful) {
                        onResumeTopActivityRemoteFailure(lastState, next, lastResumedActivity,
                                lastFocusedRootTask);
                        return true;
                    }
                }
            }

            if (next.newIntents != null) {
                final NewIntentItem item =
                        new NewIntentItem(next.token, next.newIntents, true /* resume */);
                final boolean isSuccessful = mAtmService.getLifecycleManager()
                        .scheduleTransactionItem(appThread, item);
                if (!isSuccessful) {
                    onResumeTopActivityRemoteFailure(lastState, next, lastResumedActivity,
                            lastFocusedRootTask);
                    return true;
                }
            }

            // Well the app will no longer be stopped.
            // Clear app token stopped state in window manager if needed.
            next.notifyAppResumed();

            EventLogTags.writeWmResumeActivity(next.mUserId, System.identityHashCode(next),
                    next.getTask().mTaskId, next.shortComponentName);

            mAtmService.getAppWarningsLocked().onResumeActivity(next);
            final int topProcessState = mAtmService.mTopProcessState;
            next.app.setPendingUiCleanAndForceProcessStateUpTo(topProcessState);
            next.abortAndClearOptionsAnimation();
            final ResumeActivityItem resumeActivityItem = new ResumeActivityItem(
                    next.token, topProcessState, dc.isNextTransitionForward(),
                    next.shouldSendCompatFakeFocus());
            final boolean isSuccessful = mAtmService.getLifecycleManager().scheduleTransactionItem(
                    appThread, resumeActivityItem);
            if (!isSuccessful) {
                onResumeTopActivityRemoteFailure(lastState, next, lastResumedActivity,
                        lastFocusedRootTask);
                return true;
            }

            ProtoLog.d(WM_DEBUG_STATES, "resumeTopActivity: Resumed %s", next);

            next.completeResumeLocked();
        } else {
            // Whoops, need to restart this activity!
            if (!next.hasBeenLaunched) {
                next.hasBeenLaunched = true;
            } else {
                if (SHOW_APP_STARTING_PREVIEW) {
                    next.showStartingWindow(false /* taskSwich */);
                }
                if (DEBUG_SWITCH) Slog.v(TAG_SWITCH, "Restarting: " + next);
            }
            ProtoLog.d(WM_DEBUG_STATES, "resumeTopActivity: Restarting %s", next);
            next.setVisibility(true);
            mTaskSupervisor.startSpecificActivity(next, true, true);
        }

        return true;
    }

    /** Likely app process has been killed. Needs to restart this activity. */
    private void onResumeTopActivityRemoteFailure(@NonNull ActivityRecord.State lastState,
            @NonNull ActivityRecord next, @Nullable ActivityRecord lastResumedActivity,
            @Nullable Task lastFocusedRootTask) {
        ProtoLog.v(WM_DEBUG_STATES, "Resume failed; resetting state to %s: "
                + "%s", lastState, next);
        next.setState(lastState, "resumeTopActivityInnerLocked");

        // lastResumedActivity being non-null implies there is a lastStack present.
        if (lastResumedActivity != null) {
            lastResumedActivity.setState(RESUMED, "resumeTopActivityInnerLocked");
        }

        Slog.i(TAG, "Restarting because process died: " + next);
        if (!next.hasBeenLaunched) {
            next.hasBeenLaunched = true;
        } else if (SHOW_APP_STARTING_PREVIEW && lastFocusedRootTask != null
                && lastFocusedRootTask.isTopRootTaskInDisplayArea()) {
            next.showStartingWindow(false /* taskSwitch */);
        }
        mTaskSupervisor.startSpecificActivity(next, true, false);
    }

    boolean shouldSleepOrShutDownActivities() {
        return shouldSleepActivities() || mAtmService.mShuttingDown;
    }

    /**
     * Returns true if the TaskFragment should be visible.
     *
     * @param starting The currently starting activity or null if there is none.
     */
    boolean shouldBeVisible(ActivityRecord starting) {
        return getVisibility(starting) != TASK_FRAGMENT_VISIBILITY_INVISIBLE;
    }

    /**
     * Returns {@code true} is the activity in this TaskFragment can be resumed.
     *
     * @param starting The currently starting activity or {@code null} if there is none.
     */
    boolean canBeResumed(@Nullable ActivityRecord starting) {
        // No need to resume activity in TaskFragment that is not visible.
        return isTopActivityFocusable()
                && getVisibility(starting) == TASK_FRAGMENT_VISIBILITY_VISIBLE;
    }

    boolean isFocusableAndVisible() {
        return isTopActivityFocusable() && shouldBeVisible(null /* starting */);
    }

    final boolean startPausing(boolean uiSleeping, ActivityRecord resuming, String reason) {
        return startPausing(mTaskSupervisor.mUserLeaving, uiSleeping, resuming, reason);
    }

    /**
     * Start pausing the currently resumed activity.  It is an error to call this if there
     * is already an activity being paused or there is no resumed activity.
     *
     * @param userLeaving True if this should result in an onUserLeaving to the current activity.
     * @param uiSleeping True if this is happening with the user interface going to sleep (the
     * screen turning off).
     * @param resuming The activity we are currently trying to resume or null if this is not being
     *                 called as part of resuming the top activity, so we shouldn't try to instigate
     *                 a resume here if not null.
     * @param reason The reason of pausing the activity.
     * @return Returns true if an activity now is in the PAUSING state, and we are waiting for
     * it to tell us when it is done.
     */
    boolean startPausing(boolean userLeaving, boolean uiSleeping, ActivityRecord resuming,
            String reason) {
        if (!hasDirectChildActivities()) {
            return false;
        }
        if (mResumedActivity != null && !mResumedActivity.finishing
                && mTransitionController.isTransientLaunch(mResumedActivity)) {
            // Even if the transient activity is occluded, defer pausing (addToStopping will still
            // be called) it until the transient transition is done. So the current resuming
            // activity won't need to wait for additional pause complete.
            ProtoLog.d(WM_DEBUG_STATES, "startPausing: Skipping pause for transient "
                            + "resumed activity=%s", mResumedActivity);
            return false;
        }

        ProtoLog.d(WM_DEBUG_STATES, "startPausing: taskFrag=%s mResumedActivity=%s", this,
                mResumedActivity);

        if (mPausingActivity != null) {
            Slog.wtf(TAG, "Going to pause when pause is already pending for " + mPausingActivity
                    + " state=" + mPausingActivity.getState());
            if (!shouldSleepActivities()) {
                // Avoid recursion among check for sleep and complete pause during sleeping.
                // Because activity will be paused immediately after resume, just let pause
                // be completed by the order of activity paused from clients.
                completePause(false, resuming);
            }
        }
        ActivityRecord prev = mResumedActivity;

        if (prev == null) {
            if (resuming == null) {
                Slog.wtf(TAG, "Trying to pause when nothing is resumed");
                mRootWindowContainer.resumeFocusedTasksTopActivities();
            }
            return false;
        }

        if (prev == resuming) {
            Slog.wtf(TAG, "Trying to pause activity that is in process of being resumed");
            return false;
        }

        ProtoLog.v(WM_DEBUG_STATES, "Moving to PAUSING: %s", prev);
        mPausingActivity = prev;
        mLastPausedActivity = prev;
        if (!prev.finishing && prev.isNoHistory()
                && !mTaskSupervisor.mNoHistoryActivities.contains(prev)) {
            mTaskSupervisor.mNoHistoryActivities.add(prev);
        }
        prev.setState(PAUSING, "startPausingLocked");
        prev.getTask().touchActiveTime();

        mAtmService.updateCpuStats();

        boolean pauseImmediately = false;
        boolean shouldAutoPip = false;
        if (resuming != null) {
            // We do not want to trigger auto-PiP upon launch of a translucent activity.
            final boolean resumingOccludesParent = resuming.occludesParent();

            if (ActivityTaskManagerService.isPip2ExperimentEnabled()) {
                // If a new task is being launched, then mark the existing top activity as
                // supporting picture-in-picture while pausing only if the starting activity
                // would not be considered an overlay on top of the current activity
                // (eg. not fullscreen, or the assistant)
                Task.enableEnterPipOnTaskSwitch(prev, resuming.getTask(),
                        resuming, resuming.getOptions());
            }

            // Resuming the new resume activity only if the previous activity can't go into Pip
            // since we want to give Pip activities a chance to enter Pip before resuming the
            // next activity.
            final boolean lastResumedCanPip = prev.checkEnterPictureInPictureState(
                    "shouldAutoPipWhilePausing", userLeaving);
            if (prev.supportsEnterPipOnTaskSwitch && userLeaving
                    && resumingOccludesParent && lastResumedCanPip
                    && prev.pictureInPictureArgs.isAutoEnterEnabled()) {
                shouldAutoPip = true;
            } else if (!lastResumedCanPip) {
                // If the flag RESUME_WHILE_PAUSING is set, then continue to schedule the previous
                // activity to be paused.
                pauseImmediately = (resuming.info.flags & FLAG_RESUME_WHILE_PAUSING) != 0;
            } else {
                // The previous activity may still enter PIP even though it did not allow auto-PIP.
            }
        }

        if (prev.attachedToProcess()) {
            if (shouldAutoPip && ActivityTaskManagerService.isPip2ExperimentEnabled()) {
                prev.mPauseSchedulePendingForPip = true;
                boolean willAutoPip = mAtmService.setPipCandidateIfNeeded(prev);
                ProtoLog.d(WM_DEBUG_STATES, "Auto-PIP allowed, requesting PIP mode "
                        + "via requestStartTransition(): %s, willAutoPip: %b", prev, willAutoPip);
            } else if (shouldAutoPip) {
                prev.mPauseSchedulePendingForPip = true;
                boolean didAutoPip = mAtmService.enterPictureInPictureMode(
                        prev, prev.pictureInPictureArgs, false /* fromClient */);
                ProtoLog.d(WM_DEBUG_STATES, "Auto-PIP allowed, entering PIP mode "
                        + "directly: %s, didAutoPip: %b", prev, didAutoPip);
            } else {
                schedulePauseActivity(prev, userLeaving, pauseImmediately,
                        false /* autoEnteringPip */, reason);
            }
        } else {
            mPausingActivity = null;
            mLastPausedActivity = null;
            mTaskSupervisor.mNoHistoryActivities.remove(prev);
        }

        // If we are not going to sleep, we want to ensure the device is
        // awake until the next activity is started.
        if (!uiSleeping && !mAtmService.isSleepingOrShuttingDownLocked()) {
            mTaskSupervisor.acquireLaunchWakelock();
        }

        // If already entered PIP mode, no need to keep pausing.
        if (mPausingActivity != null) {
            // Have the window manager pause its key dispatching until the new
            // activity has started.  If we're pausing the activity just because
            // the screen is being turned off and the UI is sleeping, don't interrupt
            // key dispatch; the same activity will pick it up again on wakeup.
            if (!uiSleeping) {
                prev.pauseKeyDispatchingLocked();
            } else {
                ProtoLog.v(WM_DEBUG_STATES, "Key dispatch not paused for screen off");
            }

            if (pauseImmediately) {
                // If the caller said they don't want to wait for the pause, then complete
                // the pause now.
                completePause(false, resuming);
                return false;

            } else {
                prev.schedulePauseTimeout();
                // All activities will be stopped when sleeping, don't need to wait for pause.
                if (!uiSleeping) {
                    // Unset readiness since we now need to wait until this pause is complete.
                    mTransitionController.setReady(this, false /* ready */);
                }
                return true;
            }

        } else {
            // This activity either failed to schedule the pause or it entered PIP mode,
            // so just treat it as being paused now.
            ProtoLog.v(WM_DEBUG_STATES, "Activity not running or entered PiP, resuming next.");
            if (resuming == null) {
                mRootWindowContainer.resumeFocusedTasksTopActivities();
            }
            return false;
        }
    }

    void schedulePauseActivity(ActivityRecord prev, boolean userLeaving,
            boolean pauseImmediately, boolean autoEnteringPip, String reason) {
        ProtoLog.v(WM_DEBUG_STATES, "Enqueueing pending pause: %s", prev);
        prev.mPauseSchedulePendingForPip = false;
        EventLogTags.writeWmPauseActivity(prev.mUserId, System.identityHashCode(prev),
                prev.shortComponentName, "userLeaving=" + userLeaving, reason);

        final PauseActivityItem item = new PauseActivityItem(prev.token, prev.finishing,
                userLeaving, pauseImmediately, autoEnteringPip);
        final boolean isSuccessful = mAtmService.getLifecycleManager().scheduleTransactionItem(
                prev.app.getThread(), item);
        if (!isSuccessful) {
            mPausingActivity = null;
            mLastPausedActivity = null;
            mTaskSupervisor.mNoHistoryActivities.remove(prev);
        }
    }

    @VisibleForTesting
    void completePause(boolean resumeNext, ActivityRecord resuming) {
        // Complete the pausing process of a pausing activity, so it doesn't make sense to
        // operate on non-leaf tasks.
        // warnForNonLeafTask("completePauseLocked");

        ActivityRecord prev = mPausingActivity;
        ProtoLog.v(WM_DEBUG_STATES, "Complete pause: %s", prev);

        if (prev != null) {
            prev.setWillCloseOrEnterPip(false);
            prev.supportsEnterPipOnTaskSwitch = false;
            final boolean wasStopping = prev.isState(STOPPING);
            prev.setState(PAUSED, "completePausedLocked");
            mPausingActivity = null;
            if (prev.finishing) {
                // We will update the activity visibility later, no need to do in
                // completeFinishing(). Updating visibility here might also making the next
                // activities to be resumed, and could result in wrong app transition due to
                // lack of previous activity information.
                ProtoLog.v(WM_DEBUG_STATES, "Executing finish of activity: %s", prev);
                prev = prev.completeFinishing(false /* updateVisibility */,
                        "completePausedLocked");
            } else if (prev.attachedToProcess()) {
                ProtoLog.v(WM_DEBUG_STATES, "Enqueue pending stop if needed: %s "
                                + "wasStopping=%b visibleRequested=%b",  prev,  wasStopping,
                        prev.isVisibleRequested());
                if (wasStopping) {
                    // We are also stopping, the stop request must have gone soon after the pause.
                    // We can't clobber it, because the stop confirmation will not be handled.
                    // We don't need to schedule another stop, we only need to let it happen.
                    prev.setState(STOPPING, "completePausedLocked");
                } else if (!prev.isVisibleRequested() || shouldSleepOrShutDownActivities()) {
                    // If we were visible then resumeTopActivities will release resources before
                    // stopping.
                    prev.addToStopping(true /* scheduleIdle */, false /* idleDelayed */,
                            "completePauseLocked");
                }
            } else {
                ProtoLog.v(WM_DEBUG_STATES, "App died during pause, not stopping: %s", prev);
                prev = null;
            }
        }

        if (resumeNext) {
            final Task topRootTask = mRootWindowContainer.getTopDisplayFocusedRootTask();
            if (topRootTask != null && !topRootTask.shouldSleepOrShutDownActivities()) {
                final boolean resumed =
                        mRootWindowContainer.resumeFocusedTasksTopActivities(topRootTask, prev);
                if (!resumed && mWmService.mSyncEngine.hasActiveSync()) {
                    // TODO(b/294925498): Remove this once we have accurate ready tracking.
                    mWmService.requestTraversal();
                }
            } else {
                // checkReadyForSleep();
                final ActivityRecord top =
                        topRootTask != null ? topRootTask.topRunningActivity() : null;
                if (top == null || (prev != null && top != prev)) {
                    // If there are no more activities available to run, do resume anyway to start
                    // something. Also if the top activity on the root task is not the just paused
                    // activity, we need to go ahead and resume it to ensure we complete an
                    // in-flight app switch.
                    mRootWindowContainer.resumeFocusedTasksTopActivities();
                }
            }
        }

        if (prev != null) {
            prev.resumeKeyDispatchingLocked();
        }

        mRootWindowContainer.ensureActivitiesVisible(resuming);

        // Notify when the task stack has changed, but only if visibilities changed (not just
        // focus). Also if there is an active root pinned task - we always want to notify it about
        // task stack changes, because its positioning may depend on it.
        if (mTaskSupervisor.mAppVisibilitiesChangedSinceLastPause
                || (getDisplayArea() != null && getDisplayArea().hasPinnedTask())) {
            mAtmService.getTaskChangeNotificationController().notifyTaskStackChanged();
            mTaskSupervisor.mAppVisibilitiesChangedSinceLastPause = false;
        }
    }

    @ActivityInfo.ScreenOrientation
    @Override
    int getOrientation(@ActivityInfo.ScreenOrientation int candidate) {
        if (shouldReportOrientationUnspecified()) {
            return SCREEN_ORIENTATION_UNSPECIFIED;
        }
        if (canSpecifyOrientation()) {
            return super.getOrientation(candidate);
        }
        return SCREEN_ORIENTATION_UNSET;
    }

    @ActivityInfo.ScreenOrientation
    @Override
    protected int getOverrideOrientation() {
        if (isEmbedded() && !isVisibleRequested()) {
            return SCREEN_ORIENTATION_UNSPECIFIED;
        }
        return super.getOverrideOrientation();
    }

    /**
     * Whether or not to allow this container to specify an app requested orientation.
     *
     * This is different from {@link #providesOrientation()} that
     * 1. The container may still provide an orientation even if it can't specify the app requested
     *    one, such as {@link #shouldReportOrientationUnspecified()}
     * 2. Even if the container can specify an app requested orientation, it may not be used by the
     *    parent container if it is {@link ActivityInfo#SCREEN_ORIENTATION_UNSPECIFIED}.
     */
    boolean canSpecifyOrientation() {
        final int windowingMode = getWindowingMode();
        final int activityType = getActivityType();
        return windowingMode == WINDOWING_MODE_FULLSCREEN
                || activityType == ACTIVITY_TYPE_HOME
                || activityType == ACTIVITY_TYPE_RECENTS
                || activityType == ACTIVITY_TYPE_ASSISTANT;
    }

    /**
     * Whether or not the parent container should use the orientation provided by this container
     * even if it is {@link ActivityInfo#SCREEN_ORIENTATION_UNSPECIFIED}.
     */
    @Override
    boolean providesOrientation() {
        return super.providesOrientation() || shouldReportOrientationUnspecified();
    }

    private boolean shouldReportOrientationUnspecified() {
        // Apps and their containers are not allowed to specify orientation from adjacent
        // TaskFragment.
        return hasAdjacentTaskFragment() && isVisibleRequested();
    }

    @Override
    void forAllTaskFragments(Consumer<TaskFragment> callback, boolean traverseTopToBottom) {
        super.forAllTaskFragments(callback, traverseTopToBottom);
        callback.accept(this);
    }

    @Override
    void forAllLeafTaskFragments(Consumer<TaskFragment> callback, boolean traverseTopToBottom) {
        final int count = mChildren.size();
        boolean isLeafTaskFrag = true;
        if (traverseTopToBottom) {
            for (int i = count - 1; i >= 0; --i) {
                final TaskFragment child = mChildren.get(i).asTaskFragment();
                if (child != null) {
                    isLeafTaskFrag = false;
                    child.forAllLeafTaskFragments(callback, traverseTopToBottom);
                }
            }
        } else {
            for (int i = 0; i < count; i++) {
                final TaskFragment child = mChildren.get(i).asTaskFragment();
                if (child != null) {
                    isLeafTaskFrag = false;
                    child.forAllLeafTaskFragments(callback, traverseTopToBottom);
                }
            }
        }
        if (isLeafTaskFrag) callback.accept(this);
    }

    @Override
    boolean forAllLeafTaskFragments(Predicate<TaskFragment> callback) {
        boolean isLeafTaskFrag = true;
        for (int i = mChildren.size() - 1; i >= 0; --i) {
            final TaskFragment child = mChildren.get(i).asTaskFragment();
            if (child != null) {
                isLeafTaskFrag = false;
                if (child.forAllLeafTaskFragments(callback)) {
                    return true;
                }
            }
        }
        if (isLeafTaskFrag) {
            return callback.test(this);
        }
        return false;
    }

    void addChild(ActivityRecord r) {
        addChild(r, POSITION_TOP);
    }

    @Override
    void addChild(WindowContainer child, int index) {
        ActivityRecord r = topRunningActivity();
        mClearedTaskForReuse = false;
        mClearedTaskFragmentForPip = false;
        mClearedForReorderActivityToFront = false;

        final ActivityRecord addingActivity = child.asActivityRecord();
        final boolean isAddingActivity = addingActivity != null;
        final Task task = isAddingActivity ? getTask() : null;

        // If this task had any activity before we added this one.
        boolean taskHadActivity = task != null && task.getTopMostActivity() != null;
        // getActivityType() looks at the top child, so we need to read the type before adding
        // a new child in case the new child is on top and UNDEFINED.
        final int activityType = task != null ? task.getActivityType() : ACTIVITY_TYPE_UNDEFINED;

        super.addChild(child, index);

        if (isAddingActivity && task != null) {
            addingActivity.inHistory = true;
            task.onDescendantActivityAdded(taskHadActivity, activityType, addingActivity);
        }

        final WindowProcessController hostProcess = getOrganizerProcessIfDifferent(addingActivity);
        if (hostProcess != null) {
            hostProcess.addEmbeddedActivity(addingActivity);
        }

        // Make sure the list of display UID allowlists is updated
        // now that this record is in a new task fragment.
        mRootWindowContainer.updateUIDsPresentOnDisplay();
    }

    @Override
    void onChildPositionChanged(WindowContainer child) {
        super.onChildPositionChanged(child);

        sendTaskFragmentInfoChanged();
    }

    void executeAppTransition(ActivityOptions options) {
        mDisplayContent.executeAppTransition();
        ActivityOptions.abort(options);
    }

    @Override
    boolean canCreateRemoteAnimationTarget() {
        return true;
    }

    boolean shouldSleepActivities() {
        final DisplayContent dc = mDisplayContent;
        if (dc == null) {
            return mAtmService.isSleepingLocked();
        }
        if (!dc.isSleeping()) {
            return false;
        }
        // In case the unlocking order is keyguard-going-away -> screen-turning-on (display is
        // sleeping by screen-off-token which may be notified to release from power manager's
        // thread), keep the activities resume-able to avoid extra activity lifecycle when
        // performing keyguard-going-away. This only applies to default display because currently
        // the per-display keyguard-going-away state is assigned from a global signal.
        if (!dc.isDefaultDisplay || !dc.isKeyguardGoingAway()) {
            return true;
        }
        return !shouldBeVisible(null /* starting */);
    }

    @Override
    void resolveOverrideConfiguration(Configuration newParentConfig) {
        mTmpBounds.set(getResolvedOverrideConfiguration().windowConfiguration.getBounds());
        super.resolveOverrideConfiguration(newParentConfig);
        final Configuration resolvedConfig = getResolvedOverrideConfiguration();
        final Task thisTask = asTask();
        if (!resolvedConfig.windowConfiguration.getBounds().isEmpty() && thisTask != null
                && !thisTask.isOverrideBoundsAllowed()) {
            // clear the bounds if it is not allowed from its ancestors.
            resolvedConfig.windowConfiguration.setBounds(new Rect());
        }

        if (mRelativeEmbeddedBounds != null && !mRelativeEmbeddedBounds.isEmpty()) {
            // For embedded TaskFragment, make sure the bounds is set based on the relative bounds.
            resolvedConfig.windowConfiguration.setBounds(translateRelativeBoundsToAbsoluteBounds(
                    mRelativeEmbeddedBounds, newParentConfig.windowConfiguration.getBounds()));
        }
        int windowingMode = resolvedConfig.windowConfiguration.getWindowingMode();
        final int parentWindowingMode = newParentConfig.windowConfiguration.getWindowingMode();

        // Resolve override windowing mode to fullscreen for home task (even on freeform
        // display), or split-screen if in split-screen mode.
        if (getActivityType() == ACTIVITY_TYPE_HOME && windowingMode == WINDOWING_MODE_UNDEFINED) {
            windowingMode = WINDOWING_MODE_FULLSCREEN;
            resolvedConfig.windowConfiguration.setWindowingMode(windowingMode);
        }

        // Do not allow tasks not support multi window to be in a multi-window mode, unless it is in
        // pinned windowing mode.
        if (!supportsMultiWindow()) {
            final int candidateWindowingMode =
                    windowingMode != WINDOWING_MODE_UNDEFINED ? windowingMode : parentWindowingMode;
            if (WindowConfiguration.inMultiWindowMode(candidateWindowingMode)
                    && candidateWindowingMode != WINDOWING_MODE_PINNED) {
                resolvedConfig.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
            }
        }

        if (thisTask != null) {
            thisTask.resolveLeafTaskOnlyOverrideConfigs(newParentConfig,
                    mTmpBounds /* previousBounds */);
        }
        computeConfigResourceOverrides(resolvedConfig, newParentConfig);
    }

    boolean supportsMultiWindow() {
        return supportsMultiWindowInDisplayArea(getDisplayArea());
    }

    /**
     * @return whether this task supports multi-window if it is in the given
     *         {@link TaskDisplayArea}.
     */
    boolean supportsMultiWindowInDisplayArea(@Nullable TaskDisplayArea tda) {
        if (!mAtmService.mSupportsMultiWindow) {
            return false;
        }
        if (tda == null) {
            return false;
        }
        final Task task = getTask();
        if (task == null) {
            return false;
        }
        if (!task.isResizeable() && !tda.supportsNonResizableMultiWindow()) {
            // Not support non-resizable in multi window.
            return false;
        }

        final ActivityRecord rootActivity = task.getRootActivity();
        return tda.supportsActivityMinWidthHeightMultiWindow(mMinWidth, mMinHeight,
                rootActivity != null ? rootActivity.info : null);
    }

    private int getTaskId() {
        return getTask() != null ? getTask().mTaskId : INVALID_TASK_ID;
    }

    static class ConfigOverrideHint {
        @Nullable DisplayInfo mTmpOverrideDisplayInfo;
        @Nullable AppCompatDisplayInsets mTmpCompatInsets;
        @Nullable Rect mParentAppBoundsOverride;
        @Nullable Rect mParentBoundsOverride;
        int mTmpOverrideConfigOrientation;
        boolean mUseOverrideInsetsForConfig;

        void resolveTmpOverrides(DisplayContent dc, Configuration parentConfig,
                boolean isFixedRotationTransforming, @Nullable Rect safeRegionBounds,
                boolean shouldApplyLegacyInsets) {
            mParentAppBoundsOverride = safeRegionBounds != null ? safeRegionBounds : new Rect(
                    parentConfig.windowConfiguration.getAppBounds());
            mParentBoundsOverride = safeRegionBounds != null ? safeRegionBounds : new Rect(
                    parentConfig.windowConfiguration.getBounds());
            mTmpOverrideConfigOrientation = parentConfig.orientation;
            Insets insets = Insets.NONE;
            if (safeRegionBounds != null) {
                // Modify orientation based on the parent app bounds if safe region bounds are set.
                mTmpOverrideConfigOrientation =
                        mParentAppBoundsOverride.height() >= mParentAppBoundsOverride.width()
                                ? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE;
            } else if (shouldApplyLegacyInsets && mUseOverrideInsetsForConfig && dc != null) {
                // Insets are decoupled from configuration by default from V+, use legacy
                // compatibility behaviour for apps targeting SDK earlier than 35
                // (see applySizeOverrideIfNeeded).
                int rotation = parentConfig.windowConfiguration.getRotation();
                if (rotation == ROTATION_UNDEFINED && !isFixedRotationTransforming) {
                    rotation = dc.getRotation();
                }
                final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270);
                final int dw = rotated ? dc.mBaseDisplayHeight : dc.mBaseDisplayWidth;
                final int dh = rotated ? dc.mBaseDisplayWidth : dc.mBaseDisplayHeight;
                DisplayPolicy.DecorInsets.Info decorInsets = dc.getDisplayPolicy()
                        .getDecorInsetsInfo(rotation, dw, dh);
                final Rect stableBounds = decorInsets.mOverrideConfigFrame;
                mTmpOverrideConfigOrientation = stableBounds.width() > stableBounds.height()
                        ? ORIENTATION_LANDSCAPE : ORIENTATION_PORTRAIT;
                insets = Insets.of(decorInsets.mOverrideNonDecorInsets);
            }
            mParentAppBoundsOverride.inset(insets);
        }

        void resetTmpOverrides() {
            mTmpOverrideDisplayInfo = null;
            mTmpCompatInsets = null;
            mTmpOverrideConfigOrientation = ORIENTATION_UNDEFINED;
        }
    }

    void computeConfigResourceOverrides(@NonNull Configuration inOutConfig,
            @NonNull Configuration parentConfig) {
        computeConfigResourceOverrides(inOutConfig, parentConfig, null /* configOverrideHint */);
    }

    /**
     * Forces the app bounds related configuration can be computed by
     * {@link #computeConfigResourceOverrides(Configuration, Configuration, ConfigOverrideHint)}.
     */
    private static void invalidateAppBoundsConfig(@NonNull Configuration inOutConfig) {
        final Rect appBounds = inOutConfig.windowConfiguration.getAppBounds();
        if (appBounds != null) {
            appBounds.setEmpty();
        }
        inOutConfig.screenWidthDp = Configuration.SCREEN_WIDTH_DP_UNDEFINED;
        inOutConfig.screenHeightDp = Configuration.SCREEN_HEIGHT_DP_UNDEFINED;
    }

    /**
     * Calculates configuration values used by the client to get resources. This should be run
     * using app-facing bounds (bounds unmodified by animations or transient interactions).
     *
     * This assumes bounds are non-empty/null. For the null-bounds case, the caller is likely
     * configuring an "inherit-bounds" window which means that all configuration settings would
     * just be inherited from the parent configuration.
     **/
    void computeConfigResourceOverrides(@NonNull Configuration inOutConfig,
            @NonNull Configuration parentConfig, @Nullable ConfigOverrideHint overrideHint) {
        DisplayInfo overrideDisplayInfo = null;
        AppCompatDisplayInsets compatInsets = null;
        boolean useOverrideInsetsForConfig = false;
        if (overrideHint != null) {
            overrideDisplayInfo = overrideHint.mTmpOverrideDisplayInfo;
            compatInsets = overrideHint.mTmpCompatInsets;
            useOverrideInsetsForConfig = overrideHint.mUseOverrideInsetsForConfig;
            if (overrideDisplayInfo != null) {
                // Make sure the screen related configs can be computed by the provided
                // display info.
                inOutConfig.screenLayout = Configuration.SCREENLAYOUT_UNDEFINED;
            }
            if (overrideDisplayInfo != null || compatInsets != null) {
                // Make sure the app bounds can be computed by the compat insets.
                invalidateAppBoundsConfig(inOutConfig);
            }
        }
        int windowingMode = inOutConfig.windowConfiguration.getWindowingMode();
        if (windowingMode == WINDOWING_MODE_UNDEFINED) {
            windowingMode = parentConfig.windowConfiguration.getWindowingMode();
        }

        float density = inOutConfig.densityDpi;
        if (density == Configuration.DENSITY_DPI_UNDEFINED) {
            density = parentConfig.densityDpi;
        }
        density *= DisplayMetrics.DENSITY_DEFAULT_SCALE;

        // The bounds may have been overridden at this level. If the parent cannot cover these
        // bounds, the configuration is still computed according to the override bounds.
        final boolean insideParentBounds;

        final Rect parentBounds = parentConfig.windowConfiguration.getBounds();
        final Rect resolvedBounds = inOutConfig.windowConfiguration.getBounds();
        if (resolvedBounds.isEmpty()) {
            mTmpFullBounds.set(parentBounds);
            insideParentBounds = true;
        } else {
            mTmpFullBounds.set(resolvedBounds);
            insideParentBounds = parentBounds.contains(resolvedBounds);
        }

        // Non-null compatibility insets means the activity prefers to keep its original size, so
        // out bounds doesn't need to be restricted by the parent or current display
        final boolean customContainerPolicy = compatInsets != null;

        Rect outAppBounds = inOutConfig.windowConfiguration.getAppBounds();
        if (outAppBounds == null || outAppBounds.isEmpty()) {
            // App-bounds hasn't been overridden, so calculate a value for it.
            inOutConfig.windowConfiguration.setAppBounds(mTmpFullBounds);
            outAppBounds = inOutConfig.windowConfiguration.getAppBounds();

            if (insideParentBounds && useOverrideInsetsForConfig && !customContainerPolicy
                    && overrideHint.mParentAppBoundsOverride != null
                    && !WindowConfiguration.isFloating(windowingMode)) {
                // Clip decor insets for legacy apps (no INSETS_DECOUPLED_CONFIGURATION_ENFORCED).
                outAppBounds.intersectUnchecked(overrideHint.mParentAppBoundsOverride);
            } else if (resolvedBounds.isEmpty()) {
                // Inherit from parent if there is no override bounds.
                final Rect parentAppBounds = parentConfig.windowConfiguration.getAppBounds();
                if (parentAppBounds != null) {
                    outAppBounds.set(parentAppBounds);
                }
            }
        }

        if (inOutConfig.screenWidthDp == Configuration.SCREEN_WIDTH_DP_UNDEFINED
                || inOutConfig.screenHeightDp == Configuration.SCREEN_HEIGHT_DP_UNDEFINED) {
            if (!customContainerPolicy && WindowConfiguration.isFloating(windowingMode)) {
                mTmpNonDecorBounds.set(mTmpFullBounds);
                mTmpStableBounds.set(mTmpFullBounds);
            } else if (!customContainerPolicy
                    && (overrideDisplayInfo != null || getDisplayContent() != null)) {
                final DisplayInfo di = overrideDisplayInfo != null
                        ? overrideDisplayInfo
                        : getDisplayContent().getDisplayInfo();

                // For calculating screenWidthDp, screenWidthDp, we use the stable inset screen
                // area, i.e. the screen area without the system bars.
                // The non decor inset are areas that could never be removed in Honeycomb. See
                // {@link WindowManagerPolicy#getNonDecorInsetsLw}.
                calculateInsetFrames(mTmpNonDecorBounds, mTmpStableBounds, mTmpFullBounds, di,
                        useOverrideInsetsForConfig);
            } else {
                // Apply the given non-decor and stable insets to calculate the corresponding bounds
                // for screen size of configuration.
                int rotation = inOutConfig.windowConfiguration.getRotation();
                if (rotation == ROTATION_UNDEFINED) {
                    rotation = parentConfig.windowConfiguration.getRotation();
                }
                if (rotation != ROTATION_UNDEFINED && customContainerPolicy) {
                    mTmpNonDecorBounds.set(mTmpFullBounds);
                    mTmpStableBounds.set(mTmpFullBounds);
                    compatInsets.getBoundsByRotation(mTmpBounds, rotation);
                    intersectWithInsetsIfFits(mTmpNonDecorBounds, mTmpBounds,
                            compatInsets.mNonDecorInsets[rotation]);
                    intersectWithInsetsIfFits(mTmpStableBounds, mTmpBounds,
                            compatInsets.mStableInsets[rotation]);
                    outAppBounds.set(mTmpNonDecorBounds);
                } else {
                    // Set to app bounds because it excludes decor insets.
                    mTmpNonDecorBounds.set(outAppBounds);
                    mTmpStableBounds.set(outAppBounds);
                }
            }

            if (inOutConfig.screenWidthDp == Configuration.SCREEN_WIDTH_DP_UNDEFINED) {
                final int overrideScreenWidthDp = (int) (mTmpStableBounds.width() / density + 0.5f);
                inOutConfig.screenWidthDp = (insideParentBounds && !customContainerPolicy)
                        ? Math.min(overrideScreenWidthDp, parentConfig.screenWidthDp)
                        : overrideScreenWidthDp;
            }
            if (inOutConfig.screenHeightDp == Configuration.SCREEN_HEIGHT_DP_UNDEFINED) {
                final int overrideScreenHeightDp =
                        (int) (mTmpStableBounds.height() / density + 0.5f);
                inOutConfig.screenHeightDp = (insideParentBounds && !customContainerPolicy)
                        ? Math.min(overrideScreenHeightDp, parentConfig.screenHeightDp)
                        : overrideScreenHeightDp;
            }

            // TODO(b/238331848): Consider simplifying logic that computes smallestScreenWidthDp.
            if (inOutConfig.smallestScreenWidthDp
                    == Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED) {
                // When entering to or exiting from Pip, the PipTaskOrganizer will set the
                // windowing mode of the activity in the task to WINDOWING_MODE_FULLSCREEN and
                // temporarily set the bounds of the task to fullscreen size for transitioning.
                // It will get the wrong value if the calculation is based on this temporary
                // fullscreen bounds.
                // We should just inherit the value from parent for this temporary state.
                final boolean inPipTransition = windowingMode == WINDOWING_MODE_PINNED
                        && !mTmpFullBounds.isEmpty() && mTmpFullBounds.equals(parentBounds);
                // For floating tasks and app bubbles, calculate the smallest width from the bounds
                // of the task, because they should not be affected by insets.
                boolean shouldUseTaskBounds = WindowConfiguration.isFloating(windowingMode);
                if (com.android.wm.shell.Flags.enableCreateAnyBubble()) {
                    final Task task = getTask();
                    if (task != null) {
                        // TODO(b/407669465): Update mLaunchNextToBubble usage when migrated.
                        shouldUseTaskBounds |= task.mLaunchNextToBubble;
                    }
                }
                if (shouldUseTaskBounds && !inPipTransition) {
                    // For floating tasks, calculate the smallest width from the bounds of the
                    // task, because they should not be affected by insets.
                    inOutConfig.smallestScreenWidthDp = (int) (0.5f
                            + Math.min(mTmpFullBounds.width(), mTmpFullBounds.height()) / density);
                } else if (windowingMode == WINDOWING_MODE_MULTI_WINDOW && mIsEmbedded
                        && !resolvedBounds.equals(parentBounds)) {
                    // For embedded TFs, the smallest width should be updated. Otherwise, inherit
                    // from the parent task would result in applications loaded wrong resource.
                    inOutConfig.smallestScreenWidthDp =
                            Math.min(inOutConfig.screenWidthDp, inOutConfig.screenHeightDp);
                }
                // otherwise, it will just inherit
            }
        }

        if (inOutConfig.orientation == ORIENTATION_UNDEFINED) {
            inOutConfig.orientation = (inOutConfig.screenWidthDp <= inOutConfig.screenHeightDp)
                    ? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE;
        }
        if (inOutConfig.screenLayout == Configuration.SCREENLAYOUT_UNDEFINED) {
            // For calculating screen layout, we need to use the non-decor inset screen area for the
            // calculation for compatibility reasons, i.e. screen area without system bars that
            // could never go away in Honeycomb.
            int compatScreenWidthDp = (int) (mTmpNonDecorBounds.width() / density + 0.5f);
            int compatScreenHeightDp = (int) (mTmpNonDecorBounds.height() / density + 0.5f);
            // Use overrides if provided. If both overrides are provided, mTmpNonDecorBounds is
            // undefined so it can't be used.
            if (inOutConfig.screenWidthDp != Configuration.SCREEN_WIDTH_DP_UNDEFINED) {
                compatScreenWidthDp = inOutConfig.screenWidthDp;
            }
            if (inOutConfig.screenHeightDp != Configuration.SCREEN_HEIGHT_DP_UNDEFINED) {
                compatScreenHeightDp = inOutConfig.screenHeightDp;
            }
            // Reducing the screen layout starting from its parent config.
            inOutConfig.screenLayout = computeScreenLayout(parentConfig.screenLayout,
                    compatScreenWidthDp, compatScreenHeightDp);
        }
    }

    /**
     * Gets bounds with non-decor and stable insets applied respectively.
     *
     * If bounds overhangs the display, those edges will not get insets. See
     * {@link #intersectWithInsetsIfFits}
     *
     * @param outNonDecorBounds where to place bounds with non-decor insets applied.
     * @param outStableBounds where to place bounds with stable insets applied.
     * @param bounds the bounds to inset.
     * @param useLegacyInsetsForStableBounds {@code true} if we need to use the legacy insets frame
     *                for apps targeting U or before when calculating stable bounds.
     */
    void calculateInsetFrames(Rect outNonDecorBounds, Rect outStableBounds, Rect bounds,
            DisplayInfo displayInfo, boolean useLegacyInsetsForStableBounds) {
        outNonDecorBounds.set(bounds);
        outStableBounds.set(bounds);
        if (mDisplayContent == null) {
            return;
        }
        mTmpBounds.set(0, 0, displayInfo.logicalWidth, displayInfo.logicalHeight);

        final DisplayPolicy policy = mDisplayContent.getDisplayPolicy();
        final DisplayPolicy.DecorInsets.Info info = policy.getDecorInsetsInfo(
                displayInfo.rotation, displayInfo.logicalWidth, displayInfo.logicalHeight);
        if (!useLegacyInsetsForStableBounds) {
            intersectWithInsetsIfFits(outStableBounds, mTmpBounds, info.mConfigInsets);
            intersectWithInsetsIfFits(outNonDecorBounds, mTmpBounds, info.mNonDecorInsets);
        } else {
            intersectWithInsetsIfFits(outStableBounds, mTmpBounds, info.mOverrideConfigInsets);
            intersectWithInsetsIfFits(outNonDecorBounds, mTmpBounds, info.mOverrideNonDecorInsets);
        }
    }

    /**
     * Intersects inOutBounds with intersectBounds-intersectInsets. If inOutBounds is larger than
     * intersectBounds on a side, then the respective side will not be intersected.
     *
     * The assumption is that if inOutBounds is initially larger than intersectBounds, then the
     * inset on that side is no-longer applicable. This scenario happens when a task's minimal
     * bounds are larger than the provided parent/display bounds.
     *
     * @param inOutBounds the bounds to intersect.
     * @param intersectBounds the bounds to intersect with.
     * @param intersectInsets insets to apply to intersectBounds before intersecting.
     */
    static void intersectWithInsetsIfFits(
            Rect inOutBounds, Rect intersectBounds, Rect intersectInsets) {
        if (inOutBounds.right <= intersectBounds.right) {
            inOutBounds.right =
                    Math.min(intersectBounds.right - intersectInsets.right, inOutBounds.right);
        }
        if (inOutBounds.bottom <= intersectBounds.bottom) {
            inOutBounds.bottom =
                    Math.min(intersectBounds.bottom - intersectInsets.bottom, inOutBounds.bottom);
        }
        if (inOutBounds.left >= intersectBounds.left) {
            inOutBounds.left =
                    Math.max(intersectBounds.left + intersectInsets.left, inOutBounds.left);
        }
        if (inOutBounds.top >= intersectBounds.top) {
            inOutBounds.top =
                    Math.max(intersectBounds.top + intersectInsets.top, inOutBounds.top);
        }
    }

    @Override
    public int getActivityType() {
        final int applicationType = super.getActivityType();
        if (applicationType != ACTIVITY_TYPE_UNDEFINED || !hasChild()) {
            return applicationType;
        }
        final ActivityRecord activity = getTopMostActivity();
        return activity != null ? activity.getActivityType() : getTopChild().getActivityType();
    }

    @Override
    public void onConfigurationChanged(Configuration newParentConfig) {
        super.onConfigurationChanged(newParentConfig);
        updateOrganizedTaskFragmentSurface();
        sendTaskFragmentInfoChanged();
    }

    void deferOrganizedTaskFragmentSurfaceUpdate() {
        mDelayOrganizedTaskFragmentSurfaceUpdate = true;
    }

    void continueOrganizedTaskFragmentSurfaceUpdate() {
        mDelayOrganizedTaskFragmentSurfaceUpdate = false;
        updateOrganizedTaskFragmentSurface();
    }

    /**
     * TaskFragmentOrganizer doesn't have access to the surface for security reasons, so we need to
     * update its surface on the server side if it is not collected for Shell or in pending
     * animation.
     */
    void updateOrganizedTaskFragmentSurface() {
        if (mDelayOrganizedTaskFragmentSurfaceUpdate || mTaskFragmentOrganizer == null) {
            return;
        }
        if (mTransitionController.isShellTransitionsEnabled()
                && !mTransitionController.isCollecting(this)) {
            // TaskFragmentOrganizer doesn't have access to the surface for security reasons, so
            // update the surface here if it is not collected by Shell transition.
            updateOrganizedTaskFragmentSurfaceUnchecked();
        } else if (!mTransitionController.isShellTransitionsEnabled() && !isAnimating()) {
            // Update the surface here instead of in the organizer so that we can make sure
            // it can be synced with the surface freezer for legacy app transition.
            updateOrganizedTaskFragmentSurfaceUnchecked();
        }
    }

    private void updateOrganizedTaskFragmentSurfaceUnchecked() {
        final SurfaceControl.Transaction t = getSyncTransaction();
        updateSurfacePosition(t);
        updateOrganizedTaskFragmentSurfaceSize(t, false /* forceUpdate */);
    }

    /** Updates the surface size so that the sub windows cannot be shown out of bounds. */
    private void updateOrganizedTaskFragmentSurfaceSize(SurfaceControl.Transaction t,
            boolean forceUpdate) {
        if (mTaskFragmentOrganizer == null) {
            // We only want to update for organized TaskFragment. Task will handle itself.
            return;
        }
        if (mSurfaceControl == null || mSurfaceAnimator.hasLeash()) {
            return;
        }

        // If this TaskFragment is closing while resizing, crop to the starting bounds instead.
        final Rect bounds = getBounds();
        final int width = bounds.width();
        final int height = bounds.height();
        if (!forceUpdate && width == mLastSurfaceSize.x && height == mLastSurfaceSize.y) {
            return;
        }
        if (fillsParent()) {
            // Rely on parent's crop.
            t.setCrop(mSurfaceControl, null);
        } else {
            t.setWindowCrop(mSurfaceControl, width, height);
        }
        mLastSurfaceSize.set(width, height);
    }

    @Override
    public void onAnimationLeashCreated(SurfaceControl.Transaction t, SurfaceControl leash) {
        super.onAnimationLeashCreated(t, leash);
        // Reset surface bounds for animation. It will be taken care by the animation leash, and
        // reset again onAnimationLeashLost.
        if (mTaskFragmentOrganizer != null
                && (mLastSurfaceSize.x != 0 || mLastSurfaceSize.y != 0)) {
            t.setWindowCrop(mSurfaceControl, 0, 0);
            final SurfaceControl.Transaction syncTransaction = getSyncTransaction();
            if (t != syncTransaction) {
                // Avoid restoring to old window crop if the sync transaction is applied later.
                syncTransaction.setWindowCrop(mSurfaceControl, 0, 0);
            }
            mLastSurfaceSize.set(0, 0);
        }
    }

    @Override
    public void onAnimationLeashLost(SurfaceControl.Transaction t) {
        super.onAnimationLeashLost(t);
        // Update the surface bounds after animation.
        if (mTaskFragmentOrganizer != null) {
            updateOrganizedTaskFragmentSurfaceSize(t, true /* forceUpdate */);
        }
    }

    /**
     * Gets the relative bounds of this embedded TaskFragment. This should only be called on
     * embedded TaskFragment.
     */
    @NonNull
    Rect getRelativeEmbeddedBounds() {
        if (mRelativeEmbeddedBounds == null) {
            throw new IllegalStateException("The TaskFragment is not embedded");
        }
        return mRelativeEmbeddedBounds;
    }

    /**
     * Translates the given relative bounds to screen space based on the given parent bounds.
     * When the relative bounds is outside of the parent bounds, it will be adjusted to fit the Task
     * bounds.
     */
    Rect translateRelativeBoundsToAbsoluteBounds(@NonNull Rect relativeBounds,
            @NonNull Rect parentBounds) {
        if (relativeBounds.isEmpty()) {
            mTmpAbsBounds.setEmpty();
            return mTmpAbsBounds;
        }
        // Translate the relative bounds to absolute bounds.
        mTmpAbsBounds.set(relativeBounds);
        mTmpAbsBounds.offset(parentBounds.left, parentBounds.top);

        if (!isAllowedToBeEmbeddedInTrustedMode() && !parentBounds.contains(mTmpAbsBounds)) {
            // For untrusted embedding, we want to make sure the embedded bounds will never go
            // outside of the Task bounds.
            // We expect the organizer to update the bounds after receiving the Task bounds changed,
            // so skip trusted embedding to avoid unnecessary configuration change before organizer
            // requests a new bounds.
            // When the requested TaskFragment bounds is outside of Task bounds, try use the
            // intersection.
            // This can happen when the Task resized before the TaskFragmentOrganizer request.
            if (!mTmpAbsBounds.intersect(parentBounds)) {
                // Use empty bounds to fill Task if there is no intersection.
                mTmpAbsBounds.setEmpty();
            }
        }
        return mTmpAbsBounds;
    }

    void recomputeConfiguration() {
        onRequestedOverrideConfigurationChanged(getRequestedOverrideConfiguration());
    }

    /**
     * Sets the relative bounds in parent coordinate for this embedded TaskFragment.
     * This will not override the requested bounds, and the actual bounds will be calculated in
     * {@link #resolveOverrideConfiguration}, so that it makes sure to record and use the relative
     * bounds that is set by the organizer until the organizer requests a new relative bounds.
     */
    void setRelativeEmbeddedBounds(@NonNull Rect relativeEmbeddedBounds) {
        if (mRelativeEmbeddedBounds == null) {
            throw new IllegalStateException("The TaskFragment is not embedded");
        }
        if (mRelativeEmbeddedBounds.equals(relativeEmbeddedBounds)) {
            return;
        }
        mRelativeEmbeddedBounds.set(relativeEmbeddedBounds);
    }

    /**
     * Updates the record of relative bounds of this embedded TaskFragment, and checks whether we
     * should prepare a transition for the bounds change.
     */
    boolean shouldStartChangeTransition(@NonNull Rect absStartBounds,
            @NonNull Rect relStartBounds) {
        if (mTaskFragmentOrganizer == null || !canStartChangeTransition()) {
            return false;
        }

        if (mTransitionController.isShellTransitionsEnabled()) {
            // For Shell transition, the change will be collected anyway, so only take snapshot when
            // the bounds are resized.
            final Rect endBounds = getConfiguration().windowConfiguration.getBounds();
            return endBounds.width() != absStartBounds.width()
                    || endBounds.height() != absStartBounds.height();
        } else {
            // For legacy transition, we need to trigger a change transition as long as the bounds
            // is changed, even if it is not resized.
            return !relStartBounds.equals(mRelativeEmbeddedBounds);
        }
    }

    @Override
    boolean canStartChangeTransition() {
        final Task task = getTask();
        // Skip change transition when the Task is drag resizing.
        return task != null && !task.isDragResizing() && super.canStartChangeTransition();
    }

    @Override
    boolean isSyncFinished(BLASTSyncEngine.SyncGroup group) {
        return super.isSyncFinished(group) && isReadyToTransit();
    }

    @Override
    void setSurfaceControl(SurfaceControl sc) {
        super.setSurfaceControl(sc);
        if (mTaskFragmentOrganizer != null) {
            updateOrganizedTaskFragmentSurfaceUnchecked();
            // If the TaskFragmentOrganizer was set before we created the SurfaceControl, we need to
            // emit the callbacks now.
            sendTaskFragmentAppeared();
        }
    }

    void sendTaskFragmentInfoChanged() {
        if (mTaskFragmentOrganizer != null) {
            mTaskFragmentOrganizerController
                    .onTaskFragmentInfoChanged(mTaskFragmentOrganizer, this);
        }
    }

    void sendTaskFragmentParentInfoChanged() {
        final Task parentTask = getParent().asTask();
        if (mTaskFragmentOrganizer != null && parentTask != null) {
            mTaskFragmentOrganizerController
                    .onTaskFragmentParentInfoChanged(mTaskFragmentOrganizer, parentTask);
        }
    }

    private void sendTaskFragmentAppeared() {
        if (mTaskFragmentOrganizer != null) {
            mTaskFragmentOrganizerController.onTaskFragmentAppeared(mTaskFragmentOrganizer, this);
        }
    }

    private void sendTaskFragmentVanished() {
        if (mTaskFragmentOrganizer != null) {
            mTaskFragmentOrganizerController.onTaskFragmentVanished(mTaskFragmentOrganizer, this);
        }
    }

    /**
     * Returns a {@link TaskFragmentInfo} with information from this TaskFragment. Should not be
     * called from {@link Task}.
     */
    TaskFragmentInfo getTaskFragmentInfo() {
        List<IBinder> childActivities = new ArrayList<>();
        List<IBinder> inRequestedTaskFragmentActivities = new ArrayList<>();
        for (int i = 0; i < getChildCount(); i++) {
            final WindowContainer<?> wc = getChildAt(i);
            final ActivityRecord ar = wc.asActivityRecord();
            if (mTaskFragmentOrganizerUid != INVALID_UID && ar != null
                    && ar.info.processName.equals(mTaskFragmentOrganizerProcessName)
                    && ar.getUid() == mTaskFragmentOrganizerUid && !ar.finishing) {
                // Only includes Activities that belong to the organizer process for security.
                childActivities.add(ar.token);
                if (ar.mRequestedLaunchingTaskFragmentToken == mFragmentToken) {
                    inRequestedTaskFragmentActivities.add(ar.token);
                }
            }
        }
        final Point positionInParent = new Point();
        getRelativePosition(positionInParent);
        return new TaskFragmentInfo(
                mFragmentToken,
                mRemoteToken.toWindowContainerToken(),
                getConfiguration(),
                getNonFinishingActivityCount(),
                shouldBeVisible(null /* starting */),
                childActivities,
                inRequestedTaskFragmentActivities,
                positionInParent,
                mClearedTaskForReuse,
                mClearedTaskFragmentForPip,
                mClearedForReorderActivityToFront,
                calculateMinDimension(),
                isTopNonFinishingChild());
    }

    private boolean isTopNonFinishingChild() {
        final WindowContainer<?> parent = getParent();
        if (parent == null) {
            // Either the TaskFragment is not attached or is going to destroy. Return false.
            return false;
        }
        final ActivityRecord topNonFishingActivity = parent.getActivity(ar -> !ar.finishing);
        // If the parent's top non-finishing activity is this TaskFragment's, it means
        // this TaskFragment is the top non-finishing container of its parent.
        return topNonFishingActivity != null && topNonFishingActivity
                .equals(getTopNonFinishingActivity());
    }

    /**
     * Calculates the minimum dimensions that this TaskFragment can be resized.
     * @see TaskFragmentInfo#getMinimumWidth()
     * @see TaskFragmentInfo#getMinimumHeight()
     */
    Point calculateMinDimension() {
        final int[] maxMinWidth = new int[1];
        final int[] maxMinHeight = new int[1];

        forAllActivities(a -> {
            if (a.finishing) {
                return;
            }
            final Point minDimensions = a.getMinDimensions();
            if (minDimensions == null) {
                return;
            }
            maxMinWidth[0] = Math.max(maxMinWidth[0], minDimensions.x);
            maxMinHeight[0] = Math.max(maxMinHeight[0], minDimensions.y);
        });
        return new Point(maxMinWidth[0], maxMinHeight[0]);
    }

    @Nullable
    IBinder getFragmentToken() {
        return mFragmentToken;
    }

    @Nullable
    ITaskFragmentOrganizer getTaskFragmentOrganizer() {
        return mTaskFragmentOrganizer;
    }

    @Override
    boolean isOrganized() {
        return mTaskFragmentOrganizer != null;
    }

    /** Whether this is an organized {@link TaskFragment} and not a {@link Task}. */
    final boolean isOrganizedTaskFragment() {
        return mTaskFragmentOrganizer != null;
    }

    /**
     * Whether this is an embedded {@link TaskFragment} that does not fill the parent {@link Task}.
     */
    boolean isEmbeddedWithBoundsOverride() {
        if (!mIsEmbedded) {
            return false;
        }
        final Task task = getTask();
        if (task == null) {
            return false;
        }
        final Rect taskBounds = task.getBounds();
        final Rect taskFragBounds = getBounds();
        return !taskBounds.equals(taskFragBounds) && taskBounds.contains(taskFragBounds);
    }

    /** Whether the Task should be visible. */
    boolean isTaskVisibleRequested() {
        final Task task = getTask();
        return task != null && task.isVisibleRequested();
    }

    boolean isReadyToTransit() {
        // We only wait when this is organized to give the organizer a chance to update.
        if (!isOrganizedTaskFragment()) {
            return true;
        }
        // We don't want to start the transition if the organized TaskFragment is empty, unless
        // it is requested to be removed or the mAllowTransitionWhenEmpty flag is true.
        if (getTopNonFinishingActivity() != null || mIsRemovalRequested
                || mAllowTransitionWhenEmpty) {
            return true;
        }
        // Organizer shouldn't change embedded TaskFragment in PiP.
        if (isEmbeddedTaskFragmentInPip()) {
            return true;
        }
        // The TaskFragment becomes empty because the last running activity enters PiP when the Task
        // is minimized.
        if (mClearedTaskFragmentForPip && !isTaskVisibleRequested()) {
            return true;
        }
        return false;
    }

    @Override
    boolean canCustomizeAppTransition() {
        // This is only called when the app transition is going to be played by system server. In
        // this case, we should allow custom app transition for fullscreen embedded TaskFragment
        // just like Activity.
        return isEmbedded() && matchParentBounds();
    }

    /** Clear {@link #mLastPausedActivity} for all {@link TaskFragment} children */
    void clearLastPausedActivity() {
        forAllTaskFragments(taskFragment -> taskFragment.mLastPausedActivity = null);
    }

    /**
     * Sets {@link #mMinWidth} and {@link #mMinWidth} to this TaskFragment.
     * It is usually set from the parent {@link Task} when adding the TaskFragment to the window
     * hierarchy.
     */
    void setMinDimensions(int minWidth, int minHeight) {
        if (asTask() != null) {
            throw new UnsupportedOperationException("This method must not be used to Task. The "
                    + " minimum dimension of Task should be passed from Task constructor.");
        }
        mMinWidth = minWidth;
        mMinHeight = minHeight;
    }

    /**
     * Whether this is an embedded TaskFragment in PIP Task. We don't allow any client config
     * override for such TaskFragment to prevent flight with PipTaskOrganizer.
     */
    boolean isEmbeddedTaskFragmentInPip() {
        return isOrganizedTaskFragment() && getTask() != null && getTask().inPinnedWindowingMode();
    }

    boolean shouldRemoveSelfOnLastChildRemoval() {
        if (!mCreatedByOrganizer || mIsRemovalRequested) {
            return true;
        }
        // The TaskFragmentOrganizer may be killed while the embedded TaskFragments remains in WM
        // core. The embedded TaskFragment should still be removed when the child activities are
        // all gone (like package force-stopped).
        return mIsEmbedded && mTaskFragmentOrganizer == null;
    }

    /**
     * Returns whether this TaskFragment is going to be removed.
     */
    boolean isRemovalRequested() {
        return mIsRemovalRequested;
    }

    @Override
    void removeChild(WindowContainer child) {
        removeChild(child, true /* removeSelfIfPossible */);
    }

    void removeChild(WindowContainer child, boolean removeSelfIfPossible) {
        super.removeChild(child);
        final ActivityRecord r = child.asActivityRecord();
        final WindowProcessController hostProcess = getOrganizerProcessIfDifferent(r);
        if (hostProcess != null) {
            hostProcess.removeEmbeddedActivity(r);
        }
        if (removeSelfIfPossible && shouldRemoveSelfOnLastChildRemoval() && !hasChild()) {
            removeImmediately("removeLastChild " + child);
        }
    }

    /**
     * Requests to remove this task fragment. If it doesn't have children, it is removed
     * immediately. Otherwise it will be removed until all activities are destroyed.
     *
     * @param withTransition Whether to use transition animation when removing activities. Set to
     *                       {@code false} if this is invisible to user, e.g. display removal.
     */
    void remove(boolean withTransition, String reason) {
        if (!hasChild()) {
            removeImmediately(reason);
            return;
        }
        mIsRemovalRequested = true;
        // The task order may be changed by finishIfPossible() for adjusting focus if there are
        // nested tasks, so add all activities into a list to avoid missed removals.
        final ArrayList<ActivityRecord> removingActivities = new ArrayList<>();
        forAllActivities((r) -> {
            if (!r.finishing) {
                removingActivities.add(r);
            }
        });
        for (int i = removingActivities.size() - 1; i >= 0; --i) {
            final ActivityRecord r = removingActivities.get(i);
            if (withTransition && r.isVisible()) {
                r.finishIfPossible(reason, false /* oomAdj */);
            } else {
                r.destroyIfPossible(reason);
            }
        }
    }

    void setDelayLastActivityRemoval(boolean delay) {
        if (!mIsEmbedded) {
            Slog.w(TAG, "Set delaying last activity removal on a non-embedded TF.");
        }
        mDelayLastActivityRemoval = delay;
    }

    boolean isDelayLastActivityRemoval() {
        return mDelayLastActivityRemoval;
    }

    boolean shouldDeferRemoval() {
        if (!hasChild()) {
            return false;
        }
        return isExitAnimationRunningSelfOrChild();
    }

    @Override
    boolean handleCompleteDeferredRemoval() {
        if (shouldDeferRemoval()) {
            return true;
        }
        return super.handleCompleteDeferredRemoval();
    }

    /** The overridden method must call {@link #removeImmediately()} instead of super. */
    void removeImmediately(String reason) {
        Slog.d(TAG, "Remove task fragment: " + reason);
        removeImmediately();
    }

    @Override
    void removeImmediately() {
        if (asTask() == null) {
            EventLogTags.writeWmTfRemoved(System.identityHashCode(this), getTaskId());
        }
        mIsRemovalRequested = false;
        removeFromAdjacentTaskFragments();
        cleanUpEmbeddedTaskFragment();
        final boolean shouldExecuteAppTransition =
                mClearedTaskFragmentForPip && isTaskVisibleRequested();
        super.removeImmediately();
        sendTaskFragmentVanished();
        if (shouldExecuteAppTransition && mDisplayContent != null) {
            // When the Task is still visible, and the TaskFragment is removed because the last
            // running activity is reparenting to PiP, it is possible that no activity is getting
            // paused or resumed (having an embedded activity in split), thus we need to relayout
            // and execute it explicitly.
            mAtmService.addWindowLayoutReasons(
                    ActivityTaskManagerService.LAYOUT_REASON_VISIBILITY_CHANGED);
            mDisplayContent.executeAppTransition();
        }
    }

    /** Called on remove to cleanup. */
    private void cleanUpEmbeddedTaskFragment() {
        if (!mIsEmbedded) {
            return;
        }
        mAtmService.mWindowOrganizerController.cleanUpEmbeddedTaskFragment(this);
        final Task task = getTask();
        if (task == null) {
            return;
        }
        task.forAllLeafTaskFragments(taskFragment -> {
            if (taskFragment.getCompanionTaskFragment() == this) {
                taskFragment.clearCompanionTaskFragment();
            }
        }, false /* traverseTopToBottom */);
    }

    boolean shouldBoostDimmer() {
        if (asTask() != null || !isDimmingOnParentTask()) {
            // early return if not embedded or should not dim on parent Task.
            return false;
        }

        if (!hasAdjacentTaskFragment()) {
            // early return if no adjacent TF.
            return false;
        }

        final ArrayList<WindowContainer> siblings = getParent().mChildren;
        final int zOrder = siblings.indexOf(this);
        final boolean hasAdjacentOnTop = forOtherAdjacentTaskFragments(
                tf -> siblings.indexOf(tf) > zOrder);
        if (!hasAdjacentOnTop) {
            // early return if this TF already has higher z-ordering.
            return false;
        }

        final ToBooleanFunction<WindowState> getDimBehindWindow =
                (w) -> (w.mAttrs.flags & FLAG_DIM_BEHIND) != 0 && w.mActivityRecord != null
                        && w.mActivityRecord.isEmbedded() && (w.mActivityRecord.isVisibleRequested()
                        || w.mActivityRecord.isVisible());
        final boolean adjacentHasDimmingWindow = forOtherAdjacentTaskFragments(tf -> {
            return tf.forAllWindows(getDimBehindWindow, true);
        });
        if (adjacentHasDimmingWindow) {
            // early return if the adjacent Tf has a dimming window.
            return false;
        }

        // boost if there's an Activity window that has FLAG_DIM_BEHIND flag.
        return forAllWindows(getDimBehindWindow, true);
    }

    // It is replaced by WindowState#getDimController().
    @Deprecated
    @Override
    Dimmer getDimmer() {
        // If this is in an embedded TaskFragment and we want the dim applies on the TaskFragment.
        if (mIsEmbedded && !isDimmingOnParentTask()) {
            return mDimmer;
        }

        return super.getDimmer();
    }

    /** Bounds to be used for dimming, as well as touch related tests. */
    void getDimBounds(@NonNull Rect out) {
        if (com.android.window.flags.Flags.removeGetDimmer()) {
            if (mIsEmbedded && isDimmingOnParentTask()) {
                // Return the task bounds if the dimmer is showing and should cover on the Task
                // (not just on this embedded TaskFragment).
                final Task task = getTask();
                if (task != null && task.mDimmer.hasDimState()) {
                    out.set(task.getBounds());
                    return;
                }
            }
            out.set(getBounds());
            return;
        }

        if (mDimmer.hasDimState()) {
            out.set(mDimmer.getDimBounds());
        } else {
            if (mIsEmbedded && isDimmingOnParentTask() && getDimmer().getDimBounds() != null) {
                // Return the task bounds if the dimmer is showing and should cover on the Task (not
                // just on this embedded TaskFragment).
                out.set(getTask().getBounds());
            } else {
                out.set(getBounds());
            }
        }
    }

    void setEmbeddedDimArea(@EmbeddedDimArea int embeddedDimArea) {
        mEmbeddedDimArea = embeddedDimArea;
    }

    void setMoveToBottomIfClearWhenLaunch(boolean moveToBottomIfClearWhenLaunch) {
        mMoveToBottomIfClearWhenLaunch = moveToBottomIfClearWhenLaunch;
    }

    boolean isMoveToBottomIfClearWhenLaunch() {
        return mMoveToBottomIfClearWhenLaunch;
    }

    @VisibleForTesting
    boolean isDimmingOnParentTask() {
        return mEmbeddedDimArea == EMBEDDED_DIM_AREA_PARENT_TASK;
    }

    @Override
    void prepareSurfaces() {
        mDimmer.resetDimStates();
        super.prepareSurfaces();

        if (mDimmer.hasDimState() && mDimmer.updateDims(getSyncTransaction())) {
            scheduleAnimation();
        }
    }

    @Override
    boolean fillsParent() {
        if (!com.android.window.flags.Flags.refactorMatchParentBounds()) {
            // From the perspective of policy, we still want to report that this task fills parent
            // in fullscreen windowing mode even it doesn't match parent bounds because there
            // will be letterbox around its real content.
            return getWindowingMode() == WINDOWING_MODE_FULLSCREEN || matchParentBounds();
        }

        return matchParentBounds();
    }

    @Override
    protected boolean onChildVisibleRequestedChanged(@Nullable WindowContainer child) {
        if (!super.onChildVisibleRequestedChanged(child)) return false;
        // Send the info changed to update the TaskFragment visibility.
        sendTaskFragmentInfoChanged();
        return true;
    }

    @Nullable
    @Override
    TaskFragment getTaskFragment(Predicate<TaskFragment> callback) {
        final TaskFragment taskFragment = super.getTaskFragment(callback);
        if (taskFragment != null) {
            return taskFragment;
        }
        return callback.test(this) ? this : null;
    }

    /**
     * Moves the passed child to front
     * @return whether it was actually moved (vs already being top).
     */
    boolean moveChildToFront(WindowContainer newTop) {
        int origDist = getDistanceFromTop(newTop);
        positionChildAt(POSITION_TOP, newTop, false /* includeParents */);
        return getDistanceFromTop(newTop) != origDist;
    }

    String toFullString() {
        final StringBuilder sb = new StringBuilder(128);
        sb.append(this);
        sb.setLength(sb.length() - 1); // Remove tail '}'.
        if (mTaskFragmentOrganizerUid != INVALID_UID) {
            sb.append(" organizerUid=");
            sb.append(mTaskFragmentOrganizerUid);
        }
        if (mTaskFragmentOrganizerProcessName != null) {
            sb.append(" organizerProc=");
            sb.append(mTaskFragmentOrganizerProcessName);
        }
        if (mAdjacentTaskFragments != null) {
            sb.append(" adjacent=");
            sb.append(mAdjacentTaskFragments);
        }
        sb.append('}');
        return sb.toString();
    }

    @Override
    public String toString() {
        return "TaskFragment{" + Integer.toHexString(System.identityHashCode(this))
                + " mode=" + WindowConfiguration.windowingModeToString(getWindowingMode()) + "}";
    }

    boolean dump(String prefix, FileDescriptor fd, PrintWriter pw, boolean dumpAll,
            boolean dumpClient, String dumpPackage, final boolean needSep, Runnable header) {
        boolean printed = false;
        Runnable headerPrinter = () -> {
            if (needSep) {
                pw.println();
            }
            if (header != null) {
                header.run();
            }

            dumpInner(prefix, pw, dumpAll, dumpPackage);
        };

        if (dumpPackage == null) {
            // If we are not filtering by package, we want to print absolutely everything,
            // so always print the header even if there are no tasks/activities inside.
            headerPrinter.run();
            headerPrinter = null;
            printed = true;
        }

        for (int i = mChildren.size() - 1; i >= 0; --i) {
            WindowContainer child = mChildren.get(i);
            if (child.asTaskFragment() != null) {
                printed |= child.asTaskFragment().dump(prefix + "  ", fd, pw, dumpAll,
                        dumpClient, dumpPackage, needSep, headerPrinter);
            } else if (child.asActivityRecord() != null) {
                ActivityRecord.dumpActivity(fd, pw, i, child.asActivityRecord(), prefix + "  ",
                        "Hist ", true, !dumpAll, dumpClient, dumpPackage, false, headerPrinter,
                        getTask());
            }
        }

        return printed;
    }

    void dumpInner(String prefix, PrintWriter pw, boolean dumpAll, String dumpPackage) {
        pw.print(prefix); pw.print("* "); pw.println(toFullString());
        final Rect bounds = getRequestedOverrideBounds();
        if (!bounds.isEmpty()) {
            pw.println(prefix + "  mBounds=" + bounds);
        }
        if (isForceHidden()) {
            pw.println(prefix + "  mForceHiddenFlags=" + mForceHiddenFlags);
        }
        if (isAlwaysOnTop()) {
            pw.println(prefix + "  isAlwaysOnTop");
        }
        if (mIsRemovalRequested) {
            pw.println(prefix + "  mIsRemovalRequested=true");
        }
        if (dumpAll) {
            printThisActivity(pw, mLastPausedActivity, dumpPackage, false,
                    prefix + "  mLastPausedActivity: ", null);
        }
    }

    @Override
    void dump(PrintWriter pw, String prefix, boolean dumpAll) {
        super.dump(pw, prefix, dumpAll);
        pw.println(prefix + "bounds=" + getBounds().toShortString()
                + (mIsolatedNav ? ", isolatedNav" : ""));
        final String doublePrefix = prefix + "  ";
        for (int i = mChildren.size() - 1; i >= 0; i--) {
            final WindowContainer<?> child = mChildren.get(i);
            final TaskFragment tf = child.asTaskFragment();
            pw.println(prefix + "* " + (tf != null ? tf.toFullString() : child));
            // Only dump non-activity because full activity info is already printed by
            // RootWindowContainer#dumpActivities.
            if (tf != null) {
                child.dump(pw, doublePrefix, dumpAll);
            }
        }
    }

    @Override
    void writeIdentifierToProto(ProtoOutputStream proto, long fieldId) {
        final long token = proto.start(fieldId);
        proto.write(HASH_CODE, System.identityHashCode(this));
        final ActivityRecord topActivity = topRunningActivity();
        proto.write(USER_ID, topActivity != null ? topActivity.mUserId : USER_NULL);
        proto.write(TITLE, topActivity != null ? topActivity.intent.getComponent()
                .flattenToShortString() : "TaskFragment");
        proto.end(token);
    }

    @Override
    long getProtoFieldId() {
        return TASK_FRAGMENT;
    }

    @Override
    public void dumpDebug(ProtoOutputStream proto, long fieldId,
            @WindowTracingLogLevel int logLevel) {
        if (logLevel == WindowTracingLogLevel.CRITICAL && !isVisible()) {
            return;
        }

        final long token = proto.start(fieldId);

        super.dumpDebug(proto, WINDOW_CONTAINER, logLevel);

        proto.write(DISPLAY_ID, getDisplayId());
        proto.write(ACTIVITY_TYPE, getActivityType());
        proto.write(MIN_WIDTH, mMinWidth);
        proto.write(MIN_HEIGHT, mMinHeight);

        proto.end(token);
    }

    /** Set of {@link TaskFragment}s that are adjacent to each other. */
    static class AdjacentSet {
        private final ArraySet<TaskFragment> mAdjacentSet;

        AdjacentSet(@NonNull TaskFragment... taskFragments) {
            this(new ArraySet<>(taskFragments));
        }

        AdjacentSet(@NonNull ArraySet<TaskFragment> taskFragments) {
            final int size = taskFragments.size();
            if (size < 2) {
                throw new IllegalArgumentException("Adjacent TaskFragments must contain at least"
                        + " two TaskFragments, but only " + size + " were provided.");
            }
            if (size > 2) {
                for (int i = 0; i < size; i++) {
                    if (taskFragments.valueAt(i).asTask() == null) {
                        throw new IllegalArgumentException(
                                "Not yet support 3+ adjacent for non-Task TFs");
                    }
                }
            }
            mAdjacentSet = taskFragments;
        }

        /** Updates each {@link TaskFragment} in the set to be adjacent to each other. */
        private void setAsAdjacent() {
            if (mAdjacentSet.isEmpty()
                    || equals(mAdjacentSet.valueAt(0).mAdjacentTaskFragments)) {
                // No need to update if any TaskFragment in the set has already been updated to the
                // same set.
                return;
            }
            for (int i = mAdjacentSet.size() - 1; i >= 0; i--) {
                final TaskFragment taskFragment = mAdjacentSet.valueAt(i);
                taskFragment.removeFromAdjacentTaskFragments();
                taskFragment.mAdjacentTaskFragments = this;
            }
        }

        /** Removes the {@link TaskFragment} from the adjacent set. */
        private void remove(@NonNull TaskFragment taskFragment) {
            taskFragment.mAdjacentTaskFragments = null;
            taskFragment.mDelayLastActivityRemoval = false;
            mAdjacentSet.remove(taskFragment);
            if (mAdjacentSet.size() < 2) {
                // To be considered as adjacent, there must be at least 2 TaskFragments in the set.
                clear();
            }
        }

        /** Clears the adjacent relationship. */
        private void clear() {
            for (int i = mAdjacentSet.size() - 1; i >= 0; i--) {
                final TaskFragment taskFragment = mAdjacentSet.valueAt(i);
                // Clear all reference.
                taskFragment.mAdjacentTaskFragments = null;
                taskFragment.mDelayLastActivityRemoval = false;
            }
            mAdjacentSet.clear();
        }

        /** Whether the {@link TaskFragment} is in this adjacent set. */
        boolean contains(@NonNull TaskFragment taskFragment) {
            return mAdjacentSet.contains(taskFragment);
        }

        /**
         * Runs the callback on all adjacent TaskFragments. Skips the exclude one if not null.
         */
        void forAllTaskFragments(@NonNull Consumer<TaskFragment> callback,
                @Nullable TaskFragment exclude) {
            for (int i = mAdjacentSet.size() - 1; i >= 0; i--) {
                final TaskFragment taskFragment = mAdjacentSet.valueAt(i);
                if (taskFragment != exclude) {
                    callback.accept(taskFragment);
                }
            }
        }

        /**
         * Runs the callback on all adjacent TaskFragments until one returns {@code true}. Skips the
         * exclude one if not null.
         */
        boolean forAllTaskFragments(@NonNull Predicate<TaskFragment> callback,
                @Nullable TaskFragment exclude) {
            for (int i = mAdjacentSet.size() - 1; i >= 0; i--) {
                final TaskFragment taskFragment = mAdjacentSet.valueAt(i);
                if (taskFragment == exclude) {
                    continue;
                }
                if (callback.test(taskFragment)) {
                    return true;
                }
            }
            return false;
        }

        int size() {
            return mAdjacentSet.size();
        }

        @Override
        public boolean equals(@Nullable Object o) {
            if (this == o) {
                return true;
            }
            if (!(o instanceof AdjacentSet other)) {
                return false;
            }
            return mAdjacentSet.equals(other.mAdjacentSet);
        }

        @Override
        public String toString() {
            final StringBuilder sb = new StringBuilder();
            sb.append("AdjacentSet{");
            final int size = mAdjacentSet.size();
            for (int i = 0; i < size; i++) {
                if (i != 0) {
                    sb.append(", ");
                }
                sb.append(mAdjacentSet.valueAt(i));
            }
            sb.append("}");
            return sb.toString();
        }
    }
}
