diff options
| author | 2021-01-29 12:42:48 +0000 | |
|---|---|---|
| committer | 2021-01-29 12:42:48 +0000 | |
| commit | 7ee87037f51e651e43c207c67582b36999b3de4a (patch) | |
| tree | 5438c3027a21d1f50d8ccf7e12de0ff027809311 | |
| parent | 4fb596f006478c955e7bcb785b2bddb99d5e5a6b (diff) | |
| parent | f7ff55217c975590b190e3805e5bd6ca87c2b0af (diff) | |
Merge "Create an API to get a cutout path" into sc-dev
8 files changed, 406 insertions, 62 deletions
diff --git a/core/api/current.txt b/core/api/current.txt index ec712d875323..3dd1f598f94f 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -46182,6 +46182,7 @@ package android.view { method @NonNull public android.graphics.Rect getBoundingRectRight(); method @NonNull public android.graphics.Rect getBoundingRectTop(); method @NonNull public java.util.List<android.graphics.Rect> getBoundingRects(); + method @Nullable public android.graphics.Path getCutoutPath(); method public int getSafeInsetBottom(); method public int getSafeInsetLeft(); method public int getSafeInsetRight(); diff --git a/core/java/android/util/RotationUtils.java b/core/java/android/util/RotationUtils.java index a44ed59c14d4..698cb77947cf 100644 --- a/core/java/android/util/RotationUtils.java +++ b/core/java/android/util/RotationUtils.java @@ -21,7 +21,9 @@ import static android.view.Surface.ROTATION_180; import static android.view.Surface.ROTATION_270; import static android.view.Surface.ROTATION_90; +import android.annotation.Dimension; import android.graphics.Insets; +import android.graphics.Matrix; import android.view.Surface.Rotation; /** @@ -69,4 +71,34 @@ public class RotationUtils { } return rotated; } + + /** + * Sets a matrix such that given a rotation, it transforms physical display + * coordinates to that rotation's logical coordinates. + * + * @param rotation the rotation to which the matrix should transform + * @param out the matrix to be set + */ + public static void transformPhysicalToLogicalCoordinates(@Rotation int rotation, + @Dimension int physicalWidth, @Dimension int physicalHeight, Matrix out) { + switch (rotation) { + case ROTATION_0: + out.reset(); + break; + case ROTATION_90: + out.setRotate(270); + out.postTranslate(0, physicalWidth); + break; + case ROTATION_180: + out.setRotate(180); + out.postTranslate(physicalWidth, physicalHeight); + break; + case ROTATION_270: + out.setRotate(90); + out.postTranslate(physicalHeight, 0); + break; + default: + throw new IllegalArgumentException("Unknown rotation: " + rotation); + } + } } diff --git a/core/java/android/view/DisplayCutout.java b/core/java/android/view/DisplayCutout.java index 525ac534612d..e1a4402d8964 100644 --- a/core/java/android/view/DisplayCutout.java +++ b/core/java/android/view/DisplayCutout.java @@ -23,6 +23,7 @@ import static android.view.DisplayCutoutProto.BOUND_LEFT; import static android.view.DisplayCutoutProto.BOUND_RIGHT; import static android.view.DisplayCutoutProto.BOUND_TOP; import static android.view.DisplayCutoutProto.INSETS; +import static android.view.Surface.ROTATION_0; import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE; @@ -31,13 +32,16 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.content.res.Resources; import android.graphics.Insets; +import android.graphics.Matrix; import android.graphics.Path; import android.graphics.Rect; import android.os.Parcel; import android.os.Parcelable; import android.text.TextUtils; import android.util.Pair; +import android.util.RotationUtils; import android.util.proto.ProtoOutputStream; +import android.view.Surface.Rotation; import com.android.internal.R; import com.android.internal.annotations.GuardedBy; @@ -69,6 +73,9 @@ public final class DisplayCutout { "com.android.internal.display_cutout_emulation"; private static final Rect ZERO_RECT = new Rect(); + private static final CutoutPathParserInfo EMPTY_PARSER_INFO = new CutoutPathParserInfo( + 0 /* displayWidth */, 0 /* displayHeight */, 0f /* density */, "" /* cutoutSpec */, + 0 /* rotation */, 0f /* scale */); /** * An instance where {@link #isEmpty()} returns {@code true}. @@ -76,7 +83,7 @@ public final class DisplayCutout { * @hide */ public static final DisplayCutout NO_CUTOUT = new DisplayCutout( - ZERO_RECT, Insets.NONE, ZERO_RECT, ZERO_RECT, ZERO_RECT, ZERO_RECT, + ZERO_RECT, Insets.NONE, ZERO_RECT, ZERO_RECT, ZERO_RECT, ZERO_RECT, EMPTY_PARSER_INFO, false /* copyArguments */); @@ -96,11 +103,15 @@ public final class DisplayCutout { @GuardedBy("CACHE_LOCK") private static Insets sCachedWaterfallInsets; + @GuardedBy("CACHE_LOCK") + private static CutoutPathParserInfo sCachedCutoutPathParserInfo; + @GuardedBy("CACHE_LOCK") + private static Path sCachedCutoutPath; + private final Rect mSafeInsets; @NonNull private final Insets mWaterfallInsets; - /** * The bound is at the left of the screen. * @hide @@ -210,6 +221,7 @@ public final class DisplayCutout { } return result; } + @Override public boolean equals(@Nullable Object o) { if (o == this) { @@ -232,6 +244,106 @@ public final class DisplayCutout { private final Bounds mBounds; /** + * Stores all the needed info to create the cutout paths. + * + * @hide + */ + public static class CutoutPathParserInfo { + private final int mDisplayWidth; + private final int mDisplayHeight; + private final float mDensity; + private final String mCutoutSpec; + private final @Rotation int mRotation; + private final float mScale; + + public CutoutPathParserInfo(int displayWidth, int displayHeight, float density, + String cutoutSpec, @Rotation int rotation, float scale) { + mDisplayWidth = displayWidth; + mDisplayHeight = displayHeight; + mDensity = density; + mCutoutSpec = cutoutSpec == null ? "" : cutoutSpec; + mRotation = rotation; + mScale = scale; + } + + public CutoutPathParserInfo(CutoutPathParserInfo cutoutPathParserInfo) { + mDisplayWidth = cutoutPathParserInfo.mDisplayWidth; + mDisplayHeight = cutoutPathParserInfo.mDisplayHeight; + mDensity = cutoutPathParserInfo.mDensity; + mCutoutSpec = cutoutPathParserInfo.mCutoutSpec; + mRotation = cutoutPathParserInfo.mRotation; + mScale = cutoutPathParserInfo.mScale; + } + + public int getDisplayWidth() { + return mDisplayWidth; + } + + public int getDisplayHeight() { + return mDisplayHeight; + } + + public float getDensity() { + return mDensity; + } + + public @NonNull String getCutoutSpec() { + return mCutoutSpec; + } + + public int getRotation() { + return mRotation; + } + + public float getScale() { + return mScale; + } + + private boolean hasCutout() { + return !mCutoutSpec.isEmpty(); + } + + @Override + public int hashCode() { + int result = 0; + result = result * 48271 + Integer.hashCode(mDisplayWidth); + result = result * 48271 + Integer.hashCode(mDisplayHeight); + result = result * 48271 + Float.hashCode(mDensity); + result = result * 48271 + mCutoutSpec.hashCode(); + result = result * 48271 + Integer.hashCode(mRotation); + result = result * 48271 + Float.hashCode(mScale); + return result; + } + + @Override + public boolean equals(@Nullable Object o) { + if (o == this) { + return true; + } + if (o instanceof CutoutPathParserInfo) { + CutoutPathParserInfo c = (CutoutPathParserInfo) o; + return mDisplayWidth == c.mDisplayWidth && mDisplayHeight == c.mDisplayHeight + && mDensity == c.mDensity && mCutoutSpec.equals(c.mCutoutSpec) + && mRotation == c.mRotation && mScale == c.mScale; + } + return false; + } + + @Override + public String toString() { + return "CutoutPathParserInfo{displayWidth=" + mDisplayWidth + + " displayHeight=" + mDisplayHeight + + " density={" + mDensity + "}" + + " cutoutSpec={" + mCutoutSpec + "}" + + " rotation={" + mRotation + "}" + + " scale={" + mScale + "}" + + "}"; + } + } + + private final @NonNull CutoutPathParserInfo mCutoutPathParserInfo; + + /** * Creates a DisplayCutout instance. * * <p>Note that this is only useful for tests. For production code, developers should always @@ -251,7 +363,8 @@ public final class DisplayCutout { // TODO(b/73953958): @VisibleForTesting(visibility = PRIVATE) public DisplayCutout(@NonNull Insets safeInsets, @Nullable Rect boundLeft, @Nullable Rect boundTop, @Nullable Rect boundRight, @Nullable Rect boundBottom) { - this(safeInsets.toRect(), Insets.NONE, boundLeft, boundTop, boundRight, boundBottom, true); + this(safeInsets.toRect(), Insets.NONE, boundLeft, boundTop, boundRight, boundBottom, null, + true); } /** @@ -276,7 +389,7 @@ public final class DisplayCutout { @Nullable Rect boundTop, @Nullable Rect boundRight, @Nullable Rect boundBottom, @NonNull Insets waterfallInsets) { this(safeInsets.toRect(), waterfallInsets, boundLeft, boundTop, boundRight, boundBottom, - true); + null, true); } /** @@ -294,7 +407,7 @@ public final class DisplayCutout { // TODO(b/73953958): @VisibleForTesting(visibility = PRIVATE) @Deprecated public DisplayCutout(@Nullable Rect safeInsets, @Nullable List<Rect> boundingRects) { - this(safeInsets, Insets.NONE, extractBoundsFromList(safeInsets, boundingRects), + this(safeInsets, Insets.NONE, extractBoundsFromList(safeInsets, boundingRects), null, true /* copyArguments */); } @@ -303,28 +416,42 @@ public final class DisplayCutout { * * @param safeInsets the insets from each edge which avoid the display cutout as returned by * {@link #getSafeInsetTop()} etc. + * @param waterfallInsets the insets for the curved areas in waterfall display. + * @param boundLeft the left bounding rect of the display cutout in pixels. If null is passed, + * it's treated as an empty rectangle (0,0)-(0,0). + * @param boundTop the top bounding rect of the display cutout in pixels. If null is passed, + * it's treated as an empty rectangle (0,0)-(0,0). + * @param boundRight the right bounding rect of the display cutout in pixels. If null is + * passed, it's treated as an empty rectangle (0,0)-(0,0). + * @param boundBottom the bottom bounding rect of the display cutout in pixels. If null is + * passed, it's treated as an empty rectangle (0,0)-(0,0). + * @param info the cutout path parser info. * @param copyArguments if true, create a copy of the arguments. If false, the passed arguments * are not copied and MUST remain unchanged forever. */ - private DisplayCutout(Rect safeInsets, Insets waterfallInsets, Rect boundLeft, - Rect boundTop, Rect boundRight, Rect boundBottom, boolean copyArguments) { + private DisplayCutout(Rect safeInsets, Insets waterfallInsets, Rect boundLeft, Rect boundTop, + Rect boundRight, Rect boundBottom, CutoutPathParserInfo info, + boolean copyArguments) { mSafeInsets = getCopyOrRef(safeInsets, copyArguments); mWaterfallInsets = waterfallInsets == null ? Insets.NONE : waterfallInsets; mBounds = new Bounds(boundLeft, boundTop, boundRight, boundBottom, copyArguments); + mCutoutPathParserInfo = info == null ? EMPTY_PARSER_INFO : info; } private DisplayCutout(Rect safeInsets, Insets waterfallInsets, Rect[] bounds, - boolean copyArguments) { + CutoutPathParserInfo info, boolean copyArguments) { mSafeInsets = getCopyOrRef(safeInsets, copyArguments); mWaterfallInsets = waterfallInsets == null ? Insets.NONE : waterfallInsets; mBounds = new Bounds(bounds, copyArguments); + mCutoutPathParserInfo = info == null ? EMPTY_PARSER_INFO : info; } - private DisplayCutout(Rect safeInsets, Insets waterfallInsets, Bounds bounds) { + private DisplayCutout(Rect safeInsets, Insets waterfallInsets, Bounds bounds, + CutoutPathParserInfo info) { mSafeInsets = safeInsets; mWaterfallInsets = waterfallInsets == null ? Insets.NONE : waterfallInsets; mBounds = bounds; - + mCutoutPathParserInfo = info == null ? EMPTY_PARSER_INFO : info; } private static Rect getCopyOrRef(Rect r, boolean copyArguments) { @@ -534,10 +661,70 @@ public final class DisplayCutout { return mBounds.getRect(BOUNDS_POSITION_BOTTOM); } + /** + * Returns a {@link Path} that contains the cutout paths of all sides on the display. + * + * To get a cutout path for one specific side, apps can intersect the {@link Path} with the + * {@link Rect} obtained from {@link #getBoundingRectLeft()}, {@link #getBoundingRectTop()}, + * {@link #getBoundingRectRight()} or {@link #getBoundingRectBottom()}. + * + * @return a {@link Path} contains all the cutout paths based on display coordinate. Returns + * null if there is no cutout on the display. + */ + public @Nullable Path getCutoutPath() { + if (!mCutoutPathParserInfo.hasCutout()) { + return null; + } + synchronized (CACHE_LOCK) { + if (mCutoutPathParserInfo.equals(sCachedCutoutPathParserInfo)) { + return sCachedCutoutPath; + } + } + final CutoutSpecification cutoutSpec = new CutoutSpecification.Parser( + mCutoutPathParserInfo.getDensity(), mCutoutPathParserInfo.getDisplayWidth(), + mCutoutPathParserInfo.getDisplayHeight()) + .parse(mCutoutPathParserInfo.getCutoutSpec()); + + final Path cutoutPath = cutoutSpec.getPath(); + if (cutoutPath == null || cutoutPath.isEmpty()) { + return null; + } + final Matrix matrix = new Matrix(); + if (mCutoutPathParserInfo.getRotation() != ROTATION_0) { + RotationUtils.transformPhysicalToLogicalCoordinates( + mCutoutPathParserInfo.getRotation(), + mCutoutPathParserInfo.getDisplayWidth(), + mCutoutPathParserInfo.getDisplayHeight(), + matrix + ); + } + matrix.postScale(mCutoutPathParserInfo.getScale(), mCutoutPathParserInfo.getScale()); + cutoutPath.transform(matrix); + + synchronized (CACHE_LOCK) { + sCachedCutoutPathParserInfo = new CutoutPathParserInfo(mCutoutPathParserInfo); + sCachedCutoutPath = cutoutPath; + } + return cutoutPath; + } + + /** + * @return the {@link CutoutPathParserInfo}; + * + * @hide + */ + public CutoutPathParserInfo getCutoutPathParserInfo() { + return mCutoutPathParserInfo; + } + @Override public int hashCode() { - return (mSafeInsets.hashCode() * 48271 + mBounds.hashCode()) * 48271 - + mWaterfallInsets.hashCode(); + int result = 0; + result = 48271 * result + mSafeInsets.hashCode(); + result = 48271 * result + mBounds.hashCode(); + result = 48271 * result + mWaterfallInsets.hashCode(); + result = 48271 * result + mCutoutPathParserInfo.hashCode(); + return result; } @Override @@ -548,7 +735,8 @@ public final class DisplayCutout { if (o instanceof DisplayCutout) { DisplayCutout c = (DisplayCutout) o; return mSafeInsets.equals(c.mSafeInsets) && mBounds.equals(c.mBounds) - && mWaterfallInsets.equals(c.mWaterfallInsets); + && mWaterfallInsets.equals(c.mWaterfallInsets) + && mCutoutPathParserInfo.equals(c.mCutoutPathParserInfo); } return false; } @@ -558,6 +746,7 @@ public final class DisplayCutout { return "DisplayCutout{insets=" + mSafeInsets + " waterfall=" + mWaterfallInsets + " boundingRect={" + mBounds + "}" + + " cutoutPathParserInfo={" + mCutoutPathParserInfo + "}" + "}"; } @@ -607,7 +796,7 @@ public final class DisplayCutout { } return new DisplayCutout(safeInsets, Insets.of(waterfallInsets), bounds, - false /* copyArguments */); + mCutoutPathParserInfo, false /* copyArguments */); } private Rect insetInsets(int insetLeft, int insetTop, int insetRight, int insetBottom, @@ -638,7 +827,8 @@ public final class DisplayCutout { * @hide */ public DisplayCutout replaceSafeInsets(Rect safeInsets) { - return new DisplayCutout(new Rect(safeInsets), mWaterfallInsets, mBounds); + return new DisplayCutout(new Rect(safeInsets), mWaterfallInsets, mBounds, + mCutoutPathParserInfo); } private static int atLeastZero(int value) { @@ -658,16 +848,18 @@ public final class DisplayCutout { for (int i = 0; i < BOUNDS_POSITION_LENGTH; ++i) { bounds[i] = (pos == i) ? new Rect(left, top, right, bottom) : new Rect(); } - return new DisplayCutout(ZERO_RECT, Insets.NONE, bounds, false /* copyArguments */); + return new DisplayCutout(ZERO_RECT, Insets.NONE, bounds, null, false /* copyArguments */); } /** - * Creates an instance from a bounding and waterfall insets. + * Creates an instance from bounds, waterfall insets and CutoutPathParserInfo. * * @hide */ - public static DisplayCutout fromBoundsAndWaterfall(Rect[] bounds, Insets waterfallInsets) { - return new DisplayCutout(ZERO_RECT, waterfallInsets, bounds, false /* copyArguments */); + public static DisplayCutout constructDisplayCutout(Rect[] bounds, Insets waterfallInsets, + CutoutPathParserInfo info) { + return new DisplayCutout(ZERO_RECT, waterfallInsets, bounds, info, + false /* copyArguments */); } /** @@ -676,7 +868,8 @@ public final class DisplayCutout { * @hide */ public static DisplayCutout fromBounds(Rect[] bounds) { - return new DisplayCutout(ZERO_RECT, Insets.NONE, bounds, false /* copyArguments */); + return new DisplayCutout(ZERO_RECT, Insets.NONE, bounds, null /* cutoutPathParserInfo */, + false /* copyArguments */); } /** @@ -686,10 +879,12 @@ public final class DisplayCutout { * * @hide */ - public static DisplayCutout fromResourcesRectApproximation(Resources res, int displayWidth, int displayHeight) { - return fromSpec(res.getString(R.string.config_mainBuiltInDisplayCutoutRectApproximation), + public static DisplayCutout fromResourcesRectApproximation(Resources res, int displayWidth, + int displayHeight) { + return pathAndDisplayCutoutFromSpec(res.getString(R.string.config_mainBuiltInDisplayCutout), + res.getString(R.string.config_mainBuiltInDisplayCutoutRectApproximation), displayWidth, displayHeight, DENSITY_DEVICE_STABLE / (float) DENSITY_DEFAULT, - loadWaterfallInset(res)); + loadWaterfallInset(res)).second; } /** @@ -699,7 +894,7 @@ public final class DisplayCutout { */ public static Path pathFromResources(Resources res, int displayWidth, int displayHeight) { return pathAndDisplayCutoutFromSpec( - res.getString(R.string.config_mainBuiltInDisplayCutout), + res.getString(R.string.config_mainBuiltInDisplayCutout), null, displayWidth, displayHeight, DENSITY_DEVICE_STABLE / (float) DENSITY_DEFAULT, loadWaterfallInset(res)).first; } @@ -710,14 +905,30 @@ public final class DisplayCutout { * @hide */ @VisibleForTesting(visibility = PRIVATE) - public static DisplayCutout fromSpec(String spec, int displayWidth, int displayHeight, - float density, Insets waterfallInsets) { + public static DisplayCutout fromSpec(String pathSpec, int displayWidth, + int displayHeight, float density, Insets waterfallInsets) { return pathAndDisplayCutoutFromSpec( - spec, displayWidth, displayHeight, density, waterfallInsets).second; + pathSpec, null, displayWidth, displayHeight, density, waterfallInsets) + .second; } - private static Pair<Path, DisplayCutout> pathAndDisplayCutoutFromSpec(String spec, - int displayWidth, int displayHeight, float density, Insets waterfallInsets) { + /** + * Gets the cutout path and the corresponding DisplayCutout instance from the spec string. + * + * @param pathSpec the spec string read from config_mainBuiltInDisplayCutout. + * @param rectSpec the spec string read from config_mainBuiltInDisplayCutoutRectApproximation. + * @param displayWidth the display width. + * @param displayHeight the display height. + * @param density the display density. + * @param waterfallInsets the waterfall insets of the display. + * @return a Pair contains the cutout path and the corresponding DisplayCutout instance. + */ + private static Pair<Path, DisplayCutout> pathAndDisplayCutoutFromSpec( + String pathSpec, String rectSpec, int displayWidth, int displayHeight, float density, + Insets waterfallInsets) { + // Always use the rect approximation spec to create the cutout if it's not null because + // transforming and sending a Region constructed from a path is very costly. + String spec = rectSpec != null ? rectSpec : pathSpec; if (TextUtils.isEmpty(spec) && waterfallInsets.equals(Insets.NONE)) { return NULL_PAIR; } @@ -750,9 +961,12 @@ public final class DisplayCutout { Math.max(waterfallInsets.bottom, safeInset.bottom)); } + final CutoutPathParserInfo cutoutPathParserInfo = new CutoutPathParserInfo(displayWidth, + displayHeight, density, pathSpec.trim(), ROTATION_0, 1f /* scale */); + final DisplayCutout cutout = new DisplayCutout( - safeInset, waterfallInsets, boundLeft, boundTop, - boundRight, boundBottom, false /* copyArguments */); + safeInset, waterfallInsets, boundLeft, boundTop, boundRight, boundBottom, + cutoutPathParserInfo , false /* copyArguments */); final Pair<Path, DisplayCutout> result = new Pair<>(cutoutSpec.getPath(), cutout); synchronized (CACHE_LOCK) { sCachedSpec = spec; @@ -817,6 +1031,12 @@ public final class DisplayCutout { out.writeTypedObject(cutout.mSafeInsets, flags); out.writeTypedArray(cutout.mBounds.getRects(), flags); out.writeTypedObject(cutout.mWaterfallInsets, flags); + out.writeInt(cutout.mCutoutPathParserInfo.getDisplayWidth()); + out.writeInt(cutout.mCutoutPathParserInfo.getDisplayHeight()); + out.writeFloat(cutout.mCutoutPathParserInfo.getDensity()); + out.writeString(cutout.mCutoutPathParserInfo.getCutoutSpec()); + out.writeInt(cutout.mCutoutPathParserInfo.getRotation()); + out.writeFloat(cutout.mCutoutPathParserInfo.getScale()); } } @@ -860,9 +1080,17 @@ public final class DisplayCutout { Rect[] bounds = new Rect[BOUNDS_POSITION_LENGTH]; in.readTypedArray(bounds, Rect.CREATOR); Insets waterfallInsets = in.readTypedObject(Insets.CREATOR); + int displayWidth = in.readInt(); + int displayHeight = in.readInt(); + float density = in.readFloat(); + String cutoutSpec = in.readString(); + int rotation = in.readInt(); + float scale = in.readFloat(); + final CutoutPathParserInfo info = new CutoutPathParserInfo( + displayWidth, displayHeight, density, cutoutSpec, rotation, scale); return new DisplayCutout( - safeInsets, waterfallInsets, bounds, false /* copyArguments */); + safeInsets, waterfallInsets, bounds, info, false /* copyArguments */); } public DisplayCutout get() { @@ -884,7 +1112,15 @@ public final class DisplayCutout { bounds.scale(scale); final Rect waterfallInsets = mInner.mWaterfallInsets.toRect(); waterfallInsets.scale(scale); - mInner = new DisplayCutout(safeInsets, Insets.of(waterfallInsets), bounds); + final CutoutPathParserInfo info = new CutoutPathParserInfo( + mInner.mCutoutPathParserInfo.getDisplayWidth(), + mInner.mCutoutPathParserInfo.getDisplayHeight(), + mInner.mCutoutPathParserInfo.getDensity(), + mInner.mCutoutPathParserInfo.getCutoutSpec(), + mInner.mCutoutPathParserInfo.getRotation(), + scale); + + mInner = new DisplayCutout(safeInsets, Insets.of(waterfallInsets), bounds, info); } @Override diff --git a/core/tests/coretests/src/android/view/DisplayCutoutTest.java b/core/tests/coretests/src/android/view/DisplayCutoutTest.java index d02c6d588585..a5261aecdbfb 100644 --- a/core/tests/coretests/src/android/view/DisplayCutoutTest.java +++ b/core/tests/coretests/src/android/view/DisplayCutoutTest.java @@ -22,6 +22,8 @@ import static android.view.DisplayCutout.fromSpec; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.notNullValue; +import static org.hamcrest.Matchers.nullValue; import static org.hamcrest.Matchers.sameInstance; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -30,6 +32,7 @@ import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import android.graphics.Insets; +import android.graphics.Path; import android.graphics.Rect; import android.os.Parcel; import android.platform.test.annotations.Presubmit; @@ -130,6 +133,8 @@ public class DisplayCutoutTest { @Test public void testHasCutout_noCutout() throws Exception { assertTrue(NO_CUTOUT.isBoundsEmpty()); + assertThat(NO_CUTOUT.getWaterfallInsets(), equalTo(Insets.NONE)); + assertThat(NO_CUTOUT.getCutoutPath(), nullValue()); } @Test @@ -165,6 +170,59 @@ public class DisplayCutoutTest { } @Test + public void testGetCutoutPath() throws Exception { + final String cutoutSpecString = "L1,0 L1,1 L0,1 z"; + final int displayWidth = 200; + final int displayHeight = 400; + final float density = 1f; + final DisplayCutout cutout = fromSpec(cutoutSpecString, displayWidth, displayHeight, + density, Insets.NONE); + assertThat(cutout.getCutoutPath(), notNullValue()); + } + + @Test + public void testGetCutoutPath_caches() throws Exception { + final String cutoutSpecString = "L1,0 L1,1 L0,1 z"; + final int displayWidth = 200; + final int displayHeight = 400; + final float density = 1f; + final Path first = fromSpec(cutoutSpecString, displayWidth, displayHeight, + density, Insets.NONE).getCutoutPath(); + final Path second = fromSpec(cutoutSpecString, displayWidth, displayHeight, + density, Insets.NONE).getCutoutPath(); + assertThat(first, equalTo(second)); + } + + @Test + public void testGetCutoutPath_wontCacheIfCutoutPathParerInfoChanged() throws Exception { + final int displayWidth = 200; + final int displayHeight = 400; + final float density = 1f; + final Path first = fromSpec("L1,0 L1,1 L0,1 z", displayWidth, displayHeight, + density, Insets.NONE).getCutoutPath(); + final Path second = fromSpec("L2,0 L2,2 L0,2 z", displayWidth, displayHeight, + density, Insets.NONE).getCutoutPath(); + assertThat(first, not(equalTo(second))); + } + + @Test + public void testGetCutoutPathParserInfo() throws Exception { + final String cutoutSpecString = "L1,0 L1,1 L0,1 z"; + final int displayWidth = 200; + final int displayHeight = 400; + final float density = 1f; + final DisplayCutout cutout = fromSpec(cutoutSpecString, displayWidth, displayHeight, + density, Insets.NONE); + assertThat(displayWidth, equalTo(cutout.getCutoutPathParserInfo().getDisplayWidth())); + assertThat(displayHeight, equalTo(cutout.getCutoutPathParserInfo().getDisplayHeight())); + assertThat(density, equalTo(cutout.getCutoutPathParserInfo().getDensity())); + assertThat(cutoutSpecString.trim(), + equalTo(cutout.getCutoutPathParserInfo().getCutoutSpec())); + assertThat(0, equalTo(cutout.getCutoutPathParserInfo().getRotation())); + assertThat(1f, equalTo(cutout.getCutoutPathParserInfo().getScale())); + } + + @Test public void testHashCode() throws Exception { assertEquals(mCutoutWithWaterfall.hashCode(), createCutoutWithWaterfall().hashCode()); assertNotEquals(mCutoutWithWaterfall.hashCode(), mCutoutNumbers.hashCode()); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java index 3181dbf74ace..58a4baf39614 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java @@ -356,11 +356,11 @@ public class DisplayLayout { if (cutout == null || cutout == DisplayCutout.NO_CUTOUT) { return null; } - final Insets waterfallInsets = - RotationUtils.rotateInsets(cutout.getWaterfallInsets(), rotation); if (rotation == ROTATION_0) { return computeSafeInsets(cutout, displayWidth, displayHeight); } + final Insets waterfallInsets = + RotationUtils.rotateInsets(cutout.getWaterfallInsets(), rotation); final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270); Rect[] cutoutRects = cutout.getBoundingRectsAll(); final Rect[] newBounds = new Rect[cutoutRects.length]; @@ -372,8 +372,12 @@ public class DisplayLayout { } newBounds[getBoundIndexFromRotation(i, rotation)] = rect; } + final DisplayCutout.CutoutPathParserInfo info = cutout.getCutoutPathParserInfo(); + final DisplayCutout.CutoutPathParserInfo newInfo = new DisplayCutout.CutoutPathParserInfo( + info.getDisplayWidth(), info.getDisplayHeight(), info.getDensity(), + info.getCutoutSpec(), rotation, info.getScale()); return computeSafeInsets( - DisplayCutout.fromBoundsAndWaterfall(newBounds, waterfallInsets), + DisplayCutout.constructDisplayCutout(newBounds, waterfallInsets, newInfo), rotated ? displayHeight : displayWidth, rotated ? displayWidth : displayHeight); } diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index f293fc373bfe..73bcf47979fe 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -190,6 +190,7 @@ import android.util.SparseBooleanArray; import android.util.proto.ProtoOutputStream; import android.view.Display; import android.view.DisplayCutout; +import android.view.DisplayCutout.CutoutPathParserInfo; import android.view.DisplayInfo; import android.view.Gravity; import android.view.IDisplayWindowInsetsController; @@ -1934,18 +1935,22 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp if (cutout == null || cutout == DisplayCutout.NO_CUTOUT) { return WmDisplayCutout.NO_CUTOUT; } - final Insets waterfallInsets = - RotationUtils.rotateInsets(cutout.getWaterfallInsets(), rotation); if (rotation == ROTATION_0) { return WmDisplayCutout.computeSafeInsets( cutout, mInitialDisplayWidth, mInitialDisplayHeight); } + final Insets waterfallInsets = + RotationUtils.rotateInsets(cutout.getWaterfallInsets(), rotation); final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270); final Rect[] newBounds = mRotationUtil.getRotatedBounds( cutout.getBoundingRectsAll(), rotation, mInitialDisplayWidth, mInitialDisplayHeight); + final CutoutPathParserInfo info = cutout.getCutoutPathParserInfo(); + final CutoutPathParserInfo newInfo = new CutoutPathParserInfo( + info.getDisplayWidth(), info.getDisplayHeight(), info.getDensity(), + info.getCutoutSpec(), rotation, info.getScale()); return WmDisplayCutout.computeSafeInsets( - DisplayCutout.fromBoundsAndWaterfall(newBounds, waterfallInsets), + DisplayCutout.constructDisplayCutout(newBounds, waterfallInsets, newInfo), rotated ? mInitialDisplayHeight : mInitialDisplayWidth, rotated ? mInitialDisplayWidth : mInitialDisplayHeight); } diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java index 78dd4b8119e3..4bea9a2eea45 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java @@ -27,7 +27,6 @@ import static android.os.Build.VERSION_CODES.P; import static android.os.Build.VERSION_CODES.Q; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.FLAG_PRIVATE; -import static android.view.DisplayCutout.BOUNDS_POSITION_LEFT; import static android.view.DisplayCutout.BOUNDS_POSITION_TOP; import static android.view.DisplayCutout.fromBoundingRect; import static android.view.InsetsState.ITYPE_NAVIGATION_BAR; @@ -96,6 +95,7 @@ import android.app.ActivityTaskManager; import android.app.WindowConfiguration; import android.app.servertransaction.FixedRotationAdjustmentsItem; import android.content.res.Configuration; +import android.graphics.Insets; import android.graphics.Rect; import android.graphics.Region; import android.metrics.LogMaker; @@ -707,6 +707,7 @@ public class DisplayContentTests extends WindowTestsBase { // same width and height. final int displayWidth = dc.mInitialDisplayWidth; final int displayHeight = dc.mInitialDisplayHeight; + final float density = dc.mInitialDisplayDensity; final int cutoutWidth = 40; final int cutoutHeight = 10; final int left = (displayWidth - cutoutWidth) / 2; @@ -714,9 +715,13 @@ public class DisplayContentTests extends WindowTestsBase { final int right = (displayWidth + cutoutWidth) / 2; final int bottom = cutoutHeight; - final Rect r1 = new Rect(left, top, right, bottom); + final Rect zeroRect = new Rect(); + final Rect[] bounds = new Rect[]{zeroRect, new Rect(left, top, right, bottom), zeroRect, + zeroRect}; + final DisplayCutout.CutoutPathParserInfo info = new DisplayCutout.CutoutPathParserInfo( + displayWidth, displayHeight, density, "", Surface.ROTATION_0, 1f); final DisplayCutout cutout = new WmDisplayCutout( - fromBoundingRect(r1.left, r1.top, r1.right, r1.bottom, BOUNDS_POSITION_TOP), null) + DisplayCutout.constructDisplayCutout(bounds, Insets.NONE, info), null) .computeSafeInsets(displayWidth, displayHeight).getDisplayCutout(); dc.mInitialDisplayCutout = cutout; @@ -731,9 +736,12 @@ public class DisplayContentTests extends WindowTestsBase { // | | ---o // | | | // | | ------------- - final Rect r = new Rect(top, left, bottom, right); + final Rect[] bounds90 = new Rect[]{new Rect(top, left, bottom, right), zeroRect, zeroRect, + zeroRect}; + final DisplayCutout.CutoutPathParserInfo info90 = new DisplayCutout.CutoutPathParserInfo( + displayWidth, displayHeight, density, "", Surface.ROTATION_90, 1f); assertEquals(new WmDisplayCutout( - fromBoundingRect(r.left, r.top, r.right, r.bottom, BOUNDS_POSITION_LEFT), null) + DisplayCutout.constructDisplayCutout(bounds90, Insets.NONE, info90), null) .computeSafeInsets(displayHeight, displayWidth).getDisplayCutout(), dc.getDisplayInfo().displayCutout); } diff --git a/services/tests/wmtests/src/com/android/server/wm/utils/WmDisplayCutoutTest.java b/services/tests/wmtests/src/com/android/server/wm/utils/WmDisplayCutoutTest.java index 39976a5a2af1..b2646f281eb9 100644 --- a/services/tests/wmtests/src/com/android/server/wm/utils/WmDisplayCutoutTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/utils/WmDisplayCutoutTest.java @@ -150,9 +150,9 @@ public class WmDisplayCutoutTest { @Test public void computeSafeInsets_waterfall() { WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets( - DisplayCutout.fromBoundsAndWaterfall( + DisplayCutout.constructDisplayCutout( new Rect[] {ZERO_RECT, ZERO_RECT, ZERO_RECT, ZERO_RECT}, - Insets.of(1, 2, 3, 4)), + Insets.of(1, 2, 3, 4), null), 200, 400); assertEquals(new Rect(1, 2, 3, 4), cutout.getDisplayCutout().getSafeInsets()); @@ -161,9 +161,9 @@ public class WmDisplayCutoutTest { @Test public void computeSafeInsets_cutoutTop_greaterThan_waterfallTop() { WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets( - DisplayCutout.fromBoundsAndWaterfall( + DisplayCutout.constructDisplayCutout( new Rect[] {ZERO_RECT, new Rect(80, 0, 120, 30), ZERO_RECT, ZERO_RECT}, - Insets.of(0, 20, 0, 0)), + Insets.of(0, 20, 0, 0), null), 200, 400); assertEquals(new Rect(0, 30, 0, 0), cutout.getDisplayCutout().getSafeInsets()); @@ -172,9 +172,9 @@ public class WmDisplayCutoutTest { @Test public void computeSafeInsets_cutoutTop_lessThan_waterfallTop() { WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets( - DisplayCutout.fromBoundsAndWaterfall( + DisplayCutout.constructDisplayCutout( new Rect[] {ZERO_RECT, new Rect(80, 0, 120, 30), ZERO_RECT, ZERO_RECT}, - Insets.of(0, 40, 0, 0)), + Insets.of(0, 40, 0, 0), null), 200, 400); assertEquals(new Rect(0, 40, 0, 0), cutout.getDisplayCutout().getSafeInsets()); @@ -183,9 +183,9 @@ public class WmDisplayCutoutTest { @Test public void computeSafeInsets_cutoutLeft_greaterThan_waterfallLeft() { WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets( - DisplayCutout.fromBoundsAndWaterfall( + DisplayCutout.constructDisplayCutout( new Rect[] {new Rect(0, 180, 30, 220), ZERO_RECT, ZERO_RECT, ZERO_RECT}, - Insets.of(20, 0, 0, 0)), + Insets.of(20, 0, 0, 0), null), 200, 400); assertEquals(new Rect(30, 0, 0, 0), cutout.getDisplayCutout().getSafeInsets()); @@ -194,9 +194,9 @@ public class WmDisplayCutoutTest { @Test public void computeSafeInsets_cutoutLeft_lessThan_waterfallLeft() { WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets( - DisplayCutout.fromBoundsAndWaterfall( + DisplayCutout.constructDisplayCutout( new Rect[] {new Rect(0, 180, 30, 220), ZERO_RECT, ZERO_RECT, ZERO_RECT}, - Insets.of(40, 0, 0, 0)), + Insets.of(40, 0, 0, 0), null), 200, 400); assertEquals(new Rect(40, 0, 0, 0), cutout.getDisplayCutout().getSafeInsets()); @@ -205,9 +205,9 @@ public class WmDisplayCutoutTest { @Test public void computeSafeInsets_cutoutBottom_greaterThan_waterfallBottom() { WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets( - DisplayCutout.fromBoundsAndWaterfall( + DisplayCutout.constructDisplayCutout( new Rect[] {ZERO_RECT, ZERO_RECT, ZERO_RECT, new Rect(80, 370, 120, 400)}, - Insets.of(0, 0, 0, 20)), + Insets.of(0, 0, 0, 20), null), 200, 400); assertEquals(new Rect(0, 0, 0, 30), cutout.getDisplayCutout().getSafeInsets()); @@ -216,9 +216,9 @@ public class WmDisplayCutoutTest { @Test public void computeSafeInsets_cutoutBottom_lessThan_waterfallBottom() { WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets( - DisplayCutout.fromBoundsAndWaterfall( + DisplayCutout.constructDisplayCutout( new Rect[] {ZERO_RECT, ZERO_RECT, ZERO_RECT, new Rect(80, 370, 120, 400)}, - Insets.of(0, 0, 0, 40)), + Insets.of(0, 0, 0, 40), null), 200, 400); assertEquals(new Rect(0, 0, 0, 40), cutout.getDisplayCutout().getSafeInsets()); @@ -227,9 +227,9 @@ public class WmDisplayCutoutTest { @Test public void computeSafeInsets_cutoutRight_greaterThan_waterfallRight() { WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets( - DisplayCutout.fromBoundsAndWaterfall( + DisplayCutout.constructDisplayCutout( new Rect[] {ZERO_RECT, ZERO_RECT, new Rect(170, 180, 200, 220), ZERO_RECT}, - Insets.of(0, 0, 20, 0)), + Insets.of(0, 0, 20, 0), null), 200, 400); assertEquals(new Rect(0, 0, 30, 0), cutout.getDisplayCutout().getSafeInsets()); @@ -238,9 +238,9 @@ public class WmDisplayCutoutTest { @Test public void computeSafeInsets_cutoutRight_lessThan_waterfallRight() { WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets( - DisplayCutout.fromBoundsAndWaterfall( + DisplayCutout.constructDisplayCutout( new Rect[] {ZERO_RECT, ZERO_RECT, new Rect(170, 180, 200, 220), ZERO_RECT}, - Insets.of(0, 0, 40, 0)), + Insets.of(0, 0, 40, 0), null), 200, 400); assertEquals(new Rect(0, 0, 40, 0), cutout.getDisplayCutout().getSafeInsets()); |