/*
 * Copyright (C) 2017 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.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
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.app.WindowConfiguration.activityTypeToString;
import static android.app.WindowConfiguration.isFloating;
import static android.app.WindowConfiguration.windowingModeToString;
import static android.internal.perfetto.protos.WindowConfiguration.WindowConfigurationProto.WINDOWING_MODE;
import static android.internal.perfetto.protos.Windowmanagerservice.ConfigurationContainerProto.FULL_CONFIGURATION;
import static android.internal.perfetto.protos.Windowmanagerservice.ConfigurationContainerProto.MERGED_OVERRIDE_CONFIGURATION;
import static android.internal.perfetto.protos.Windowmanagerservice.ConfigurationContainerProto.OVERRIDE_CONFIGURATION;
import static android.internal.perfetto.protos.Configuration.ConfigurationProto.WINDOW_CONFIGURATION;
import static android.content.pm.ActivityInfo.INSETS_DECOUPLED_CONFIGURATION_ENFORCED;
import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_INSETS_DECOUPLED_CONFIGURATION;
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.view.Surface.ROTATION_270;
import static android.view.Surface.ROTATION_90;

import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_CONFIGURATION;

import android.annotation.CallSuper;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.WindowConfiguration;
import android.content.pm.ApplicationInfo;
import android.content.res.Configuration;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.LocaleList;
import android.util.DisplayMetrics;
import android.util.proto.ProtoOutputStream;
import android.view.DisplayInfo;

import com.android.internal.protolog.ProtoLog;

import java.io.PrintWriter;
import java.util.ArrayList;

/**
 * Contains common logic for classes that have override configurations and are organized in a
 * hierarchy.
 */
public abstract class ConfigurationContainer<E extends ConfigurationContainer> {
    /**
     * {@link Rect} returned from {@link #getRequestedOverrideBounds()} to prevent original value
     * from being set directly.
     */
    @NonNull
    private final Rect mReturnBounds = new Rect();

    /**
     * Contains requested override configuration settings applied to this configuration container.
     */
    @NonNull
    private final Configuration mRequestedOverrideConfiguration = new Configuration();

    /**
     * Contains the requested override configuration with parent and policy constraints applied.
     * This is the set of overrides that gets applied to the full and merged configurations.
     */
    @NonNull
    private final Configuration mResolvedOverrideConfiguration = new Configuration();

    /** True if mRequestedOverrideConfiguration is not empty */
    private boolean mHasOverrideConfiguration;

    /**
     * Contains full configuration applied to this configuration container. Corresponds to full
     * parent's config with applied {@link #mResolvedOverrideConfiguration}.
     */
    @NonNull
    private final Configuration mFullConfiguration = new Configuration();

    /**
     * Contains merged override configuration settings from the top of the hierarchy down to this
     * particular instance. It is different from {@link #mFullConfiguration} because it starts from
     * topmost container's override config instead of global config.
     */
    @NonNull
    private final Configuration mMergedOverrideConfiguration = new Configuration();

    @NonNull
    private final ArrayList<ConfigurationContainerListener> mChangeListeners = new ArrayList<>();

    // TODO: Can't have ag/2592611 soon enough!
    @NonNull
    private final Configuration mRequestsTmpConfig = new Configuration();
    @NonNull
    private final Configuration mResolvedTmpConfig = new Configuration();

    // Used for setting bounds
    @NonNull
    private final Rect mTmpRect = new Rect();

    static final int BOUNDS_CHANGE_NONE = 0;

    /**
     * Return value from {@link #setBounds(Rect)} indicating the position of the override bounds
     * changed.
     */
    static final int BOUNDS_CHANGE_POSITION = 1;

    /**
     * Return value from {@link #setBounds(Rect)} indicating the size of the override bounds
     * changed.
     */
    static final int BOUNDS_CHANGE_SIZE = 1 << 1;

    /**
     * Returns full configuration applied to this configuration container.
     * This method should be used for getting settings applied in each particular level of the
     * hierarchy.
     */
    @NonNull
    public Configuration getConfiguration() {
        return mFullConfiguration;
    }

    /**
     * Notify that parent config changed and we need to update full configuration.
     * @see #mFullConfiguration
     */
    public void onConfigurationChanged(@NonNull Configuration newParentConfig) {
        mResolvedTmpConfig.setTo(mResolvedOverrideConfiguration);
        resolveOverrideConfiguration(newParentConfig);
        mFullConfiguration.setTo(newParentConfig);
        // Do not inherit always-on-top property from parent, otherwise the always-on-top
        // property is propagated to all children. In that case, newly added child is
        // always being positioned at bottom (behind the always-on-top siblings).
        mFullConfiguration.windowConfiguration.unsetAlwaysOnTop();
        mFullConfiguration.updateFrom(mResolvedOverrideConfiguration);
        onMergedOverrideConfigurationChanged();
        if (!mResolvedTmpConfig.equals(mResolvedOverrideConfiguration)) {
            // This depends on the assumption that change-listeners don't do
            // their own override resolution. This way, dependent hierarchies
            // can stay properly synced-up with a primary hierarchy's constraints.
            // Since the hierarchies will be merged, this whole thing will go away
            // before the assumption will be broken.
            // Inform listeners of the change.
            for (int i = mChangeListeners.size() - 1; i >= 0; --i) {
                mChangeListeners.get(i).onRequestedOverrideConfigurationChanged(
                        mResolvedOverrideConfiguration);
            }
        }
        for (int i = mChangeListeners.size() - 1; i >= 0; --i) {
            mChangeListeners.get(i).onMergedOverrideConfigurationChanged(
                    mMergedOverrideConfiguration);
        }
        for (int i = getChildCount() - 1; i >= 0; --i) {
            dispatchConfigurationToChild(getChildAt(i), mFullConfiguration);
        }
    }

