/*
 * 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 android.app;

import static android.app.ActivityThread.isSystem;
import static android.app.WindowConfigurationProto.ACTIVITY_TYPE;
import static android.app.WindowConfigurationProto.APP_BOUNDS;
import static android.app.WindowConfigurationProto.BOUNDS;
import static android.app.WindowConfigurationProto.MAX_BOUNDS;
import static android.app.WindowConfigurationProto.WINDOWING_MODE;
import static android.view.Surface.rotationToString;

import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.TestApi;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.proto.ProtoInputStream;
import android.util.proto.ProtoOutputStream;
import android.util.proto.WireTypeMismatchException;
import android.view.DisplayInfo;
import android.view.Surface;
import android.view.WindowManager;

import java.io.IOException;
import java.util.Objects;

/**
 * Class that contains windowing configuration/state for other objects that contain windows directly
 * or indirectly. E.g. Activities, Task, Displays, ...
 * The test class is {@link com.android.server.wm.WindowConfigurationTests} which must be kept
 * up-to-date and ran anytime changes are made to this class.
 * @hide
 */
@TestApi
public class WindowConfiguration implements Parcelable, Comparable<WindowConfiguration> {
    /**
     * bounds that can differ from app bounds, which may include things such as insets.
     *
     * TODO: Investigate combining with {@link #mAppBounds}. Can the latter be a product of the
     * former?
     */
    private final Rect mBounds = new Rect();

    /**
     * {@link android.graphics.Rect} defining app bounds. The dimensions override usages of
     * {@link DisplayInfo#appHeight} and {@link DisplayInfo#appWidth} and mirrors these values at
     * the display level. Lower levels can override these values to provide custom bounds to enforce
     * features such as a max aspect ratio.
     */
    private Rect mAppBounds;

    /**
     * The maximum {@link Rect} bounds that an app can expect. It is used to report value of
     * {@link WindowManager#getMaximumWindowMetrics()}.
     */
    private final Rect mMaxBounds = new Rect();

    /**
     * The rotation of this window's apparent display. This can differ from mRotation in some
     * situations (like letterbox).
     */
    @Surface.Rotation
    private int mDisplayRotation = ROTATION_UNDEFINED;

    /**
     * The current rotation of this window container relative to the default
     * orientation of the display it is on (regardless of how deep in the hierarchy
     * it is). It is used by the configuration hierarchy to apply rotation-dependent
     * policy during bounds calculation.
     */
    private int mRotation = ROTATION_UNDEFINED;

    /** Rotation is not defined, use the parent containers rotation. */
    public static final int ROTATION_UNDEFINED = -1;

    /** The current windowing mode of the configuration. */
    private @WindowingMode int mWindowingMode;

    /** The display windowing mode of the configuration */
    private @WindowingMode int mDisplayWindowingMode;

    /** Windowing mode is currently not defined. */
    public static final int WINDOWING_MODE_UNDEFINED = 0;
    /** Occupies the full area of the screen or the parent container. */
    public static final int WINDOWING_MODE_FULLSCREEN = 1;
    /** Always on-top (always visible). of other siblings in its parent container. */
    public static final int WINDOWING_MODE_PINNED = 2;
    /** Can be freely resized within its parent container. */
    // TODO: Remove once freeform is migrated to wm-shell.
    public static final int WINDOWING_MODE_FREEFORM = 5;
    /** Generic multi-window with no presentation attribution from the window manager. */
    public static final int WINDOWING_MODE_MULTI_WINDOW = 6;

    /** @hide */
    @IntDef(prefix = { "WINDOWING_MODE_" }, value = {
            WINDOWING_MODE_UNDEFINED,
            WINDOWING_MODE_FULLSCREEN,
            WINDOWING_MODE_MULTI_WINDOW,
            WINDOWING_MODE_PINNED,
            WINDOWING_MODE_FREEFORM,
    })
    public @interface WindowingMode {}

    /** The current activity type of the configuration. */
    private @ActivityType int mActivityType;

