Support DisplayShape
Provide APIs to query display shape information:
- Added a DisplayShape class which provide a Path.
- Added a new API in Display to get the DisplayShape based on the
display coordinates.
- Added a new API in WindowInsets to get the relative DisplayShape
based on the window frame.
Bug: 236935288
Test: atest DisplayShapeTest LocalDisplayAdapterTest WindowInsetsTest
InsetsStateTest DisplayPolicyLayoutTests DisplayPolicyTests
WallpaperControllerTests
Change-Id: I7aacfcae42203d3f57cd577da5a69180f66780ad
diff --git a/core/api/current.txt b/core/api/current.txt
index d4c461c..e2c2dfeb 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -48515,6 +48515,7 @@
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 @@
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 @@
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 @@
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 121741e0..5f4f1da 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -2882,6 +2882,10 @@
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 5933ae4..36eb671 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -996,6 +996,28 @@
}
/**
+ * 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 0ba3072..138017c 100644
--- a/core/java/android/view/DisplayInfo.java
+++ b/core/java/android/view/DisplayInfo.java
@@ -323,6 +323,9 @@
@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 @@
&& 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 @@
brightnessDefault = other.brightnessDefault;
roundedCorners = other.roundedCorners;
installOrientation = other.installOrientation;
+ displayShape = other.displayShape;
}
public void readFromParcel(Parcel source) {
@@ -506,6 +511,7 @@
userDisabledHdrTypes[i] = source.readInt();
}
installOrientation = source.readInt();
+ displayShape = source.readTypedObject(DisplayShape.CREATOR);
}
@Override
@@ -562,6 +568,7 @@
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 0000000..af8b417
--- /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 0000000..43bd773
--- /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 a8cc9b6..c56d618 100644
--- a/core/java/android/view/InsetsState.java
+++ b/core/java/android/view/InsetsState.java
@@ -197,6 +197,9 @@
private PrivacyIndicatorBounds mPrivacyIndicatorBounds =
new PrivacyIndicatorBounds();
+ /** The display shape */
+ private DisplayShape mDisplayShape = DisplayShape.NONE;
+
public InsetsState() {
}
@@ -271,6 +274,7 @@
alwaysConsumeSystemBars, calculateRelativeCutout(frame),
calculateRelativeRoundedCorners(frame),
calculateRelativePrivacyIndicatorBounds(frame),
+ calculateRelativeDisplayShape(frame),
compatInsetsTypes, (legacySystemUiFlags & SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0);
}
@@ -335,6 +339,16 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
|| !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 @@
@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 @@
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 @@
mRoundedCorners = in.readTypedObject(RoundedCorners.CREATOR);
mRoundedCornerFrame.readFromParcel(in);
mPrivacyIndicatorBounds = in.readTypedObject(PrivacyIndicatorBounds.CREATOR);
+ mDisplayShape = in.readTypedObject(DisplayShape.CREATOR);
}
@Override
@@ -998,6 +1027,7 @@
+ ", 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 03b25c2..8de15c1 100644
--- a/core/java/android/view/WindowInsets.java
+++ b/core/java/android/view/WindowInsets.java
@@ -42,7 +42,6 @@
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 @@
@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 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 @@
boolean alwaysConsumeSystemBars, DisplayCutout displayCutout,
RoundedCorners roundedCorners,
PrivacyIndicatorBounds privacyIndicatorBounds,
+ DisplayShape displayShape,
@InsetsType int compatInsetsTypes, boolean compatIgnoreVisibility) {
mSystemWindowInsetsConsumed = typeInsetsMap == null;
mTypeInsetsMap = mSystemWindowInsetsConsumed
@@ -177,6 +163,7 @@
mRoundedCorners = roundedCorners;
mPrivacyIndicatorBounds = privacyIndicatorBounds;
+ mDisplayShape = displayShape;
}
/**
@@ -191,6 +178,7 @@
src.mAlwaysConsumeSystemBars, displayCutoutCopyConstructorArgument(src),
src.mRoundedCorners,
src.mPrivacyIndicatorBounds,
+ src.mDisplayShape,
src.mCompatInsetsTypes,
src.mCompatIgnoreVisibility);
}
@@ -244,15 +232,18 @@
@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 @@
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 @@
}
/**
+ * 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 @@
mStableInsetsConsumed ? null : mTypeMaxInsetsMap,
mTypeVisibilityMap,
mIsRound, mAlwaysConsumeSystemBars,
- null /* displayCutout */, mRoundedCorners, mPrivacyIndicatorBounds,
+ null /* displayCutout */, mRoundedCorners, mPrivacyIndicatorBounds, mDisplayShape,
mCompatInsetsTypes, mCompatIgnoreVisibility);
}
@@ -602,7 +608,7 @@
// it.
(mCompatInsetsTypes & displayCutout()) != 0
? null : displayCutoutCopyConstructorArgument(this),
- mRoundedCorners, mPrivacyIndicatorBounds, mCompatInsetsTypes,
+ mRoundedCorners, mPrivacyIndicatorBounds, mDisplayShape, mCompatInsetsTypes,
mCompatIgnoreVisibility);
}
@@ -911,6 +917,8 @@
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 @@
mPrivacyIndicatorBounds == null
? null
: mPrivacyIndicatorBounds.inset(left, top, right, bottom),
+ mDisplayShape,
mCompatInsetsTypes, mCompatIgnoreVisibility);
}
@@ -1037,7 +1046,8 @@
&& 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 @@
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 @@
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 @@
mIsRound = insets.mIsRound;
mAlwaysConsumeSystemBars = insets.mAlwaysConsumeSystemBars;
mPrivacyIndicatorBounds = insets.mPrivacyIndicatorBounds;
+ mDisplayShape = insets.mDisplayShape;
}
/**
@@ -1381,6 +1393,19 @@
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 @@
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 ccce9ba..732caca 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 ae033ca..e28b864 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 0000000..77dd8bd
--- /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 be9da11..6a96f28 100644
--- a/core/tests/coretests/src/android/view/InsetsStateTest.java
+++ b/core/tests/coretests/src/android/view/InsetsStateTest.java
@@ -517,6 +517,19 @@
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 dd9634b..4fed396 100644
--- a/core/tests/coretests/src/android/view/WindowInsetsTest.java
+++ b/core/tests/coretests/src/android/view/WindowInsetsTest.java
@@ -39,13 +39,16 @@
@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 @@
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 d10f173..4d4ec35 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 @@
}
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 84dfe86..a9d40f9 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.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 @@
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 @@
|| !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 @@
brightnessDefault = other.brightnessDefault;
roundedCorners = other.roundedCorners;
installOrientation = other.installOrientation;
+ displayShape = other.displayShape;
}
// For debugging purposes
@@ -533,6 +541,9 @@
}
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 5a714f5..4bf1e98 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.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 @@
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 dedc56a..7586a81 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 @@
info.displayCutout = mOverrideDisplayInfo.displayCutout;
info.logicalDensityDpi = mOverrideDisplayInfo.logicalDensityDpi;
info.roundedCorners = mOverrideDisplayInfo.roundedCorners;
+ info.displayShape = mOverrideDisplayInfo.displayShape;
}
mInfo.set(info);
}
@@ -434,6 +435,7 @@
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 b0de844..0e11b53 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.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 @@
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 20b82c3..e5cbb0f 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.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 @@
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 146b003..c759d98 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.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 @@
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 0119e4d..b68d8ba 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.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 @@
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 @@
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 @@
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 @@
// 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 @@
}
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 @@
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 @@
return displayFrames.update(rotation, w, h,
calculateDisplayCutoutForRotation(rotation),
calculateRoundedCornersForRotation(rotation),
- calculatePrivacyIndicatorBoundsForRotation(rotation));
+ calculatePrivacyIndicatorBoundsForRotation(rotation),
+ calculateDisplayShapeForRotation(rotation));
}
@Override
@@ -2821,6 +2848,7 @@
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 @@
? 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 @@
|| 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 @@
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 33641f7..e984456 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.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 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 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 @@
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 d41ac70..395e6ac 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 @@
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 70b68c7..6733470 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 @@
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 d99946f..10f2270 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.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 @@
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 9090c55..d805032 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.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 @@
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