diff --git a/core/api/current.txt b/core/api/current.txt index 88c961afb8e07..f8b3acac3a915 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -46120,6 +46120,7 @@ package android.view { method @Deprecated public void getRectSize(android.graphics.Rect); method public float getRefreshRate(); method public int getRotation(); + method @Nullable public android.view.RoundedCorner getRoundedCorner(int); method @Deprecated public void getSize(android.graphics.Point); method public int getState(); method public android.view.Display.Mode[] getSupportedModes(); @@ -47373,6 +47374,20 @@ package android.view { field public static final int TYPE_ZOOM_OUT = 1019; // 0x3fb } + public final class RoundedCorner implements android.os.Parcelable { + ctor public RoundedCorner(int, int, int, int); + method public int describeContents(); + method @NonNull public android.graphics.Point getCenter(); + method public int getPosition(); + method public int getRadius(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator CREATOR; + field public static final int POSITION_BOTTOM_LEFT = 3; // 0x3 + field public static final int POSITION_BOTTOM_RIGHT = 2; // 0x2 + field public static final int POSITION_TOP_LEFT = 0; // 0x0 + field public static final int POSITION_TOP_RIGHT = 1; // 0x1 + } + public class ScaleGestureDetector { ctor public ScaleGestureDetector(android.content.Context, android.view.ScaleGestureDetector.OnScaleGestureListener); ctor public ScaleGestureDetector(android.content.Context, android.view.ScaleGestureDetector.OnScaleGestureListener, android.os.Handler); @@ -49382,6 +49397,7 @@ package android.view { method @NonNull public android.graphics.Insets getInsets(int); method @NonNull public android.graphics.Insets getInsetsIgnoringVisibility(int); method @Deprecated @NonNull public android.graphics.Insets getMandatorySystemGestureInsets(); + method @Nullable public android.view.RoundedCorner getRoundedCorner(int); method @Deprecated public int getStableInsetBottom(); method @Deprecated public int getStableInsetLeft(); method @Deprecated public int getStableInsetRight(); @@ -49415,6 +49431,7 @@ package android.view { 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); + method @NonNull public android.view.WindowInsets.Builder setRoundedCorner(int, @Nullable android.view.RoundedCorner); method @Deprecated @NonNull public android.view.WindowInsets.Builder setStableInsets(@NonNull android.graphics.Insets); method @Deprecated @NonNull public android.view.WindowInsets.Builder setSystemGestureInsets(@NonNull android.graphics.Insets); method @Deprecated @NonNull public android.view.WindowInsets.Builder setSystemWindowInsets(@NonNull android.graphics.Insets); diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java index c664ccba4ca78..0ba1dfee16f39 100644 --- a/core/java/android/view/Display.java +++ b/core/java/android/view/Display.java @@ -899,6 +899,32 @@ public final class Display { } } + /** + * Returns the {@link RoundedCorner} of the given position if there is one. + * + * @param position the position of the rounded corner on the display. + * + * @return the rounded corner of the given position. Returns {@code null} if there is none. + */ + @SuppressLint("VisiblySynchronized") + @Nullable + public RoundedCorner getRoundedCorner(@RoundedCorner.Position int position) { + synchronized (this) { + updateDisplayInfoLocked(); + RoundedCorners roundedCorners; + if (mMayAdjustByFixedRotation) { + roundedCorners = getDisplayAdjustments().adjustRoundedCorner( + mDisplayInfo.roundedCorners, + mDisplayInfo.rotation, + mDisplayInfo.logicalWidth, + mDisplayInfo.logicalHeight); + } else { + roundedCorners = mDisplayInfo.roundedCorners; + } + return roundedCorners == null ? null : roundedCorners.getRoundedCorner(position); + } + } + /** * 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/DisplayAdjustments.java b/core/java/android/view/DisplayAdjustments.java index 5d5771c9148d3..e307eff7a2dd7 100644 --- a/core/java/android/view/DisplayAdjustments.java +++ b/core/java/android/view/DisplayAdjustments.java @@ -151,6 +151,23 @@ public class DisplayAdjustments { : realCutout; } + /** + * Returns the adjusted {@link RoundedCorners} if available. Otherwise the original + * {@link RoundedCorners} is returned. + */ + @Nullable + public RoundedCorners adjustRoundedCorner(@Nullable RoundedCorners realRoundedCorners, + @Surface.Rotation int realRotation, int displayWidth, int displayHeight) { + final FixedRotationAdjustments rotationAdjustments = mFixedRotationAdjustments; + if (realRoundedCorners == null || rotationAdjustments == null + || rotationAdjustments.mRotation == realRotation) { + return realRoundedCorners; + } + + return realRoundedCorners.rotate( + rotationAdjustments.mRotation, displayWidth, displayHeight); + } + /** Returns the adjusted rotation if available. Otherwise the original rotation is returned. */ @Surface.Rotation public int getRotation(@Surface.Rotation int realRotation) { diff --git a/core/java/android/view/DisplayInfo.java b/core/java/android/view/DisplayInfo.java index d200a328773b9..2a00b5a2e5135 100644 --- a/core/java/android/view/DisplayInfo.java +++ b/core/java/android/view/DisplayInfo.java @@ -301,6 +301,12 @@ public final class DisplayInfo implements Parcelable { */ public float brightnessDefault; + /** + * The {@link RoundedCorners} if present, otherwise {@code null}. + */ + @Nullable + public RoundedCorners roundedCorners; + public static final @android.annotation.NonNull Creator CREATOR = new Creator() { @Override public DisplayInfo createFromParcel(Parcel source) { @@ -369,7 +375,8 @@ public final class DisplayInfo implements Parcelable { && refreshRateOverride == other.refreshRateOverride && brightnessMinimum == other.brightnessMinimum && brightnessMaximum == other.brightnessMaximum - && brightnessDefault == other.brightnessDefault; + && brightnessDefault == other.brightnessDefault + && Objects.equals(roundedCorners, other.roundedCorners); } @Override @@ -418,6 +425,7 @@ public final class DisplayInfo implements Parcelable { brightnessMinimum = other.brightnessMinimum; brightnessMaximum = other.brightnessMaximum; brightnessDefault = other.brightnessDefault; + roundedCorners = other.roundedCorners; } public void readFromParcel(Parcel source) { @@ -468,6 +476,7 @@ public final class DisplayInfo implements Parcelable { brightnessMinimum = source.readFloat(); brightnessMaximum = source.readFloat(); brightnessDefault = source.readFloat(); + roundedCorners = source.readTypedObject(RoundedCorners.CREATOR); } @Override @@ -517,6 +526,7 @@ public final class DisplayInfo implements Parcelable { dest.writeFloat(brightnessMinimum); dest.writeFloat(brightnessMaximum); dest.writeFloat(brightnessDefault); + dest.writeTypedObject(roundedCorners, flags); } @Override diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java index b4e1172f49aeb..e681c0e3580ea 100644 --- a/core/java/android/view/InsetsController.java +++ b/core/java/android/view/InsetsController.java @@ -653,6 +653,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation private void updateState(InsetsState newState) { mState.setDisplayFrame(newState.getDisplayFrame()); mState.setDisplayCutout(newState.getDisplayCutout()); + mState.setRoundedCorners(newState.getRoundedCorners()); @InsetsType int disabledUserAnimationTypes = 0; @InsetsType int[] cancelledUserAnimationTypes = {0}; for (@InternalInsetsType int type = 0; type < InsetsState.SIZE; type++) { diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java index d68e9032c19d9..fe6b6e4d6fd17 100644 --- a/core/java/android/view/InsetsState.java +++ b/core/java/android/view/InsetsState.java @@ -173,6 +173,9 @@ public class InsetsState implements Parcelable { private final DisplayCutout.ParcelableWrapper mDisplayCutout = new DisplayCutout.ParcelableWrapper(); + /** The rounded corners on the display */ + private RoundedCorners mRoundedCorners = RoundedCorners.NO_ROUNDED_CORNERS; + public InsetsState() { } @@ -256,7 +259,8 @@ public class InsetsState implements Parcelable { } return new WindowInsets(typeInsetsMap, typeMaxInsetsMap, typeVisibilityMap, isScreenRound, - alwaysConsumeSystemBars, calculateRelativeCutout(frame), compatInsetsTypes, + alwaysConsumeSystemBars, calculateRelativeCutout(frame), + calculateRelativeRoundedCorners(frame), compatInsetsTypes, (legacySystemUiFlags & SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0); } @@ -281,6 +285,20 @@ public class InsetsState implements Parcelable { return raw.inset(insetLeft, insetTop, insetRight, insetBottom); } + private RoundedCorners calculateRelativeRoundedCorners(Rect frame) { + if (mDisplayFrame.equals(frame)) { + return mRoundedCorners; + } + if (frame == null) { + return RoundedCorners.NO_ROUNDED_CORNERS; + } + final int insetLeft = frame.left - mDisplayFrame.left; + final int insetTop = frame.top - mDisplayFrame.top; + final int insetRight = mDisplayFrame.right - frame.right; + final int insetBottom = mDisplayFrame.bottom - frame.bottom; + return mRoundedCorners.inset(insetLeft, insetTop, insetRight, insetBottom); + } + public Rect calculateInsets(Rect frame, @InsetsType int types, boolean ignoreVisibility) { Insets insets = Insets.NONE; for (int type = FIRST_TYPE; type <= LAST_TYPE; type++) { @@ -462,6 +480,14 @@ public class InsetsState implements Parcelable { return mDisplayCutout.get(); } + public void setRoundedCorners(RoundedCorners roundedCorners) { + mRoundedCorners = roundedCorners; + } + + public RoundedCorners getRoundedCorners() { + return mRoundedCorners; + } + /** * Modifies the state of this class to exclude a certain type to make it ready for dispatching * to the client. @@ -493,6 +519,7 @@ public class InsetsState implements Parcelable { public void scale(float scale) { mDisplayFrame.scale(scale); mDisplayCutout.scale(scale); + mRoundedCorners = mRoundedCorners.scale(scale); for (int i = 0; i < SIZE; i++) { final InsetsSource source = mSources[i]; if (source != null) { @@ -512,6 +539,7 @@ public class InsetsState implements Parcelable { public void set(InsetsState other, boolean copySources) { mDisplayFrame.set(other.mDisplayFrame); mDisplayCutout.set(other.mDisplayCutout); + mRoundedCorners = other.getRoundedCorners(); if (copySources) { for (int i = 0; i < SIZE; i++) { InsetsSource source = other.mSources[i]; @@ -619,11 +647,15 @@ public class InsetsState implements Parcelable { } public void dump(String prefix, PrintWriter pw) { + final String newPrefix = prefix + " "; pw.println(prefix + "InsetsState"); + pw.println(newPrefix + "mDisplayFrame=" + mDisplayFrame); + pw.println(newPrefix + "mDisplayCutout=" + mDisplayCutout.get()); + pw.println(newPrefix + "mRoundedCorners=" + mRoundedCorners); for (int i = 0; i < SIZE; i++) { InsetsSource source = mSources[i]; if (source == null) continue; - source.dump(prefix + " ", pw); + source.dump(newPrefix + " ", pw); } } @@ -713,7 +745,8 @@ public class InsetsState implements Parcelable { InsetsState state = (InsetsState) o; if (!mDisplayFrame.equals(state.mDisplayFrame) - || !mDisplayCutout.equals(state.mDisplayCutout)) { + || !mDisplayCutout.equals(state.mDisplayCutout) + || !mRoundedCorners.equals(state.mRoundedCorners)) { return false; } for (int i = 0; i < SIZE; i++) { @@ -737,7 +770,8 @@ public class InsetsState implements Parcelable { @Override public int hashCode() { - return Objects.hash(mDisplayFrame, mDisplayCutout, Arrays.hashCode(mSources)); + return Objects.hash(mDisplayFrame, mDisplayCutout, Arrays.hashCode(mSources), + mRoundedCorners); } public InsetsState(Parcel in) { @@ -754,6 +788,7 @@ public class InsetsState implements Parcelable { mDisplayFrame.writeToParcel(dest, flags); mDisplayCutout.writeToParcel(dest, flags); dest.writeParcelableArray(mSources, 0); + dest.writeTypedObject(mRoundedCorners, flags); } public static final @android.annotation.NonNull Creator CREATOR = new Creator() { @@ -771,6 +806,7 @@ public class InsetsState implements Parcelable { mDisplayFrame.set(Rect.CREATOR.createFromParcel(in)); mDisplayCutout.set(DisplayCutout.ParcelableWrapper.CREATOR.createFromParcel(in)); mSources = in.readParcelableArray(null, InsetsSource.class); + mRoundedCorners = in.readTypedObject(RoundedCorners.CREATOR); } @Override @@ -785,6 +821,7 @@ public class InsetsState implements Parcelable { return "InsetsState: {" + "mDisplayFrame=" + mDisplayFrame + ", mDisplayCutout=" + mDisplayCutout + + ", mRoundedCorners=" + mRoundedCorners + ", mSources= { " + joiner + " }"; } diff --git a/core/java/android/view/RoundedCorner.java b/core/java/android/view/RoundedCorner.java new file mode 100644 index 0000000000000..cc7525b48aa2a --- /dev/null +++ b/core/java/android/view/RoundedCorner.java @@ -0,0 +1,239 @@ +/* + * Copyright (C) 2021 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 android.annotation.IntDef; +import android.annotation.NonNull; +import android.graphics.Point; +import android.os.Parcel; +import android.os.Parcelable; + +import com.android.internal.annotations.VisibleForTesting; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Represents a rounded corner of the display. + * + * + * ________ + * / ^ + * / | Radius + * | v + * | X <- Center point + * |<-----> + * Radius + * + * + *

Note: The rounded corner formed by the radius and the center is an approximation.

+ * + *

{@link RoundedCorner} is immutable.

+ */ +public final class RoundedCorner implements Parcelable { + + /** + * The rounded corner is at the top-left of the screen. + */ + public static final int POSITION_TOP_LEFT = 0; + /** + * The rounded corner is at the top-right of the screen. + */ + public static final int POSITION_TOP_RIGHT = 1; + /** + * The rounded corner is at the bottom-right of the screen. + */ + public static final int POSITION_BOTTOM_RIGHT = 2; + /** + * The rounded corner is at the bottom-left of the screen. + */ + public static final int POSITION_BOTTOM_LEFT = 3; + + /** + * @hide + */ + @IntDef(prefix = { "POSITION_" }, value = { + POSITION_TOP_LEFT, + POSITION_TOP_RIGHT, + POSITION_BOTTOM_RIGHT, + POSITION_BOTTOM_LEFT + }) + @Retention(RetentionPolicy.SOURCE) + public @interface Position {} + + private final @Position int mPosition; + private final int mRadius; + @NonNull + private final Point mCenter; + + /** + * Creates an empty {@link RoundedCorner} on the given position. + * @hide + */ + @VisibleForTesting + public RoundedCorner(@Position int position) { + mPosition = position; + mRadius = 0; + mCenter = new Point(0, 0); + } + + /** + * Creates a {@link RoundedCorner}. + * + *

Note that this is only useful for tests. For production code, developers should always + * use a {@link RoundedCorner} obtained from the system via + * {@link WindowInsets#getRoundedCorner} or {@link Display#getRoundedCorner}.

+ * + * @param position the position of the rounded corner. + * @param radius the radius of the rounded corner. + * @param centerX the x of center point of the rounded corner. + * @param centerY the y of center point of the rounded corner. + * + */ + public RoundedCorner(@Position int position, int radius, int centerX, + int centerY) { + mPosition = position; + mRadius = radius; + mCenter = new Point(centerX, centerY); + } + + /** + * Creates a {@link RoundedCorner} from a passed in {@link RoundedCorner}. + * + * @hide + */ + RoundedCorner(RoundedCorner rc) { + mPosition = rc.getPosition(); + mRadius = rc.getRadius(); + mCenter = new Point(rc.getCenter()); + } + + /** + * Get the position of this {@link RoundedCorner}. + * + * @see #POSITION_TOP_LEFT + * @see #POSITION_TOP_RIGHT + * @see #POSITION_BOTTOM_RIGHT + * @see #POSITION_BOTTOM_LEFT + */ + public @Position int getPosition() { + return mPosition; + } + + /** + * Returns the radius of a quarter circle approximation of this {@link RoundedCorner}. + * + * @return the rounded corner radius of this {@link RoundedCorner}. Returns 0 if there is no + * rounded corner. + */ + public int getRadius() { + return mRadius; + } + + /** + * Returns the circle center of a quarter circle approximation of this {@link RoundedCorner}. + * + * @return the center point of this {@link RoundedCorner} in the application's coordinate. + */ + @NonNull + public Point getCenter() { + return new Point(mCenter); + } + + /** + * Checks whether this {@link RoundedCorner} exists and is inside the application's bounds. + * + * @return {@code false} if there is a rounded corner and is contained in the application's + * bounds. Otherwise return {@code true}. + * + * @hide + */ + public boolean isEmpty() { + return mRadius == 0 || mCenter.x == 0 || mCenter.y == 0; + } + + private String getPositionString(@Position int position) { + switch (position) { + case POSITION_TOP_LEFT: + return "TopLeft"; + case POSITION_TOP_RIGHT: + return "TopRight"; + case POSITION_BOTTOM_RIGHT: + return "BottomRight"; + case POSITION_BOTTOM_LEFT: + return "BottomLeft"; + default: + return "Invalid"; + } + } + + @Override + public boolean equals(Object o) { + if (o == this) { + return true; + } + if (o instanceof RoundedCorner) { + RoundedCorner r = (RoundedCorner) o; + return mPosition == r.mPosition && mRadius == r.mRadius + && mCenter.equals(r.mCenter); + } + return false; + } + + @Override + public int hashCode() { + int result = 0; + result = 31 * result + mPosition; + result = 31 * result + mRadius; + result = 31 * result + mCenter.hashCode(); + return result; + } + + @Override + public String toString() { + return "RoundedCorner{" + + "position=" + getPositionString(mPosition) + + ", radius=" + mRadius + + ", center=" + mCenter + + '}'; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel out, int flags) { + out.writeInt(mPosition); + out.writeInt(mRadius); + out.writeInt(mCenter.x); + out.writeInt(mCenter.y); + } + + public static final @NonNull Creator CREATOR = new Creator() { + @Override + public RoundedCorner createFromParcel(Parcel in) { + return new RoundedCorner(in.readInt(), in.readInt(), in.readInt(), in.readInt()); + } + + @Override + public RoundedCorner[] newArray(int size) { + return new RoundedCorner[size]; + } + }; +} diff --git a/core/java/android/view/RoundedCorners.aidl b/core/java/android/view/RoundedCorners.aidl new file mode 100644 index 0000000000000..0a901c050a165 --- /dev/null +++ b/core/java/android/view/RoundedCorners.aidl @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2021, 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 RoundedCorners; diff --git a/core/java/android/view/RoundedCorners.java b/core/java/android/view/RoundedCorners.java new file mode 100644 index 0000000000000..015e804da2f63 --- /dev/null +++ b/core/java/android/view/RoundedCorners.java @@ -0,0 +1,380 @@ +/* + * Copyright (C) 2021 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.RoundedCorner.POSITION_BOTTOM_LEFT; +import static android.view.RoundedCorner.POSITION_BOTTOM_RIGHT; +import static android.view.RoundedCorner.POSITION_TOP_LEFT; +import static android.view.RoundedCorner.POSITION_TOP_RIGHT; +import static android.view.Surface.ROTATION_0; +import static android.view.Surface.ROTATION_270; +import static android.view.Surface.ROTATION_90; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.res.Resources; +import android.graphics.Point; +import android.os.Parcel; +import android.os.Parcelable; +import android.util.Pair; +import android.view.RoundedCorner.Position; + +import com.android.internal.R; +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; + +import java.util.Arrays; + +/** + * A class to create & manage all the {@link RoundedCorner} on the display. + * + * @hide + */ +public class RoundedCorners implements Parcelable { + + public static final RoundedCorners NO_ROUNDED_CORNERS = new RoundedCorners( + new RoundedCorner(POSITION_TOP_LEFT), new RoundedCorner(POSITION_TOP_RIGHT), + new RoundedCorner(POSITION_BOTTOM_RIGHT), new RoundedCorner(POSITION_BOTTOM_LEFT)); + + /** + * The number of possible positions at which rounded corners can be located. + */ + public static final int ROUNDED_CORNER_POSITION_LENGTH = 4; + + private static final Object CACHE_LOCK = new Object(); + + @GuardedBy("CACHE_LOCK") + private static int sCachedDisplayWidth; + @GuardedBy("CACHE_LOCK") + private static int sCachedDisplayHeight; + @GuardedBy("CACHE_LOCK") + private static Pair sCachedRadii; + @GuardedBy("CACHE_LOCK") + private static RoundedCorners sCachedRoundedCorners; + + @VisibleForTesting + public final RoundedCorner[] mRoundedCorners; + + public RoundedCorners(RoundedCorner[] roundedCorners) { + mRoundedCorners = roundedCorners; + } + + public RoundedCorners(RoundedCorner topLeft, RoundedCorner topRight, RoundedCorner bottomRight, + RoundedCorner bottomLeft) { + mRoundedCorners = new RoundedCorner[ROUNDED_CORNER_POSITION_LENGTH]; + mRoundedCorners[POSITION_TOP_LEFT] = topLeft; + mRoundedCorners[POSITION_TOP_RIGHT] = topRight; + mRoundedCorners[POSITION_BOTTOM_RIGHT] = bottomRight; + mRoundedCorners[POSITION_BOTTOM_LEFT] = bottomLeft; + } + + public RoundedCorners(RoundedCorners roundedCorners) { + mRoundedCorners = new RoundedCorner[ROUNDED_CORNER_POSITION_LENGTH]; + for (int i = 0; i < ROUNDED_CORNER_POSITION_LENGTH; ++i) { + mRoundedCorners[i] = new RoundedCorner(roundedCorners.mRoundedCorners[i]); + } + } + + /** + * Creates the rounded corners according to @android:dimen/rounded_corner_radius, + * @android:dimen/rounded_corner_radius_top and @android:dimen/rounded_corner_radius_bottom + */ + public static RoundedCorners fromResources( + Resources res, int displayWidth, int displayHeight) { + return fromRadii(loadRoundedCornerRadii(res), displayWidth, displayHeight); + } + + /** + * Creates the rounded corners from radius + */ + @VisibleForTesting + public static RoundedCorners fromRadii(Pair radii, int displayWidth, + int displayHeight) { + if (radii == null) { + return null; + } + + synchronized (CACHE_LOCK) { + if (radii.equals(sCachedRadii) && sCachedDisplayWidth == displayWidth + && sCachedDisplayHeight == displayHeight) { + return sCachedRoundedCorners; + } + } + + final RoundedCorner[] roundedCorners = new RoundedCorner[ROUNDED_CORNER_POSITION_LENGTH]; + final int topRadius = radii.first > 0 ? radii.first : 0; + final int bottomRadius = radii.second > 0 ? radii.second : 0; + for (int i = 0; i < ROUNDED_CORNER_POSITION_LENGTH; i++) { + roundedCorners[i] = createRoundedCorner( + i, + i <= POSITION_TOP_RIGHT ? topRadius : bottomRadius, + displayWidth, + displayHeight); + } + + final RoundedCorners result = new RoundedCorners(roundedCorners); + synchronized (CACHE_LOCK) { + sCachedDisplayWidth = displayWidth; + sCachedDisplayHeight = displayHeight; + sCachedRadii = radii; + sCachedRoundedCorners = result; + } + return result; + } + + /** + * Loads the rounded corner radii from resources. + * + * @param res + * @return a Pair of radius. The first is the top rounded corner radius and second is the + * bottom corner radius. + */ + @Nullable + private static Pair loadRoundedCornerRadii(Resources res) { + final int radiusDefault = res.getDimensionPixelSize(R.dimen.rounded_corner_radius); + final int radiusTop = res.getDimensionPixelSize(R.dimen.rounded_corner_radius_top); + final int radiusBottom = res.getDimensionPixelSize(R.dimen.rounded_corner_radius_bottom); + if (radiusDefault == 0 && radiusTop == 0 && radiusBottom == 0) { + return null; + } + final Pair radii = new Pair<>( + radiusTop > 0 ? radiusTop : radiusDefault, + radiusBottom > 0 ? radiusBottom : radiusDefault); + return radii; + } + + /** + * Insets the reference frame of the rounded corners. + * + * @return a copy of this instance which has been inset + */ + public RoundedCorners inset(int insetLeft, int insetTop, int insetRight, int insetBottom) { + final RoundedCorner[] roundedCorners = new RoundedCorner[ROUNDED_CORNER_POSITION_LENGTH]; + for (int i = 0; i < ROUNDED_CORNER_POSITION_LENGTH; i++) { + roundedCorners[i] = insetRoundedCorner(i, insetLeft, insetTop, insetRight, insetBottom); + } + return new RoundedCorners(roundedCorners); + } + + private RoundedCorner insetRoundedCorner(@Position int position, int insetLeft, + int insetTop, int insetRight, int insetBottom) { + if (mRoundedCorners[position].isEmpty()) { + return new RoundedCorner(position); + } + + final int radius = mRoundedCorners[position].getRadius(); + final Point center = mRoundedCorners[position].getCenter(); + boolean hasRoundedCorner; + switch (position) { + case POSITION_TOP_LEFT: + hasRoundedCorner = radius > insetTop || radius > insetLeft; + break; + case POSITION_TOP_RIGHT: + hasRoundedCorner = radius > insetTop || radius > insetRight; + break; + case POSITION_BOTTOM_RIGHT: + hasRoundedCorner = radius > insetBottom || radius > insetRight; + break; + case POSITION_BOTTOM_LEFT: + hasRoundedCorner = radius > insetBottom || radius > insetLeft; + break; + default: + throw new IllegalArgumentException( + "The position is not one of the RoundedCornerPosition =" + position); + } + return new RoundedCorner( + position, radius, + hasRoundedCorner ? center.x - insetLeft : 0, + hasRoundedCorner ? center.y - insetTop : 0); + } + + /** + * Returns the {@link RoundedCorner} of the given position if there is one. + * + * @param position the position of the rounded corner on the display. + * @return the rounded corner of the given position. Returns {@code null} if + * {@link RoundedCorner#isEmpty()} is {@code true}. + */ + @Nullable + public RoundedCorner getRoundedCorner(@Position int position) { + return mRoundedCorners[position].isEmpty() + ? null : new RoundedCorner(mRoundedCorners[position]); + } + + /** + * Sets the rounded corner of given position. + * + * @param position the position of this rounded corner + * @param roundedCorner the rounded corner or null if there is none + */ + public void setRoundedCorner(@Position int position, @Nullable RoundedCorner roundedCorner) { + mRoundedCorners[position] = roundedCorner == null + ? new RoundedCorner(position) : roundedCorner; + } + + /** + * Returns an array of {@link RoundedCorner}s. Ordinal value of RoundedCornerPosition is used + * as an index of the array. + * + * @return an array of {@link RoundedCorner}s, one for each rounded corner area. + */ + public RoundedCorner[] getAllRoundedCorners() { + RoundedCorner[] roundedCorners = new RoundedCorner[ROUNDED_CORNER_POSITION_LENGTH]; + for (int i = 0; i < ROUNDED_CORNER_POSITION_LENGTH; ++i) { + roundedCorners[i] = new RoundedCorner(roundedCorners[i]); + } + return roundedCorners; + } + + /** + * Returns a scaled RoundedCorners. + */ + public RoundedCorners scale(float scale) { + if (scale == 1f) { + return this; + } + + RoundedCorner[] roundedCorners = new RoundedCorner[ROUNDED_CORNER_POSITION_LENGTH]; + for (int i = 0; i < ROUNDED_CORNER_POSITION_LENGTH; ++i) { + final RoundedCorner roundedCorner = mRoundedCorners[i]; + roundedCorners[i] = new RoundedCorner( + i, + (int) (roundedCorner.getRadius() * scale), + (int) (roundedCorner.getCenter().x * scale), + (int) (roundedCorner.getCenter().y * scale)); + } + return new RoundedCorners(roundedCorners); + } + + /** + * Returns a rotated RoundedCorners. + */ + public RoundedCorners rotate(@Surface.Rotation int rotation, int initialDisplayWidth, + int initialDisplayHeight) { + if (rotation == ROTATION_0) { + return this; + } + final boolean isSizeFlipped = rotation == ROTATION_90 || rotation == ROTATION_270; + RoundedCorner[] newCorners = new RoundedCorner[ROUNDED_CORNER_POSITION_LENGTH]; + int newPosistion; + for (int i = 0; i < mRoundedCorners.length; i++) { + newPosistion = getRotatedIndex(i, rotation); + newCorners[newPosistion] = createRoundedCorner( + newPosistion, + mRoundedCorners[i].getRadius(), + isSizeFlipped ? initialDisplayHeight : initialDisplayWidth, + isSizeFlipped ? initialDisplayWidth : initialDisplayHeight); + } + return new RoundedCorners(newCorners); + } + + private static RoundedCorner createRoundedCorner(@Position int position, + int radius, int displayWidth, int displayHeight) { + switch (position) { + case POSITION_TOP_LEFT: + return new RoundedCorner( + POSITION_TOP_LEFT, + radius, + radius > 0 ? radius : 0, + radius > 0 ? radius : 0); + case POSITION_TOP_RIGHT: + return new RoundedCorner( + POSITION_TOP_RIGHT, + radius, + radius > 0 ? displayWidth - radius : 0, + radius > 0 ? radius : 0); + case POSITION_BOTTOM_RIGHT: + return new RoundedCorner( + POSITION_BOTTOM_RIGHT, + radius, + radius > 0 ? displayWidth - radius : 0, + radius > 0 ? displayHeight - radius : 0); + case POSITION_BOTTOM_LEFT: + return new RoundedCorner( + POSITION_BOTTOM_LEFT, + radius, + radius > 0 ? radius : 0, + radius > 0 ? displayHeight - radius : 0); + default: + throw new IllegalArgumentException( + "The position is not one of the RoundedCornerPosition =" + position); + } + } + + private static int getRotatedIndex(int position, int rotation) { + return (position - rotation + ROUNDED_CORNER_POSITION_LENGTH) % 4; + } + + @Override + public int hashCode() { + int result = 0; + for (RoundedCorner roundedCorner : mRoundedCorners) { + result = result * 31 + roundedCorner.hashCode(); + } + return result; + } + + @Override + public boolean equals(Object o) { + if (o == this) { + return true; + } + if (o instanceof RoundedCorners) { + RoundedCorners r = (RoundedCorners) o; + return Arrays.deepEquals(mRoundedCorners, ((RoundedCorners) o).mRoundedCorners); + } + return false; + } + + @Override + public String toString() { + return "RoundedCorners{" + Arrays.toString(mRoundedCorners) + "}"; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + if (equals(NO_ROUNDED_CORNERS)) { + dest.writeInt(0); + } else { + dest.writeInt(1); + dest.writeTypedArray(mRoundedCorners, flags); + } + } + + public static final @NonNull Creator CREATOR = new Creator() { + @Override + public RoundedCorners createFromParcel(Parcel in) { + int variant = in.readInt(); + if (variant == 0) { + return NO_ROUNDED_CORNERS; + } + RoundedCorner[] roundedCorners = new RoundedCorner[ROUNDED_CORNER_POSITION_LENGTH]; + in.readTypedArray(roundedCorners, RoundedCorner.CREATOR); + return new RoundedCorners(roundedCorners); + } + + @Override + public RoundedCorners[] newArray(int size) { + return new RoundedCorners[size]; + } + }; +} diff --git a/core/java/android/view/WindowInsets.java b/core/java/android/view/WindowInsets.java index 7a5561c4fac9a..41c38a113e051 100644 --- a/core/java/android/view/WindowInsets.java +++ b/core/java/android/view/WindowInsets.java @@ -82,6 +82,7 @@ public final class WindowInsets { @Nullable private Rect mTempRect; private final boolean mIsRound; @Nullable private final DisplayCutout mDisplayCutout; + @Nullable private final RoundedCorners mRoundedCorners; /** * In multi-window we force show the navigation bar. Because we don't want that the surface size @@ -125,11 +126,12 @@ public final class WindowInsets { * @hide * @deprecated Use {@link WindowInsets(SparseArray, SparseArray, boolean, boolean, DisplayCutout)} */ - public WindowInsets(Rect systemWindowInsetsRect, Rect stableInsetsRect, - boolean isRound, boolean alwaysConsumeSystemBars, DisplayCutout 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, systemBars(), + isRound, alwaysConsumeSystemBars, displayCutout, null, systemBars(), false /* compatIgnoreVisibility */); } @@ -150,7 +152,8 @@ public final class WindowInsets { boolean[] typeVisibilityMap, boolean isRound, boolean alwaysConsumeSystemBars, DisplayCutout displayCutout, - @InsetsType int compatInsetsTypes, boolean compatIgnoreVisibility) { + RoundedCorners roundedCorners, @InsetsType int compatInsetsTypes, + boolean compatIgnoreVisibility) { mSystemWindowInsetsConsumed = typeInsetsMap == null; mTypeInsetsMap = mSystemWindowInsetsConsumed ? new Insets[SIZE] @@ -170,6 +173,8 @@ public final class WindowInsets { mDisplayCutoutConsumed = displayCutout == null; mDisplayCutout = (mDisplayCutoutConsumed || displayCutout.isEmpty()) ? null : displayCutout; + + mRoundedCorners = roundedCorners; } /** @@ -182,6 +187,7 @@ public final class WindowInsets { src.mStableInsetsConsumed ? null : src.mTypeMaxInsetsMap, src.mTypeVisibilityMap, src.mIsRound, src.mAlwaysConsumeSystemBars, displayCutoutCopyConstructorArgument(src), + src.mRoundedCorners, src.mCompatInsetsTypes, src.mCompatIgnoreVisibility); } @@ -235,7 +241,7 @@ public final class WindowInsets { @UnsupportedAppUsage public WindowInsets(Rect systemWindowInsets) { this(createCompatTypeMap(systemWindowInsets), null, new boolean[SIZE], false, false, null, - systemBars(), false /* compatIgnoreVisibility */); + null, systemBars(), false /* compatIgnoreVisibility */); } /** @@ -466,7 +472,7 @@ public final class WindowInsets { public boolean hasInsets() { return !getInsets(mTypeInsetsMap, all()).equals(Insets.NONE) || !getInsets(mTypeMaxInsetsMap, all()).equals(Insets.NONE) - || mDisplayCutout != null; + || mDisplayCutout != null || mRoundedCorners != null; } /** @@ -486,6 +492,23 @@ public final class WindowInsets { return mDisplayCutout; } + /** + * Returns the {@link RoundedCorner} of the given position if there is one. + * + * @param position the position of the rounded corner on the display. The value should be one of + * the following: + * {@link RoundedCorner#POSITION_TOP_LEFT}, + * {@link RoundedCorner#POSITION_TOP_RIGHT}, + * {@link RoundedCorner#POSITION_BOTTOM_RIGHT}, + * {@link RoundedCorner#POSITION_BOTTOM_LEFT}. + * @return the rounded corner of the given position. Returns {@code null} if there is none or + * the rounded corner area is not inside the application's bounds. + */ + @Nullable + public RoundedCorner getRoundedCorner(@RoundedCorner.Position int position) { + return mRoundedCorners == null ? null : mRoundedCorners.getRoundedCorner(position); + } + /** * Returns a copy of this WindowInsets with the cutout fully consumed. * @@ -501,7 +524,7 @@ public final class WindowInsets { mStableInsetsConsumed ? null : mTypeMaxInsetsMap, mTypeVisibilityMap, mIsRound, mAlwaysConsumeSystemBars, - null /* displayCutout */, + null /* displayCutout */, mRoundedCorners, mCompatInsetsTypes, mCompatIgnoreVisibility); } @@ -553,7 +576,7 @@ public final class WindowInsets { mTypeVisibilityMap, mIsRound, mAlwaysConsumeSystemBars, displayCutoutCopyConstructorArgument(this), - mCompatInsetsTypes, mCompatIgnoreVisibility); + mRoundedCorners, mCompatInsetsTypes, mCompatIgnoreVisibility); } // TODO(b/119190588): replace @code with @link below @@ -856,6 +879,8 @@ public final class WindowInsets { result.append(mDisplayCutout != null ? "cutout=" + mDisplayCutout : ""); result.append("\n "); + result.append(mRoundedCorners != null ? "roundedCorners=" + mRoundedCorners : ""); + result.append("\n "); result.append(isRound() ? "round" : ""); result.append("}"); return result.toString(); @@ -947,6 +972,9 @@ public final class WindowInsets { : mDisplayCutout == null ? DisplayCutout.NO_CUTOUT : mDisplayCutout.inset(left, top, right, bottom), + mRoundedCorners == null + ? RoundedCorners.NO_ROUNDED_CORNERS + : mRoundedCorners.inset(left, top, right, bottom), mCompatInsetsTypes, mCompatIgnoreVisibility); } @@ -964,13 +992,14 @@ public final class WindowInsets { && Arrays.equals(mTypeInsetsMap, that.mTypeInsetsMap) && Arrays.equals(mTypeMaxInsetsMap, that.mTypeMaxInsetsMap) && Arrays.equals(mTypeVisibilityMap, that.mTypeVisibilityMap) - && Objects.equals(mDisplayCutout, that.mDisplayCutout); + && Objects.equals(mDisplayCutout, that.mDisplayCutout) + && Objects.equals(mRoundedCorners, that.mRoundedCorners); } @Override public int hashCode() { return Objects.hash(Arrays.hashCode(mTypeInsetsMap), Arrays.hashCode(mTypeMaxInsetsMap), - Arrays.hashCode(mTypeVisibilityMap), mIsRound, mDisplayCutout, + Arrays.hashCode(mTypeVisibilityMap), mIsRound, mDisplayCutout, mRoundedCorners, mAlwaysConsumeSystemBars, mSystemWindowInsetsConsumed, mStableInsetsConsumed, mDisplayCutoutConsumed); } @@ -1032,6 +1061,7 @@ public final class WindowInsets { private boolean mStableInsetsConsumed = true; private DisplayCutout mDisplayCutout; + private RoundedCorners mRoundedCorners = RoundedCorners.NO_ROUNDED_CORNERS; private boolean mIsRound; private boolean mAlwaysConsumeSystemBars; @@ -1057,6 +1087,7 @@ public final class WindowInsets { mSystemInsetsConsumed = insets.mSystemWindowInsetsConsumed; mStableInsetsConsumed = insets.mStableInsetsConsumed; mDisplayCutout = displayCutoutCopyConstructorArgument(insets); + mRoundedCorners = insets.mRoundedCorners; mIsRound = insets.mIsRound; mAlwaysConsumeSystemBars = insets.mAlwaysConsumeSystemBars; } @@ -1260,6 +1291,29 @@ public final class WindowInsets { return this; } + /** @hide */ + @NonNull + public Builder setRoundedCorners(RoundedCorners roundedCorners) { + mRoundedCorners = roundedCorners != null + ? roundedCorners : RoundedCorners.NO_ROUNDED_CORNERS; + return this; + } + + /** + * Sets the rounded corner of given position. + * + * @see #getRoundedCorner(int) + * @param position the position of this rounded corner + * @param roundedCorner the rounded corner or null if there is none + * @return itself + */ + @NonNull + public Builder setRoundedCorner(@RoundedCorner.Position int position, + @Nullable RoundedCorner roundedCorner) { + mRoundedCorners.setRoundedCorner(position, roundedCorner); + return this; + } + /** @hide */ @NonNull public Builder setRound(boolean round) { @@ -1283,7 +1337,7 @@ public final class WindowInsets { public WindowInsets build() { return new WindowInsets(mSystemInsetsConsumed ? null : mTypeInsetsMap, mStableInsetsConsumed ? null : mTypeMaxInsetsMap, mTypeVisibilityMap, - mIsRound, mAlwaysConsumeSystemBars, mDisplayCutout, + mIsRound, mAlwaysConsumeSystemBars, mDisplayCutout, mRoundedCorners, systemBars(), false /* compatIgnoreVisibility */); } } diff --git a/core/tests/coretests/src/android/view/InsetsStateTest.java b/core/tests/coretests/src/android/view/InsetsStateTest.java index 9705284d7d744..cfe38033c1dd5 100644 --- a/core/tests/coretests/src/android/view/InsetsStateTest.java +++ b/core/tests/coretests/src/android/view/InsetsStateTest.java @@ -27,6 +27,10 @@ import static android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR; import static android.view.InsetsState.ITYPE_IME; import static android.view.InsetsState.ITYPE_NAVIGATION_BAR; import static android.view.InsetsState.ITYPE_STATUS_BAR; +import static android.view.RoundedCorner.POSITION_BOTTOM_LEFT; +import static android.view.RoundedCorner.POSITION_BOTTOM_RIGHT; +import static android.view.RoundedCorner.POSITION_TOP_LEFT; +import static android.view.RoundedCorner.POSITION_TOP_RIGHT; import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_STABLE; import static android.view.WindowInsets.Type.ime; import static android.view.WindowInsets.Type.navigationBars; @@ -427,6 +431,27 @@ public class InsetsStateTest { cutout.getBoundingRectBottom()); } + @Test + public void testCalculateRelativeRoundedCorners() { + mState.setDisplayFrame(new Rect(0, 0, 200, 400)); + mState.setRoundedCorners(new RoundedCorners( + new RoundedCorner(POSITION_TOP_LEFT, 10, 10, 10), + new RoundedCorner(POSITION_TOP_RIGHT, 10, 190, 10), + new RoundedCorner(POSITION_BOTTOM_RIGHT, 20, 180, 380), + new RoundedCorner(POSITION_BOTTOM_LEFT, 20, 20, 380))); + WindowInsets windowInsets = mState.calculateInsets(new Rect(1, 2, 197, 396), null, false, + false, SOFT_INPUT_ADJUST_UNSPECIFIED, 0, 0, TYPE_APPLICATION, + WINDOWING_MODE_UNDEFINED, new SparseIntArray()); + assertEquals(new RoundedCorner(POSITION_TOP_LEFT, 10, 9, 8), + windowInsets.getRoundedCorner(POSITION_TOP_LEFT)); + assertEquals(new RoundedCorner(POSITION_TOP_RIGHT, 10, 189, 8), + windowInsets.getRoundedCorner(POSITION_TOP_RIGHT)); + assertEquals(new RoundedCorner(POSITION_BOTTOM_RIGHT, 20, 179, 378), + windowInsets.getRoundedCorner(POSITION_BOTTOM_RIGHT)); + assertEquals(new RoundedCorner(POSITION_BOTTOM_LEFT, 20, 19, 378), + windowInsets.getRoundedCorner(POSITION_BOTTOM_LEFT)); + } + private void assertEqualsAndHashCode() { assertEquals(mState, mState2); assertEquals(mState.hashCode(), mState2.hashCode()); diff --git a/core/tests/coretests/src/android/view/RoundedCornerTest.java b/core/tests/coretests/src/android/view/RoundedCornerTest.java new file mode 100644 index 0000000000000..8eb13bc03b4be --- /dev/null +++ b/core/tests/coretests/src/android/view/RoundedCornerTest.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2021 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 org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; + +import android.graphics.Point; +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; + +@RunWith(AndroidJUnit4.class) +@SmallTest +@Presubmit +public class RoundedCornerTest { + + @Test + public void testGetPosition() { + RoundedCorner roundedCorner = new RoundedCorner( + RoundedCorner.POSITION_BOTTOM_LEFT, 2, 3, 4); + assertThat(roundedCorner.getPosition(), is(RoundedCorner.POSITION_BOTTOM_LEFT)); + } + + @Test + public void testGetRadius() { + RoundedCorner roundedCorner = new RoundedCorner( + RoundedCorner.POSITION_BOTTOM_LEFT, 2, 3, 4); + assertThat(roundedCorner.getRadius(), is(2)); + } + + @Test + public void testGetCenter() { + RoundedCorner roundedCorner = new RoundedCorner( + RoundedCorner.POSITION_BOTTOM_LEFT, 2, 3, 4); + assertThat(roundedCorner.getCenter(), equalTo(new Point(3, 4))); + } + + @Test + public void testIsEmpty() { + RoundedCorner roundedCorner = new RoundedCorner(RoundedCorner.POSITION_BOTTOM_LEFT); + assertThat(roundedCorner.isEmpty(), is(true)); + } + + @Test + public void testEquals() { + RoundedCorner roundedCorner = new RoundedCorner( + RoundedCorner.POSITION_BOTTOM_LEFT, 2, 3, 4); + RoundedCorner roundedCorner2 = new RoundedCorner( + RoundedCorner.POSITION_BOTTOM_LEFT, 2, 3, 4); + assertThat(roundedCorner, equalTo(roundedCorner2)); + } +} diff --git a/core/tests/coretests/src/android/view/RoundedCornersTest.java b/core/tests/coretests/src/android/view/RoundedCornersTest.java new file mode 100644 index 0000000000000..07ef33af3088e --- /dev/null +++ b/core/tests/coretests/src/android/view/RoundedCornersTest.java @@ -0,0 +1,202 @@ +/* + * Copyright (C) 2021 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.RoundedCorner.POSITION_BOTTOM_LEFT; +import static android.view.RoundedCorner.POSITION_BOTTOM_RIGHT; +import static android.view.RoundedCorner.POSITION_TOP_LEFT; +import static android.view.RoundedCorner.POSITION_TOP_RIGHT; +import static android.view.Surface.ROTATION_270; +import static android.view.Surface.ROTATION_90; + +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.nullValue; +import static org.hamcrest.Matchers.sameInstance; +import static org.junit.Assert.assertThat; + +import android.platform.test.annotations.Presubmit; +import android.util.Pair; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +@SmallTest +@Presubmit +public class RoundedCornersTest { + + final RoundedCorners mRoundedCorners = new RoundedCorners( + new RoundedCorner(POSITION_TOP_LEFT, 10, 10, 10), + new RoundedCorner(POSITION_TOP_RIGHT, 10, 190, 10), + new RoundedCorner(POSITION_BOTTOM_RIGHT, 20, 180, 380), + new RoundedCorner(POSITION_BOTTOM_LEFT, 20, 20, 380)); + + @Test + public void testGetRoundedCorner() { + final Pair radius = new Pair<>(10, 20); + RoundedCorners roundedCorners = RoundedCorners.fromRadii(radius, 200, 400); + + assertThat(roundedCorners.getRoundedCorner(POSITION_TOP_LEFT), + equalTo(new RoundedCorner(POSITION_TOP_LEFT, 10, 10, 10))); + assertThat(roundedCorners.getRoundedCorner(POSITION_TOP_RIGHT), + equalTo(new RoundedCorner(POSITION_TOP_RIGHT, 10, 190, 10))); + assertThat(roundedCorners.getRoundedCorner(POSITION_BOTTOM_RIGHT), + equalTo(new RoundedCorner(POSITION_BOTTOM_RIGHT, 20, 180, 380))); + assertThat(roundedCorners.getRoundedCorner(POSITION_BOTTOM_LEFT), + equalTo(new RoundedCorner(POSITION_BOTTOM_LEFT, 20, 20, 380))); + } + + @Test + public void testGetRoundedCorner_noRoundedCorners() { + RoundedCorners roundedCorners = RoundedCorners.NO_ROUNDED_CORNERS; + + assertThat(roundedCorners.getRoundedCorner(POSITION_TOP_LEFT), nullValue()); + assertThat(roundedCorners.getRoundedCorner(POSITION_TOP_RIGHT), nullValue()); + assertThat(roundedCorners.getRoundedCorner(POSITION_BOTTOM_RIGHT), nullValue()); + assertThat(roundedCorners.getRoundedCorner(POSITION_BOTTOM_LEFT), nullValue()); + } + + @Test + public void testHashCode() { + assertThat(mRoundedCorners.hashCode(), + equalTo(RoundedCorners.fromRadii(new Pair<>(10, 20), 200, 400).hashCode())); + assertThat(mRoundedCorners.hashCode(), + not(equalTo(RoundedCorners.fromRadii(new Pair<>(5, 10), 200, 400).hashCode()))); + } + + @Test + public void testEquals() { + assertThat(mRoundedCorners, + equalTo(RoundedCorners.fromRadii(new Pair<>(10, 20), 200, 400))); + + assertThat(mRoundedCorners, + not(equalTo(RoundedCorners.fromRadii(new Pair<>(5, 10), 200, 400)))); + } + + @Test + public void testSetRoundedCorner() { + RoundedCorner roundedCorner = new RoundedCorner(POSITION_BOTTOM_LEFT, 5, 6, 7); + mRoundedCorners.setRoundedCorner(POSITION_BOTTOM_LEFT, roundedCorner); + + assertThat(mRoundedCorners.getRoundedCorner(POSITION_BOTTOM_LEFT), equalTo(roundedCorner)); + } + + @Test + public void testSetRoundedCorner_null() { + mRoundedCorners.setRoundedCorner(POSITION_BOTTOM_LEFT, null); + + assertThat(mRoundedCorners.mRoundedCorners[POSITION_BOTTOM_LEFT], + equalTo(new RoundedCorner(POSITION_BOTTOM_LEFT))); + assertThat(mRoundedCorners.getRoundedCorner(POSITION_BOTTOM_LEFT), nullValue()); + } + + @Test + public void testInsetRoundedCorners_partialOverlap() { + RoundedCorners roundedCorners = mRoundedCorners.inset(1, 2, 3, 4); + + assertThat(roundedCorners.mRoundedCorners[POSITION_TOP_LEFT], + equalTo(new RoundedCorner(POSITION_TOP_LEFT, 10, 9, 8))); + assertThat(roundedCorners.mRoundedCorners[POSITION_TOP_RIGHT], + equalTo(new RoundedCorner(POSITION_TOP_RIGHT, 10, 189, 8))); + assertThat(roundedCorners.mRoundedCorners[POSITION_BOTTOM_RIGHT], + equalTo(new RoundedCorner(POSITION_BOTTOM_RIGHT, 20, 179, 378))); + assertThat(roundedCorners.mRoundedCorners[POSITION_BOTTOM_LEFT], + equalTo(new RoundedCorner(POSITION_BOTTOM_LEFT, 20, 19, 378))); + } + + @Test + public void testInsetRoundedCorners_noOverlap() { + RoundedCorners roundedCorners = mRoundedCorners.inset(20, 20, 20, 20); + + assertThat(roundedCorners.mRoundedCorners[POSITION_TOP_LEFT].isEmpty(), is(true)); + assertThat(roundedCorners.mRoundedCorners[POSITION_TOP_RIGHT].isEmpty(), is(true)); + assertThat(roundedCorners.mRoundedCorners[POSITION_BOTTOM_RIGHT].isEmpty(), is(true)); + assertThat(roundedCorners.mRoundedCorners[POSITION_BOTTOM_LEFT].isEmpty(), is(true)); + } + + @Test + public void testRotateRoundedCorners_90() { + final Pair radius = new Pair<>(10, 20); + RoundedCorners roundedCorners = RoundedCorners.fromRadii(radius, 200, 400) + .rotate(ROTATION_90, 200, 400); + + assertThat(roundedCorners.getRoundedCorner(POSITION_TOP_LEFT), + equalTo(new RoundedCorner(POSITION_TOP_LEFT, 10, 10, 10))); + assertThat(roundedCorners.getRoundedCorner(POSITION_TOP_RIGHT), + equalTo(new RoundedCorner(POSITION_TOP_RIGHT, 20, 380, 20))); + assertThat(roundedCorners.getRoundedCorner(POSITION_BOTTOM_RIGHT), + equalTo(new RoundedCorner(POSITION_BOTTOM_RIGHT, 20, 380, 180))); + assertThat(roundedCorners.getRoundedCorner(POSITION_BOTTOM_LEFT), + equalTo(new RoundedCorner(POSITION_BOTTOM_LEFT, 10, 10, 190))); + } + + @Test + public void testRotateRoundedCorners_270() { + final Pair radius = new Pair<>(10, 20); + RoundedCorners roundedCorners = RoundedCorners.fromRadii(radius, 200, 400) + .rotate(ROTATION_270, 200, 400); + + assertThat(roundedCorners.getRoundedCorner(POSITION_TOP_LEFT), + equalTo(new RoundedCorner(POSITION_TOP_LEFT, 20, 20, 20))); + assertThat(roundedCorners.getRoundedCorner(POSITION_TOP_RIGHT), + equalTo(new RoundedCorner(POSITION_TOP_RIGHT, 10, 390, 10))); + assertThat(roundedCorners.getRoundedCorner(POSITION_BOTTOM_RIGHT), + equalTo(new RoundedCorner(POSITION_BOTTOM_RIGHT, 10, 390, 190))); + assertThat(roundedCorners.getRoundedCorner(POSITION_BOTTOM_LEFT), + equalTo(new RoundedCorner(POSITION_BOTTOM_LEFT, 20, 20, 180))); + } + + @Test + public void testFromRadius_cache() { + final Pair radius = new Pair<>(10, 20); + RoundedCorners cached = RoundedCorners.fromRadii(radius, 200, 400); + + assertThat(RoundedCorners.fromRadii(radius, 200, 400), sameInstance(cached)); + } + + @Test + public void testFromRadius_wontCacheIfRadiusChanged() { + final Pair radius = new Pair<>(10, 20); + RoundedCorners cached = RoundedCorners.fromRadii(radius, 200, 400); + + assertThat(RoundedCorners.fromRadii(new Pair<>(5, 10), 200, 400), + not(sameInstance(cached))); + } + + @Test + public void testFromRadius_wontCacheIfDisplayWidthChanged() { + final Pair radius = new Pair<>(10, 20); + RoundedCorners cached = RoundedCorners.fromRadii(radius, 200, 400); + + assertThat(RoundedCorners.fromRadii(radius, 100, 400), + not(sameInstance(cached))); + } + + @Test + public void testFromRadius_wontCacheIfDisplayHeightChanged() { + final Pair radius = new Pair<>(10, 20); + RoundedCorners cached = RoundedCorners.fromRadii(radius, 200, 400); + + assertThat(RoundedCorners.fromRadii(radius, 200, 300), + not(sameInstance(cached))); + } +} diff --git a/core/tests/coretests/src/android/view/WindowInsetsTest.java b/core/tests/coretests/src/android/view/WindowInsetsTest.java index 8e4e73597eaf6..b80837fbe6ca8 100644 --- a/core/tests/coretests/src/android/view/WindowInsetsTest.java +++ b/core/tests/coretests/src/android/view/WindowInsetsTest.java @@ -61,7 +61,7 @@ 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, - systemBars(), true /* compatIgnoreVisibility */); + null, systemBars(), true /* compatIgnoreVisibility */); assertEquals(Insets.of(0, 10, 0, 0), windowInsets.getSystemWindowInsets()); } } diff --git a/services/core/java/com/android/server/display/DisplayDeviceInfo.java b/services/core/java/com/android/server/display/DisplayDeviceInfo.java index 2641ee7ed33ed..bf16a6d5efb9c 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.RoundedCorners; import android.view.Surface; import com.android.internal.BrightnessSynchronizer; @@ -280,6 +281,11 @@ final class DisplayDeviceInfo { */ public DisplayCutout displayCutout; + /** + * The {@link RoundedCorners} if present or {@code null} otherwise. + */ + public RoundedCorners roundedCorners; + /** * The touch attachment, per {@link DisplayViewport#touch}. */ @@ -401,7 +407,8 @@ final class DisplayDeviceInfo { || !BrightnessSynchronizer.floatEquals(brightnessMinimum, other.brightnessMinimum) || !BrightnessSynchronizer.floatEquals(brightnessMaximum, other.brightnessMaximum) || !BrightnessSynchronizer.floatEquals(brightnessDefault, - other.brightnessDefault)) { + other.brightnessDefault) + || !Objects.equals(roundedCorners, other.roundedCorners)) { diff |= DIFF_OTHER; } return diff; @@ -444,6 +451,7 @@ final class DisplayDeviceInfo { brightnessMinimum = other.brightnessMinimum; brightnessMaximum = other.brightnessMaximum; brightnessDefault = other.brightnessDefault; + roundedCorners = other.roundedCorners; } // For debugging purposes @@ -487,6 +495,9 @@ final class DisplayDeviceInfo { sb.append(", brightnessMinimum ").append(brightnessMinimum); sb.append(", brightnessMaximum ").append(brightnessMaximum); sb.append(", brightnessDefault ").append(brightnessDefault); + if (roundedCorners != null) { + sb.append(", roundedCorners ").append(roundedCorners); + } sb.append(flagsToString(flags)); 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 8198e510f8e2a..7e6a13766746d 100644 --- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java +++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java @@ -35,6 +35,7 @@ import android.view.Display; import android.view.DisplayAddress; import android.view.DisplayCutout; import android.view.DisplayEventReceiver; +import android.view.RoundedCorners; import android.view.SurfaceControl; import com.android.internal.BrightnessSynchronizer; @@ -604,6 +605,8 @@ final class LocalDisplayAdapter extends DisplayAdapter { } mInfo.displayCutout = DisplayCutout.fromResourcesRectApproximation(res, mInfo.width, mInfo.height); + mInfo.roundedCorners = RoundedCorners.fromResources( + res, mInfo.width, mInfo.height); } else { if (!res.getBoolean( com.android.internal.R.bool.config_localDisplaysMirrorContent)) { diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java index 86de159b5824e..80781d280d5ee 100644 --- a/services/core/java/com/android/server/display/LogicalDisplay.java +++ b/services/core/java/com/android/server/display/LogicalDisplay.java @@ -195,6 +195,7 @@ final class LogicalDisplay { info.logicalDensityDpi = mOverrideDisplayInfo.logicalDensityDpi; info.physicalXDpi = mOverrideDisplayInfo.physicalXDpi; info.physicalYDpi = mOverrideDisplayInfo.physicalYDpi; + info.roundedCorners = mOverrideDisplayInfo.roundedCorners; } mInfo.set(info); } @@ -386,6 +387,7 @@ final class LogicalDisplay { mBaseDisplayInfo.brightnessMinimum = deviceInfo.brightnessMinimum; mBaseDisplayInfo.brightnessMaximum = deviceInfo.brightnessMaximum; mBaseDisplayInfo.brightnessDefault = deviceInfo.brightnessDefault; + mBaseDisplayInfo.roundedCorners = deviceInfo.roundedCorners; mPrimaryDisplayDeviceInfo = deviceInfo; mInfo.set(null); } diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index d5d06f9a00332..2d2f6f8284c27 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -204,6 +204,7 @@ import android.view.InsetsState; import android.view.InsetsState.InternalInsetsType; import android.view.MagnificationSpec; import android.view.RemoteAnimationDefinition; +import android.view.RoundedCorners; import android.view.Surface; import android.view.SurfaceControl; import android.view.SurfaceControl.Transaction; @@ -337,6 +338,10 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp = new RotationCache<>(this::calculateDisplayCutoutForRotationUncached); boolean mIgnoreDisplayCutout; + RoundedCorners mInitialRoundedCorners; + private final RotationCache mRoundedCornerCache = + new RotationCache<>(this::calculateRoundedCornersForRotationUncached); + /** * Overridden display size. Initialized with {@link #mInitialDisplayWidth} * and {@link #mInitialDisplayHeight}, but can be set via shell command "adb shell wm size". @@ -983,7 +988,8 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp isDefaultDisplay = mDisplayId == DEFAULT_DISPLAY; mInsetsStateController = new InsetsStateController(this); mDisplayFrames = new DisplayFrames(mDisplayId, mInsetsStateController.getRawInsetsState(), - mDisplayInfo, calculateDisplayCutoutForRotation(mDisplayInfo.rotation)); + mDisplayInfo, calculateDisplayCutoutForRotation(mDisplayInfo.rotation), + calculateRoundedCornersForRotation(mDisplayInfo.rotation)); initializeDisplayBaseInfo(); mAppTransition = new AppTransition(mWmService.mContext, mWmService, this); @@ -1696,8 +1702,9 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp mTmpConfiguration.unset(); final DisplayInfo info = computeScreenConfiguration(mTmpConfiguration, rotation); final WmDisplayCutout cutout = calculateDisplayCutoutForRotation(rotation); + final RoundedCorners roundedCorners = calculateRoundedCornersForRotation(rotation); final DisplayFrames displayFrames = new DisplayFrames(mDisplayId, new InsetsState(), info, - cutout); + cutout, roundedCorners); token.applyFixedRotationTransform(info, displayFrames, mTmpConfiguration); } @@ -1958,6 +1965,23 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp rotated ? mInitialDisplayWidth : mInitialDisplayHeight); } + RoundedCorners calculateRoundedCornersForRotation(int rotation) { + return mRoundedCornerCache.getOrCompute(mInitialRoundedCorners, rotation); + } + + private RoundedCorners calculateRoundedCornersForRotationUncached( + RoundedCorners roundedCorners, int rotation) { + if (roundedCorners == null || roundedCorners == RoundedCorners.NO_ROUNDED_CORNERS) { + return RoundedCorners.NO_ROUNDED_CORNERS; + } + + if (rotation == ROTATION_0) { + return roundedCorners; + } + + return roundedCorners.rotate(rotation, mInitialDisplayWidth, mInitialDisplayHeight); + } + /** * Compute display info and configuration according to the given rotation without changing * current display. @@ -2485,7 +2509,8 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp void onDisplayInfoChanged() { final DisplayInfo info = mDisplayInfo; - mDisplayFrames.onDisplayInfoUpdated(info, calculateDisplayCutoutForRotation(info.rotation)); + mDisplayFrames.onDisplayInfoUpdated(info, calculateDisplayCutoutForRotation(info.rotation), + calculateRoundedCornersForRotation(info.rotation)); mInputMonitor.layoutInputConsumers(info.logicalWidth, info.logicalHeight); mDisplayPolicy.onDisplayInfoChanged(info); } @@ -2518,6 +2543,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp mInitialDisplayHeight = mDisplayInfo.logicalHeight; mInitialDisplayDensity = mDisplayInfo.logicalDensityDpi; mInitialDisplayCutout = mDisplayInfo.displayCutout; + mInitialRoundedCorners = mDisplayInfo.roundedCorners; } /** @@ -2535,11 +2561,13 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp final DisplayCutout newCutout = mIgnoreDisplayCutout ? DisplayCutout.NO_CUTOUT : mDisplayInfo.displayCutout; final String newUniqueId = mDisplayInfo.uniqueId; + final RoundedCorners newRoundedCorners = mDisplayInfo.roundedCorners; final boolean displayMetricsChanged = mInitialDisplayWidth != newWidth || mInitialDisplayHeight != newHeight || mInitialDisplayDensity != mDisplayInfo.logicalDensityDpi - || !Objects.equals(mInitialDisplayCutout, newCutout); + || !Objects.equals(mInitialDisplayCutout, newCutout) + || !Objects.equals(mInitialRoundedCorners, newRoundedCorners); final boolean physicalDisplayChanged = !newUniqueId.equals(mCurrentUniqueDisplayId); if (displayMetricsChanged || physicalDisplayChanged) { @@ -2558,6 +2586,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp mInitialDisplayHeight = newHeight; mInitialDisplayDensity = newDensity; mInitialDisplayCutout = newCutout; + mInitialRoundedCorners = newRoundedCorners; 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 43c143596918a..e4230a2c87603 100644 --- a/services/core/java/com/android/server/wm/DisplayFrames.java +++ b/services/core/java/com/android/server/wm/DisplayFrames.java @@ -21,12 +21,12 @@ import static android.view.InsetsState.ITYPE_LEFT_DISPLAY_CUTOUT; import static android.view.InsetsState.ITYPE_RIGHT_DISPLAY_CUTOUT; import static android.view.InsetsState.ITYPE_TOP_DISPLAY_CUTOUT; -import android.annotation.NonNull; import android.graphics.Rect; import android.util.proto.ProtoOutputStream; import android.view.DisplayCutout; import android.view.DisplayInfo; import android.view.InsetsState; +import android.view.RoundedCorners; import com.android.server.wm.utils.WmDisplayCutout; @@ -47,9 +47,6 @@ public class DisplayFrames { */ public final Rect mUnrestricted = new Rect(); - /** The display cutout used for layout (after rotation) */ - @NonNull public WmDisplayCutout mDisplayCutout = WmDisplayCutout.NO_CUTOUT; - /** * During layout, the frame that is display-cutout safe, i.e. that does not intersect with it. */ @@ -61,26 +58,37 @@ public class DisplayFrames { public int mRotation; public DisplayFrames(int displayId, InsetsState insetsState, DisplayInfo info, - WmDisplayCutout displayCutout) { + WmDisplayCutout displayCutout, RoundedCorners roundedCorners) { mDisplayId = displayId; mInsetsState = insetsState; - onDisplayInfoUpdated(info, displayCutout); + onDisplayInfoUpdated(info, displayCutout, roundedCorners); } - public void onDisplayInfoUpdated(DisplayInfo info, WmDisplayCutout displayCutout) { + /** + * Update {@link DisplayFrames} when {@link DisplayInfo} is updated. + * + * @param info the updated {@link DisplayInfo}. + * @param displayCutout the updated {@link DisplayCutout}. + * @param roundedCorners the updated {@link RoundedCorners}. + */ + public void onDisplayInfoUpdated(DisplayInfo info, WmDisplayCutout displayCutout, + RoundedCorners roundedCorners) { mDisplayWidth = info.logicalWidth; mDisplayHeight = info.logicalHeight; mRotation = info.rotation; - mDisplayCutout = displayCutout != null ? displayCutout : WmDisplayCutout.NO_CUTOUT; + final WmDisplayCutout wmDisplayCutout = + displayCutout != null ? displayCutout : WmDisplayCutout.NO_CUTOUT; final InsetsState state = mInsetsState; final Rect unrestricted = mUnrestricted; final Rect safe = mDisplayCutoutSafe; - final DisplayCutout cutout = mDisplayCutout.getDisplayCutout(); + final DisplayCutout cutout = wmDisplayCutout.getDisplayCutout(); unrestricted.set(0, 0, mDisplayWidth, mDisplayHeight); safe.set(Integer.MIN_VALUE, Integer.MIN_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE); state.setDisplayFrame(unrestricted); state.setDisplayCutout(cutout); + state.setRoundedCorners(roundedCorners != null ? roundedCorners + : RoundedCorners.NO_ROUNDED_CORNERS); if (!cutout.isEmpty()) { if (cutout.getSafeInsetLeft() > 0) { safe.left = unrestricted.left + cutout.getSafeInsetLeft(); @@ -118,12 +126,5 @@ public class DisplayFrames { public void dump(String prefix, PrintWriter pw) { pw.println(prefix + "DisplayFrames w=" + mDisplayWidth + " h=" + mDisplayHeight + " r=" + mRotation); - final String myPrefix = prefix + " "; - dumpFrame(mUnrestricted, "mUnrestricted", myPrefix, pw); - pw.println(myPrefix + "mDisplayCutout=" + mDisplayCutout); - } - - private void dumpFrame(Rect frame, String name, String prefix, PrintWriter pw) { - pw.print(prefix + name + "="); frame.printShortString(pw); pw.println(); } } 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 37fb0e930acce..0afdc589ad4ef 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java @@ -66,6 +66,7 @@ import android.view.DisplayCutout; import android.view.DisplayInfo; import android.view.Gravity; import android.view.InsetsState; +import android.view.RoundedCorners; import android.view.WindowInsets.Side; import android.view.WindowInsets.Type; import android.view.WindowManager; @@ -99,6 +100,7 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase { private int mRotation = ROTATION_0; private boolean mHasDisplayCutout; private boolean mIsLongEdgeDisplayCutout; + private boolean mHasRoundedCorners; private final Rect mDisplayBounds = new Rect(); @@ -153,6 +155,11 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase { updateDisplayFrames(); } + public void addRoundedCorners() { + mHasRoundedCorners = true; + updateDisplayFrames(); + } + private void updateDisplayFrames() { mFrames = createDisplayFrames( mDisplayContent.getInsetsStateController().getRawInsetsState()); @@ -166,9 +173,11 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase { private DisplayFrames createDisplayFrames(InsetsState insetsState) { final Pair info = displayInfoAndCutoutForRotation(mRotation, mHasDisplayCutout, mIsLongEdgeDisplayCutout); + final RoundedCorners roundedCorners = mHasRoundedCorners + ? mDisplayContent.calculateRoundedCornersForRotation(mRotation) + : RoundedCorners.NO_ROUNDED_CORNERS; return new DisplayFrames(mDisplayContent.getDisplayId(), - insetsState, - info.first, info.second); + insetsState, info.first, info.second, roundedCorners); } @Test @@ -753,6 +762,8 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase { assertSimulateLayoutSameDisplayFrames(); addDisplayCutout(); assertSimulateLayoutSameDisplayFrames(); + addRoundedCorners(); + assertSimulateLayoutSameDisplayFrames(); } private void assertSimulateLayoutSameDisplayFrames() { 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 499507e969cf7..2163661b7506f 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java @@ -302,7 +302,7 @@ public class DisplayPolicyTests extends WindowTestsBase { final InsetsState state = mDisplayContent.getInsetsStateController().getRawInsetsState(); mImeWindow.mAboveInsetsState = state; mDisplayContent.mDisplayFrames = new DisplayFrames(mDisplayContent.getDisplayId(), - state, displayInfo, null /* displayCutout */); + state, displayInfo, null /* displayCutout */, null /* roundedCorners*/); 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 53cea5a8714a6..d1d0ac68017af 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java @@ -47,6 +47,7 @@ import android.platform.test.annotations.Presubmit; import android.view.DisplayInfo; import android.view.Gravity; import android.view.InsetsState; +import android.view.RoundedCorners; import android.view.Surface; import android.view.WindowManager; @@ -146,7 +147,7 @@ public class WallpaperControllerTests extends WindowTestsBase { final DisplayInfo info = dc.computeScreenConfiguration(config, Surface.ROTATION_0); final WmDisplayCutout cutout = dc.calculateDisplayCutoutForRotation(Surface.ROTATION_0); final DisplayFrames displayFrames = new DisplayFrames(dc.getDisplayId(), new InsetsState(), - info, cutout); + info, cutout, RoundedCorners.NO_ROUNDED_CORNERS); wallpaperWindowToken.applyFixedRotationTransform(info, displayFrames, config); // Check that the wallpaper has the same frame in landscape than in portrait