Make DisplayCutout support waterfall insets

For waterfall display, also take the waterfall insets into accout to
comput safe insets.

Change-Id: I1d5663ebd8588021387b7ae5296851769954f996
Bug: 146876976
Test: atest DisplayCutoutTest
This commit is contained in:
shawnlin
2020-01-13 22:24:43 +08:00
parent 0a5e31ee3f
commit 7dabcab3dd
3 changed files with 303 additions and 113 deletions

View File

@@ -50509,6 +50509,7 @@ package android.view {
public final class DisplayCutout { public final class DisplayCutout {
ctor public DisplayCutout(@NonNull android.graphics.Insets, @Nullable android.graphics.Rect, @Nullable android.graphics.Rect, @Nullable android.graphics.Rect, @Nullable android.graphics.Rect); ctor public DisplayCutout(@NonNull android.graphics.Insets, @Nullable android.graphics.Rect, @Nullable android.graphics.Rect, @Nullable android.graphics.Rect, @Nullable android.graphics.Rect);
ctor public DisplayCutout(@NonNull android.graphics.Insets, @Nullable android.graphics.Rect, @Nullable android.graphics.Rect, @Nullable android.graphics.Rect, @Nullable android.graphics.Rect, @NonNull android.graphics.Insets);
ctor @Deprecated public DisplayCutout(@Nullable android.graphics.Rect, @Nullable java.util.List<android.graphics.Rect>); ctor @Deprecated public DisplayCutout(@Nullable android.graphics.Rect, @Nullable java.util.List<android.graphics.Rect>);
method @NonNull public android.graphics.Rect getBoundingRectBottom(); method @NonNull public android.graphics.Rect getBoundingRectBottom();
method @NonNull public android.graphics.Rect getBoundingRectLeft(); method @NonNull public android.graphics.Rect getBoundingRectLeft();
@@ -50519,6 +50520,7 @@ package android.view {
method public int getSafeInsetLeft(); method public int getSafeInsetLeft();
method public int getSafeInsetRight(); method public int getSafeInsetRight();
method public int getSafeInsetTop(); method public int getSafeInsetTop();
method @NonNull public android.graphics.Insets getWaterfallInsets();
} }
public final class DragAndDropPermissions implements android.os.Parcelable { public final class DragAndDropPermissions implements android.os.Parcelable {

View File

@@ -86,7 +86,7 @@ public final class DisplayCutout {
* @hide * @hide
*/ */
public static final DisplayCutout NO_CUTOUT = new DisplayCutout( public static final DisplayCutout NO_CUTOUT = new DisplayCutout(
ZERO_RECT, ZERO_RECT, ZERO_RECT, ZERO_RECT, ZERO_RECT, ZERO_RECT, Insets.NONE, ZERO_RECT, ZERO_RECT, ZERO_RECT, ZERO_RECT,
false /* copyArguments */); false /* copyArguments */);
@@ -103,8 +103,12 @@ public final class DisplayCutout {
private static float sCachedDensity; private static float sCachedDensity;
@GuardedBy("CACHE_LOCK") @GuardedBy("CACHE_LOCK")
private static Pair<Path, DisplayCutout> sCachedCutout = NULL_PAIR; private static Pair<Path, DisplayCutout> sCachedCutout = NULL_PAIR;
@GuardedBy("CACHE_LOCK")
private static Insets sCachedWaterfallInsets;
private final Rect mSafeInsets; private final Rect mSafeInsets;
@NonNull
private final Insets mWaterfallInsets;
/** /**
@@ -251,7 +255,32 @@ public final class DisplayCutout {
// TODO(b/73953958): @VisibleForTesting(visibility = PRIVATE) // TODO(b/73953958): @VisibleForTesting(visibility = PRIVATE)
public DisplayCutout(@NonNull Insets safeInsets, @Nullable Rect boundLeft, public DisplayCutout(@NonNull Insets safeInsets, @Nullable Rect boundLeft,
@Nullable Rect boundTop, @Nullable Rect boundRight, @Nullable Rect boundBottom) { @Nullable Rect boundTop, @Nullable Rect boundRight, @Nullable Rect boundBottom) {
this(safeInsets.toRect(), boundLeft, boundTop, boundRight, boundBottom, true); this(safeInsets.toRect(), Insets.NONE, boundLeft, boundTop, boundRight, boundBottom, true);
}
/**
* Creates a DisplayCutout instance.
*
* <p>Note that this is only useful for tests. For production code, developers should always
* use a {@link DisplayCutout} obtained from the system.</p>
*
* @param safeInsets the insets from each edge which avoid the display cutout as returned by
* {@link #getSafeInsetTop()} etc.
* @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 waterfallInsets the insets for the curved areas in waterfall display.
*/
public DisplayCutout(@NonNull Insets safeInsets, @Nullable Rect boundLeft,
@Nullable Rect boundTop, @Nullable Rect boundRight, @Nullable Rect boundBottom,
@NonNull Insets waterfallInsets) {
this(safeInsets.toRect(), waterfallInsets, boundLeft, boundTop, boundRight, boundBottom,
true);
} }
/** /**
@@ -269,7 +298,7 @@ public final class DisplayCutout {
// TODO(b/73953958): @VisibleForTesting(visibility = PRIVATE) // TODO(b/73953958): @VisibleForTesting(visibility = PRIVATE)
@Deprecated @Deprecated
public DisplayCutout(@Nullable Rect safeInsets, @Nullable List<Rect> boundingRects) { public DisplayCutout(@Nullable Rect safeInsets, @Nullable List<Rect> boundingRects) {
this(safeInsets, extractBoundsFromList(safeInsets, boundingRects), this(safeInsets, Insets.NONE, extractBoundsFromList(safeInsets, boundingRects),
true /* copyArguments */); true /* copyArguments */);
} }
@@ -281,19 +310,23 @@ public final class DisplayCutout {
* @param copyArguments if true, create a copy of the arguments. If false, the passed arguments * @param copyArguments if true, create a copy of the arguments. If false, the passed arguments
* are not copied and MUST remain unchanged forever. * are not copied and MUST remain unchanged forever.
*/ */
private DisplayCutout(Rect safeInsets, Rect boundLeft, Rect boundTop, Rect boundRight, private DisplayCutout(Rect safeInsets, Insets waterfallInsets, Rect boundLeft,
Rect boundBottom, boolean copyArguments) { Rect boundTop, Rect boundRight, Rect boundBottom, boolean copyArguments) {
mSafeInsets = getCopyOrRef(safeInsets, copyArguments); mSafeInsets = getCopyOrRef(safeInsets, copyArguments);
mWaterfallInsets = waterfallInsets == null ? Insets.NONE : waterfallInsets;
mBounds = new Bounds(boundLeft, boundTop, boundRight, boundBottom, copyArguments); mBounds = new Bounds(boundLeft, boundTop, boundRight, boundBottom, copyArguments);
} }
private DisplayCutout(Rect safeInsets, Rect[] bounds, boolean copyArguments) { private DisplayCutout(Rect safeInsets, Insets waterfallInsets, Rect[] bounds,
boolean copyArguments) {
mSafeInsets = getCopyOrRef(safeInsets, copyArguments); mSafeInsets = getCopyOrRef(safeInsets, copyArguments);
mWaterfallInsets = waterfallInsets == null ? Insets.NONE : waterfallInsets;
mBounds = new Bounds(bounds, copyArguments); mBounds = new Bounds(bounds, copyArguments);
} }
private DisplayCutout(Rect safeInsets, Bounds bounds) { private DisplayCutout(Rect safeInsets, Insets waterfallInsets, Bounds bounds) {
mSafeInsets = safeInsets; mSafeInsets = safeInsets;
mWaterfallInsets = waterfallInsets == null ? Insets.NONE : waterfallInsets;
mBounds = bounds; mBounds = bounds;
} }
@@ -308,6 +341,14 @@ public final class DisplayCutout {
} }
} }
/**
* Return the waterfall insets.
*/
public @NonNull Insets getWaterfallInsets() {
return mWaterfallInsets;
}
/** /**
* Find the position of the bounding rect, and create an array of Rect whose index represents * Find the position of the bounding rect, and create an array of Rect whose index represents
* the position (= BoundsPosition). * the position (= BoundsPosition).
@@ -476,7 +517,8 @@ public final class DisplayCutout {
@Override @Override
public int hashCode() { public int hashCode() {
return mSafeInsets.hashCode() * 48271 + mBounds.hashCode(); return (mSafeInsets.hashCode() * 48271 + mBounds.hashCode()) * 48271
+ mWaterfallInsets.hashCode();
} }
@Override @Override
@@ -486,7 +528,8 @@ public final class DisplayCutout {
} }
if (o instanceof DisplayCutout) { if (o instanceof DisplayCutout) {
DisplayCutout c = (DisplayCutout) o; DisplayCutout c = (DisplayCutout) o;
return mSafeInsets.equals(c.mSafeInsets) && mBounds.equals(c.mBounds); return mSafeInsets.equals(c.mSafeInsets) && mBounds.equals(c.mBounds)
&& mWaterfallInsets.equals(c.mWaterfallInsets);
} }
return false; return false;
} }
@@ -494,6 +537,7 @@ public final class DisplayCutout {
@Override @Override
public String toString() { public String toString() {
return "DisplayCutout{insets=" + mSafeInsets return "DisplayCutout{insets=" + mSafeInsets
+ " waterfall=" + mWaterfallInsets
+ " boundingRect={" + mBounds + "}" + " boundingRect={" + mBounds + "}"
+ "}"; + "}";
} }
@@ -508,6 +552,7 @@ public final class DisplayCutout {
mBounds.getRect(BOUNDS_POSITION_TOP).dumpDebug(proto, BOUND_TOP); mBounds.getRect(BOUNDS_POSITION_TOP).dumpDebug(proto, BOUND_TOP);
mBounds.getRect(BOUNDS_POSITION_RIGHT).dumpDebug(proto, BOUND_RIGHT); mBounds.getRect(BOUNDS_POSITION_RIGHT).dumpDebug(proto, BOUND_RIGHT);
mBounds.getRect(BOUNDS_POSITION_BOTTOM).dumpDebug(proto, BOUND_BOTTOM); mBounds.getRect(BOUNDS_POSITION_BOTTOM).dumpDebug(proto, BOUND_BOTTOM);
mWaterfallInsets.toRect().dumpDebug(proto, INSETS);
proto.end(token); proto.end(token);
} }
@@ -553,7 +598,7 @@ public final class DisplayCutout {
} }
} }
return new DisplayCutout(safeInsets, bounds, false /* copyArguments */); return new DisplayCutout(safeInsets, mWaterfallInsets, bounds, false /* copyArguments */);
} }
/** /**
@@ -565,7 +610,7 @@ public final class DisplayCutout {
* @hide * @hide
*/ */
public DisplayCutout replaceSafeInsets(Rect safeInsets) { public DisplayCutout replaceSafeInsets(Rect safeInsets) {
return new DisplayCutout(new Rect(safeInsets), mBounds); return new DisplayCutout(new Rect(safeInsets), mWaterfallInsets, mBounds);
} }
private static int atLeastZero(int value) { private static int atLeastZero(int value) {
@@ -585,7 +630,16 @@ public final class DisplayCutout {
for (int i = 0; i < BOUNDS_POSITION_LENGTH; ++i) { for (int i = 0; i < BOUNDS_POSITION_LENGTH; ++i) {
bounds[i] = (pos == i) ? new Rect(left, top, right, bottom) : new Rect(); bounds[i] = (pos == i) ? new Rect(left, top, right, bottom) : new Rect();
} }
return new DisplayCutout(ZERO_RECT, bounds, false /* copyArguments */); return new DisplayCutout(ZERO_RECT, Insets.NONE, bounds, false /* copyArguments */);
}
/**
* Creates an instance from a bounding and waterfall insets.
*
* @hide
*/
public static DisplayCutout fromBoundsAndWaterfall(Rect[] bounds, Insets waterfallInsets) {
return new DisplayCutout(ZERO_RECT, waterfallInsets, bounds, false /* copyArguments */);
} }
/** /**
@@ -594,7 +648,7 @@ public final class DisplayCutout {
* @hide * @hide
*/ */
public static DisplayCutout fromBounds(Rect[] bounds) { public static DisplayCutout fromBounds(Rect[] bounds) {
return new DisplayCutout(ZERO_RECT, bounds, false /* copyArguments */); return new DisplayCutout(ZERO_RECT, Insets.NONE, bounds, false /* copyArguments */);
} }
/** /**
@@ -606,7 +660,8 @@ public final class DisplayCutout {
*/ */
public static DisplayCutout fromResourcesRectApproximation(Resources res, int displayWidth, int displayHeight) { public static DisplayCutout fromResourcesRectApproximation(Resources res, int displayWidth, int displayHeight) {
return fromSpec(res.getString(R.string.config_mainBuiltInDisplayCutoutRectApproximation), return fromSpec(res.getString(R.string.config_mainBuiltInDisplayCutoutRectApproximation),
displayWidth, displayHeight, DENSITY_DEVICE_STABLE / (float) DENSITY_DEFAULT); displayWidth, displayHeight, DENSITY_DEVICE_STABLE / (float) DENSITY_DEFAULT,
loadWaterfallInset(res));
} }
/** /**
@@ -617,7 +672,8 @@ public final class DisplayCutout {
public static Path pathFromResources(Resources res, int displayWidth, int displayHeight) { public static Path pathFromResources(Resources res, int displayWidth, int displayHeight) {
return pathAndDisplayCutoutFromSpec( return pathAndDisplayCutoutFromSpec(
res.getString(R.string.config_mainBuiltInDisplayCutout), res.getString(R.string.config_mainBuiltInDisplayCutout),
displayWidth, displayHeight, DENSITY_DEVICE_STABLE / (float) DENSITY_DEFAULT).first; displayWidth, displayHeight, DENSITY_DEVICE_STABLE / (float) DENSITY_DEFAULT,
loadWaterfallInset(res)).first;
} }
/** /**
@@ -627,91 +683,109 @@ public final class DisplayCutout {
*/ */
@VisibleForTesting(visibility = PRIVATE) @VisibleForTesting(visibility = PRIVATE)
public static DisplayCutout fromSpec(String spec, int displayWidth, int displayHeight, public static DisplayCutout fromSpec(String spec, int displayWidth, int displayHeight,
float density) { float density, Insets waterfallInsets) {
return pathAndDisplayCutoutFromSpec(spec, displayWidth, displayHeight, density).second; return pathAndDisplayCutoutFromSpec(
spec, displayWidth, displayHeight, density, waterfallInsets).second;
} }
private static Pair<Path, DisplayCutout> pathAndDisplayCutoutFromSpec(String spec, private static Pair<Path, DisplayCutout> pathAndDisplayCutoutFromSpec(String spec,
int displayWidth, int displayHeight, float density) { int displayWidth, int displayHeight, float density, Insets waterfallInsets) {
if (TextUtils.isEmpty(spec)) { if (TextUtils.isEmpty(spec) && waterfallInsets.equals(Insets.NONE)) {
return NULL_PAIR; return NULL_PAIR;
} }
synchronized (CACHE_LOCK) { synchronized (CACHE_LOCK) {
if (spec.equals(sCachedSpec) && sCachedDisplayWidth == displayWidth if (spec.equals(sCachedSpec) && sCachedDisplayWidth == displayWidth
&& sCachedDisplayHeight == displayHeight && sCachedDisplayHeight == displayHeight
&& sCachedDensity == density) { && sCachedDensity == density
&& waterfallInsets.equals(sCachedWaterfallInsets)) {
return sCachedCutout; return sCachedCutout;
} }
} }
spec = spec.trim();
final float offsetX;
if (spec.endsWith(RIGHT_MARKER)) {
offsetX = displayWidth;
spec = spec.substring(0, spec.length() - RIGHT_MARKER.length()).trim();
} else if (spec.endsWith(LEFT_MARKER)) {
offsetX = 0;
spec = spec.substring(0, spec.length() - LEFT_MARKER.length()).trim();
} else {
offsetX = displayWidth / 2f;
}
final boolean inDp = spec.endsWith(DP_MARKER);
if (inDp) {
spec = spec.substring(0, spec.length() - DP_MARKER.length());
}
String bottomSpec = null;
if (spec.contains(BOTTOM_MARKER)) {
String[] splits = spec.split(BOTTOM_MARKER, 2);
spec = splits[0].trim();
bottomSpec = splits[1].trim();
}
final Path p;
final Region r = Region.obtain();
try {
p = PathParser.createPathFromPathData(spec);
} catch (Throwable e) {
Log.wtf(TAG, "Could not inflate cutout: ", e);
return NULL_PAIR;
}
final Matrix m = new Matrix();
if (inDp) {
m.postScale(density, density);
}
m.postTranslate(offsetX, 0);
p.transform(m);
Rect boundTop = new Rect();
toRectAndAddToRegion(p, r, boundTop);
final int topInset = boundTop.bottom;
Path p = null;
Rect boundTop = null;
Rect boundBottom = null; Rect boundBottom = null;
final int bottomInset; Rect safeInset = new Rect();
if (bottomSpec != null) { String bottomSpec = null;
final Path bottomPath; if (!TextUtils.isEmpty(spec)) {
try { spec = spec.trim();
bottomPath = PathParser.createPathFromPathData(bottomSpec); final float offsetX;
} catch (Throwable e) { if (spec.endsWith(RIGHT_MARKER)) {
Log.wtf(TAG, "Could not inflate bottom cutout: ", e); offsetX = displayWidth;
return NULL_PAIR; spec = spec.substring(0, spec.length() - RIGHT_MARKER.length()).trim();
} else if (spec.endsWith(LEFT_MARKER)) {
offsetX = 0;
spec = spec.substring(0, spec.length() - LEFT_MARKER.length()).trim();
} else {
offsetX = displayWidth / 2f;
}
final boolean inDp = spec.endsWith(DP_MARKER);
if (inDp) {
spec = spec.substring(0, spec.length() - DP_MARKER.length());
}
if (spec.contains(BOTTOM_MARKER)) {
String[] splits = spec.split(BOTTOM_MARKER, 2);
spec = splits[0].trim();
bottomSpec = splits[1].trim();
}
final Matrix m = new Matrix();
final Region r = Region.obtain();
if (!spec.isEmpty()) {
try {
p = PathParser.createPathFromPathData(spec);
} catch (Throwable e) {
Log.wtf(TAG, "Could not inflate cutout: ", e);
}
if (p != null) {
if (inDp) {
m.postScale(density, density);
}
m.postTranslate(offsetX, 0);
p.transform(m);
boundTop = new Rect();
toRectAndAddToRegion(p, r, boundTop);
safeInset.top = boundTop.bottom;
}
}
if (bottomSpec != null) {
int bottomInset = 0;
Path bottomPath = null;
try {
bottomPath = PathParser.createPathFromPathData(bottomSpec);
} catch (Throwable e) {
Log.wtf(TAG, "Could not inflate bottom cutout: ", e);
}
if (bottomPath != null) {
// Keep top transform
m.postTranslate(0, displayHeight);
bottomPath.transform(m);
p.addPath(bottomPath);
boundBottom = new Rect();
toRectAndAddToRegion(bottomPath, r, boundBottom);
bottomInset = displayHeight - boundBottom.top;
}
safeInset.bottom = bottomInset;
} }
// Keep top transform
m.postTranslate(0, displayHeight);
bottomPath.transform(m);
p.addPath(bottomPath);
boundBottom = new Rect();
toRectAndAddToRegion(bottomPath, r, boundBottom);
bottomInset = displayHeight - boundBottom.top;
} else {
bottomInset = 0;
} }
Rect safeInset = new Rect(0, topInset, 0, bottomInset); if (!waterfallInsets.equals(Insets.NONE)) {
final DisplayCutout cutout = new DisplayCutout( safeInset.set(
safeInset, null /* boundLeft */, boundTop, null /* boundRight */, boundBottom, Math.max(waterfallInsets.left, safeInset.left),
false /* copyArguments */); Math.max(waterfallInsets.top, safeInset.top),
Math.max(waterfallInsets.right, safeInset.right),
Math.max(waterfallInsets.bottom, safeInset.bottom));
}
final DisplayCutout cutout = new DisplayCutout(
safeInset, waterfallInsets, null /* boundLeft */, boundTop,
null /* boundRight */, boundBottom, false /* copyArguments */);
final Pair<Path, DisplayCutout> result = new Pair<>(p, cutout); final Pair<Path, DisplayCutout> result = new Pair<>(p, cutout);
synchronized (CACHE_LOCK) { synchronized (CACHE_LOCK) {
sCachedSpec = spec; sCachedSpec = spec;
@@ -719,6 +793,7 @@ public final class DisplayCutout {
sCachedDisplayHeight = displayHeight; sCachedDisplayHeight = displayHeight;
sCachedDensity = density; sCachedDensity = density;
sCachedCutout = result; sCachedCutout = result;
sCachedWaterfallInsets = waterfallInsets;
} }
return result; return result;
} }
@@ -730,6 +805,15 @@ public final class DisplayCutout {
inoutRegion.op(inoutRect, Op.UNION); inoutRegion.op(inoutRect, Op.UNION);
} }
private static Insets loadWaterfallInset(Resources res) {
return Insets.of(
res.getDimensionPixelSize(R.dimen.waterfall_display_left_edge_size),
res.getDimensionPixelSize(R.dimen.waterfall_display_top_edge_size),
res.getDimensionPixelSize(R.dimen.waterfall_display_right_edge_size),
res.getDimensionPixelSize(R.dimen.waterfall_display_bottom_edge_size));
}
/** /**
* Helper class for passing {@link DisplayCutout} through binder. * Helper class for passing {@link DisplayCutout} through binder.
* *
@@ -773,6 +857,7 @@ public final class DisplayCutout {
out.writeInt(1); out.writeInt(1);
out.writeTypedObject(cutout.mSafeInsets, flags); out.writeTypedObject(cutout.mSafeInsets, flags);
out.writeTypedArray(cutout.mBounds.getRects(), flags); out.writeTypedArray(cutout.mBounds.getRects(), flags);
out.writeTypedObject(cutout.mWaterfallInsets, flags);
} }
} }
@@ -815,8 +900,10 @@ public final class DisplayCutout {
Rect safeInsets = in.readTypedObject(Rect.CREATOR); Rect safeInsets = in.readTypedObject(Rect.CREATOR);
Rect[] bounds = new Rect[BOUNDS_POSITION_LENGTH]; Rect[] bounds = new Rect[BOUNDS_POSITION_LENGTH];
in.readTypedArray(bounds, Rect.CREATOR); in.readTypedArray(bounds, Rect.CREATOR);
Insets waterfallInsets = in.readTypedObject(Insets.CREATOR);
return new DisplayCutout(safeInsets, bounds, false /* copyArguments */); return new DisplayCutout(
safeInsets, waterfallInsets, bounds, false /* copyArguments */);
} }
public DisplayCutout get() { public DisplayCutout get() {

View File

@@ -69,6 +69,8 @@ public class DisplayCutoutTest {
null /* boundBottom */); null /* boundBottom */);
final DisplayCutout mCutoutTop = createCutoutTop(); final DisplayCutout mCutoutTop = createCutoutTop();
final DisplayCutout mCutoutWithWaterfall = createCutoutWithWaterfall();
final DisplayCutout mWaterfallOnly = createCutoutWaterfallOnly();
@Test @Test
public void testExtractBoundsFromList_left() { public void testExtractBoundsFromList_left() {
@@ -126,9 +128,23 @@ public class DisplayCutoutTest {
} }
@Test @Test
public void hasCutout() throws Exception { public void testHasCutout_noCutout() throws Exception {
assertTrue(NO_CUTOUT.isEmpty()); assertTrue(NO_CUTOUT.isBoundsEmpty());
assertFalse(mCutoutTop.isEmpty()); }
@Test
public void testHasCutout_cutoutOnly() {
assertFalse(mCutoutTop.isBoundsEmpty());
}
@Test
public void testHasCutout_cutoutWithWaterfall() {
assertFalse(mCutoutWithWaterfall.isBoundsEmpty());
}
@Test
public void testHasCutout_waterfallOnly() {
assertTrue(mWaterfallOnly.isBoundsEmpty());
} }
@Test @Test
@@ -141,21 +157,28 @@ public class DisplayCutoutTest {
assertEquals(new Rect(5, 6, 7, 8), mCutoutNumbers.getSafeInsets()); assertEquals(new Rect(5, 6, 7, 8), mCutoutNumbers.getSafeInsets());
} }
@Test
public void testGetWaterfallInsets() throws Exception {
DisplayCutout cutout =
createCutoutWaterfallOnly(Insets.of(5, 6, 7, 8));
assertEquals(Insets.of(5, 6, 7, 8), cutout.getWaterfallInsets());
}
@Test @Test
public void testHashCode() throws Exception { public void testHashCode() throws Exception {
assertEquals(mCutoutTop.hashCode(), createCutoutTop().hashCode()); assertEquals(mCutoutWithWaterfall.hashCode(), createCutoutWithWaterfall().hashCode());
assertNotEquals(mCutoutTop.hashCode(), mCutoutNumbers.hashCode()); assertNotEquals(mCutoutWithWaterfall.hashCode(), mCutoutNumbers.hashCode());
} }
@Test @Test
public void testEquals() throws Exception { public void testEquals() throws Exception {
assertEquals(mCutoutTop, createCutoutTop()); assertEquals(mCutoutWithWaterfall, createCutoutWithWaterfall());
assertNotEquals(mCutoutTop, mCutoutNumbers); assertNotEquals(mCutoutWithWaterfall, mCutoutNumbers);
} }
@Test @Test
public void testToString() throws Exception { public void testToString() throws Exception {
assertFalse(mCutoutTop.toString().isEmpty()); assertFalse(mCutoutWithWaterfall.toString().isEmpty());
assertFalse(mCutoutNumbers.toString().isEmpty()); assertFalse(mCutoutNumbers.toString().isEmpty());
} }
@@ -240,12 +263,12 @@ public class DisplayCutoutTest {
public void parcel_unparcel_regular() { public void parcel_unparcel_regular() {
Parcel p = Parcel.obtain(); Parcel p = Parcel.obtain();
new ParcelableWrapper(mCutoutTop).writeToParcel(p, 0); new ParcelableWrapper(mCutoutWithWaterfall).writeToParcel(p, 0);
int posAfterWrite = p.dataPosition(); int posAfterWrite = p.dataPosition();
p.setDataPosition(0); p.setDataPosition(0);
assertEquals(mCutoutTop, ParcelableWrapper.CREATOR.createFromParcel(p).get()); assertEquals(mCutoutWithWaterfall, ParcelableWrapper.CREATOR.createFromParcel(p).get());
assertEquals(posAfterWrite, p.dataPosition()); assertEquals(posAfterWrite, p.dataPosition());
} }
@@ -264,44 +287,64 @@ public class DisplayCutoutTest {
@Test @Test
public void fromSpec_caches() { public void fromSpec_caches() {
DisplayCutout cached = fromSpec("L1,0 L1,1 L0,1 z", 200, 400, 1f); Insets waterfallInsets = Insets.of(0, 20, 0, 20);
assertThat(fromSpec("L1,0 L1,1 L0,1 z", 200, 400, 1f), sameInstance(cached)); DisplayCutout cached = fromSpec("L1,0 L1,1 L0,1 z", 200, 400, 1f, waterfallInsets);
assertThat(
fromSpec("L1,0 L1,1 L0,1 z", 200, 400, 1f, waterfallInsets),
sameInstance(cached));
} }
@Test @Test
public void fromSpec_wontCacheIfSpecChanges() { public void fromSpec_wontCacheIfSpecChanges() {
DisplayCutout cached = fromSpec("L1,0 L1000,1000 L0,1 z", 200, 400, 1f); DisplayCutout cached = fromSpec("L1,0 L1000,1000 L0,1 z", 200, 400, 1f, Insets.NONE);
assertThat(fromSpec("L1,0 L1,1 L0,1 z", 200, 400, 1f), not(sameInstance(cached))); assertThat(
fromSpec("L1,0 L1,1 L0,1 z", 200, 400, 1f, Insets.NONE),
not(sameInstance(cached)));
} }
@Test @Test
public void fromSpec_wontCacheIfScreenWidthChanges() { public void fromSpec_wontCacheIfScreenWidthChanges() {
DisplayCutout cached = fromSpec("L1,0 L1,1 L0,1 z", 2000, 400, 1f); DisplayCutout cached = fromSpec("L1,0 L1,1 L0,1 z", 2000, 400, 1f, Insets.NONE);
assertThat(fromSpec("L1,0 L1,1 L0,1 z", 200, 400, 1f), not(sameInstance(cached))); assertThat(
fromSpec("L1,0 L1,1 L0,1 z", 200, 400, 1f, Insets.NONE),
not(sameInstance(cached)));
} }
@Test @Test
public void fromSpec_wontCacheIfScreenHeightChanges() { public void fromSpec_wontCacheIfScreenHeightChanges() {
DisplayCutout cached = fromSpec("L1,0 L1,1 L0,1 z", 200, 4000, 1f); DisplayCutout cached = fromSpec("L1,0 L1,1 L0,1 z", 200, 4000, 1f, Insets.NONE);
assertThat(fromSpec("L1,0 L1,1 L0,1 z", 200, 400, 1f), not(sameInstance(cached))); assertThat(
fromSpec("L1,0 L1,1 L0,1 z", 200, 400, 1f, Insets.NONE),
not(sameInstance(cached)));
} }
@Test @Test
public void fromSpec_wontCacheIfDensityChanges() { public void fromSpec_wontCacheIfDensityChanges() {
DisplayCutout cached = fromSpec("L1,0 L1,1 L0,1 z", 200, 400, 2f); DisplayCutout cached = fromSpec("L1,0 L1,1 L0,1 z", 200, 400, 2f, Insets.NONE);
assertThat(fromSpec("L1,0 L1,1 L0,1 z", 200, 400, 1f), not(sameInstance(cached))); assertThat(
fromSpec("L1,0 L1,1 L0,1 z", 200, 400, 1f, Insets.NONE),
not(sameInstance(cached)));
}
@Test
public void fromSpec_wontCacheIfWaterfallInsetsChange() {
Insets waterfallInsets = Insets.of(0, 20, 0, 20);
DisplayCutout cached = fromSpec("L1,0 L1,1 L0,1 z", 200, 400, 2f, Insets.NONE);
assertThat(
fromSpec("L1,0 L1,1 L0,1 z", 200, 400, 2f, waterfallInsets),
not(sameInstance(cached)));
} }
@Test @Test
public void fromSpec_setsSafeInsets_top() { public void fromSpec_setsSafeInsets_top() {
DisplayCutout cutout = fromSpec("M -50,0 v 20 h 100 v -20 z", 200, 400, 2f); DisplayCutout cutout = fromSpec("M -50,0 v 20 h 100 v -20 z", 200, 400, 2f, Insets.NONE);
assertThat(cutout.getSafeInsets(), equalTo(new Rect(0, 20, 0, 0))); assertThat(cutout.getSafeInsets(), equalTo(new Rect(0, 20, 0, 0)));
} }
@Test @Test
public void fromSpec_setsSafeInsets_top_and_bottom() { public void fromSpec_setsSafeInsets_top_and_bottom() {
DisplayCutout cutout = fromSpec("M -50,0 v 20 h 100 v -20 z" DisplayCutout cutout = fromSpec("M -50,0 v 20 h 100 v -20 z"
+ "@bottom M -50,0 v -10,0 h 100 v 20 z", 200, 400, 2f); + "@bottom M -50,0 v -10,0 h 100 v 20 z", 200, 400, 2f, Insets.NONE);
assertThat(cutout.getSafeInsets(), equalTo(new Rect(0, 20, 0, 10))); assertThat(cutout.getSafeInsets(), equalTo(new Rect(0, 20, 0, 10)));
assertThat(cutout.getBoundingRectsAll(), equalTo(new Rect[]{ assertThat(cutout.getBoundingRectsAll(), equalTo(new Rect[]{
ZERO_RECT, new Rect(50, 0, 150, 20), ZERO_RECT, new Rect(50, 0, 150, 20),
@@ -309,6 +352,38 @@ public class DisplayCutoutTest {
})); }));
} }
@Test
public void fromSpec_setsSafeInsets_waterfallTopBottom() {
DisplayCutout cutout = fromSpec("", 200, 400, 2f, Insets.of(0, 30, 0, 30));
assertThat(cutout.getSafeInsets(), equalTo(new Rect(0, 30, 0, 30)));
}
@Test
public void fromSpec_setsSafeInsets_waterfallLeftRight() {
DisplayCutout cutout = fromSpec("", 200, 400, 2f, Insets.of(30, 0, 30, 0));
assertThat(cutout.getSafeInsets(), equalTo(new Rect(30, 0, 30, 0)));
}
@Test
public void fromSpec_setsSafeInsets_waterfall_allEdges() {
DisplayCutout cutout = fromSpec("", 200, 400, 2f, Insets.of(30, 30, 30, 30));
assertThat(cutout.getSafeInsets(), equalTo(new Rect(30, 30, 30, 30)));
}
@Test
public void fromSpec_setsSafeInsets_cutoutTopBottom_waterfallTopBottom() {
DisplayCutout cutout = fromSpec("M -50,0 v 20 h 100 v -20 z"
+ "@bottom M -50,0 v -20,0 h 100 v 20 z", 200, 400, 2f, Insets.of(0, 30, 0, 30));
assertThat(cutout.getSafeInsets(), equalTo(new Rect(0, 30, 0, 30)));
}
@Test
public void fromSpec_setsSafeInsets_cutoutTopBottom_waterfallLeftRight() {
DisplayCutout cutout = fromSpec("M -50,0 v 20 h 100 v -20 z"
+ "@bottom M -50,0 v -20,0 h 100 v 20 z", 200, 400, 2f, Insets.of(30, 0, 30, 0));
assertThat(cutout.getSafeInsets(), equalTo(new Rect(30, 20, 30, 20)));
}
@Test @Test
public void parcel_unparcel_nocutout() { public void parcel_unparcel_nocutout() {
Parcel p = Parcel.obtain(); Parcel p = Parcel.obtain();
@@ -326,7 +401,7 @@ public class DisplayCutoutTest {
public void parcel_unparcel_inplace() { public void parcel_unparcel_inplace() {
Parcel p = Parcel.obtain(); Parcel p = Parcel.obtain();
new ParcelableWrapper(mCutoutTop).writeToParcel(p, 0); new ParcelableWrapper(mCutoutWithWaterfall).writeToParcel(p, 0);
int posAfterWrite = p.dataPosition(); int posAfterWrite = p.dataPosition();
p.setDataPosition(0); p.setDataPosition(0);
@@ -334,22 +409,24 @@ public class DisplayCutoutTest {
ParcelableWrapper wrapper = new ParcelableWrapper(); ParcelableWrapper wrapper = new ParcelableWrapper();
wrapper.readFromParcel(p); wrapper.readFromParcel(p);
assertEquals(mCutoutTop, wrapper.get()); assertEquals(mCutoutWithWaterfall, wrapper.get());
assertEquals(posAfterWrite, p.dataPosition()); assertEquals(posAfterWrite, p.dataPosition());
} }
@Test @Test
public void wrapper_hashcode() throws Exception { public void wrapper_hashcode() throws Exception {
assertEquals(new ParcelableWrapper(mCutoutTop).hashCode(), assertEquals(new ParcelableWrapper(mCutoutWithWaterfall).hashCode(),
new ParcelableWrapper(createCutoutTop()).hashCode()); new ParcelableWrapper(createCutoutWithWaterfall()).hashCode());
assertNotEquals(new ParcelableWrapper(mCutoutTop).hashCode(), assertNotEquals(new ParcelableWrapper(mCutoutWithWaterfall).hashCode(),
new ParcelableWrapper(mCutoutNumbers).hashCode()); new ParcelableWrapper(mCutoutNumbers).hashCode());
} }
@Test @Test
public void wrapper_equals() throws Exception { public void wrapper_equals() throws Exception {
assertEquals(new ParcelableWrapper(mCutoutTop), new ParcelableWrapper(createCutoutTop())); assertEquals(new ParcelableWrapper(mCutoutWithWaterfall),
assertNotEquals(new ParcelableWrapper(mCutoutTop), new ParcelableWrapper(mCutoutNumbers)); new ParcelableWrapper(createCutoutWithWaterfall()));
assertNotEquals(new ParcelableWrapper(mCutoutWithWaterfall),
new ParcelableWrapper(mCutoutNumbers));
} }
private static DisplayCutout createCutoutTop() { private static DisplayCutout createCutoutTop() {
@@ -363,4 +440,28 @@ public class DisplayCutoutTest {
safeInset, null /* boundLeft */, boundTop, null /* boundRight */, safeInset, null /* boundLeft */, boundTop, null /* boundRight */,
null /* boundBottom */); null /* boundBottom */);
} }
private static DisplayCutout createCutoutWithWaterfall() {
return new DisplayCutout(
Insets.of(20, 100, 20, 0),
ZERO_RECT,
new Rect(50, 0, 75, 100),
ZERO_RECT,
ZERO_RECT,
Insets.of(20, 0, 20, 0));
}
private static DisplayCutout createCutoutWaterfallOnly() {
return createCutoutWaterfallOnly(Insets.of(20, 0, 20, 0));
}
private static DisplayCutout createCutoutWaterfallOnly(Insets waterfallInsets) {
return new DisplayCutout(
Insets.of(20, 0, 20, 0),
ZERO_RECT,
ZERO_RECT,
ZERO_RECT,
ZERO_RECT,
waterfallInsets);
}
} }