    /**
     * Dispatches the configuration to child when {@link #onConfigurationChanged(Configuration)} is
     * called. This allows the derived classes to override how to dispatch the configuration.
     */
    void dispatchConfigurationToChild(@NonNull E child, @NonNull Configuration config) {
        child.onConfigurationChanged(config);
    }

    /**
     * Resolves the current requested override configuration into
     * {@link #mResolvedOverrideConfiguration}
     *
     * @param newParentConfig The new parent configuration to resolve overrides against.
     */
    void resolveOverrideConfiguration(@NonNull Configuration newParentConfig) {
        mResolvedOverrideConfiguration.setTo(mRequestedOverrideConfiguration);
    }

    /**
     * If necessary, override configuration fields related to app bounds.
     * This will happen when the app is targeting SDK earlier than 35.
     * The insets and configuration has decoupled since SDK level 35, to make the system
     * compatible to existing apps, override the configuration with legacy metrics. In legacy
     * metrics, fields such as appBounds will exclude some of the system bar areas.
     * The override contains all potentially affected fields in Configuration, including
     * screenWidthDp, screenHeightDp, smallestScreenWidthDp, and orientation.
     * All overrides to those fields should be in this method.
     *
     * <p>Task is only needed for split-screen to apply an offset special handling.
     *
     * TODO: Consider integrate this with computeConfigByResolveHint()
     */
    static void applySizeOverrideIfNeeded(@Nullable DisplayContent displayContent,
            @NonNull ApplicationInfo appInfo, @NonNull Configuration newParentConfiguration,
            @NonNull Configuration inOutConfig, boolean optsOutEdgeToEdge,
            boolean hasFixedRotationTransform, boolean hasCompatDisplayInsets,
            @Nullable Task task) {
        if (displayContent == null) {
            return;
        }
        final boolean useOverrideInsetsForConfig =
                !appInfo.isChangeEnabled(INSETS_DECOUPLED_CONFIGURATION_ENFORCED)
                        && !appInfo.isChangeEnabled(
                                OVERRIDE_ENABLE_INSETS_DECOUPLED_CONFIGURATION);
        final int parentWindowingMode =
                newParentConfiguration.windowConfiguration.getWindowingMode();
        final boolean isFloating = isFloating(parentWindowingMode)
                // Check the requested windowing mode of activity as well in case it is
                // switching between PiP and fullscreen.
                && (inOutConfig.windowConfiguration.getWindowingMode() == WINDOWING_MODE_UNDEFINED
                        || isFloating(inOutConfig.windowConfiguration.getWindowingMode()));
        int rotation = newParentConfiguration.windowConfiguration.getRotation();
        if (rotation == ROTATION_UNDEFINED && !hasFixedRotationTransform) {
            rotation = displayContent.getRotation();
        }

        if (!optsOutEdgeToEdge && (!useOverrideInsetsForConfig
                || hasCompatDisplayInsets
                || rotation == ROTATION_UNDEFINED)) {
            // If the insets configuration decoupled logic is not enabled for the app, or the app
            // already has a compat override, or the context doesn't contain enough info to
            // calculate the override, skip the override.
            return;
        }
        if (isFloating) {
            // Floating window won't have any insets affect configuration. Skip the override.
            return;
        }
        ProtoLog.v(WM_DEBUG_CONFIGURATION,
                "Applying configuration size overrides, optsOutEdgeToEdge=%b overrideInsets=%b "
                        + "compatDisplayInsets=%b rotation=%d",
                optsOutEdgeToEdge, useOverrideInsetsForConfig, hasCompatDisplayInsets, rotation);
        // Make sure the orientation related fields will be updated by the override insets, because
        // fixed rotation has assigned the fields from display's configuration.
        if (hasFixedRotationTransform) {
            ProtoLog.v(WM_DEBUG_CONFIGURATION, "\tHas fixed rotation transform");
            inOutConfig.windowConfiguration.setAppBounds(null);
            inOutConfig.screenWidthDp = Configuration.SCREEN_WIDTH_DP_UNDEFINED;
            inOutConfig.screenHeightDp = Configuration.SCREEN_HEIGHT_DP_UNDEFINED;
            inOutConfig.smallestScreenWidthDp = Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED;
            inOutConfig.orientation = ORIENTATION_UNDEFINED;
        }

        // Override starts here.
        final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270);
        final int dw = rotated
                ? displayContent.mBaseDisplayHeight
                : displayContent.mBaseDisplayWidth;
        final int dh = rotated
                ? displayContent.mBaseDisplayWidth
                : displayContent.mBaseDisplayHeight;
        // This should be the only place override the configuration for ActivityRecord. Override
        // the value if not calculated yet.
        Rect outAppBounds = inOutConfig.windowConfiguration.getAppBounds();
        Rect outConfigBounds = new Rect(outAppBounds);
        if (outAppBounds == null || outAppBounds.isEmpty()) {
            inOutConfig.windowConfiguration.setAppBounds(
                    newParentConfiguration.windowConfiguration.getBounds());
            outAppBounds = inOutConfig.windowConfiguration.getAppBounds();
            outConfigBounds.set(outAppBounds);
            ProtoLog.v(WM_DEBUG_CONFIGURATION, "\tInitial bounds app=%s config=%s",
                    outAppBounds, outConfigBounds);
            if (task != null) {
                task = task.getCreatedByOrganizerTask();
                if (task != null && (task.mOffsetYForInsets != 0 || task.mOffsetXForInsets != 0)) {
                    outAppBounds.offset(task.mOffsetXForInsets, task.mOffsetYForInsets);
                    outConfigBounds.offset(task.mOffsetXForInsets, task.mOffsetYForInsets);
                    ProtoLog.v(WM_DEBUG_CONFIGURATION,
                            "\tApplying task offsets for task=%s x=%d y=%d app=%s config=%s",
                            task, task.mOffsetXForInsets, task.mOffsetYForInsets,
                            outAppBounds, outConfigBounds);
                }
            }
            final DisplayPolicy.DecorInsets.Info decor =
                    displayContent.getDisplayPolicy().getDecorInsetsInfo(rotation, dw, dh);
            if (!outAppBounds.intersect(decor.mOverrideNonDecorFrame)) {
                // TODO (b/364883053): When a split screen is requested from an app intent for a new
                //  task, the bounds is not the final bounds, and this is also not a bounds change
                //  event handled correctly with the offset. Revert back to legacy method for this
                //  case.
                if (inOutConfig.windowConfiguration.getWindowingMode()
                        == WINDOWING_MODE_MULTI_WINDOW) {
                    outAppBounds.inset(decor.mOverrideNonDecorInsets);
                    ProtoLog.v(WM_DEBUG_CONFIGURATION,
                            "\tInsetting app bounds by override non-decor insets=%s",
                            decor.mOverrideConfigInsets);
                }
            } else {
                ProtoLog.v(WM_DEBUG_CONFIGURATION,
                        "\tApp bounds intersects with override non-decor frame=%s bounds=%s",
                        decor.mOverrideNonDecorFrame, outAppBounds);
            }
            if (!outConfigBounds.intersect(decor.mOverrideConfigFrame)) {
                if (inOutConfig.windowConfiguration.getWindowingMode()
                        == WINDOWING_MODE_MULTI_WINDOW) {
                    outConfigBounds.inset(decor.mOverrideConfigInsets);
                    ProtoLog.v(WM_DEBUG_CONFIGURATION,
                            "\tInsetting config bounds by override config insets=%s",
                            decor.mOverrideConfigInsets);
                }
            } else {
                ProtoLog.v(WM_DEBUG_CONFIGURATION,
                        "\tConfig bounds intersects with override config frame=%s bounds=%s",
                        decor.mOverrideConfigFrame, outConfigBounds);
            }
            if (task != null && (task.mOffsetYForInsets != 0 || task.mOffsetXForInsets != 0)) {
                outAppBounds.offset(-task.mOffsetXForInsets, -task.mOffsetYForInsets);
                outConfigBounds.offset(-task.mOffsetXForInsets, -task.mOffsetYForInsets);
                ProtoLog.v(WM_DEBUG_CONFIGURATION,
                        "\tApplying inverse task offsets for x=%d y=%d app=%s config=%s",
                        -task.mOffsetXForInsets, -task.mOffsetYForInsets,
                        outAppBounds, outConfigBounds);
            }
        }
        float density = inOutConfig.densityDpi;
        if (density == Configuration.DENSITY_DPI_UNDEFINED) {
            density = newParentConfiguration.densityDpi;
        }
        density *= DisplayMetrics.DENSITY_DEFAULT_SCALE;
        if (inOutConfig.screenWidthDp == Configuration.SCREEN_WIDTH_DP_UNDEFINED) {
            inOutConfig.screenWidthDp = (int) (outConfigBounds.width() / density + 0.5f);
        }
        if (inOutConfig.screenHeightDp == Configuration.SCREEN_HEIGHT_DP_UNDEFINED) {
            inOutConfig.screenHeightDp = (int) (outConfigBounds.height() / density + 0.5f);
        }
        if (inOutConfig.smallestScreenWidthDp == Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED
                && parentWindowingMode == WINDOWING_MODE_FULLSCREEN) {
            // For the case of PIP transition and multi-window environment, the
            // smallestScreenWidthDp is handled already. Override only if the app is in
            // fullscreen.
            final DisplayInfo info = new DisplayInfo(displayContent.getDisplayInfo());
            displayContent.computeSizeRanges(info, rotated, dw, dh,
                    displayContent.getDisplayMetrics().density,
                    inOutConfig, true /* overrideConfig */);
        }

