diff options
26 files changed, 786 insertions, 45 deletions
diff --git a/core/api/current.txt b/core/api/current.txt index d4c461cd9384..e2c2dfeb9331 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -48515,6 +48515,7 @@ package android.view { method public float getRefreshRate(); method public int getRotation(); method @Nullable public android.view.RoundedCorner getRoundedCorner(int); + method @NonNull public android.view.DisplayShape getShape(); method @Deprecated public void getSize(android.graphics.Point); method public int getState(); method public android.view.Display.Mode[] getSupportedModes(); @@ -48595,6 +48596,13 @@ package android.view { method @NonNull public android.view.DisplayCutout.Builder setWaterfallInsets(@NonNull android.graphics.Insets); } + public final class DisplayShape implements android.os.Parcelable { + method public int describeContents(); + method @NonNull public android.graphics.Path getPath(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.view.DisplayShape> CREATOR; + } + public final class DragAndDropPermissions implements android.os.Parcelable { method public int describeContents(); method public void release(); @@ -51976,6 +51984,7 @@ package android.view { method @Deprecated @NonNull public android.view.WindowInsets consumeStableInsets(); method @Deprecated @NonNull public android.view.WindowInsets consumeSystemWindowInsets(); method @Nullable public android.view.DisplayCutout getDisplayCutout(); + method @Nullable public android.view.DisplayShape getDisplayShape(); method @NonNull public android.graphics.Insets getInsets(int); method @NonNull public android.graphics.Insets getInsetsIgnoringVisibility(int); method @Deprecated @NonNull public android.graphics.Insets getMandatorySystemGestureInsets(); @@ -52011,6 +52020,7 @@ package android.view { ctor public WindowInsets.Builder(@NonNull android.view.WindowInsets); method @NonNull public android.view.WindowInsets build(); method @NonNull public android.view.WindowInsets.Builder setDisplayCutout(@Nullable android.view.DisplayCutout); + method @NonNull public android.view.WindowInsets.Builder setDisplayShape(@NonNull android.view.DisplayShape); method @NonNull public android.view.WindowInsets.Builder setInsets(int, @NonNull android.graphics.Insets); method @NonNull public android.view.WindowInsets.Builder setInsetsIgnoringVisibility(int, @NonNull android.graphics.Insets) throws java.lang.IllegalArgumentException; method @Deprecated @NonNull public android.view.WindowInsets.Builder setMandatorySystemGestureInsets(@NonNull android.graphics.Insets); diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 121741e07d43..5f4f1dac0fe5 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -2882,6 +2882,10 @@ package android.view { method @NonNull public android.view.Display.Mode.Builder setResolution(int, int); } + public final class DisplayShape implements android.os.Parcelable { + method @NonNull public static android.view.DisplayShape fromSpecString(@NonNull String, float, int, int); + } + public class FocusFinder { method public static void sort(android.view.View[], int, int, android.view.ViewGroup, boolean); } diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java index 5933ae4f8ca4..36eb671a666a 100644 --- a/core/java/android/view/Display.java +++ b/core/java/android/view/Display.java @@ -996,6 +996,28 @@ public final class Display { } /** + * Returns the {@link DisplayShape} which is based on display coordinates. + * + * To get the {@link DisplayShape} based on the window frame, use + * {@link WindowInsets#getDisplayShape()} instead. + * + * @see DisplayShape + */ + @SuppressLint("VisiblySynchronized") + @NonNull + public DisplayShape getShape() { + synchronized (mLock) { + updateDisplayInfoLocked(); + final DisplayShape displayShape = mDisplayInfo.displayShape; + final @Surface.Rotation int rotation = getLocalRotation(); + if (displayShape != null && rotation != mDisplayInfo.rotation) { + return displayShape.setRotation(rotation); + } + return displayShape; + } + } + + /** * Gets the pixel format of the display. * @return One of the constants defined in {@link android.graphics.PixelFormat}. * diff --git a/core/java/android/view/DisplayInfo.java b/core/java/android/view/DisplayInfo.java index 0ba3072c0813..138017c5f0ec 100644 --- a/core/java/android/view/DisplayInfo.java +++ b/core/java/android/view/DisplayInfo.java @@ -323,6 +323,9 @@ public final class DisplayInfo implements Parcelable { @Surface.Rotation public int installOrientation; + @Nullable + public DisplayShape displayShape; + public static final @android.annotation.NonNull Creator<DisplayInfo> CREATOR = new Creator<DisplayInfo>() { @Override public DisplayInfo createFromParcel(Parcel source) { @@ -395,7 +398,8 @@ public final class DisplayInfo implements Parcelable { && brightnessMaximum == other.brightnessMaximum && brightnessDefault == other.brightnessDefault && Objects.equals(roundedCorners, other.roundedCorners) - && installOrientation == other.installOrientation; + && installOrientation == other.installOrientation + && Objects.equals(displayShape, other.displayShape); } @Override @@ -448,6 +452,7 @@ public final class DisplayInfo implements Parcelable { brightnessDefault = other.brightnessDefault; roundedCorners = other.roundedCorners; installOrientation = other.installOrientation; + displayShape = other.displayShape; } public void readFromParcel(Parcel source) { @@ -506,6 +511,7 @@ public final class DisplayInfo implements Parcelable { userDisabledHdrTypes[i] = source.readInt(); } installOrientation = source.readInt(); + displayShape = source.readTypedObject(DisplayShape.CREATOR); } @Override @@ -562,6 +568,7 @@ public final class DisplayInfo implements Parcelable { dest.writeInt(userDisabledHdrTypes[i]); } dest.writeInt(installOrientation); + dest.writeTypedObject(displayShape, flags); } @Override diff --git a/core/java/android/view/DisplayShape.aidl b/core/java/android/view/DisplayShape.aidl new file mode 100644 index 000000000000..af8b4176a12d --- /dev/null +++ b/core/java/android/view/DisplayShape.aidl @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2022, 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.view; + +parcelable DisplayShape; diff --git a/core/java/android/view/DisplayShape.java b/core/java/android/view/DisplayShape.java new file mode 100644 index 000000000000..43bd773159f1 --- /dev/null +++ b/core/java/android/view/DisplayShape.java @@ -0,0 +1,357 @@ +/* + * Copyright (C) 2022 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.view; + +import static android.view.Surface.ROTATION_0; + +import android.annotation.Nullable; +import android.annotation.TestApi; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.graphics.Matrix; +import android.graphics.Path; +import android.os.Parcel; +import android.os.Parcelable; +import android.util.DisplayUtils; +import android.util.PathParser; +import android.util.RotationUtils; + +import androidx.annotation.NonNull; + +import com.android.internal.R; +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; + +import java.util.Objects; + +/** + * A class representing the shape of a display. It provides a {@link Path} of the display shape of + * the display shape. + * + * {@link DisplayShape} is immutable. + */ +public final class DisplayShape implements Parcelable { + + /** @hide */ + public static final DisplayShape NONE = new DisplayShape("" /* displayShapeSpec */, + 0 /* displayWidth */, 0 /* displayHeight */, 0 /* physicalPixelDisplaySizeRatio */, + 0 /* rotation */); + + /** @hide */ + @VisibleForTesting + public final String mDisplayShapeSpec; + private final float mPhysicalPixelDisplaySizeRatio; + private final int mDisplayWidth; + private final int mDisplayHeight; + private final int mRotation; + private final int mOffsetX; + private final int mOffsetY; + private final float mScale; + + private DisplayShape(@NonNull String displayShapeSpec, int displayWidth, int displayHeight, + float physicalPixelDisplaySizeRatio, int rotation) { + this(displayShapeSpec, displayWidth, displayHeight, physicalPixelDisplaySizeRatio, + rotation, 0, 0, 1f); + } + + private DisplayShape(@NonNull String displayShapeSpec, int displayWidth, int displayHeight, + float physicalPixelDisplaySizeRatio, int rotation, int offsetX, int offsetY, + float scale) { + mDisplayShapeSpec = displayShapeSpec; + mDisplayWidth = displayWidth; + mDisplayHeight = displayHeight; + mPhysicalPixelDisplaySizeRatio = physicalPixelDisplaySizeRatio; + mRotation = rotation; + mOffsetX = offsetX; + mOffsetY = offsetY; + mScale = scale; + } + + /** + * @hide + */ + @NonNull + public static DisplayShape fromResources( + @NonNull Resources res, @NonNull String displayUniqueId, int physicalDisplayWidth, + int physicalDisplayHeight, int displayWidth, int displayHeight) { + final boolean isScreenRound = RoundedCorners.getBuiltInDisplayIsRound(res, displayUniqueId); + final String spec = getSpecString(res, displayUniqueId); + if (spec == null || spec.isEmpty()) { + return createDefaultDisplayShape(displayWidth, displayHeight, isScreenRound); + } + final float physicalPixelDisplaySizeRatio = DisplayUtils.getPhysicalPixelDisplaySizeRatio( + physicalDisplayWidth, physicalDisplayHeight, displayWidth, displayHeight); + return fromSpecString(spec, physicalPixelDisplaySizeRatio, displayWidth, displayHeight); + } + + /** + * @hide + */ + @NonNull + public static DisplayShape createDefaultDisplayShape( + int displayWidth, int displayHeight, boolean isScreenRound) { + return fromSpecString(createDefaultSpecString(displayWidth, displayHeight, isScreenRound), + 1f, displayWidth, displayHeight); + } + + /** + * @hide + */ + @TestApi + @NonNull + public static DisplayShape fromSpecString(@NonNull String spec, + float physicalPixelDisplaySizeRatio, int displayWidth, int displayHeight) { + return Cache.getDisplayShape(spec, physicalPixelDisplaySizeRatio, displayWidth, + displayHeight); + } + + private static String createDefaultSpecString(int displayWidth, int displayHeight, + boolean isCircular) { + final String spec; + if (isCircular) { + final float xRadius = displayWidth / 2f; + final float yRadius = displayHeight / 2f; + // Draw a circular display shape. + spec = "M0," + yRadius + // Draw upper half circle with arcTo command. + + " A" + xRadius + "," + yRadius + " 0 1,1 " + displayWidth + "," + yRadius + // Draw lower half circle with arcTo command. + + " A" + xRadius + "," + yRadius + " 0 1,1 0," + yRadius + " Z"; + } else { + // Draw a rectangular display shape. + spec = "M0,0" + // Draw top edge. + + " L" + displayWidth + ",0" + // Draw right edge. + + " L" + displayWidth + "," + displayHeight + // Draw bottom edge. + + " L0," + displayHeight + // Draw left edge by close command which draws a line from current position to + // the initial points (0,0). + + " Z"; + } + return spec; + } + + /** + * Gets the display shape svg spec string of a display which is determined by the given display + * unique id. + * + * Loads the default config {@link R.string#config_mainDisplayShape} if + * {@link R.array#config_displayUniqueIdArray} is not set. + * + * @hide + */ + public static String getSpecString(Resources res, String displayUniqueId) { + final int index = DisplayUtils.getDisplayUniqueIdConfigIndex(res, displayUniqueId); + final TypedArray array = res.obtainTypedArray(R.array.config_displayShapeArray); + final String spec; + if (index >= 0 && index < array.length()) { + spec = array.getString(index); + } else { + spec = res.getString(R.string.config_mainDisplayShape); + } + array.recycle(); + return spec; + } + + /** + * @hide + */ + public DisplayShape setRotation(int rotation) { + return new DisplayShape(mDisplayShapeSpec, mDisplayWidth, mDisplayHeight, + mPhysicalPixelDisplaySizeRatio, rotation, mOffsetX, mOffsetY, mScale); + } + + /** + * @hide + */ + public DisplayShape setOffset(int offsetX, int offsetY) { + return new DisplayShape(mDisplayShapeSpec, mDisplayWidth, mDisplayHeight, + mPhysicalPixelDisplaySizeRatio, mRotation, offsetX, offsetY, mScale); + } + + /** + * @hide + */ + public DisplayShape setScale(float scale) { + return new DisplayShape(mDisplayShapeSpec, mDisplayWidth, mDisplayHeight, + mPhysicalPixelDisplaySizeRatio, mRotation, mOffsetX, mOffsetY, scale); + } + + @Override + public int hashCode() { + return Objects.hash(mDisplayShapeSpec, mDisplayWidth, mDisplayHeight, + mPhysicalPixelDisplaySizeRatio, mRotation, mOffsetX, mOffsetY, mScale); + } + + @Override + public boolean equals(@Nullable Object o) { + if (o == this) { + return true; + } + if (o instanceof DisplayShape) { + DisplayShape ds = (DisplayShape) o; + return Objects.equals(mDisplayShapeSpec, ds.mDisplayShapeSpec) + && mDisplayWidth == ds.mDisplayWidth && mDisplayHeight == ds.mDisplayHeight + && mPhysicalPixelDisplaySizeRatio == ds.mPhysicalPixelDisplaySizeRatio + && mRotation == ds.mRotation && mOffsetX == ds.mOffsetX + && mOffsetY == ds.mOffsetY && mScale == ds.mScale; + } + return false; + } + + @Override + public String toString() { + return "DisplayShape{" + + " spec=" + mDisplayShapeSpec + + " displayWidth=" + mDisplayWidth + + " displayHeight=" + mDisplayHeight + + " physicalPixelDisplaySizeRatio=" + mPhysicalPixelDisplaySizeRatio + + " rotation=" + mRotation + + " offsetX=" + mOffsetX + + " offsetY=" + mOffsetY + + " scale=" + mScale + "}"; + } + + /** + * Returns a {@link Path} of the display shape. + * + * @return a {@link Path} of the display shape. + */ + @NonNull + public Path getPath() { + return Cache.getPath(this); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeString8(mDisplayShapeSpec); + dest.writeInt(mDisplayWidth); + dest.writeInt(mDisplayHeight); + dest.writeFloat(mPhysicalPixelDisplaySizeRatio); + dest.writeInt(mRotation); + dest.writeInt(mOffsetX); + dest.writeInt(mOffsetY); + dest.writeFloat(mScale); + } + + public static final @NonNull Creator<DisplayShape> CREATOR = new Creator<DisplayShape>() { + @Override + public DisplayShape createFromParcel(Parcel in) { + final String spec = in.readString8(); + final int displayWidth = in.readInt(); + final int displayHeight = in.readInt(); + final float ratio = in.readFloat(); + final int rotation = in.readInt(); + final int offsetX = in.readInt(); + final int offsetY = in.readInt(); + final float scale = in.readFloat(); + return new DisplayShape(spec, displayWidth, displayHeight, ratio, rotation, offsetX, + offsetY, scale); + } + + @Override + public DisplayShape[] newArray(int size) { + return new DisplayShape[size]; + } + }; + + private static final class Cache { + private static final Object CACHE_LOCK = new Object(); + + @GuardedBy("CACHE_LOCK") + private static String sCachedSpec; + @GuardedBy("CACHE_LOCK") + private static int sCachedDisplayWidth; + @GuardedBy("CACHE_LOCK") + private static int sCachedDisplayHeight; + @GuardedBy("CACHE_LOCK") + private static float sCachedPhysicalPixelDisplaySizeRatio; + @GuardedBy("CACHE_LOCK") + private static DisplayShape sCachedDisplayShape; + + @GuardedBy("CACHE_LOCK") + private static DisplayShape sCacheForPath; + @GuardedBy("CACHE_LOCK") + private static Path sCachedPath; + + static DisplayShape getDisplayShape(String spec, float physicalPixelDisplaySizeRatio, + int displayWidth, int displayHeight) { + synchronized (CACHE_LOCK) { + if (spec.equals(sCachedSpec) + && sCachedDisplayWidth == displayWidth + && sCachedDisplayHeight == displayHeight + && sCachedPhysicalPixelDisplaySizeRatio == physicalPixelDisplaySizeRatio) { + return sCachedDisplayShape; + } + } + + final DisplayShape shape = new DisplayShape(spec, displayWidth, displayHeight, + physicalPixelDisplaySizeRatio, ROTATION_0); + + synchronized (CACHE_LOCK) { + sCachedSpec = spec; + sCachedDisplayWidth = displayWidth; + sCachedDisplayHeight = displayHeight; + sCachedPhysicalPixelDisplaySizeRatio = physicalPixelDisplaySizeRatio; + sCachedDisplayShape = shape; + } + return shape; + } + + static Path getPath(@NonNull DisplayShape shape) { + synchronized (CACHE_LOCK) { + if (shape.equals(sCacheForPath)) { + return sCachedPath; + } + } + + final Path path = PathParser.createPathFromPathData(shape.mDisplayShapeSpec); + + if (!path.isEmpty()) { + final Matrix matrix = new Matrix(); + if (shape.mRotation != ROTATION_0) { + RotationUtils.transformPhysicalToLogicalCoordinates( + shape.mRotation, shape.mDisplayWidth, shape.mDisplayHeight, matrix); + } + if (shape.mPhysicalPixelDisplaySizeRatio != 1f) { + matrix.preScale(shape.mPhysicalPixelDisplaySizeRatio, + shape.mPhysicalPixelDisplaySizeRatio); + } + if (shape.mOffsetX != 0 || shape.mOffsetY != 0) { + matrix.postTranslate(shape.mOffsetX, shape.mOffsetY); + } + if (shape.mScale != 1f) { + matrix.postScale(shape.mScale, shape.mScale); + } + path.transform(matrix); + } + + synchronized (CACHE_LOCK) { + sCacheForPath = shape; + sCachedPath = path; + } + return path; + } + } +} diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java index a8cc9b62d30a..c56d61808665 100644 --- a/core/java/android/view/InsetsState.java +++ b/core/java/android/view/InsetsState.java @@ -197,6 +197,9 @@ public class InsetsState implements Parcelable { private PrivacyIndicatorBounds mPrivacyIndicatorBounds = new PrivacyIndicatorBounds(); + /** The display shape */ + private DisplayShape mDisplayShape = DisplayShape.NONE; + public InsetsState() { } @@ -271,6 +274,7 @@ public class InsetsState implements Parcelable { alwaysConsumeSystemBars, calculateRelativeCutout(frame), calculateRelativeRoundedCorners(frame), calculateRelativePrivacyIndicatorBounds(frame), + calculateRelativeDisplayShape(frame), compatInsetsTypes, (legacySystemUiFlags & SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0); } @@ -335,6 +339,16 @@ public class InsetsState implements Parcelable { return mPrivacyIndicatorBounds.inset(insetLeft, insetTop, insetRight, insetBottom); } + private DisplayShape calculateRelativeDisplayShape(Rect frame) { + if (mDisplayFrame.equals(frame)) { + return mDisplayShape; + } + if (frame == null) { + return DisplayShape.NONE; + } + return mDisplayShape.setOffset(-frame.left, -frame.top); + } + public Insets calculateInsets(Rect frame, @InsetsType int types, boolean ignoreVisibility) { Insets insets = Insets.NONE; for (int type = FIRST_TYPE; type <= LAST_TYPE; type++) { @@ -589,6 +603,14 @@ public class InsetsState implements Parcelable { return mPrivacyIndicatorBounds; } + public void setDisplayShape(DisplayShape displayShape) { + mDisplayShape = displayShape; + } + + public DisplayShape getDisplayShape() { + return mDisplayShape; + } + /** * Modifies the state of this class to exclude a certain type to make it ready for dispatching * to the client. @@ -628,6 +650,7 @@ public class InsetsState implements Parcelable { mRoundedCorners = mRoundedCorners.scale(scale); mRoundedCornerFrame.scale(scale); mPrivacyIndicatorBounds = mPrivacyIndicatorBounds.scale(scale); + mDisplayShape = mDisplayShape.setScale(scale); for (int i = 0; i < SIZE; i++) { final InsetsSource source = mSources[i]; if (source != null) { @@ -650,6 +673,7 @@ public class InsetsState implements Parcelable { mRoundedCorners = other.getRoundedCorners(); mRoundedCornerFrame.set(other.mRoundedCornerFrame); mPrivacyIndicatorBounds = other.getPrivacyIndicatorBounds(); + mDisplayShape = other.getDisplayShape(); if (copySources) { for (int i = 0; i < SIZE; i++) { InsetsSource source = other.mSources[i]; @@ -675,6 +699,7 @@ public class InsetsState implements Parcelable { mRoundedCorners = other.getRoundedCorners(); mRoundedCornerFrame.set(other.mRoundedCornerFrame); mPrivacyIndicatorBounds = other.getPrivacyIndicatorBounds(); + mDisplayShape = other.getDisplayShape(); final ArraySet<Integer> t = toInternalType(types); for (int i = t.size() - 1; i >= 0; i--) { final int type = t.valueAt(i); @@ -807,6 +832,7 @@ public class InsetsState implements Parcelable { pw.println(newPrefix + "mRoundedCorners=" + mRoundedCorners); pw.println(newPrefix + "mRoundedCornerFrame=" + mRoundedCornerFrame); pw.println(newPrefix + "mPrivacyIndicatorBounds=" + mPrivacyIndicatorBounds); + pw.println(newPrefix + "mDisplayShape=" + mDisplayShape); for (int i = 0; i < SIZE; i++) { InsetsSource source = mSources[i]; if (source == null) continue; @@ -911,7 +937,8 @@ public class InsetsState implements Parcelable { || !mDisplayCutout.equals(state.mDisplayCutout) || !mRoundedCorners.equals(state.mRoundedCorners) || !mRoundedCornerFrame.equals(state.mRoundedCornerFrame) - || !mPrivacyIndicatorBounds.equals(state.mPrivacyIndicatorBounds)) { + || !mPrivacyIndicatorBounds.equals(state.mPrivacyIndicatorBounds) + || !mDisplayShape.equals(state.mDisplayShape)) { return false; } for (int i = 0; i < SIZE; i++) { @@ -941,7 +968,7 @@ public class InsetsState implements Parcelable { @Override public int hashCode() { return Objects.hash(mDisplayFrame, mDisplayCutout, Arrays.hashCode(mSources), - mRoundedCorners, mPrivacyIndicatorBounds, mRoundedCornerFrame); + mRoundedCorners, mPrivacyIndicatorBounds, mRoundedCornerFrame, mDisplayShape); } public InsetsState(Parcel in) { @@ -961,6 +988,7 @@ public class InsetsState implements Parcelable { dest.writeTypedObject(mRoundedCorners, flags); mRoundedCornerFrame.writeToParcel(dest, flags); dest.writeTypedObject(mPrivacyIndicatorBounds, flags); + dest.writeTypedObject(mDisplayShape, flags); } public static final @NonNull Creator<InsetsState> CREATOR = new Creator<InsetsState>() { @@ -981,6 +1009,7 @@ public class InsetsState implements Parcelable { mRoundedCorners = in.readTypedObject(RoundedCorners.CREATOR); mRoundedCornerFrame.readFromParcel(in); mPrivacyIndicatorBounds = in.readTypedObject(PrivacyIndicatorBounds.CREATOR); + mDisplayShape = in.readTypedObject(DisplayShape.CREATOR); } @Override @@ -998,6 +1027,7 @@ public class InsetsState implements Parcelable { + ", mRoundedCorners=" + mRoundedCorners + " mRoundedCornerFrame=" + mRoundedCornerFrame + ", mPrivacyIndicatorBounds=" + mPrivacyIndicatorBounds + + ", mDisplayShape=" + mDisplayShape + ", mSources= { " + joiner + " }"; } diff --git a/core/java/android/view/WindowInsets.java b/core/java/android/view/WindowInsets.java index 03b25c25ab99..8de15c1d0ce6 100644 --- a/core/java/android/view/WindowInsets.java +++ b/core/java/android/view/WindowInsets.java @@ -42,7 +42,6 @@ import android.compat.annotation.UnsupportedAppUsage; import android.content.Intent; import android.graphics.Insets; import android.graphics.Rect; -import android.util.SparseArray; import android.view.View.OnApplyWindowInsetsListener; import android.view.WindowInsets.Type.InsetsType; import android.view.inputmethod.EditorInfo; @@ -83,6 +82,7 @@ public final class WindowInsets { @Nullable private final DisplayCutout mDisplayCutout; @Nullable private final RoundedCorners mRoundedCorners; @Nullable private final PrivacyIndicatorBounds mPrivacyIndicatorBounds; + @Nullable private final DisplayShape mDisplayShape; /** * In multi-window we force show the navigation bar. Because we don't want that the surface size @@ -115,24 +115,9 @@ public final class WindowInsets { public static final @NonNull WindowInsets CONSUMED; static { - CONSUMED = new WindowInsets((Rect) null, null, false, false, null); - } - - /** - * Construct a new WindowInsets from individual insets. - * - * A {@code null} inset indicates that the respective inset is consumed. - * - * @hide - * @deprecated Use {@link WindowInsets(SparseArray, SparseArray, boolean, boolean, DisplayCutout)} - */ - @Deprecated - public WindowInsets(Rect systemWindowInsetsRect, Rect stableInsetsRect, boolean isRound, - boolean alwaysConsumeSystemBars, DisplayCutout displayCutout) { - this(createCompatTypeMap(systemWindowInsetsRect), createCompatTypeMap(stableInsetsRect), - createCompatVisibilityMap(createCompatTypeMap(systemWindowInsetsRect)), - isRound, alwaysConsumeSystemBars, displayCutout, null, null, - systemBars(), false /* compatIgnoreVisibility */); + CONSUMED = new WindowInsets(createCompatTypeMap(null), createCompatTypeMap(null), + createCompatVisibilityMap(createCompatTypeMap(null)), false, false, null, null, + null, null, systemBars(), false); } /** @@ -154,6 +139,7 @@ public final class WindowInsets { boolean alwaysConsumeSystemBars, DisplayCutout displayCutout, RoundedCorners roundedCorners, PrivacyIndicatorBounds privacyIndicatorBounds, + DisplayShape displayShape, @InsetsType int compatInsetsTypes, boolean compatIgnoreVisibility) { mSystemWindowInsetsConsumed = typeInsetsMap == null; mTypeInsetsMap = mSystemWindowInsetsConsumed @@ -177,6 +163,7 @@ public final class WindowInsets { mRoundedCorners = roundedCorners; mPrivacyIndicatorBounds = privacyIndicatorBounds; + mDisplayShape = displayShape; } /** @@ -191,6 +178,7 @@ public final class WindowInsets { src.mAlwaysConsumeSystemBars, displayCutoutCopyConstructorArgument(src), src.mRoundedCorners, src.mPrivacyIndicatorBounds, + src.mDisplayShape, src.mCompatInsetsTypes, src.mCompatIgnoreVisibility); } @@ -244,15 +232,18 @@ public final class WindowInsets { @UnsupportedAppUsage public WindowInsets(Rect systemWindowInsets) { this(createCompatTypeMap(systemWindowInsets), null, new boolean[SIZE], false, false, null, - null, null, systemBars(), false /* compatIgnoreVisibility */); + null, null, null, systemBars(), false /* compatIgnoreVisibility */); } /** * Creates a indexOf(type) -> inset map for which the {@code insets} is just mapped to * {@link Type#statusBars()} and {@link Type#navigationBars()}, depending on the * location of the inset. + * + * @hide */ - private static Insets[] createCompatTypeMap(@Nullable Rect insets) { + @VisibleForTesting + public static Insets[] createCompatTypeMap(@Nullable Rect insets) { if (insets == null) { return null; } @@ -271,6 +262,10 @@ public final class WindowInsets { Insets.of(insets.left, 0, insets.right, insets.bottom); } + /** + * @hide + */ + @VisibleForTesting private static boolean[] createCompatVisibilityMap(@Nullable Insets[] typeInsetsMap) { boolean[] typeVisibilityMap = new boolean[SIZE]; if (typeInsetsMap == null) { @@ -533,6 +528,17 @@ public final class WindowInsets { } /** + * Returns the display shape in the coordinate space of the window. + * + * @return the display shape + * @see DisplayShape + */ + @Nullable + public DisplayShape getDisplayShape() { + return mDisplayShape; + } + + /** * Returns a copy of this WindowInsets with the cutout fully consumed. * * @return A modified copy of this WindowInsets @@ -547,7 +553,7 @@ public final class WindowInsets { mStableInsetsConsumed ? null : mTypeMaxInsetsMap, mTypeVisibilityMap, mIsRound, mAlwaysConsumeSystemBars, - null /* displayCutout */, mRoundedCorners, mPrivacyIndicatorBounds, + null /* displayCutout */, mRoundedCorners, mPrivacyIndicatorBounds, mDisplayShape, mCompatInsetsTypes, mCompatIgnoreVisibility); } @@ -602,7 +608,7 @@ public final class WindowInsets { // it. (mCompatInsetsTypes & displayCutout()) != 0 ? null : displayCutoutCopyConstructorArgument(this), - mRoundedCorners, mPrivacyIndicatorBounds, mCompatInsetsTypes, + mRoundedCorners, mPrivacyIndicatorBounds, mDisplayShape, mCompatInsetsTypes, mCompatIgnoreVisibility); } @@ -911,6 +917,8 @@ public final class WindowInsets { result.append(mPrivacyIndicatorBounds != null ? "privacyIndicatorBounds=" + mPrivacyIndicatorBounds : ""); result.append("\n "); + result.append(mDisplayShape != null ? "displayShape=" + mDisplayShape : ""); + result.append("\n "); result.append("compatInsetsTypes=" + mCompatInsetsTypes); result.append("\n "); result.append("compatIgnoreVisibility=" + mCompatIgnoreVisibility); @@ -1018,6 +1026,7 @@ public final class WindowInsets { mPrivacyIndicatorBounds == null ? null : mPrivacyIndicatorBounds.inset(left, top, right, bottom), + mDisplayShape, mCompatInsetsTypes, mCompatIgnoreVisibility); } @@ -1037,7 +1046,8 @@ public final class WindowInsets { && Arrays.equals(mTypeVisibilityMap, that.mTypeVisibilityMap) && Objects.equals(mDisplayCutout, that.mDisplayCutout) && Objects.equals(mRoundedCorners, that.mRoundedCorners) - && Objects.equals(mPrivacyIndicatorBounds, that.mPrivacyIndicatorBounds); + && Objects.equals(mPrivacyIndicatorBounds, that.mPrivacyIndicatorBounds) + && Objects.equals(mDisplayShape, that.mDisplayShape); } @Override @@ -1045,7 +1055,7 @@ public final class WindowInsets { return Objects.hash(Arrays.hashCode(mTypeInsetsMap), Arrays.hashCode(mTypeMaxInsetsMap), Arrays.hashCode(mTypeVisibilityMap), mIsRound, mDisplayCutout, mRoundedCorners, mAlwaysConsumeSystemBars, mSystemWindowInsetsConsumed, mStableInsetsConsumed, - mDisplayCutoutConsumed, mPrivacyIndicatorBounds); + mDisplayCutoutConsumed, mPrivacyIndicatorBounds, mDisplayShape); } @@ -1106,6 +1116,7 @@ public final class WindowInsets { private DisplayCutout mDisplayCutout; private RoundedCorners mRoundedCorners = RoundedCorners.NO_ROUNDED_CORNERS; + private DisplayShape mDisplayShape = DisplayShape.NONE; private boolean mIsRound; private boolean mAlwaysConsumeSystemBars; @@ -1137,6 +1148,7 @@ public final class WindowInsets { mIsRound = insets.mIsRound; mAlwaysConsumeSystemBars = insets.mAlwaysConsumeSystemBars; mPrivacyIndicatorBounds = insets.mPrivacyIndicatorBounds; + mDisplayShape = insets.mDisplayShape; } /** @@ -1381,6 +1393,19 @@ public final class WindowInsets { return this; } + /** + * Sets the display shape. + * + * @see #getDisplayShape(). + * @param displayShape the display shape. + * @return itself. + */ + @NonNull + public Builder setDisplayShape(@NonNull DisplayShape displayShape) { + mDisplayShape = displayShape; + return this; + } + /** @hide */ @NonNull public Builder setRound(boolean round) { @@ -1405,7 +1430,8 @@ public final class WindowInsets { return new WindowInsets(mSystemInsetsConsumed ? null : mTypeInsetsMap, mStableInsetsConsumed ? null : mTypeMaxInsetsMap, mTypeVisibilityMap, mIsRound, mAlwaysConsumeSystemBars, mDisplayCutout, mRoundedCorners, - mPrivacyIndicatorBounds, systemBars(), false /* compatIgnoreVisibility */); + mPrivacyIndicatorBounds, mDisplayShape, systemBars(), + false /* compatIgnoreVisibility */); } } diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index ccce9ba6a709..732caca55ae7 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -5977,4 +5977,35 @@ <string-array translatable="false" name="config_fontManagerServiceCerts"> </string-array> + <!-- A string config in svg path format for the main display shape. + (@see https://www.w3.org/TR/SVG/paths.html#PathData). + + This config must be set unless: + 1. {@link Configuration#isScreenRound} is true which means the display shape is circular + and the system will auto-generate a circular shape. + 2. The display has no rounded corner and the system will auto-generate a rectangular shape. + (@see DisplayShape#createDefaultDisplayShape) + + Note: If the display supports multiple resolutions, please define the path config based on + the highest resolution so that it can be scaled correctly in each resolution. --> + <string name="config_mainDisplayShape" translatable="false"></string> + + <!-- A string config in svg path format for the secondary display shape. + (@see https://www.w3.org/TR/SVG/paths.html#PathData). + + This config must be set unless: + 1. {@link Configuration#isScreenRound} is true which means the display shape is circular + and the system will auto-generate a circular shape. + 2. The display has no rounded corner and the system will auto-generate a rectangular shape. + (@see DisplayShape#createDefaultDisplayShape) + + Note: If the display supports multiple resolutions, please define the path config based on + the highest resolution so that it can be scaled correctly in each resolution. --> + <string name="config_secondaryDisplayShape" translatable="false"></string> + + <!-- The display shape config for each display in a multi-display device. --> + <string-array name="config_displayShapeArray" translatable="false"> + <item>@string/config_mainDisplayShape</item> + <item>@string/config_secondaryDisplayShape</item> + </string-array> </resources> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index ae033cab0807..e28b864444b0 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -4913,4 +4913,8 @@ <java-symbol type="dimen" name="status_bar_height_default" /> <java-symbol type="string" name="default_card_name"/> + + <java-symbol type="string" name="config_mainDisplayShape"/> + <java-symbol type="string" name="config_secondaryDisplayShape"/> + <java-symbol type="array" name="config_displayShapeArray" /> </resources> diff --git a/core/tests/coretests/src/android/view/DisplayShapeTest.java b/core/tests/coretests/src/android/view/DisplayShapeTest.java new file mode 100644 index 000000000000..77dd8bd7f976 --- /dev/null +++ b/core/tests/coretests/src/android/view/DisplayShapeTest.java @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2022 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.view; + +import static android.view.Surface.ROTATION_270; +import static android.view.Surface.ROTATION_90; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.sameInstance; +import static org.junit.Assert.assertEquals; + +import android.graphics.Path; +import android.graphics.RectF; +import android.platform.test.annotations.Presubmit; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Tests for {@link DisplayShape}. + */ +@RunWith(AndroidJUnit4.class) +@SmallTest +@Presubmit +public class DisplayShapeTest { + // Rectangle with w=100, height=200 + private static final String SPEC_RECTANGULAR_SHAPE = "M0,0 L100,0 L100,200 L0,200 Z"; + + @Test + public void testGetPath() { + final DisplayShape displayShape = DisplayShape.fromSpecString( + SPEC_RECTANGULAR_SHAPE, 1f, 100, 200); + final Path path = displayShape.getPath(); + final RectF actualRect = new RectF(); + path.computeBounds(actualRect, false); + + final RectF expectRect = new RectF(0f, 0f, 100f, 200f); + assertEquals(actualRect, expectRect); + } + + @Test + public void testDefaultShape_screenIsRound() { + final DisplayShape displayShape = DisplayShape.createDefaultDisplayShape(100, 100, true); + + // A circle with radius = 50. + final String expect = "M0,50.0 A50.0,50.0 0 1,1 100,50.0 A50.0,50.0 0 1,1 0,50.0 Z"; + assertEquals(displayShape.mDisplayShapeSpec, expect); + } + + @Test + public void testDefaultShape_screenIsNotRound() { + final DisplayShape displayShape = DisplayShape.createDefaultDisplayShape(100, 200, false); + + // A rectangle with width/height = 100/200. + final String expect = "M0,0 L100,0 L100,200 L0,200 Z"; + assertEquals(displayShape.mDisplayShapeSpec, expect); + } + + @Test + public void testFromSpecString_cache() { + final DisplayShape cached = DisplayShape.fromSpecString( + SPEC_RECTANGULAR_SHAPE, 1f, 100, 200); + assertThat(DisplayShape.fromSpecString(SPEC_RECTANGULAR_SHAPE, 1f, 100, 200), + sameInstance(cached)); + } + + @Test + public void testGetPath_cache() { + final Path cached = DisplayShape.fromSpecString( + SPEC_RECTANGULAR_SHAPE, 1f, 100, 200).getPath(); + assertThat(DisplayShape.fromSpecString( + SPEC_RECTANGULAR_SHAPE, 1f, 100, 200).getPath(), + sameInstance(cached)); + } + + @Test + public void testRotate_90() { + DisplayShape displayShape = DisplayShape.fromSpecString( + SPEC_RECTANGULAR_SHAPE, 1f, 100, 200); + displayShape = displayShape.setRotation(ROTATION_90); + final Path path = displayShape.getPath(); + final RectF actualRect = new RectF(); + path.computeBounds(actualRect, false); + + final RectF expectRect = new RectF(0f, 0f, 200f, 100f); + assertEquals(actualRect, expectRect); + } + + @Test + public void testRotate_270() { + DisplayShape displayShape = DisplayShape.fromSpecString( + SPEC_RECTANGULAR_SHAPE, 1f, 100, 200); + displayShape = displayShape.setRotation(ROTATION_270); + final Path path = displayShape.getPath(); + final RectF actualRect = new RectF(); + path.computeBounds(actualRect, false); + + final RectF expectRect = new RectF(0f, 0f, 200f, 100f); + assertEquals(actualRect, expectRect); + } + + @Test + public void testOffset() { + DisplayShape displayShape = DisplayShape.fromSpecString( + SPEC_RECTANGULAR_SHAPE, 1f, 100, 200); + displayShape = displayShape.setOffset(-10, -20); + final Path path = displayShape.getPath(); + final RectF actualRect = new RectF(); + path.computeBounds(actualRect, false); + + final RectF expectRect = new RectF(-10f, -20f, 90f, 180f); + assertEquals(actualRect, expectRect); + } + + @Test + public void testPhysicalPixelDisplaySizeRatio() { + final DisplayShape displayShape = DisplayShape.fromSpecString( + SPEC_RECTANGULAR_SHAPE, 0.5f, 100, 200); + final Path path = displayShape.getPath(); + final RectF actualRect = new RectF(); + path.computeBounds(actualRect, false); + + final RectF expectRect = new RectF(0f, 0f, 50f, 100f); + assertEquals(actualRect, expectRect); + } +} diff --git a/core/tests/coretests/src/android/view/InsetsStateTest.java b/core/tests/coretests/src/android/view/InsetsStateTest.java index be9da11057a2..6a96f280d603 100644 --- a/core/tests/coretests/src/android/view/InsetsStateTest.java +++ b/core/tests/coretests/src/android/view/InsetsStateTest.java @@ -517,6 +517,19 @@ public class InsetsStateTest { windowInsets.getRoundedCorner(POSITION_BOTTOM_LEFT)); } + @Test + public void testCalculateRelativeDisplayShape() { + mState.setDisplayFrame(new Rect(0, 0, 200, 400)); + mState.setDisplayShape(DisplayShape.createDefaultDisplayShape(200, 400, false)); + WindowInsets windowInsets = mState.calculateInsets(new Rect(10, 20, 200, 400), null, false, + false, SOFT_INPUT_ADJUST_UNSPECIFIED, 0, 0, TYPE_APPLICATION, + WINDOWING_MODE_UNDEFINED, new SparseIntArray()); + + final DisplayShape expect = + DisplayShape.createDefaultDisplayShape(200, 400, false).setOffset(-10, -20); + assertEquals(expect, windowInsets.getDisplayShape()); + } + private void assertEqualsAndHashCode() { assertEquals(mState, mState2); assertEquals(mState.hashCode(), mState2.hashCode()); diff --git a/core/tests/coretests/src/android/view/WindowInsetsTest.java b/core/tests/coretests/src/android/view/WindowInsetsTest.java index dd9634bd68fb..4fed396456da 100644 --- a/core/tests/coretests/src/android/view/WindowInsetsTest.java +++ b/core/tests/coretests/src/android/view/WindowInsetsTest.java @@ -39,13 +39,16 @@ public class WindowInsetsTest { @Test public void systemWindowInsets_afterConsuming_isConsumed() { - assertTrue(new WindowInsets(new Rect(1, 2, 3, 4), null, false, false, null) + assertTrue(new WindowInsets(WindowInsets.createCompatTypeMap(new Rect(1, 2, 3, 4)), null, + null, false, false, null, null, null, null, + WindowInsets.Type.systemBars(), false) .consumeSystemWindowInsets().isConsumed()); } @Test public void multiNullConstructor_isConsumed() { - assertTrue(new WindowInsets((Rect) null, null, false, false, null).isConsumed()); + assertTrue(new WindowInsets(null, null, null, false, false, null, null, null, null, + WindowInsets.Type.systemBars(), false).isConsumed()); } @Test @@ -61,7 +64,8 @@ public class WindowInsetsTest { WindowInsets.assignCompatInsets(maxInsets, new Rect(0, 10, 0, 0)); WindowInsets.assignCompatInsets(insets, new Rect(0, 0, 0, 0)); WindowInsets windowInsets = new WindowInsets(insets, maxInsets, visible, false, false, null, - null, null, systemBars(), true /* compatIgnoreVisibility */); + null, null, DisplayShape.NONE, systemBars(), + true /* compatIgnoreVisibility */); assertEquals(Insets.of(0, 10, 0, 0), windowInsets.getSystemWindowInsets()); } } diff --git a/core/tests/coretests/src/com/android/internal/widget/ActionBarOverlayLayoutTest.java b/core/tests/coretests/src/com/android/internal/widget/ActionBarOverlayLayoutTest.java index d10f173328be..4d4ec3523d39 100644 --- a/core/tests/coretests/src/com/android/internal/widget/ActionBarOverlayLayoutTest.java +++ b/core/tests/coretests/src/com/android/internal/widget/ActionBarOverlayLayoutTest.java @@ -168,7 +168,8 @@ public class ActionBarOverlayLayoutTest { } private WindowInsets insetsWith(Insets content, DisplayCutout cutout) { - return new WindowInsets(content.toRect(), null, false, false, cutout); + return new WindowInsets(WindowInsets.createCompatTypeMap(content.toRect()), null, null, + false, false, cutout, null, null, null, WindowInsets.Type.systemBars(), false); } private ViewGroup createViewGroupWithId(int id) { diff --git a/services/core/java/com/android/server/display/DisplayDeviceInfo.java b/services/core/java/com/android/server/display/DisplayDeviceInfo.java index 84dfe860bc84..a9d40f9cf25c 100644 --- a/services/core/java/com/android/server/display/DisplayDeviceInfo.java +++ b/services/core/java/com/android/server/display/DisplayDeviceInfo.java @@ -23,6 +23,7 @@ import android.view.Display; import android.view.DisplayAddress; import android.view.DisplayCutout; import android.view.DisplayEventReceiver; +import android.view.DisplayShape; import android.view.RoundedCorners; import android.view.Surface; @@ -303,6 +304,11 @@ final class DisplayDeviceInfo { public RoundedCorners roundedCorners; /** + * The {@link RoundedCorners} if present or {@code null} otherwise. + */ + public DisplayShape displayShape; + + /** * The touch attachment, per {@link DisplayViewport#touch}. */ public int touch; @@ -438,7 +444,8 @@ final class DisplayDeviceInfo { || !BrightnessSynchronizer.floatEquals(brightnessDefault, other.brightnessDefault) || !Objects.equals(roundedCorners, other.roundedCorners) - || installOrientation != other.installOrientation) { + || installOrientation != other.installOrientation + || !Objects.equals(displayShape, other.displayShape)) { diff |= DIFF_OTHER; } return diff; @@ -484,6 +491,7 @@ final class DisplayDeviceInfo { brightnessDefault = other.brightnessDefault; roundedCorners = other.roundedCorners; installOrientation = other.installOrientation; + displayShape = other.displayShape; } // For debugging purposes @@ -533,6 +541,9 @@ final class DisplayDeviceInfo { } sb.append(flagsToString(flags)); sb.append(", installOrientation ").append(installOrientation); + if (displayShape != null) { + sb.append(", displayShape ").append(displayShape); + } sb.append("}"); return sb.toString(); } diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java index 5a714f59485c..4bf1e98f99a5 100644 --- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java +++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java @@ -38,6 +38,7 @@ import android.view.Display; import android.view.DisplayAddress; import android.view.DisplayCutout; import android.view.DisplayEventReceiver; +import android.view.DisplayShape; import android.view.RoundedCorners; import android.view.SurfaceControl; @@ -686,6 +687,9 @@ final class LocalDisplayAdapter extends DisplayAdapter { res, mInfo.uniqueId, maxWidth, maxHeight, mInfo.width, mInfo.height); mInfo.installOrientation = mStaticDisplayInfo.installOrientation; + mInfo.displayShape = DisplayShape.fromResources( + res, mInfo.uniqueId, maxWidth, maxHeight, mInfo.width, mInfo.height); + if (mStaticDisplayInfo.isInternal) { mInfo.type = Display.TYPE_INTERNAL; mInfo.touch = DisplayDeviceInfo.TOUCH_INTERNAL; diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java index dedc56ac9b41..7586a8134939 100644 --- a/services/core/java/com/android/server/display/LogicalDisplay.java +++ b/services/core/java/com/android/server/display/LogicalDisplay.java @@ -233,6 +233,7 @@ final class LogicalDisplay { info.displayCutout = mOverrideDisplayInfo.displayCutout; info.logicalDensityDpi = mOverrideDisplayInfo.logicalDensityDpi; info.roundedCorners = mOverrideDisplayInfo.roundedCorners; + info.displayShape = mOverrideDisplayInfo.displayShape; } mInfo.set(info); } @@ -434,6 +435,7 @@ final class LogicalDisplay { mBaseDisplayInfo.brightnessDefault = deviceInfo.brightnessDefault; mBaseDisplayInfo.roundedCorners = deviceInfo.roundedCorners; mBaseDisplayInfo.installOrientation = deviceInfo.installOrientation; + mBaseDisplayInfo.displayShape = deviceInfo.displayShape; mPrimaryDisplayDeviceInfo = deviceInfo; mInfo.set(null); } diff --git a/services/core/java/com/android/server/display/OverlayDisplayAdapter.java b/services/core/java/com/android/server/display/OverlayDisplayAdapter.java index b0de844389b6..0e11b53e0257 100644 --- a/services/core/java/com/android/server/display/OverlayDisplayAdapter.java +++ b/services/core/java/com/android/server/display/OverlayDisplayAdapter.java @@ -29,6 +29,7 @@ import android.text.TextUtils; import android.util.DisplayMetrics; import android.util.Slog; import android.view.Display; +import android.view.DisplayShape; import android.view.Gravity; import android.view.Surface; import android.view.SurfaceControl; @@ -361,6 +362,8 @@ final class OverlayDisplayAdapter extends DisplayAdapter { mInfo.state = mState; // The display is trusted since it is created by system. mInfo.flags |= FLAG_TRUSTED; + mInfo.displayShape = + DisplayShape.createDefaultDisplayShape(mInfo.width, mInfo.height, false); } return mInfo; } diff --git a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java index 20b82c3e3c97..e5cbb0f2bd4c 100644 --- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java +++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java @@ -51,6 +51,7 @@ import android.os.SystemProperties; import android.util.ArrayMap; import android.util.Slog; import android.view.Display; +import android.view.DisplayShape; import android.view.Surface; import android.view.SurfaceControl; @@ -511,6 +512,9 @@ public class VirtualDisplayAdapter extends DisplayAdapter { mInfo.ownerUid = mOwnerUid; mInfo.ownerPackageName = mOwnerPackageName; + + mInfo.displayShape = + DisplayShape.createDefaultDisplayShape(mInfo.width, mInfo.height, false); } return mInfo; } diff --git a/services/core/java/com/android/server/display/WifiDisplayAdapter.java b/services/core/java/com/android/server/display/WifiDisplayAdapter.java index 146b003650ac..c759d98561f9 100644 --- a/services/core/java/com/android/server/display/WifiDisplayAdapter.java +++ b/services/core/java/com/android/server/display/WifiDisplayAdapter.java @@ -34,6 +34,7 @@ import android.os.UserHandle; import android.util.Slog; import android.view.Display; import android.view.DisplayAddress; +import android.view.DisplayShape; import android.view.Surface; import android.view.SurfaceControl; @@ -655,6 +656,8 @@ final class WifiDisplayAdapter extends DisplayAdapter { mInfo.setAssumedDensityForExternalDisplay(mWidth, mHeight); // The display is trusted since it is created by system. mInfo.flags |= DisplayDeviceInfo.FLAG_TRUSTED; + mInfo.displayShape = + DisplayShape.createDefaultDisplayShape(mInfo.width, mInfo.height, false); } return mInfo; } diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 0119e4df9578..b68d8ba2f56a 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -207,6 +207,7 @@ import android.view.ContentRecordingSession; import android.view.Display; import android.view.DisplayCutout; import android.view.DisplayInfo; +import android.view.DisplayShape; import android.view.Gravity; import android.view.IDisplayWindowInsetsController; import android.view.ISystemGestureExclusionListener; @@ -391,6 +392,10 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp mPrivacyIndicatorBoundsCache = new RotationCache<>(this::calculatePrivacyIndicatorBoundsForRotationUncached); + DisplayShape mInitialDisplayShape; + private final RotationCache<DisplayShape, DisplayShape> mDisplayShapeCache = + new RotationCache<>(this::calculateDisplayShapeForRotationUncached); + /** * Overridden display size. Initialized with {@link #mInitialDisplayWidth} * and {@link #mInitialDisplayHeight}, but can be set via shell command "adb shell wm size". @@ -1095,7 +1100,8 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp mDisplayFrames = new DisplayFrames(mInsetsStateController.getRawInsetsState(), mDisplayInfo, calculateDisplayCutoutForRotation(mDisplayInfo.rotation), calculateRoundedCornersForRotation(mDisplayInfo.rotation), - calculatePrivacyIndicatorBoundsForRotation(mDisplayInfo.rotation)); + calculatePrivacyIndicatorBoundsForRotation(mDisplayInfo.rotation), + calculateDisplayShapeForRotation(mDisplayInfo.rotation)); initializeDisplayBaseInfo(); mHoldScreenWakeLock = mWmService.mPowerManager.newWakeLock( @@ -1969,8 +1975,9 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp final RoundedCorners roundedCorners = calculateRoundedCornersForRotation(rotation); final PrivacyIndicatorBounds indicatorBounds = calculatePrivacyIndicatorBoundsForRotation(rotation); + final DisplayShape displayShape = calculateDisplayShapeForRotation(rotation); final DisplayFrames displayFrames = new DisplayFrames(new InsetsState(), info, - cutout, roundedCorners, indicatorBounds); + cutout, roundedCorners, indicatorBounds, displayShape); token.applyFixedRotationTransform(info, displayFrames, mTmpConfiguration); } @@ -2178,6 +2185,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp // Update application display metrics. final DisplayCutout displayCutout = calculateDisplayCutoutForRotation(rotation); final RoundedCorners roundedCorners = calculateRoundedCornersForRotation(rotation); + final DisplayShape displayShape = calculateDisplayShapeForRotation(rotation); final Rect appFrame = mDisplayPolicy.getDecorInsetsInfo(rotation, dw, dh).mNonDecorFrame; mDisplayInfo.rotation = rotation; @@ -2194,6 +2202,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp } mDisplayInfo.displayCutout = displayCutout.isEmpty() ? null : displayCutout; mDisplayInfo.roundedCorners = roundedCorners; + mDisplayInfo.displayShape = displayShape; mDisplayInfo.getAppMetrics(mDisplayMetrics); if (mDisplayScalingDisabled) { mDisplayInfo.flags |= Display.FLAG_SCALING_DISABLED; @@ -2280,6 +2289,23 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp return bounds.rotate(rotation); } + DisplayShape calculateDisplayShapeForRotation(int rotation) { + return mDisplayShapeCache.getOrCompute(mInitialDisplayShape, rotation); + } + + private DisplayShape calculateDisplayShapeForRotationUncached( + DisplayShape displayShape, int rotation) { + if (displayShape == null) { + return DisplayShape.NONE; + } + + if (rotation == ROTATION_0) { + return displayShape; + } + + return displayShape.setRotation(rotation); + } + /** * Compute display info and configuration according to the given rotation without changing * current display. @@ -2781,7 +2807,8 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp return displayFrames.update(rotation, w, h, calculateDisplayCutoutForRotation(rotation), calculateRoundedCornersForRotation(rotation), - calculatePrivacyIndicatorBoundsForRotation(rotation)); + calculatePrivacyIndicatorBoundsForRotation(rotation), + calculateDisplayShapeForRotation(rotation)); } @Override @@ -2821,6 +2848,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp mInitialRoundedCorners = mDisplayInfo.roundedCorners; mCurrentPrivacyIndicatorBounds = new PrivacyIndicatorBounds(new Rect[4], mDisplayInfo.rotation); + mInitialDisplayShape = mDisplayInfo.displayShape; final Display.Mode maxDisplayMode = DisplayUtils.getMaximumResolutionDisplayMode(mDisplayInfo.supportedModes); mPhysicalDisplaySize = new Point( @@ -2848,6 +2876,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp ? DisplayCutout.NO_CUTOUT : mDisplayInfo.displayCutout; final String newUniqueId = mDisplayInfo.uniqueId; final RoundedCorners newRoundedCorners = mDisplayInfo.roundedCorners; + final DisplayShape newDisplayShape = mDisplayInfo.displayShape; final boolean displayMetricsChanged = mInitialDisplayWidth != newWidth || mInitialDisplayHeight != newHeight @@ -2855,7 +2884,8 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp || mInitialPhysicalXDpi != newXDpi || mInitialPhysicalYDpi != newYDpi || !Objects.equals(mInitialDisplayCutout, newCutout) - || !Objects.equals(mInitialRoundedCorners, newRoundedCorners); + || !Objects.equals(mInitialRoundedCorners, newRoundedCorners) + || !Objects.equals(mInitialDisplayShape, newDisplayShape); final boolean physicalDisplayChanged = !newUniqueId.equals(mCurrentUniqueDisplayId); if (displayMetricsChanged || physicalDisplayChanged) { @@ -2893,6 +2923,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp mInitialPhysicalYDpi = newYDpi; mInitialDisplayCutout = newCutout; mInitialRoundedCorners = newRoundedCorners; + mInitialDisplayShape = newDisplayShape; mCurrentUniqueDisplayId = newUniqueId; reconfigureDisplayLocked(); diff --git a/services/core/java/com/android/server/wm/DisplayFrames.java b/services/core/java/com/android/server/wm/DisplayFrames.java index 33641f72b2ff..e984456161e4 100644 --- a/services/core/java/com/android/server/wm/DisplayFrames.java +++ b/services/core/java/com/android/server/wm/DisplayFrames.java @@ -26,6 +26,7 @@ import android.graphics.Rect; import android.util.proto.ProtoOutputStream; import android.view.DisplayCutout; import android.view.DisplayInfo; +import android.view.DisplayShape; import android.view.InsetsState; import android.view.PrivacyIndicatorBounds; import android.view.RoundedCorners; @@ -56,10 +57,11 @@ public class DisplayFrames { public int mRotation; public DisplayFrames(InsetsState insetsState, DisplayInfo info, DisplayCutout cutout, - RoundedCorners roundedCorners, PrivacyIndicatorBounds indicatorBounds) { + RoundedCorners roundedCorners, PrivacyIndicatorBounds indicatorBounds, + DisplayShape displayShape) { mInsetsState = insetsState; update(info.rotation, info.logicalWidth, info.logicalHeight, cutout, roundedCorners, - indicatorBounds); + indicatorBounds, displayShape); } DisplayFrames() { @@ -73,7 +75,8 @@ public class DisplayFrames { */ public boolean update(int rotation, int w, int h, @NonNull DisplayCutout displayCutout, @NonNull RoundedCorners roundedCorners, - @NonNull PrivacyIndicatorBounds indicatorBounds) { + @NonNull PrivacyIndicatorBounds indicatorBounds, + @NonNull DisplayShape displayShape) { final InsetsState state = mInsetsState; final Rect safe = mDisplayCutoutSafe; if (mRotation == rotation && mWidth == w && mHeight == h @@ -91,6 +94,7 @@ public class DisplayFrames { state.setDisplayCutout(displayCutout); state.setRoundedCorners(roundedCorners); state.setPrivacyIndicatorBounds(indicatorBounds); + state.setDisplayShape(displayShape); state.getDisplayCutoutSafe(safe); if (safe.left > unrestricted.left) { state.getSource(ITYPE_LEFT_DISPLAY_CUTOUT).setFrame( diff --git a/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java b/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java index d41ac7021077..395e6ac1017d 100644 --- a/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java @@ -161,6 +161,10 @@ public class LocalDisplayAdapterTest { when(mMockedResources.getIntArray( com.android.internal.R.array.config_autoBrightnessLevels)) .thenReturn(new int[]{}); + when(mMockedResources.obtainTypedArray(R.array.config_displayShapeArray)) + .thenReturn(mockArray); + when(mMockedResources.obtainTypedArray(R.array.config_builtInDisplayIsRoundArray)) + .thenReturn(mockArray); } @After diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java index 70b68c78f092..67334707db2a 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java @@ -133,8 +133,8 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase { final RoundedCorners roundedCorners = mHasRoundedCorners ? mDisplayContent.calculateRoundedCornersForRotation(mRotation) : RoundedCorners.NO_ROUNDED_CORNERS; - return new DisplayFrames(insetsState, info, - info.displayCutout, roundedCorners, new PrivacyIndicatorBounds()); + return new DisplayFrames(insetsState, info, info.displayCutout, roundedCorners, + new PrivacyIndicatorBounds(), info.displayShape); } @Test diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java index d99946f0a5c4..10f2270db19e 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java @@ -58,6 +58,7 @@ import android.graphics.PixelFormat; import android.graphics.Rect; import android.platform.test.annotations.Presubmit; import android.view.DisplayInfo; +import android.view.DisplayShape; import android.view.InsetsSource; import android.view.InsetsState; import android.view.PrivacyIndicatorBounds; @@ -321,7 +322,8 @@ public class DisplayPolicyTests extends WindowTestsBase { final InsetsState state = mDisplayContent.getInsetsStateController().getRawInsetsState(); mImeWindow.mAboveInsetsState.set(state); mDisplayContent.mDisplayFrames = new DisplayFrames( - state, displayInfo, NO_CUTOUT, NO_ROUNDED_CORNERS, new PrivacyIndicatorBounds()); + state, displayInfo, NO_CUTOUT, NO_ROUNDED_CORNERS, new PrivacyIndicatorBounds(), + DisplayShape.NONE); mDisplayContent.setInputMethodWindowLocked(mImeWindow); mImeWindow.mAttrs.setFitInsetsSides(Side.all() & ~Side.BOTTOM); diff --git a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java index 9090c5550248..d805032776ec 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java @@ -54,6 +54,7 @@ import android.os.RemoteException; import android.platform.test.annotations.Presubmit; import android.view.DisplayCutout; import android.view.DisplayInfo; +import android.view.DisplayShape; import android.view.Gravity; import android.view.InsetsState; import android.view.PrivacyIndicatorBounds; @@ -165,7 +166,8 @@ public class WallpaperControllerTests extends WindowTestsBase { final DisplayInfo info = dc.computeScreenConfiguration(config, Surface.ROTATION_0); final DisplayCutout cutout = dc.calculateDisplayCutoutForRotation(Surface.ROTATION_0); final DisplayFrames displayFrames = new DisplayFrames(new InsetsState(), - info, cutout, RoundedCorners.NO_ROUNDED_CORNERS, new PrivacyIndicatorBounds()); + info, cutout, RoundedCorners.NO_ROUNDED_CORNERS, new PrivacyIndicatorBounds(), + DisplayShape.NONE); wallpaperWindow.mToken.applyFixedRotationTransform(info, displayFrames, config); // Check that the wallpaper has the same frame in landscape than in portrait |