    /** Activity type is currently not defined. */
    public static final int ACTIVITY_TYPE_UNDEFINED = 0;
    /** Standard activity type. Nothing special about the activity... */
    public static final int ACTIVITY_TYPE_STANDARD = 1;
    /** Home/Launcher activity type. */
    public static final int ACTIVITY_TYPE_HOME = 2;
    /** Recents/Overview activity type. There is only one activity with this type in the system. */
    public static final int ACTIVITY_TYPE_RECENTS = 3;
    /** Assistant activity type. */
    public static final int ACTIVITY_TYPE_ASSISTANT = 4;
    /** Dream activity type. */
    public static final int ACTIVITY_TYPE_DREAM = 5;

    /** @hide */
    @IntDef(prefix = { "ACTIVITY_TYPE_" }, value = {
            ACTIVITY_TYPE_UNDEFINED,
            ACTIVITY_TYPE_STANDARD,
            ACTIVITY_TYPE_HOME,
            ACTIVITY_TYPE_RECENTS,
            ACTIVITY_TYPE_ASSISTANT,
            ACTIVITY_TYPE_DREAM,
    })
    public @interface ActivityType {}

    /** The current always on top status of the configuration. */
    private @AlwaysOnTop int mAlwaysOnTop;

    /** Always on top is currently not defined. */
    private static final int ALWAYS_ON_TOP_UNDEFINED = 0;
    /** Always on top is currently on for this configuration. */
    private static final int ALWAYS_ON_TOP_ON = 1;
    /** Always on top is currently off for this configuration. */
    private static final int ALWAYS_ON_TOP_OFF = 2;

    /** @hide */
    @IntDef(prefix = { "ALWAYS_ON_TOP_" }, value = {
            ALWAYS_ON_TOP_UNDEFINED,
            ALWAYS_ON_TOP_ON,
            ALWAYS_ON_TOP_OFF,
    })
    private @interface AlwaysOnTop {}

    /** Bit that indicates that the {@link #mBounds} changed.
     * @hide */
    public static final int WINDOW_CONFIG_BOUNDS = 1 << 0;
    /** Bit that indicates that the {@link #mAppBounds} changed.
     * @hide */
    public static final int WINDOW_CONFIG_APP_BOUNDS = 1 << 1;
    /** Bit that indicates that the {@link #mMaxBounds} changed.
     * @hide */
    public static final int WINDOW_CONFIG_MAX_BOUNDS = 1 << 2;
    /** Bit that indicates that the {@link #mWindowingMode} changed.
     * @hide */
    public static final int WINDOW_CONFIG_WINDOWING_MODE = 1 << 3;
    /** Bit that indicates that the {@link #mActivityType} changed.
     * @hide */
    public static final int WINDOW_CONFIG_ACTIVITY_TYPE = 1 << 4;
    /** Bit that indicates that the {@link #mAlwaysOnTop} changed.
     * @hide */
    public static final int WINDOW_CONFIG_ALWAYS_ON_TOP = 1 << 5;
    /** Bit that indicates that the {@link #mRotation} changed.
     * @hide */
    public static final int WINDOW_CONFIG_ROTATION = 1 << 6;
    /** Bit that indicates that the {@link #mDisplayWindowingMode} changed.
     * @hide */
    public static final int WINDOW_CONFIG_DISPLAY_WINDOWING_MODE = 1 << 7;
    /** Bit that indicates that the apparent-display changed.
     * @hide */
    public static final int WINDOW_CONFIG_DISPLAY_ROTATION = 1 << 8;

    /** @hide */
    @IntDef(flag = true, prefix = { "WINDOW_CONFIG_" }, value = {
            WINDOW_CONFIG_BOUNDS,
            WINDOW_CONFIG_APP_BOUNDS,
            WINDOW_CONFIG_MAX_BOUNDS,
            WINDOW_CONFIG_WINDOWING_MODE,
            WINDOW_CONFIG_ACTIVITY_TYPE,
            WINDOW_CONFIG_ALWAYS_ON_TOP,
            WINDOW_CONFIG_ROTATION,
            WINDOW_CONFIG_DISPLAY_WINDOWING_MODE,
            WINDOW_CONFIG_DISPLAY_ROTATION,
    })
    public @interface WindowConfig {}

    @UnsupportedAppUsage
    public WindowConfiguration() {
        unset();
    }

    /** @hide */
    public WindowConfiguration(WindowConfiguration configuration) {
        setTo(configuration);
    }

