Create an API to get a cutout path

- Added a new API to get the cutout paths.
- Create a @hide class CutoutPathParserInfo
  - Used to store the needed info to create the cutout paths.
  - Will be used when DisplayCutout.getCutoutPath() is called to
    create the cutout paths.

Bug: 172646505
Test: atest DisplayCutoutTest DisplayContentTests WmDisplayCutoutTest
Change-Id: Ief92ce4466f6bffb2a9375388b71de43049b4eef
This commit is contained in:
shawnlin
2021-01-15 11:46:01 +08:00
parent 8241f19a03
commit f7ff55217c
8 changed files with 406 additions and 62 deletions

View File

@@ -46174,6 +46174,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();

View File

@@ -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);
}
}
}

View File

@@ -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) {
@@ -231,6 +243,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.
*
@@ -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

View File

@@ -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
@@ -164,6 +169,59 @@ public class DisplayCutoutTest {
assertEquals(Insets.of(5, 6, 7, 8), cutout.getWaterfallInsets());
}
@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());

View File

@@ -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);
}

View File

@@ -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;
@@ -1937,18 +1938,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);
}

View File

@@ -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);
}

View File

@@ -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());