        // It's possible that screen size will be considered in different orientation with or
        // without considering the system bar insets. Override orientation as well.
        if (inOutConfig.orientation == ORIENTATION_UNDEFINED) {
            inOutConfig.orientation = (inOutConfig.screenWidthDp <= inOutConfig.screenHeightDp)
                    ? ORIENTATION_PORTRAIT
                    : ORIENTATION_LANDSCAPE;
        }
        ProtoLog.v(WM_DEBUG_CONFIGURATION, "\tComputed size override config=%s", inOutConfig);
    }

    /** Returns {@code true} if requested override override configuration is not empty. */
    boolean hasRequestedOverrideConfiguration() {
        return mHasOverrideConfiguration;
    }

    /** Returns requested override configuration applied to this configuration container. */
    @NonNull
    public Configuration getRequestedOverrideConfiguration() {
        return mRequestedOverrideConfiguration;
    }

    /** Returns the resolved override configuration. */
    @NonNull
    Configuration getResolvedOverrideConfiguration() {
        return mResolvedOverrideConfiguration;
    }

    /**
     * Update override configuration and recalculate full config.
     * @see #mRequestedOverrideConfiguration
     * @see #mFullConfiguration
     */
    public void onRequestedOverrideConfigurationChanged(
            @NonNull Configuration overrideConfiguration) {
        updateRequestedOverrideConfiguration(overrideConfiguration);
        // Update full configuration of this container and all its children.
        final ConfigurationContainer parent = getParent();
        onConfigurationChanged(parent != null ? parent.getConfiguration() : Configuration.EMPTY);
    }

    /** Updates override configuration without recalculate full config. */
    void updateRequestedOverrideConfiguration(@NonNull Configuration overrideConfiguration) {
        // Pre-compute this here, so we don't need to go through the entire Configuration when
        // writing to proto (which has significant cost if we write a lot of empty configurations).
        mHasOverrideConfiguration = !Configuration.EMPTY.equals(overrideConfiguration);
        mRequestedOverrideConfiguration.setTo(overrideConfiguration);
        final Rect newBounds = mRequestedOverrideConfiguration.windowConfiguration.getBounds();
        if (mHasOverrideConfiguration && providesMaxBounds()
                && diffRequestedOverrideMaxBounds(newBounds) != BOUNDS_CHANGE_NONE) {
            mRequestedOverrideConfiguration.windowConfiguration.setMaxBounds(newBounds);
        }
    }

    /**
     * Get merged override configuration from the top of the hierarchy down to this particular
     * instance. This should be reported to client as override config.
     */
    @NonNull
    public Configuration getMergedOverrideConfiguration() {
        return mMergedOverrideConfiguration;
    }

    /**
     * Update merged override configuration based on corresponding parent's config. If there is no
     * parent, merged override configuration will set equal to current override config. This
     * doesn't cascade on its own since it's called by {@link #onConfigurationChanged}.
     * @see #mMergedOverrideConfiguration
     */
    void onMergedOverrideConfigurationChanged() {
        final ConfigurationContainer parent = getParent();
        if (parent != null) {
            mMergedOverrideConfiguration.setTo(parent.getMergedOverrideConfiguration());
            // Do not inherit always-on-top property from parent, otherwise the always-on-top
            // property is propagated to all children. In that case, newly added child is
            // always being positioned at bottom (behind the always-on-top siblings).
            mMergedOverrideConfiguration.windowConfiguration.unsetAlwaysOnTop();
            mMergedOverrideConfiguration.updateFrom(mResolvedOverrideConfiguration);
        } else {
            mMergedOverrideConfiguration.setTo(mResolvedOverrideConfiguration);
        }
    }

    /**
     * Indicates whether this container chooses not to override any bounds from its parent, either
     * because it doesn't request to override them or the request is dropped during configuration
     * resolution. In this case, it will inherit the bounds of the first ancestor which specifies a
     * bounds subject to policy constraints. Or this container is fullscreen windowingMode.
     *
     * @return {@code true} if this container level uses bounds from parent level, or is
     *         fullscreen. {@code false} otherwise.
     */
    public boolean matchParentBounds() {
        if (!com.android.window.flags.Flags.refactorMatchParentBounds()) {
            return getResolvedOverrideBounds().isEmpty();
        }

        final int windowingMode = getWindowingMode();
        if (windowingMode == WINDOWING_MODE_PINNED) return false;
        if (windowingMode == WINDOWING_MODE_FULLSCREEN) return true;
        return getResolvedOverrideBounds().isEmpty();
    }

    /**
     * Returns whether the bounds specified are considered the same as the existing requested
     * override bounds. This is either when the two bounds are equal or the requested override
     * bounds are empty and the specified bounds is null.
     *
     * @return {@code true} if the bounds are equivalent, {@code false} otherwise
     */
    public boolean equivalentRequestedOverrideBounds(@Nullable Rect bounds) {
        return equivalentBounds(getRequestedOverrideBounds(),  bounds);
    }

    /** Similar to {@link #equivalentRequestedOverrideBounds(Rect)}, but compares max bounds. */
    public boolean equivalentRequestedOverrideMaxBounds(@Nullable Rect bounds) {
        return equivalentBounds(getRequestedOverrideMaxBounds(),  bounds);
    }

    /**
     * Returns whether the two bounds are equal to each other or are a combination of null or empty.
     */
    public static boolean equivalentBounds(@Nullable Rect bounds, @Nullable Rect other) {
        return bounds == other
                || (bounds != null && (bounds.equals(other) || (bounds.isEmpty() && other == null)))
                || (other != null && other.isEmpty() && bounds == null);
    }

    /**
     * Returns the effective bounds of this container, inheriting the first non-empty bounds set in
     * its ancestral hierarchy, including itself.
     */
    @NonNull
    public Rect getBounds() {
        mReturnBounds.set(getConfiguration().windowConfiguration.getBounds());
        return mReturnBounds;
    }

    public void getBounds(@NonNull Rect outBounds) {
        outBounds.set(getBounds());
    }

    /** Similar to {@link #getBounds()}, but reports the max bounds. */
    @NonNull
    public Rect getMaxBounds() {
        mReturnBounds.set(getConfiguration().windowConfiguration.getMaxBounds());
        return mReturnBounds;
    }

    /**
     * Sets {@code out} to the top-left corner of the bounds as returned by {@link #getBounds()}.
     */
    public void getPosition(@NonNull Point out) {
        Rect bounds = getBounds();
        out.set(bounds.left, bounds.top);
    }

    @NonNull
    Rect getResolvedOverrideBounds() {
        mReturnBounds.set(getResolvedOverrideConfiguration().windowConfiguration.getBounds());
        return mReturnBounds;
    }

    /**
     * Returns the bounds requested on this container. These may not be the actual bounds the
     * container ends up with due to policy constraints. The {@link Rect} handed back is
     * shared for all calls to this method and should not be modified.
     */
    @NonNull
    public Rect getRequestedOverrideBounds() {
        mReturnBounds.set(getRequestedOverrideConfiguration().windowConfiguration.getBounds());

        return mReturnBounds;
    }

    /** Similar to {@link #getRequestedOverrideBounds()}, but returns the max bounds. */
    @NonNull
    public Rect getRequestedOverrideMaxBounds() {
        mReturnBounds.set(getRequestedOverrideConfiguration().windowConfiguration.getMaxBounds());

        return mReturnBounds;
    }

    /**
     * Returns {@code true} if the {@link WindowConfiguration} in the requested override
     * {@link Configuration} specifies bounds.
     */
    public boolean hasOverrideBounds() {
        return !getRequestedOverrideBounds().isEmpty();
    }

    /**
     * Sets the passed in {@link Rect} to the current bounds.
     * @see #getRequestedOverrideBounds()
     */
    public void getRequestedOverrideBounds(@NonNull Rect outBounds) {
        outBounds.set(getRequestedOverrideBounds());
    }

    /**
     * Sets the bounds at the current hierarchy level, overriding any bounds set on an ancestor.
     * This value will be reported when {@link #getBounds()} and
     * {@link #getRequestedOverrideBounds()}. If
     * an empty {@link Rect} or null is specified, this container will be considered to match its
     * parent bounds {@see #matchParentBounds} and will inherit bounds from its parent.
     *
     * @param bounds The bounds defining the container size.
     *
     * @return a bitmask representing the types of changes made to the bounds.
     */
    public int setBounds(@Nullable Rect bounds) {
        int boundsChange = diffRequestedOverrideBounds(bounds);
        final boolean overrideMaxBounds = providesMaxBounds()
                && diffRequestedOverrideMaxBounds(bounds) != BOUNDS_CHANGE_NONE;

        if (boundsChange == BOUNDS_CHANGE_NONE && !overrideMaxBounds) {
            return boundsChange;
        }

        mRequestsTmpConfig.setTo(getRequestedOverrideConfiguration());
        mRequestsTmpConfig.windowConfiguration.setBounds(bounds);
        onRequestedOverrideConfigurationChanged(mRequestsTmpConfig);

        return boundsChange;
    }

    public int setBounds(int left, int top, int right, int bottom) {
        mTmpRect.set(left, top, right, bottom);
        return setBounds(mTmpRect);
    }

    /**
     * Returns {@code true} if this {@link ConfigurationContainer} provides the maximum bounds to
     * its child {@link ConfigurationContainer}s. Returns {@code false}, otherwise.
     * <p>
     * The maximum bounds is how large a window can be expanded.
     * </p>
     */
    protected boolean providesMaxBounds() {
        return false;
    }

    int diffRequestedOverrideMaxBounds(@Nullable Rect bounds) {
        if (equivalentRequestedOverrideMaxBounds(bounds)) {
            return BOUNDS_CHANGE_NONE;
        }

        int boundsChange = BOUNDS_CHANGE_NONE;

        final Rect existingBounds = getRequestedOverrideMaxBounds();

        if (bounds == null || existingBounds.left != bounds.left
                || existingBounds.top != bounds.top) {
            boundsChange |= BOUNDS_CHANGE_POSITION;
        }

        if (bounds == null || existingBounds.width() != bounds.width()
                || existingBounds.height() != bounds.height()) {
            boundsChange |= BOUNDS_CHANGE_SIZE;
        }

        return boundsChange;
    }

    int diffRequestedOverrideBounds(@Nullable Rect bounds) {
        if (equivalentRequestedOverrideBounds(bounds)) {
            return BOUNDS_CHANGE_NONE;
        }

        int boundsChange = BOUNDS_CHANGE_NONE;

        final Rect existingBounds = getRequestedOverrideBounds();

        if (bounds == null || existingBounds.left != bounds.left
                || existingBounds.top != bounds.top) {
            boundsChange |= BOUNDS_CHANGE_POSITION;
        }

        if (bounds == null || existingBounds.width() != bounds.width()
                || existingBounds.height() != bounds.height()) {
            boundsChange |= BOUNDS_CHANGE_SIZE;
        }

        return boundsChange;
    }

    @NonNull
    public WindowConfiguration getWindowConfiguration() {
        return mFullConfiguration.windowConfiguration;
    }

    /** Returns the windowing mode the configuration container is currently in. */
    @WindowConfiguration.WindowingMode
    public int getWindowingMode() {
        return mFullConfiguration.windowConfiguration.getWindowingMode();
    }

    /** Returns the windowing mode override that is requested by this container. */
    @WindowConfiguration.WindowingMode
    public int getRequestedOverrideWindowingMode() {
        return mRequestedOverrideConfiguration.windowConfiguration.getWindowingMode();
    }

    /** Sets the requested windowing mode override for the configuration container. */
    public void setWindowingMode(@WindowConfiguration.WindowingMode int windowingMode) {
        mRequestsTmpConfig.setTo(getRequestedOverrideConfiguration());
        mRequestsTmpConfig.windowConfiguration.setWindowingMode(windowingMode);
        onRequestedOverrideConfigurationChanged(mRequestsTmpConfig);
    }

    /**
     * Sets the always on top flag for this configuration container. When you call this function,
     * make sure that you also call {@link TaskDisplayArea#positionChildAt} with
     * {@link WindowContainer#POSITION_TOP} to keep proper z-order.
     */
    public void setAlwaysOnTop(boolean alwaysOnTop) {
        mRequestsTmpConfig.setTo(getRequestedOverrideConfiguration());
        mRequestsTmpConfig.windowConfiguration.setAlwaysOnTop(alwaysOnTop);
        onRequestedOverrideConfigurationChanged(mRequestsTmpConfig);
    }

    /**
     * Returns true if this container is currently in multi-window mode. I.e. sharing the screen
     * with another activity.
     */
    public boolean inMultiWindowMode() {
        @WindowConfiguration.WindowingMode int windowingMode =
                mFullConfiguration.windowConfiguration.getWindowingMode();
        return WindowConfiguration.inMultiWindowMode(windowingMode);
    }

    public boolean inPinnedWindowingMode() {
        return mFullConfiguration.windowConfiguration.getWindowingMode() == WINDOWING_MODE_PINNED;
    }

    public boolean inFreeformWindowingMode() {
        return mFullConfiguration.windowConfiguration.getWindowingMode() == WINDOWING_MODE_FREEFORM;
    }

    /** Returns the activity type associated with the configuration container. */
    @WindowConfiguration.ActivityType
    public int getActivityType() {
        return mFullConfiguration.windowConfiguration.getActivityType();
    }

    /** Sets the activity type to associate with the configuration container. */
    public void setActivityType(@WindowConfiguration.ActivityType int activityType) {
        int currentActivityType = getActivityType();
        if (currentActivityType == activityType) {
            return;
        }
        if (currentActivityType != ACTIVITY_TYPE_UNDEFINED) {
            throw new IllegalStateException("Can't change activity type once set: " + this
                    + " activityType=" + activityTypeToString(activityType));
        }
        mRequestsTmpConfig.setTo(getRequestedOverrideConfiguration());
        mRequestsTmpConfig.windowConfiguration.setActivityType(activityType);
        onRequestedOverrideConfigurationChanged(mRequestsTmpConfig);
    }

    public boolean isActivityTypeHome() {
        return getActivityType() == ACTIVITY_TYPE_HOME;
    }

    public boolean isActivityTypeRecents() {
        return getActivityType() == ACTIVITY_TYPE_RECENTS;
    }

    final boolean isActivityTypeHomeOrRecents() {
        final int activityType = getActivityType();
        return activityType == ACTIVITY_TYPE_HOME || activityType == ACTIVITY_TYPE_RECENTS;
    }

    public boolean isActivityTypeAssistant() {
        return getActivityType() == ACTIVITY_TYPE_ASSISTANT;
    }

    /**
     * Applies app-specific nightMode and {@link LocaleList} on requested configuration.
     * @return true if any of the requested configuration has been updated.
     */
    public boolean applyAppSpecificConfig(@Nullable Integer nightMode,
            @Nullable LocaleList locales,
            @Nullable @Configuration.GrammaticalGender Integer gender) {
        mRequestsTmpConfig.setTo(getRequestedOverrideConfiguration());
        boolean newNightModeSet = (nightMode != null) && setOverrideNightMode(mRequestsTmpConfig,
                nightMode);
        boolean newLocalesSet = (locales != null) && setOverrideLocales(mRequestsTmpConfig,
                locales);
        boolean newGenderSet = setOverrideGender(mRequestsTmpConfig,
                gender == null ? Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED : gender);
        if (newNightModeSet || newLocalesSet || newGenderSet) {
            onRequestedOverrideConfigurationChanged(mRequestsTmpConfig);
        }
        return newNightModeSet || newLocalesSet || newGenderSet;
    }

    /**
     * Overrides the night mode applied to this ConfigurationContainer.
     * @return true if the nightMode has been changed.
     */
    private boolean setOverrideNightMode(@NonNull Configuration requestsTmpConfig, int nightMode) {
        final int currentUiMode = mRequestedOverrideConfiguration.uiMode;
        final int currentNightMode = currentUiMode & Configuration.UI_MODE_NIGHT_MASK;
        final int validNightMode = nightMode & Configuration.UI_MODE_NIGHT_MASK;
        if (currentNightMode == validNightMode) {
            return false;
        }
        requestsTmpConfig.uiMode = validNightMode
                | (currentUiMode & ~Configuration.UI_MODE_NIGHT_MASK);
        return true;
    }

    /**
     * Overrides the locales applied to this ConfigurationContainer.
     * @return true if the LocaleList has been changed.
     */
    private boolean setOverrideLocales(@NonNull Configuration requestsTmpConfig,
            @NonNull LocaleList overrideLocales) {
        if (mRequestedOverrideConfiguration.getLocales().equals(overrideLocales)) {
            return false;
        }
        requestsTmpConfig.setLocales(overrideLocales);
        requestsTmpConfig.userSetLocale = true;
        return true;
    }

    /**
     * Overrides the gender to this ConfigurationContainer.
     *
     * @return true if the grammatical gender has been changed.
     */
    protected boolean setOverrideGender(@NonNull Configuration requestsTmpConfig,
            @Configuration.GrammaticalGender int gender) {
        // Noop, only ActivityRecord and WindowProcessController have enough knowledge about the
        // app to apply gender correctly.
        return false;
    }

    public boolean isActivityTypeDream() {
        return getActivityType() == ACTIVITY_TYPE_DREAM;
    }

    public boolean isActivityTypeStandard() {
        return getActivityType() == ACTIVITY_TYPE_STANDARD;
    }

    public boolean isActivityTypeStandardOrUndefined() {
        @WindowConfiguration.ActivityType final int activityType = getActivityType();
        return activityType == ACTIVITY_TYPE_STANDARD || activityType == ACTIVITY_TYPE_UNDEFINED;
    }

    public static boolean isCompatibleActivityType(
            @WindowConfiguration.ActivityType int currentType,
            @WindowConfiguration.ActivityType int otherType) {
        if (currentType == otherType) {
            return true;
        }
        if (currentType == ACTIVITY_TYPE_ASSISTANT) {
            // Assistant activities are only compatible with themselves...
            return false;
        }
        // Otherwise we are compatible if us or other is not currently defined.
        return currentType == ACTIVITY_TYPE_UNDEFINED || otherType == ACTIVITY_TYPE_UNDEFINED;
    }

    /**
     * Returns true if this container is compatible with the input windowing mode and activity type.
     * The container is compatible:
     * - If {@param activityType} and {@param windowingMode} match this container activity type and
     * windowing mode.
     * - If {@param activityType} is {@link WindowConfiguration#ACTIVITY_TYPE_UNDEFINED} or
     * {@link WindowConfiguration#ACTIVITY_TYPE_STANDARD} and this containers activity type is also
     * standard or undefined and its windowing mode matches {@param windowingMode}.
     * - If {@param activityType} isn't {@link WindowConfiguration#ACTIVITY_TYPE_UNDEFINED} or
     * {@link WindowConfiguration#ACTIVITY_TYPE_STANDARD} or this containers activity type isn't
     * also standard or undefined and its activity type matches {@param activityType} regardless of
     * if {@param windowingMode} matches the containers windowing mode.
     */
    public boolean isCompatible(@WindowConfiguration.WindowingMode int windowingMode,
            @WindowConfiguration.ActivityType int activityType) {
        final int thisActivityType = getActivityType();
        final int thisWindowingMode = getWindowingMode();
        final boolean sameActivityType = thisActivityType == activityType;
        final boolean sameWindowingMode = thisWindowingMode == windowingMode;

        if (sameActivityType && sameWindowingMode) {
            return true;
        }

        if ((activityType != ACTIVITY_TYPE_UNDEFINED && activityType != ACTIVITY_TYPE_STANDARD)
                || !isActivityTypeStandardOrUndefined()) {
            // Only activity type need to match for non-standard activity types that are defined.
            return sameActivityType;
        }

        // Otherwise we are compatible if the windowing mode is the same.
        return sameWindowingMode;
    }

    void registerConfigurationChangeListener(@NonNull ConfigurationContainerListener listener) {
        registerConfigurationChangeListener(listener, true /* shouldDispatchConfig */);
    }

    void registerConfigurationChangeListener(@NonNull ConfigurationContainerListener listener,
            boolean shouldDispatchConfig) {
        if (mChangeListeners.contains(listener)) {
            return;
        }
        mChangeListeners.add(listener);
        if (shouldDispatchConfig) {
            listener.onRequestedOverrideConfigurationChanged(mResolvedOverrideConfiguration);
            listener.onMergedOverrideConfigurationChanged(mMergedOverrideConfiguration);
        }
    }

    void unregisterConfigurationChangeListener(@NonNull ConfigurationContainerListener listener) {
        mChangeListeners.remove(listener);
    }

    boolean containsListener(@NonNull ConfigurationContainerListener listener) {
        return mChangeListeners.contains(listener);
    }

    /**
     * Must be called when new parent for the container was set.
     */
    void onParentChanged(@Nullable ConfigurationContainer newParent,
            @Nullable ConfigurationContainer oldParent) {
        // Removing parent usually means that we've detached this entity to destroy it or to attach
        // to another parent. In both cases we don't need to update the configuration now.
        if (newParent != null) {
            // Update full configuration of this container and all its children.
            onConfigurationChanged(newParent.mFullConfiguration);
        }
    }

    /**
     * Write to a protocol buffer output stream. Protocol buffer message definition is at
     * {@link android.internal.perfetto.protos.Windowmanagerservice.ConfigurationContainerProto}.
     *
     * @param proto    Stream to write the ConfigurationContainer object to.
     * @param fieldId  Field Id of the ConfigurationContainer as defined in the parent
     *                 message.
     * @param logLevel Determines the amount of data to be written to the Protobuf.
     */
    @CallSuper
    protected void dumpDebug(ProtoOutputStream proto, long fieldId,
            @WindowTracingLogLevel int logLevel) {
        final long token = proto.start(fieldId);

        if (logLevel == WindowTracingLogLevel.ALL || mHasOverrideConfiguration) {
            mRequestedOverrideConfiguration.dumpDebug(proto, OVERRIDE_CONFIGURATION,
                    logLevel == WindowTracingLogLevel.CRITICAL);
        }

        // Unless trace level is set to `WindowTraceLogLevel.ALL` don't dump anything that isn't
        // required to mitigate performance overhead
        if (logLevel == WindowTracingLogLevel.ALL) {
            mFullConfiguration.dumpDebug(proto, FULL_CONFIGURATION, false /* critical */);
            mMergedOverrideConfiguration.dumpDebug(proto, MERGED_OVERRIDE_CONFIGURATION,
                    false /* critical */);
        }

        if (logLevel == WindowTracingLogLevel.TRIM) {
            // Required for Fass to automatically detect pip transitions in Winscope traces
            dumpDebugWindowingMode(proto);
        }

        proto.end(token);
    }

    private void dumpDebugWindowingMode(@NonNull ProtoOutputStream proto) {
        final long fullConfigToken = proto.start(FULL_CONFIGURATION);
        final long windowConfigToken = proto.start(WINDOW_CONFIGURATION);

        int windowingMode = mFullConfiguration.windowConfiguration.getWindowingMode();
        proto.write(WINDOWING_MODE, windowingMode);

        proto.end(windowConfigToken);
        proto.end(fullConfigToken);
    }

    /**
     * Dumps the names of this container children in the input print writer indenting each
     * level with the input prefix.
     */
    public void dumpChildrenNames(@NonNull PrintWriter pw, @NonNull String prefix) {
        dumpChildrenNames(pw, prefix, true /* isLastChild */);
    }

    /**
     * Dumps the names of this container children in the input print writer indenting each
     * level with the input prefix.
     */
    public void dumpChildrenNames(@NonNull PrintWriter pw, @NonNull String prefix,
            boolean isLastChild) {
        int curWinMode = getWindowingMode();
        String winMode = windowingModeToString(curWinMode);
        if (curWinMode != WINDOWING_MODE_UNDEFINED &&
                curWinMode != WINDOWING_MODE_FULLSCREEN) {
            winMode = winMode.toUpperCase();
        }
        int requestedWinMode = getRequestedOverrideWindowingMode();
        String overrideWinMode = windowingModeToString(requestedWinMode);
        if (requestedWinMode != WINDOWING_MODE_UNDEFINED &&
                requestedWinMode != WINDOWING_MODE_FULLSCREEN) {
            overrideWinMode = overrideWinMode.toUpperCase();
        }
        String actType = activityTypeToString(getActivityType());
        if (getActivityType() != ACTIVITY_TYPE_UNDEFINED
                && getActivityType() != ACTIVITY_TYPE_STANDARD) {
            actType = actType.toUpperCase();
        }
        pw.print(prefix + (isLastChild ? "└─ " : "├─ "));
        pw.println(getName()
                + " type=" + actType
                + " mode=" + winMode
                + " override-mode=" + overrideWinMode
                + " requested-bounds=" + getRequestedOverrideBounds().toShortString()
                + " bounds=" + getBounds().toShortString());

        String childPrefix = prefix + (isLastChild ? "   " : "│  ");
        for (int i = getChildCount() - 1; i >= 0; --i) {
            final E cc = getChildAt(i);
            cc.dumpChildrenNames(pw, childPrefix, i == 0 /* isLastChild */);
        }
    }

    @NonNull
    String getName() {
        return toString();
    }

    public boolean isAlwaysOnTop() {
        return mFullConfiguration.windowConfiguration.isAlwaysOnTop();
    }

    boolean hasChild() {
        return getChildCount() > 0;
    }

    protected abstract int getChildCount();

    @Nullable
    protected abstract E getChildAt(int index);

    @Nullable
    protected abstract ConfigurationContainer getParent();

}
