| /* | |
| * 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.SuppressLint; | |
| 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.Display; | |
| import android.view.DisplayInfo; | |
| import android.view.Surface; | |
| import android.view.WindowManager; | |
| import java.io.IOException; | |
| import java.lang.annotation.Retention; | |
| import java.lang.annotation.RetentionPolicy; | |
| 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. | |
| */ | |
| @Nullable | |
| 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; | |
| /** 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, | |
| }) | |
| @Retention(RetentionPolicy.SOURCE) | |
| 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, | |
| }) | |
| @Retention(RetentionPolicy.SOURCE) | |
| 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 apparent-display changed. | |
| * @hide */ | |
| public static final int WINDOW_CONFIG_DISPLAY_ROTATION = 1 << 7; | |
| /** @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_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(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(); | |
| 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}. | |
| * Passing {@code null} sets the bounds {@link Rect} to empty. | |
| * | |
| * @param rect the new bounds value. | |
| */ | |
| public void setBounds(@Nullable Rect rect) { | |
| if (rect == null) { | |
| mBounds.setEmpty(); | |
| return; | |
| } | |
| mBounds.set(rect); | |
| } | |
| /** | |
| * Sets the app bounds to the provided {@link Rect}. | |
| * Passing {@code null} sets the bounds to {@code null}. | |
| * | |
| * @param rect the new app bounds value. | |
| * @see #getAppBounds() | |
| */ | |
| public void setAppBounds(@Nullable 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}. | |
| * Passing {@code null} sets the bounds {@link Rect} to empty. | |
| * | |
| * @param rect the new max 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 display rotation. | |
| * @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) */ | |
| @Nullable | |
| public Rect getAppBounds() { | |
| return mAppBounds; | |
| } | |
| /** @see #setBounds(Rect) */ | |
| @NonNull | |
| public Rect getBounds() { | |
| return mBounds; | |
| } | |
| /** @see #setMaxBounds(Rect) */ | |
| @NonNull | |
| public Rect getMaxBounds() { | |
| return mMaxBounds; | |
| } | |
| /** | |
| * Gets the display rotation. | |
| */ | |
| @SuppressLint("UnflaggedApi") // @TestApi without associated feature. | |
| 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; | |
| } | |
| 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); | |
| } | |
| /** 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); | |
| } | |
| /** @hide */ | |
| public void scale(float scale) { | |
| scaleBounds(scale, mBounds); | |
| scaleBounds(scale, mMaxBounds); | |
| if (mAppBounds != null) { | |
| scaleBounds(scale, mAppBounds); | |
| } | |
| } | |
| /** | |
| * Size based scaling. This avoid inconsistent length when rounding 4 sides. | |
| * E.g. left=12, right=18, scale=0.8. The scaled width can be: | |
| * int((right - left) * scale + 0.5) = int(4.8 + 0.5) = 5 | |
| * But with rounding both left and right, the width will be inconsistent: | |
| * int(right * scale + 0.5) - int(left * scale + 0.5) = int(14.9) - int(10.1) = 4 | |
| * @hide | |
| */ | |
| private static void scaleBounds(float scale, Rect bounds) { | |
| final int w = bounds.width(); | |
| final int h = bounds.height(); | |
| bounds.left = (int) (bounds.left * scale + .5f); | |
| bounds.top = (int) (bounds.top * scale + .5f); | |
| bounds.right = bounds.left + (int) (w * scale + .5f); | |
| bounds.bottom = bounds.top + (int) (h * scale + .5f); | |
| } | |
| /** | |
| * 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.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_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.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 = 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 + 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) | |
| + " 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 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 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; | |
| } | |
| /** | |
| * Checks if the two {@link Configuration}s are equal to each other for the fields that are read | |
| * by {@link Display}. | |
| * @hide | |
| */ | |
| public static boolean areConfigurationsEqualForDisplay(@NonNull Configuration newConfig, | |
| @NonNull Configuration oldConfig) { | |
| // Only report different if max bounds and display rotation is changed, so that it will not | |
| // report on Task resizing. | |
| if (!newConfig.windowConfiguration.getMaxBounds().equals( | |
| oldConfig.windowConfiguration.getMaxBounds())) { | |
| return false; | |
| } | |
| return newConfig.windowConfiguration.getDisplayRotation() | |
| == oldConfig.windowConfiguration.getDisplayRotation(); | |
| } | |
| /** @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); | |
| } | |
| } |