    private WindowConfiguration(Parcel in) {
        readFromParcel(in);
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        mBounds.writeToParcel(dest, flags);
        dest.writeTypedObject(mAppBounds, flags);
        mMaxBounds.writeToParcel(dest, flags);
        dest.writeInt(mWindowingMode);
        dest.writeInt(mActivityType);
        dest.writeInt(mAlwaysOnTop);
        dest.writeInt(mRotation);
        dest.writeInt(mDisplayWindowingMode);
        dest.writeInt(mDisplayRotation);
    }

    /** @hide */
    public void readFromParcel(@NonNull Parcel source) {
        mBounds.readFromParcel(source);
        mAppBounds = source.readTypedObject(Rect.CREATOR);
        mMaxBounds.readFromParcel(source);
        mWindowingMode = source.readInt();
        mActivityType = source.readInt();
        mAlwaysOnTop = source.readInt();
        mRotation = source.readInt();
        mDisplayWindowingMode = source.readInt();
        mDisplayRotation = source.readInt();
    }

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

    /** @hide */
    public static final @android.annotation.NonNull Creator<WindowConfiguration> CREATOR = new Creator<WindowConfiguration>() {
        @Override
        public WindowConfiguration createFromParcel(Parcel in) {
            return new WindowConfiguration(in);
        }

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

    /**
     * Sets the bounds to the provided {@link Rect}.
     * @param rect the new bounds value.
     */
    public void setBounds(Rect rect) {
        if (rect == null) {
            mBounds.setEmpty();
            return;
        }

        mBounds.set(rect);
    }

    /**
     * Set {@link #mAppBounds} to the input Rect.
     * @param rect The rect value to set {@link #mAppBounds} to.
     * @see #getAppBounds()
     */
    public void setAppBounds(Rect rect) {
        if (rect == null) {
            mAppBounds = null;
            return;
        }

        setAppBounds(rect.left, rect.top, rect.right, rect.bottom);
    }

    /**
     * Sets the maximum bounds to the provided {@link Rect}.
     * @param rect the new bounds value.
     * @see #getMaxBounds()
     */
    public void setMaxBounds(@Nullable Rect rect) {
        if (rect == null) {
            mMaxBounds.setEmpty();
            return;
        }
        mMaxBounds.set(rect);
    }

    /**
     * @see #setMaxBounds(Rect)
     * @hide
     */
    public void setMaxBounds(int left, int top, int right, int bottom) {
        mMaxBounds.set(left, top, right, bottom);
    }

    /**
     * Sets the apparent display cutout.
     * @hide
     */
    public void setDisplayRotation(@Surface.Rotation int rotation) {
        mDisplayRotation = rotation;
    }

    /**
     * Sets whether this window should be always on top.
     * @param alwaysOnTop {@code true} to set window always on top, otherwise {@code false}
     * @hide
     */
    public void setAlwaysOnTop(boolean alwaysOnTop) {
        mAlwaysOnTop = alwaysOnTop ? ALWAYS_ON_TOP_ON : ALWAYS_ON_TOP_OFF;
    }

    /**
     * Unsets always-on-top to undefined.
     * @hide
     */
    public void unsetAlwaysOnTop() {
        mAlwaysOnTop = ALWAYS_ON_TOP_UNDEFINED;
    }

    private void setAlwaysOnTop(@AlwaysOnTop int alwaysOnTop) {
        mAlwaysOnTop = alwaysOnTop;
    }

    /**
     * @see #setAppBounds(Rect)
     * @see #getAppBounds()
     * @hide
     */
    public void setAppBounds(int left, int top, int right, int bottom) {
        if (mAppBounds == null) {
            mAppBounds = new Rect();
        }

        mAppBounds.set(left, top, right, bottom);
    }

    /** @see #setAppBounds(Rect) */
    public Rect getAppBounds() {
        return mAppBounds;
    }

    /** @see #setBounds(Rect) */
    public Rect getBounds() {
        return mBounds;
    }

    /** @see #setMaxBounds(Rect) */
    @NonNull
    public Rect getMaxBounds() {
        return mMaxBounds;
    }

    /**
     * @see #setDisplayRotation
     * @hide
     */
    public @Surface.Rotation int getDisplayRotation() {
        return mDisplayRotation;
    }

    public int getRotation() {
        return mRotation;
    }

    public void setRotation(int rotation) {
        mRotation = rotation;
    }

    public void setWindowingMode(@WindowingMode int windowingMode) {
        mWindowingMode = windowingMode;
    }

    @WindowingMode
    public int getWindowingMode() {
        return mWindowingMode;
    }

    /** @hide */
    public void setDisplayWindowingMode(@WindowingMode int windowingMode) {
        mDisplayWindowingMode = windowingMode;
    }

    /** @hide */
    @WindowingMode
    public int getDisplayWindowingMode() {
        return mDisplayWindowingMode;
    }

    public void setActivityType(@ActivityType int activityType) {
        if (mActivityType == activityType) {
            return;
        }

        // Error check within system server that we are not changing activity type which can be
        // dangerous. It is okay for things to change in the application process as it doesn't
        // affect how other things is the system is managed.
        if (isSystem()
                && mActivityType != ACTIVITY_TYPE_UNDEFINED
                && activityType != ACTIVITY_TYPE_UNDEFINED) {
            throw new IllegalStateException("Can't change activity type once set: " + this
                    + " activityType=" + activityTypeToString(activityType));
        }
        mActivityType = activityType;
    }

    @ActivityType
    public int getActivityType() {
        return mActivityType;
    }

    public void setTo(WindowConfiguration other) {
        setBounds(other.mBounds);
        setAppBounds(other.mAppBounds);
        setMaxBounds(other.mMaxBounds);
        setDisplayRotation(other.mDisplayRotation);
        setWindowingMode(other.mWindowingMode);
        setActivityType(other.mActivityType);
        setAlwaysOnTop(other.mAlwaysOnTop);
        setRotation(other.mRotation);
        setDisplayWindowingMode(other.mDisplayWindowingMode);
    }

    /** Set this object to completely undefined.
     * @hide */
    public void unset() {
        setToDefaults();
    }

    /** @hide */
    public void setToDefaults() {
        setAppBounds(null);
        setBounds(null);
        setMaxBounds(null);
        setDisplayRotation(ROTATION_UNDEFINED);
        setWindowingMode(WINDOWING_MODE_UNDEFINED);
        setActivityType(ACTIVITY_TYPE_UNDEFINED);
        setAlwaysOnTop(ALWAYS_ON_TOP_UNDEFINED);
        setRotation(ROTATION_UNDEFINED);
        setDisplayWindowingMode(WINDOWING_MODE_UNDEFINED);
    }

    /** @hide */
    public void scale(float scale) {
        mBounds.scale(scale);
        mMaxBounds.scale(scale);
        if (mAppBounds != null) {
            mAppBounds.scale(scale);
        }
    }

    /**
     * Copies the fields from delta into this Configuration object, keeping
     * track of which ones have changed. Any undefined fields in {@code delta}
     * are ignored and not copied in to the current Configuration.
     *
     * @return a bit mask of the changed fields, as per {@link #diff}
     * @hide
     */
    public @WindowConfig int updateFrom(@NonNull WindowConfiguration delta) {
        int changed = 0;
        // Only allow override if bounds is not empty
        if (!delta.mBounds.isEmpty() && !delta.mBounds.equals(mBounds)) {
            changed |= WINDOW_CONFIG_BOUNDS;
            setBounds(delta.mBounds);
        }
        if (delta.mAppBounds != null && !delta.mAppBounds.equals(mAppBounds)) {
            changed |= WINDOW_CONFIG_APP_BOUNDS;
            setAppBounds(delta.mAppBounds);
        }
        if (!delta.mMaxBounds.isEmpty() && !delta.mMaxBounds.equals(mMaxBounds)) {
            changed |= WINDOW_CONFIG_MAX_BOUNDS;
            setMaxBounds(delta.mMaxBounds);
        }
        if (delta.mWindowingMode != WINDOWING_MODE_UNDEFINED
                && mWindowingMode != delta.mWindowingMode) {
            changed |= WINDOW_CONFIG_WINDOWING_MODE;
            setWindowingMode(delta.mWindowingMode);
        }
        if (delta.mActivityType != ACTIVITY_TYPE_UNDEFINED
                && mActivityType != delta.mActivityType) {
            changed |= WINDOW_CONFIG_ACTIVITY_TYPE;
            setActivityType(delta.mActivityType);
        }
        if (delta.mAlwaysOnTop != ALWAYS_ON_TOP_UNDEFINED
                && mAlwaysOnTop != delta.mAlwaysOnTop) {
            changed |= WINDOW_CONFIG_ALWAYS_ON_TOP;
            setAlwaysOnTop(delta.mAlwaysOnTop);
        }
        if (delta.mRotation != ROTATION_UNDEFINED && delta.mRotation != mRotation) {
            changed |= WINDOW_CONFIG_ROTATION;
            setRotation(delta.mRotation);
        }
        if (delta.mDisplayWindowingMode != WINDOWING_MODE_UNDEFINED
                && mDisplayWindowingMode != delta.mDisplayWindowingMode) {
            changed |= WINDOW_CONFIG_DISPLAY_WINDOWING_MODE;
            setDisplayWindowingMode(delta.mDisplayWindowingMode);
        }
        if (delta.mDisplayRotation != ROTATION_UNDEFINED
                && delta.mDisplayRotation != mDisplayRotation) {
            changed |= WINDOW_CONFIG_DISPLAY_ROTATION;
            setDisplayRotation(delta.mDisplayRotation);
        }
        return changed;
    }

    /**
     * Copies the fields specified by mask from delta into this Configuration object.
     * @hide
     */
    public void setTo(@NonNull WindowConfiguration delta, @WindowConfig int mask) {
        if ((mask & WINDOW_CONFIG_BOUNDS) != 0) {
            setBounds(delta.mBounds);
        }
        if ((mask & WINDOW_CONFIG_APP_BOUNDS) != 0) {
            setAppBounds(delta.mAppBounds);
        }
        if ((mask & WINDOW_CONFIG_MAX_BOUNDS) != 0) {
            setMaxBounds(delta.mMaxBounds);
        }
        if ((mask & WINDOW_CONFIG_WINDOWING_MODE) != 0) {
            setWindowingMode(delta.mWindowingMode);
        }
        if ((mask & WINDOW_CONFIG_ACTIVITY_TYPE) != 0) {
            setActivityType(delta.mActivityType);
        }
        if ((mask & WINDOW_CONFIG_ALWAYS_ON_TOP) != 0) {
            setAlwaysOnTop(delta.mAlwaysOnTop);
        }
        if ((mask & WINDOW_CONFIG_ROTATION) != 0) {
            setRotation(delta.mRotation);
        }
        if ((mask & WINDOW_CONFIG_DISPLAY_WINDOWING_MODE) != 0) {
            setDisplayWindowingMode(delta.mDisplayWindowingMode);
        }
        if ((mask & WINDOW_CONFIG_DISPLAY_ROTATION) != 0) {
            setDisplayRotation(delta.mDisplayRotation);
        }
    }

    /**
     * Return a bit mask of the differences between this Configuration object and the given one.
     * Does not change the values of either. Any undefined fields in <var>other</var> are ignored.
     * @param other The configuration to diff against.
     * @param compareUndefined If undefined values should be compared.
     * @return Returns a bit mask indicating which configuration
     * values has changed, containing any combination of {@link WindowConfig} flags.
     *
     * @see Configuration#diff(Configuration)
     * @hide
     */
    public @WindowConfig long diff(WindowConfiguration other, boolean compareUndefined) {
        long changes = 0;

        if (!mBounds.equals(other.mBounds)) {
            changes |= WINDOW_CONFIG_BOUNDS;
        }

        // Make sure that one of the values is not null and that they are not equal.
        if ((compareUndefined || other.mAppBounds != null)
                && mAppBounds != other.mAppBounds
                && (mAppBounds == null || !mAppBounds.equals(other.mAppBounds))) {
            changes |= WINDOW_CONFIG_APP_BOUNDS;
        }

        if (!mMaxBounds.equals(other.mMaxBounds)) {
            changes |= WINDOW_CONFIG_MAX_BOUNDS;
        }

        if ((compareUndefined || other.mWindowingMode != WINDOWING_MODE_UNDEFINED)
                && mWindowingMode != other.mWindowingMode) {
            changes |= WINDOW_CONFIG_WINDOWING_MODE;
        }

        if ((compareUndefined || other.mActivityType != ACTIVITY_TYPE_UNDEFINED)
                && mActivityType != other.mActivityType) {
            changes |= WINDOW_CONFIG_ACTIVITY_TYPE;
        }

        if ((compareUndefined || other.mAlwaysOnTop != ALWAYS_ON_TOP_UNDEFINED)
                && mAlwaysOnTop != other.mAlwaysOnTop) {
            changes |= WINDOW_CONFIG_ALWAYS_ON_TOP;
        }

        if ((compareUndefined || other.mRotation != ROTATION_UNDEFINED)
                && mRotation != other.mRotation) {
            changes |= WINDOW_CONFIG_ROTATION;
        }

        if ((compareUndefined || other.mDisplayWindowingMode != WINDOWING_MODE_UNDEFINED)
                && mDisplayWindowingMode != other.mDisplayWindowingMode) {
            changes |= WINDOW_CONFIG_DISPLAY_WINDOWING_MODE;
        }

        if ((compareUndefined || other.mDisplayRotation != ROTATION_UNDEFINED)
                && mDisplayRotation != other.mDisplayRotation) {
            changes |= WINDOW_CONFIG_DISPLAY_ROTATION;
        }

        return changes;
    }

    @Override
    public int compareTo(WindowConfiguration that) {
        int n = 0;
        if (mAppBounds == null && that.mAppBounds != null) {
            return 1;
        } else if (mAppBounds != null && that.mAppBounds == null) {
            return -1;
        } else if (mAppBounds != null && that.mAppBounds != null) {
            n = mAppBounds.left - that.mAppBounds.left;
            if (n != 0) return n;
            n = mAppBounds.top - that.mAppBounds.top;
            if (n != 0) return n;
            n = mAppBounds.right - that.mAppBounds.right;
            if (n != 0) return n;
            n = mAppBounds.bottom - that.mAppBounds.bottom;
            if (n != 0) return n;
        }

        n = mMaxBounds.left - that.mMaxBounds.left;
        if (n != 0) return n;
        n = mMaxBounds.top - that.mMaxBounds.top;
        if (n != 0) return n;
        n = mMaxBounds.right - that.mMaxBounds.right;
        if (n != 0) return n;
        n = mMaxBounds.bottom - that.mMaxBounds.bottom;
        if (n != 0) return n;

        n = mBounds.left - that.mBounds.left;
        if (n != 0) return n;
        n = mBounds.top - that.mBounds.top;
        if (n != 0) return n;
        n = mBounds.right - that.mBounds.right;
        if (n != 0) return n;
        n = mBounds.bottom - that.mBounds.bottom;
        if (n != 0) return n;

        n = mWindowingMode - that.mWindowingMode;
        if (n != 0) return n;
        n = mActivityType - that.mActivityType;
        if (n != 0) return n;
        n = mAlwaysOnTop - that.mAlwaysOnTop;
        if (n != 0) return n;
        n = mRotation - that.mRotation;
        if (n != 0) return n;

        n = mDisplayWindowingMode - that.mDisplayWindowingMode;
        if (n != 0) return n;
        n = mDisplayRotation - that.mDisplayRotation;
        if (n != 0) return n;

        // if (n != 0) return n;
        return n;
    }

    /** @hide */
    @Override
    public boolean equals(@Nullable Object that) {
        if (that == null) return false;
        if (that == this) return true;
        if (!(that instanceof WindowConfiguration)) {
            return false;
        }
        return this.compareTo((WindowConfiguration) that) == 0;
    }

    /** @hide */
    @Override
    public int hashCode() {
        int result = 0;
        result = 31 * result + Objects.hashCode(mAppBounds);
        result = 31 * result + Objects.hashCode(mBounds);
        result = 31 * result + Objects.hashCode(mMaxBounds);
        result = 31 * result + mWindowingMode;
        result = 31 * result + mActivityType;
        result = 31 * result + mAlwaysOnTop;
        result = 31 * result + mRotation;
        result = 31 * result + mDisplayWindowingMode;
        result = 31 * result + mDisplayRotation;
        return result;
    }

    /** @hide */
    @Override
    public String toString() {
        return "{ mBounds=" + mBounds
                + " mAppBounds=" + mAppBounds
                + " mMaxBounds=" + mMaxBounds
                + " mDisplayRotation=" + (mRotation == ROTATION_UNDEFINED
                        ? "undefined" : rotationToString(mDisplayRotation))
                + " mWindowingMode=" + windowingModeToString(mWindowingMode)
                + " mDisplayWindowingMode=" + windowingModeToString(mDisplayWindowingMode)
                + " mActivityType=" + activityTypeToString(mActivityType)
                + " mAlwaysOnTop=" + alwaysOnTopToString(mAlwaysOnTop)
                + " mRotation=" + (mRotation == ROTATION_UNDEFINED
                        ? "undefined" : rotationToString(mRotation))
                + "}";
    }

    /**
     * Write to a protocol buffer output stream.
     * Protocol buffer message definition at {@link android.app.WindowConfigurationProto}
     *
     * @param protoOutputStream Stream to write the WindowConfiguration object to.
     * @param fieldId           Field Id of the WindowConfiguration as defined in the parent message
     * @hide
     */
    public void dumpDebug(ProtoOutputStream protoOutputStream, long fieldId) {
        final long token = protoOutputStream.start(fieldId);
        if (mAppBounds != null) {
            mAppBounds.dumpDebug(protoOutputStream, APP_BOUNDS);
        }
        protoOutputStream.write(WINDOWING_MODE, mWindowingMode);
        protoOutputStream.write(ACTIVITY_TYPE, mActivityType);
        mBounds.dumpDebug(protoOutputStream, BOUNDS);
        mMaxBounds.dumpDebug(protoOutputStream, MAX_BOUNDS);
        protoOutputStream.end(token);
    }

    /**
     * Read from a protocol buffer input stream.
     * Protocol buffer message definition at {@link android.app.WindowConfigurationProto}
     *
     * @param proto   Stream to read the WindowConfiguration object from.
     * @param fieldId Field Id of the WindowConfiguration as defined in the parent message
     * @hide
     */
    public void readFromProto(ProtoInputStream proto, long fieldId)
            throws IOException, WireTypeMismatchException {
        final long token = proto.start(fieldId);
        try {
            while (proto.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
                switch (proto.getFieldNumber()) {
                    case (int) APP_BOUNDS:
                        mAppBounds = new Rect();
                        mAppBounds.readFromProto(proto, APP_BOUNDS);
                        break;
                    case (int) BOUNDS:
                        mBounds.readFromProto(proto, BOUNDS);
                        break;
                    case (int) MAX_BOUNDS:
                        mMaxBounds.readFromProto(proto, MAX_BOUNDS);
                        break;
                    case (int) WINDOWING_MODE:
                        mWindowingMode = proto.readInt(WINDOWING_MODE);
                        break;
                    case (int) ACTIVITY_TYPE:
                        mActivityType = proto.readInt(ACTIVITY_TYPE);
                        break;
                }
            }
        } finally {
            // Let caller handle any exceptions
            proto.end(token);
        }
    }

    /**
     * Returns true if the activities associated with this window configuration display a shadow
     * around their border.
     * @hide
     */
    public boolean hasWindowShadow() {
        return mWindowingMode != WINDOWING_MODE_MULTI_WINDOW && tasksAreFloating();
    }

    /**
     * Returns true if the activities associated with this window configuration display a decor
     * view.
     * @hide
     */
    public boolean hasWindowDecorCaption() {
        return mActivityType == ACTIVITY_TYPE_STANDARD && (mWindowingMode == WINDOWING_MODE_FREEFORM
                || mDisplayWindowingMode == WINDOWING_MODE_FREEFORM);
    }

    /**
     * Returns true if the tasks associated with this window configuration can be resized
     * independently of their parent container.
     * @hide
     */
    public boolean canResizeTask() {
        return mWindowingMode == WINDOWING_MODE_FREEFORM
                || mWindowingMode == WINDOWING_MODE_MULTI_WINDOW;
    }

    /** Returns true if the task bounds should persist across power cycles.
     * @hide */
    public boolean persistTaskBounds() {
        return mWindowingMode == WINDOWING_MODE_FREEFORM;
    }

    /**
     * Returns true if the tasks associated with this window configuration are floating.
     * Floating tasks are laid out differently as they are allowed to extend past the display bounds
     * without overscan insets.
     * @hide
     */
    public boolean tasksAreFloating() {
        return isFloating(mWindowingMode);
    }

    /** Returns true if the windowingMode represents a floating window. */
    public static boolean isFloating(@WindowingMode int windowingMode) {
        return windowingMode == WINDOWING_MODE_FREEFORM || windowingMode == WINDOWING_MODE_PINNED;
    }

    /**
     * Returns {@code true} if the windowingMode represents a window in multi-window mode.
     * I.e. sharing the screen with another activity.
     * @hide
     */
    public static boolean inMultiWindowMode(int windowingMode) {
        return windowingMode != WINDOWING_MODE_FULLSCREEN
                && windowingMode != WINDOWING_MODE_UNDEFINED;
    }

    /**
     * Returns true if the windows associated with this window configuration can receive input keys.
     * @hide
     */
    public boolean canReceiveKeys() {
        return mWindowingMode != WINDOWING_MODE_PINNED;
    }

    /**
     * Returns true if the container associated with this window configuration is always-on-top of
     * its siblings.
     * @hide
     */
    public boolean isAlwaysOnTop() {
        if (mWindowingMode == WINDOWING_MODE_PINNED) return true;
        if (mActivityType == ACTIVITY_TYPE_DREAM) return true;
        if (mAlwaysOnTop != ALWAYS_ON_TOP_ON) return false;
        return mWindowingMode == WINDOWING_MODE_FREEFORM
                    || mWindowingMode == WINDOWING_MODE_MULTI_WINDOW;
    }

    /**
     * Returns true if any visible windows belonging to apps with this window configuration should
     * be kept on screen when the app is killed due to something like the low memory killer.
     * @hide
     */
    public boolean keepVisibleDeadAppWindowOnScreen() {
        return mWindowingMode != WINDOWING_MODE_PINNED;
    }

    /**
     * Returns true if the backdrop on the client side should match the frame of the window.
     * Returns false, if the backdrop should be fullscreen.
     * @hide
     */
    public boolean useWindowFrameForBackdrop() {
        return mWindowingMode == WINDOWING_MODE_FREEFORM || mWindowingMode == WINDOWING_MODE_PINNED;
    }

    /**
     * Returns true if windows in this container should be given move animations by default.
     * @hide
     */
    public boolean hasMovementAnimations() {
        return mWindowingMode != WINDOWING_MODE_PINNED;
    }

    /**
     * Returns true if this container can be put in {@link #WINDOWING_MODE_MULTI_WINDOW}
     * windowing mode based on its current state.
     * @hide
     */
    public boolean supportSplitScreenWindowingMode() {
        return supportSplitScreenWindowingMode(mActivityType);
    }

    /** @hide */
    public static boolean supportSplitScreenWindowingMode(int activityType) {
        return activityType != ACTIVITY_TYPE_ASSISTANT && activityType != ACTIVITY_TYPE_DREAM;
    }

    /** @hide */
    public static String windowingModeToString(@WindowingMode int windowingMode) {
        switch (windowingMode) {
            case WINDOWING_MODE_UNDEFINED: return "undefined";
            case WINDOWING_MODE_FULLSCREEN: return "fullscreen";
            case WINDOWING_MODE_MULTI_WINDOW: return "multi-window";
            case WINDOWING_MODE_PINNED: return "pinned";
            case WINDOWING_MODE_FREEFORM: return "freeform";
        }
        return String.valueOf(windowingMode);
    }

    /** @hide */
    public static String activityTypeToString(@ActivityType int applicationType) {
        switch (applicationType) {
            case ACTIVITY_TYPE_UNDEFINED: return "undefined";
            case ACTIVITY_TYPE_STANDARD: return "standard";
            case ACTIVITY_TYPE_HOME: return "home";
            case ACTIVITY_TYPE_RECENTS: return "recents";
            case ACTIVITY_TYPE_ASSISTANT: return "assistant";
            case ACTIVITY_TYPE_DREAM: return "dream";
        }
        return String.valueOf(applicationType);
    }

    /** @hide */
    public static String alwaysOnTopToString(@AlwaysOnTop int alwaysOnTop) {
        switch (alwaysOnTop) {
            case ALWAYS_ON_TOP_UNDEFINED: return "undefined";
            case ALWAYS_ON_TOP_ON: return "on";
            case ALWAYS_ON_TOP_OFF: return "off";
        }
        return String.valueOf(alwaysOnTop);
    }
}
