Merge "To add new marker to support long edge cutout"
This commit is contained in:
committed by
Android (Google) Code Review
commit
d0db68c890
@ -0,0 +1,240 @@
|
||||
/*
|
||||
* Copyright (C) 2020 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.view;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Matrix;
|
||||
import android.graphics.Path;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.RectF;
|
||||
import android.graphics.Region;
|
||||
import android.perftests.utils.BenchmarkState;
|
||||
import android.perftests.utils.PerfStatusReporter;
|
||||
import android.text.TextUtils;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.Log;
|
||||
import android.util.PathParser;
|
||||
|
||||
import androidx.test.filters.LargeTest;
|
||||
import androidx.test.platform.app.InstrumentationRegistry;
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@LargeTest
|
||||
public class CutoutSpecificationBenchmark {
|
||||
private static final String TAG = "CutoutSpecificationBenchmark";
|
||||
|
||||
private static final String BOTTOM_MARKER = "@bottom";
|
||||
private static final String DP_MARKER = "@dp";
|
||||
private static final String RIGHT_MARKER = "@right";
|
||||
private static final String LEFT_MARKER = "@left";
|
||||
|
||||
private static final String DOUBLE_CUTOUT_SPEC = "M 0,0\n"
|
||||
+ "L -72, 0\n"
|
||||
+ "L -69.9940446283, 20.0595537175\n"
|
||||
+ "C -69.1582133885, 28.4178661152 -65.2, 32.0 -56.8, 32.0\n"
|
||||
+ "L 56.8, 32.0\n"
|
||||
+ "C 65.2, 32.0 69.1582133885, 28.4178661152 69.9940446283, 20.0595537175\n"
|
||||
+ "L 72, 0\n"
|
||||
+ "Z\n"
|
||||
+ "@bottom\n"
|
||||
+ "M 0,0\n"
|
||||
+ "L -72, 0\n"
|
||||
+ "L -69.9940446283, -20.0595537175\n"
|
||||
+ "C -69.1582133885, -28.4178661152 -65.2, -32.0 -56.8, -32.0\n"
|
||||
+ "L 56.8, -32.0\n"
|
||||
+ "C 65.2, -32.0 69.1582133885, -28.4178661152 69.9940446283, -20.0595537175\n"
|
||||
+ "L 72, 0\n"
|
||||
+ "Z\n"
|
||||
+ "@dp";
|
||||
@Rule
|
||||
public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
|
||||
|
||||
private Context mContext;
|
||||
private DisplayMetrics mDisplayMetrics;
|
||||
|
||||
/**
|
||||
* Setup the necessary member field used by test methods.
|
||||
*/
|
||||
@Before
|
||||
public void setUp() {
|
||||
mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
|
||||
|
||||
mDisplayMetrics = new DisplayMetrics();
|
||||
mContext.getDisplay().getRealMetrics(mDisplayMetrics);
|
||||
}
|
||||
|
||||
|
||||
private static void toRectAndAddToRegion(Path p, Region inoutRegion, Rect inoutRect) {
|
||||
final RectF rectF = new RectF();
|
||||
p.computeBounds(rectF, false /* unused */);
|
||||
rectF.round(inoutRect);
|
||||
inoutRegion.op(inoutRect, Region.Op.UNION);
|
||||
}
|
||||
|
||||
private static void oldMethodParsingSpec(String spec, int displayWidth, int displayHeight,
|
||||
float density) {
|
||||
Path p = null;
|
||||
Rect boundTop = null;
|
||||
Rect boundBottom = null;
|
||||
Rect safeInset = new Rect();
|
||||
String bottomSpec = null;
|
||||
if (!TextUtils.isEmpty(spec)) {
|
||||
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());
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseByOldMethodForDoubleCutout() {
|
||||
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
|
||||
while (state.keepRunning()) {
|
||||
oldMethodParsingSpec(DOUBLE_CUTOUT_SPEC, mDisplayMetrics.widthPixels,
|
||||
mDisplayMetrics.heightPixels, mDisplayMetrics.density);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseByNewMethodForDoubleCutout() {
|
||||
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
|
||||
while (state.keepRunning()) {
|
||||
new CutoutSpecification.Parser(mDisplayMetrics.density,
|
||||
mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels)
|
||||
.parse(DOUBLE_CUTOUT_SPEC);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseLongEdgeCutout() {
|
||||
final String spec = "M 0,0\n"
|
||||
+ "H 48\n"
|
||||
+ "V 48\n"
|
||||
+ "H -48\n"
|
||||
+ "Z\n"
|
||||
+ "@left\n"
|
||||
+ "@center_vertical\n"
|
||||
+ "M 0,0\n"
|
||||
+ "H 48\n"
|
||||
+ "V 48\n"
|
||||
+ "H -48\n"
|
||||
+ "Z\n"
|
||||
+ "@left\n"
|
||||
+ "@center_vertical\n"
|
||||
+ "M 0,0\n"
|
||||
+ "H -48\n"
|
||||
+ "V 48\n"
|
||||
+ "H 48\n"
|
||||
+ "Z\n"
|
||||
+ "@right\n"
|
||||
+ "@dp";
|
||||
|
||||
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
|
||||
while (state.keepRunning()) {
|
||||
new CutoutSpecification.Parser(mDisplayMetrics.density,
|
||||
mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels).parse(spec);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseShortEdgeCutout() {
|
||||
final String spec = "M 0,0\n"
|
||||
+ "H 48\n"
|
||||
+ "V 48\n"
|
||||
+ "H -48\n"
|
||||
+ "Z\n"
|
||||
+ "@bottom\n"
|
||||
+ "M 0,0\n"
|
||||
+ "H 48\n"
|
||||
+ "V -48\n"
|
||||
+ "H -48\n"
|
||||
+ "Z\n"
|
||||
+ "@dp";
|
||||
|
||||
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
|
||||
while (state.keepRunning()) {
|
||||
new CutoutSpecification.Parser(mDisplayMetrics.density,
|
||||
mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels).parse(spec);
|
||||
}
|
||||
}
|
||||
}
|
486
core/java/android/view/CutoutSpecification.java
Normal file
486
core/java/android/view/CutoutSpecification.java
Normal file
@ -0,0 +1,486 @@
|
||||
/*
|
||||
* Copyright (C) 2020 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.view;
|
||||
|
||||
import static android.view.Gravity.BOTTOM;
|
||||
import static android.view.Gravity.LEFT;
|
||||
import static android.view.Gravity.RIGHT;
|
||||
import static android.view.Gravity.TOP;
|
||||
|
||||
import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.graphics.Insets;
|
||||
import android.graphics.Matrix;
|
||||
import android.graphics.Path;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.RectF;
|
||||
import android.graphics.Region;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.util.PathParser;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* In order to accept the cutout specification for all of edges in devices, the specification
|
||||
* parsing method is extracted from
|
||||
* {@link android.view.DisplayCutout#fromResourcesRectApproximation(Resources, int, int)} to be
|
||||
* the specified class for parsing the specification.
|
||||
* BNF definition:
|
||||
* <ul>
|
||||
* <li>Cutouts Specification = ([Cutout Delimiter],Cutout Specification) {...}, [Dp] ; </li>
|
||||
* <li>Cutout Specification = [Vertical Position], (SVG Path Element), [Horizontal Position]
|
||||
* [Bind Cutout] ;</li>
|
||||
* <li>Vertical Position = "@bottom" | "@center_vertical" ;</li>
|
||||
* <li>Horizontal Position = "@left" | "@right" ;</li>
|
||||
* <li>Bind Cutout = "@bind_left_cutout" | "@bind_right_cutout" ;</li>
|
||||
* <li>Cutout Delimiter = "@cutout" ;</li>
|
||||
* <li>Dp = "@dp"</li>
|
||||
* </ul>
|
||||
*
|
||||
* <ul>
|
||||
* <li>Vertical position is top by default if there is neither "@bottom" nor "@center_vertical"
|
||||
* </li>
|
||||
* <li>Horizontal position is center horizontal by default if there is neither "@left" nor
|
||||
* "@right".</li>
|
||||
* <li>@bottom make the cutout piece bind to bottom edge.</li>
|
||||
* <li>both of @bind_left_cutout and @bind_right_cutout are use to claim the cutout belong to
|
||||
* left or right edge cutout.</li>
|
||||
* </ul>
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@VisibleForTesting(visibility = PACKAGE)
|
||||
public class CutoutSpecification {
|
||||
private static final String TAG = "CutoutSpecification";
|
||||
private static final boolean DEBUG = false;
|
||||
|
||||
private static final int MINIMAL_ACCEPTABLE_PATH_LENGTH = "H1V1Z".length();
|
||||
|
||||
private static final char MARKER_START_CHAR = '@';
|
||||
private static final String DP_MARKER = MARKER_START_CHAR + "dp";
|
||||
|
||||
private static final String BOTTOM_MARKER = MARKER_START_CHAR + "bottom";
|
||||
private static final String RIGHT_MARKER = MARKER_START_CHAR + "right";
|
||||
private static final String LEFT_MARKER = MARKER_START_CHAR + "left";
|
||||
private static final String CUTOUT_MARKER = MARKER_START_CHAR + "cutout";
|
||||
private static final String CENTER_VERTICAL_MARKER = MARKER_START_CHAR + "center_vertical";
|
||||
|
||||
/* By default, it's top bound cutout. That's why TOP_BOUND_CUTOUT_MARKER is not defined */
|
||||
private static final String BIND_RIGHT_CUTOUT_MARKER = MARKER_START_CHAR + "bind_right_cutout";
|
||||
private static final String BIND_LEFT_CUTOUT_MARKER = MARKER_START_CHAR + "bind_left_cutout";
|
||||
|
||||
private final Path mPath;
|
||||
private final Rect mLeftBound;
|
||||
private final Rect mTopBound;
|
||||
private final Rect mRightBound;
|
||||
private final Rect mBottomBound;
|
||||
private final Insets mInsets;
|
||||
|
||||
private CutoutSpecification(@NonNull Parser parser) {
|
||||
mPath = parser.mPath;
|
||||
mLeftBound = parser.mLeftBound;
|
||||
mTopBound = parser.mTopBound;
|
||||
mRightBound = parser.mRightBound;
|
||||
mBottomBound = parser.mBottomBound;
|
||||
mInsets = parser.mInsets;
|
||||
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, String.format(Locale.ENGLISH,
|
||||
"left cutout = %s, top cutout = %s, right cutout = %s, bottom cutout = %s",
|
||||
mLeftBound != null ? mLeftBound.toString() : "",
|
||||
mTopBound != null ? mTopBound.toString() : "",
|
||||
mRightBound != null ? mRightBound.toString() : "",
|
||||
mBottomBound != null ? mBottomBound.toString() : ""));
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting(visibility = PACKAGE)
|
||||
@Nullable
|
||||
public Path getPath() {
|
||||
return mPath;
|
||||
}
|
||||
|
||||
@VisibleForTesting(visibility = PACKAGE)
|
||||
@Nullable
|
||||
public Rect getLeftBound() {
|
||||
return mLeftBound;
|
||||
}
|
||||
|
||||
@VisibleForTesting(visibility = PACKAGE)
|
||||
@Nullable
|
||||
public Rect getTopBound() {
|
||||
return mTopBound;
|
||||
}
|
||||
|
||||
@VisibleForTesting(visibility = PACKAGE)
|
||||
@Nullable
|
||||
public Rect getRightBound() {
|
||||
return mRightBound;
|
||||
}
|
||||
|
||||
@VisibleForTesting(visibility = PACKAGE)
|
||||
@Nullable
|
||||
public Rect getBottomBound() {
|
||||
return mBottomBound;
|
||||
}
|
||||
|
||||
/**
|
||||
* To count the safe inset according to the cutout bounds and waterfall inset.
|
||||
*
|
||||
* @return the safe inset.
|
||||
*/
|
||||
@VisibleForTesting(visibility = PACKAGE)
|
||||
@NonNull
|
||||
public Rect getSafeInset() {
|
||||
return mInsets.toRect();
|
||||
}
|
||||
|
||||
private static int decideWhichEdge(boolean isTopEdgeShortEdge,
|
||||
boolean isShortEdge, boolean isStart) {
|
||||
return (isTopEdgeShortEdge)
|
||||
? ((isShortEdge) ? (isStart ? TOP : BOTTOM) : (isStart ? LEFT : RIGHT))
|
||||
: ((isShortEdge) ? (isStart ? LEFT : RIGHT) : (isStart ? TOP : BOTTOM));
|
||||
}
|
||||
|
||||
/**
|
||||
* The CutoutSpecification Parser.
|
||||
*/
|
||||
@VisibleForTesting(visibility = PACKAGE)
|
||||
public static class Parser {
|
||||
private final boolean mIsShortEdgeOnTop;
|
||||
private final float mDensity;
|
||||
private final int mDisplayWidth;
|
||||
private final int mDisplayHeight;
|
||||
private final Matrix mMatrix;
|
||||
private Insets mInsets;
|
||||
private int mSafeInsetLeft;
|
||||
private int mSafeInsetTop;
|
||||
private int mSafeInsetRight;
|
||||
private int mSafeInsetBottom;
|
||||
|
||||
private final Rect mTmpRect = new Rect();
|
||||
private final RectF mTmpRectF = new RectF();
|
||||
|
||||
private boolean mInDp;
|
||||
|
||||
private Path mPath;
|
||||
private Rect mLeftBound;
|
||||
private Rect mTopBound;
|
||||
private Rect mRightBound;
|
||||
private Rect mBottomBound;
|
||||
|
||||
private boolean mPositionFromLeft = false;
|
||||
private boolean mPositionFromRight = false;
|
||||
private boolean mPositionFromBottom = false;
|
||||
private boolean mPositionFromCenterVertical = false;
|
||||
|
||||
private boolean mBindLeftCutout = false;
|
||||
private boolean mBindRightCutout = false;
|
||||
private boolean mBindBottomCutout = false;
|
||||
|
||||
private boolean mIsTouchShortEdgeStart;
|
||||
private boolean mIsTouchShortEdgeEnd;
|
||||
private boolean mIsCloserToStartSide;
|
||||
|
||||
/**
|
||||
* The constructor of the CutoutSpecification parser to parse the specification of cutout.
|
||||
* @param density the display density.
|
||||
* @param displayWidth the display width.
|
||||
* @param displayHeight the display height.
|
||||
*/
|
||||
@VisibleForTesting(visibility = PACKAGE)
|
||||
public Parser(float density, int displayWidth, int displayHeight) {
|
||||
mDensity = density;
|
||||
mDisplayWidth = displayWidth;
|
||||
mDisplayHeight = displayHeight;
|
||||
mMatrix = new Matrix();
|
||||
mIsShortEdgeOnTop = mDisplayWidth < mDisplayHeight;
|
||||
}
|
||||
|
||||
private void computeBoundsRectAndAddToRegion(Path p, Region inoutRegion, Rect inoutRect) {
|
||||
mTmpRectF.setEmpty();
|
||||
p.computeBounds(mTmpRectF, false /* unused */);
|
||||
mTmpRectF.round(inoutRect);
|
||||
inoutRegion.op(inoutRect, Region.Op.UNION);
|
||||
}
|
||||
|
||||
private void resetStatus(StringBuilder sb) {
|
||||
sb.setLength(0);
|
||||
mPositionFromBottom = false;
|
||||
mPositionFromLeft = false;
|
||||
mPositionFromRight = false;
|
||||
mPositionFromCenterVertical = false;
|
||||
|
||||
mBindLeftCutout = false;
|
||||
mBindRightCutout = false;
|
||||
mBindBottomCutout = false;
|
||||
}
|
||||
|
||||
private void translateMatrix() {
|
||||
final float offsetX;
|
||||
if (mPositionFromRight) {
|
||||
offsetX = mDisplayWidth;
|
||||
} else if (mPositionFromLeft) {
|
||||
offsetX = 0;
|
||||
} else {
|
||||
offsetX = mDisplayWidth / 2f;
|
||||
}
|
||||
|
||||
final float offsetY;
|
||||
if (mPositionFromBottom) {
|
||||
offsetY = mDisplayHeight;
|
||||
} else if (mPositionFromCenterVertical) {
|
||||
offsetY = mDisplayHeight / 2f;
|
||||
} else {
|
||||
offsetY = 0;
|
||||
}
|
||||
|
||||
mMatrix.reset();
|
||||
if (mInDp) {
|
||||
mMatrix.postScale(mDensity, mDensity);
|
||||
}
|
||||
mMatrix.postTranslate(offsetX, offsetY);
|
||||
}
|
||||
|
||||
private int computeSafeInsets(int gravity, Rect rect) {
|
||||
if (gravity == LEFT && rect.right > 0 && rect.right < mDisplayWidth) {
|
||||
return rect.right;
|
||||
} else if (gravity == TOP && rect.bottom > 0 && rect.bottom < mDisplayHeight) {
|
||||
return rect.bottom;
|
||||
} else if (gravity == RIGHT && rect.left > 0 && rect.left < mDisplayWidth) {
|
||||
return mDisplayWidth - rect.left;
|
||||
} else if (gravity == BOTTOM && rect.top > 0 && rect.top < mDisplayHeight) {
|
||||
return mDisplayHeight - rect.top;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private void setSafeInset(int gravity, int inset) {
|
||||
if (gravity == LEFT) {
|
||||
mSafeInsetLeft = inset;
|
||||
} else if (gravity == TOP) {
|
||||
mSafeInsetTop = inset;
|
||||
} else if (gravity == RIGHT) {
|
||||
mSafeInsetRight = inset;
|
||||
} else if (gravity == BOTTOM) {
|
||||
mSafeInsetBottom = inset;
|
||||
}
|
||||
}
|
||||
|
||||
private int getSafeInset(int gravity) {
|
||||
if (gravity == LEFT) {
|
||||
return mSafeInsetLeft;
|
||||
} else if (gravity == TOP) {
|
||||
return mSafeInsetTop;
|
||||
} else if (gravity == RIGHT) {
|
||||
return mSafeInsetRight;
|
||||
} else if (gravity == BOTTOM) {
|
||||
return mSafeInsetBottom;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private Rect onSetEdgeCutout(boolean isStart, boolean isShortEdge, @NonNull Rect rect) {
|
||||
final int gravity;
|
||||
if (isShortEdge) {
|
||||
gravity = decideWhichEdge(mIsShortEdgeOnTop, true, isStart);
|
||||
} else {
|
||||
if (mIsTouchShortEdgeStart && mIsTouchShortEdgeEnd) {
|
||||
gravity = decideWhichEdge(mIsShortEdgeOnTop, false, isStart);
|
||||
} else if (mIsTouchShortEdgeStart || mIsTouchShortEdgeEnd) {
|
||||
gravity = decideWhichEdge(mIsShortEdgeOnTop, true,
|
||||
mIsCloserToStartSide);
|
||||
} else {
|
||||
gravity = decideWhichEdge(mIsShortEdgeOnTop, isShortEdge, isStart);
|
||||
}
|
||||
}
|
||||
|
||||
int oldSafeInset = getSafeInset(gravity);
|
||||
int newSafeInset = computeSafeInsets(gravity, rect);
|
||||
if (oldSafeInset < newSafeInset) {
|
||||
setSafeInset(gravity, newSafeInset);
|
||||
}
|
||||
|
||||
return new Rect(rect);
|
||||
}
|
||||
|
||||
private void setEdgeCutout(@NonNull Path newPath) {
|
||||
if (mBindRightCutout && mRightBound == null) {
|
||||
mRightBound = onSetEdgeCutout(false, !mIsShortEdgeOnTop, mTmpRect);
|
||||
} else if (mBindLeftCutout && mLeftBound == null) {
|
||||
mLeftBound = onSetEdgeCutout(true, !mIsShortEdgeOnTop, mTmpRect);
|
||||
} else if (mBindBottomCutout && mBottomBound == null) {
|
||||
mBottomBound = onSetEdgeCutout(false, mIsShortEdgeOnTop, mTmpRect);
|
||||
} else if (!(mBindBottomCutout || mBindLeftCutout || mBindRightCutout)
|
||||
&& mTopBound == null) {
|
||||
mTopBound = onSetEdgeCutout(true, mIsShortEdgeOnTop, mTmpRect);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mPath != null) {
|
||||
mPath.addPath(newPath);
|
||||
} else {
|
||||
mPath = newPath;
|
||||
}
|
||||
}
|
||||
|
||||
private void parseSvgPathSpec(Region region, String spec) {
|
||||
if (TextUtils.length(spec) < MINIMAL_ACCEPTABLE_PATH_LENGTH) {
|
||||
Log.e(TAG, "According to SVG definition, it shouldn't happen");
|
||||
return;
|
||||
}
|
||||
spec.trim();
|
||||
translateMatrix();
|
||||
|
||||
final Path newPath = PathParser.createPathFromPathData(spec);
|
||||
newPath.transform(mMatrix);
|
||||
computeBoundsRectAndAddToRegion(newPath, region, mTmpRect);
|
||||
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, String.format(Locale.ENGLISH,
|
||||
"hasLeft = %b, hasRight = %b, hasBottom = %b, hasCenterVertical = %b",
|
||||
mPositionFromLeft, mPositionFromRight, mPositionFromBottom,
|
||||
mPositionFromCenterVertical));
|
||||
Log.d(TAG, "region = " + region);
|
||||
Log.d(TAG, "spec = \"" + spec + "\" rect = " + mTmpRect + " newPath = " + newPath);
|
||||
}
|
||||
|
||||
if (mTmpRect.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mIsShortEdgeOnTop) {
|
||||
mIsTouchShortEdgeStart = mTmpRect.top <= 0;
|
||||
mIsTouchShortEdgeEnd = mTmpRect.bottom >= mDisplayHeight;
|
||||
mIsCloserToStartSide = mTmpRect.centerY() < mDisplayHeight / 2;
|
||||
} else {
|
||||
mIsTouchShortEdgeStart = mTmpRect.left <= 0;
|
||||
mIsTouchShortEdgeEnd = mTmpRect.right >= mDisplayWidth;
|
||||
mIsCloserToStartSide = mTmpRect.centerX() < mDisplayWidth / 2;
|
||||
}
|
||||
|
||||
setEdgeCutout(newPath);
|
||||
}
|
||||
|
||||
private void parseSpecWithoutDp(@NonNull String specWithoutDp) {
|
||||
Region region = Region.obtain();
|
||||
StringBuilder sb = null;
|
||||
int currentIndex = 0;
|
||||
int lastIndex = 0;
|
||||
while ((currentIndex = specWithoutDp.indexOf(MARKER_START_CHAR, lastIndex)) != -1) {
|
||||
if (sb == null) {
|
||||
sb = new StringBuilder(specWithoutDp.length());
|
||||
}
|
||||
sb.append(specWithoutDp, lastIndex, currentIndex);
|
||||
|
||||
if (specWithoutDp.startsWith(LEFT_MARKER, currentIndex)) {
|
||||
if (!mPositionFromRight) {
|
||||
mPositionFromLeft = true;
|
||||
}
|
||||
currentIndex += LEFT_MARKER.length();
|
||||
} else if (specWithoutDp.startsWith(RIGHT_MARKER, currentIndex)) {
|
||||
if (!mPositionFromLeft) {
|
||||
mPositionFromRight = true;
|
||||
}
|
||||
currentIndex += RIGHT_MARKER.length();
|
||||
} else if (specWithoutDp.startsWith(BOTTOM_MARKER, currentIndex)) {
|
||||
if (!mPositionFromCenterVertical) {
|
||||
parseSvgPathSpec(region, sb.toString());
|
||||
}
|
||||
currentIndex += BOTTOM_MARKER.length();
|
||||
|
||||
/* prepare to parse the rest path */
|
||||
resetStatus(sb);
|
||||
mBindBottomCutout = true;
|
||||
mPositionFromBottom = true;
|
||||
} else if (specWithoutDp.startsWith(CENTER_VERTICAL_MARKER, currentIndex)) {
|
||||
if (!mPositionFromBottom) {
|
||||
parseSvgPathSpec(region, sb.toString());
|
||||
}
|
||||
currentIndex += CENTER_VERTICAL_MARKER.length();
|
||||
|
||||
/* prepare to parse the rest path */
|
||||
resetStatus(sb);
|
||||
mPositionFromCenterVertical = true;
|
||||
} else if (specWithoutDp.startsWith(CUTOUT_MARKER, currentIndex)) {
|
||||
parseSvgPathSpec(region, sb.toString());
|
||||
currentIndex += CUTOUT_MARKER.length();
|
||||
|
||||
/* prepare to parse the rest path */
|
||||
resetStatus(sb);
|
||||
} else if (specWithoutDp.startsWith(BIND_LEFT_CUTOUT_MARKER, currentIndex)) {
|
||||
if (!mBindBottomCutout && !mBindRightCutout) {
|
||||
mBindLeftCutout = true;
|
||||
}
|
||||
currentIndex += BIND_LEFT_CUTOUT_MARKER.length();
|
||||
} else if (specWithoutDp.startsWith(BIND_RIGHT_CUTOUT_MARKER, currentIndex)) {
|
||||
if (!mBindBottomCutout && !mBindLeftCutout) {
|
||||
mBindRightCutout = true;
|
||||
}
|
||||
currentIndex += BIND_RIGHT_CUTOUT_MARKER.length();
|
||||
} else {
|
||||
currentIndex += 1;
|
||||
}
|
||||
|
||||
lastIndex = currentIndex;
|
||||
}
|
||||
|
||||
if (sb == null) {
|
||||
parseSvgPathSpec(region, specWithoutDp);
|
||||
} else {
|
||||
sb.append(specWithoutDp, lastIndex, specWithoutDp.length());
|
||||
parseSvgPathSpec(region, sb.toString());
|
||||
}
|
||||
|
||||
region.recycle();
|
||||
}
|
||||
|
||||
/**
|
||||
* To parse specification string as the CutoutSpecification.
|
||||
*
|
||||
* @param originalSpec the specification string
|
||||
* @return the CutoutSpecification instance
|
||||
*/
|
||||
@VisibleForTesting(visibility = PACKAGE)
|
||||
public CutoutSpecification parse(@NonNull String originalSpec) {
|
||||
Objects.requireNonNull(originalSpec);
|
||||
|
||||
int dpIndex = originalSpec.lastIndexOf(DP_MARKER);
|
||||
mInDp = (dpIndex != -1);
|
||||
final String spec;
|
||||
if (dpIndex != -1) {
|
||||
spec = originalSpec.substring(0, dpIndex)
|
||||
+ originalSpec.substring(dpIndex + DP_MARKER.length());
|
||||
} else {
|
||||
spec = originalSpec;
|
||||
}
|
||||
|
||||
parseSpecWithoutDp(spec);
|
||||
|
||||
mInsets = Insets.of(mSafeInsetLeft, mSafeInsetTop, mSafeInsetRight, mSafeInsetBottom);
|
||||
return new CutoutSpecification(this);
|
||||
}
|
||||
}
|
||||
}
|
@ -31,18 +31,12 @@ 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.graphics.RectF;
|
||||
import android.graphics.Region;
|
||||
import android.graphics.Region.Op;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
import android.util.PathParser;
|
||||
import android.util.proto.ProtoOutputStream;
|
||||
|
||||
import com.android.internal.R;
|
||||
@ -63,10 +57,6 @@ import java.util.List;
|
||||
public final class DisplayCutout {
|
||||
|
||||
private static final String TAG = "DisplayCutout";
|
||||
private static final String BOTTOM_MARKER = "@bottom";
|
||||
private static final String DP_MARKER = "@dp";
|
||||
private static final String RIGHT_MARKER = "@right";
|
||||
private static final String LEFT_MARKER = "@left";
|
||||
|
||||
/**
|
||||
* Category for overlays that allow emulating a display cutout on devices that don't have
|
||||
@ -703,77 +693,16 @@ public final class DisplayCutout {
|
||||
}
|
||||
}
|
||||
|
||||
Path p = null;
|
||||
Rect boundTop = null;
|
||||
Rect boundBottom = null;
|
||||
Rect safeInset = new Rect();
|
||||
String bottomSpec = null;
|
||||
if (!TextUtils.isEmpty(spec)) {
|
||||
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());
|
||||
}
|
||||
spec = spec.trim();
|
||||
|
||||
if (spec.contains(BOTTOM_MARKER)) {
|
||||
String[] splits = spec.split(BOTTOM_MARKER, 2);
|
||||
spec = splits[0].trim();
|
||||
bottomSpec = splits[1].trim();
|
||||
}
|
||||
CutoutSpecification cutoutSpec = new CutoutSpecification.Parser(density,
|
||||
displayWidth, displayHeight).parse(spec);
|
||||
Rect safeInset = cutoutSpec.getSafeInset();
|
||||
final Rect boundLeft = cutoutSpec.getLeftBound();
|
||||
final Rect boundTop = cutoutSpec.getTopBound();
|
||||
final Rect boundRight = cutoutSpec.getRightBound();
|
||||
final Rect boundBottom = cutoutSpec.getBottomBound();
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
if (!waterfallInsets.equals(Insets.NONE)) {
|
||||
safeInset.set(
|
||||
@ -784,9 +713,9 @@ public final class DisplayCutout {
|
||||
}
|
||||
|
||||
final DisplayCutout cutout = new DisplayCutout(
|
||||
safeInset, waterfallInsets, null /* boundLeft */, boundTop,
|
||||
null /* boundRight */, boundBottom, false /* copyArguments */);
|
||||
final Pair<Path, DisplayCutout> result = new Pair<>(p, cutout);
|
||||
safeInset, waterfallInsets, boundLeft, boundTop,
|
||||
boundRight, boundBottom, false /* copyArguments */);
|
||||
final Pair<Path, DisplayCutout> result = new Pair<>(cutoutSpec.getPath(), cutout);
|
||||
synchronized (CACHE_LOCK) {
|
||||
sCachedSpec = spec;
|
||||
sCachedDisplayWidth = displayWidth;
|
||||
@ -798,14 +727,6 @@ public final class DisplayCutout {
|
||||
return result;
|
||||
}
|
||||
|
||||
private static void toRectAndAddToRegion(Path p, Region inoutRegion, Rect inoutRect) {
|
||||
final RectF rectF = new RectF();
|
||||
p.computeBounds(rectF, false /* unused */);
|
||||
rectF.round(inoutRect);
|
||||
inoutRegion.op(inoutRect, Op.UNION);
|
||||
}
|
||||
|
||||
|
||||
private static Insets loadWaterfallInset(Resources res) {
|
||||
return Insets.of(
|
||||
res.getDimensionPixelSize(R.dimen.waterfall_display_left_edge_size),
|
||||
|
@ -0,0 +1,262 @@
|
||||
/*
|
||||
* Copyright (C) 2020 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.view;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.testng.Assert.assertThrows;
|
||||
|
||||
import android.graphics.Rect;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
public class CutoutSpecificationTest {
|
||||
private static final String WITHOUT_BIND_CUTOUT_SPECIFICATION = "M 0,0\n"
|
||||
+ "h 48\n"
|
||||
+ "v 48\n"
|
||||
+ "h -48\n"
|
||||
+ "z\n"
|
||||
+ "@left\n"
|
||||
+ "@center_vertical\n"
|
||||
+ "M 0,0\n"
|
||||
+ "h 48\n"
|
||||
+ "v 48\n"
|
||||
+ "h -48\n"
|
||||
+ "z\n"
|
||||
+ "@left\n"
|
||||
+ "@center_vertical\n"
|
||||
+ "M 0,0\n"
|
||||
+ "h -48\n"
|
||||
+ "v 48\n"
|
||||
+ "h 48\n"
|
||||
+ "z\n"
|
||||
+ "@right\n"
|
||||
+ "@dp";
|
||||
private static final String WITH_BIND_CUTOUT_SPECIFICATION = "M 0,0\n"
|
||||
+ "h 48\n"
|
||||
+ "v 48\n"
|
||||
+ "h -48\n"
|
||||
+ "z\n"
|
||||
+ "@left\n"
|
||||
+ "@center_vertical\n"
|
||||
+ "M 0,0\n"
|
||||
+ "h 48\n"
|
||||
+ "v 48\n"
|
||||
+ "h -48\n"
|
||||
+ "z\n"
|
||||
+ "@left\n"
|
||||
+ "@bind_left_cutout\n"
|
||||
+ "@center_vertical\n"
|
||||
+ "M 0,0\n"
|
||||
+ "h -48\n"
|
||||
+ "v 48\n"
|
||||
+ "h 48\n"
|
||||
+ "z\n"
|
||||
+ "@right\n"
|
||||
+ "@bind_right_cutout\n"
|
||||
+ "@dp";
|
||||
private static final String CORNER_CUTOUT_SPECIFICATION = "M 0,0\n"
|
||||
+ "h 1\n"
|
||||
+ "v 1\n"
|
||||
+ "h -1\n"
|
||||
+ "z\n"
|
||||
+ "@left\n"
|
||||
+ "@cutout\n"
|
||||
+ "M 0, 0\n"
|
||||
+ "h -2\n"
|
||||
+ "v 2\n"
|
||||
+ "h 2\n"
|
||||
+ "z\n"
|
||||
+ "@right\n"
|
||||
+ "@bind_right_cutout\n"
|
||||
+ "@cutout\n"
|
||||
+ "M 0, 200\n"
|
||||
+ "h 3\n"
|
||||
+ "v -3\n"
|
||||
+ "h -3\n"
|
||||
+ "z\n"
|
||||
+ "@left\n"
|
||||
+ "@bind_left_cutout\n"
|
||||
+ "@bottom\n"
|
||||
+ "M 0, 0\n"
|
||||
+ "h -4\n"
|
||||
+ "v -4\n"
|
||||
+ "h 4\n"
|
||||
+ "z\n"
|
||||
+ "@right\n"
|
||||
+ "@dp";
|
||||
|
||||
private CutoutSpecification.Parser mParser;
|
||||
|
||||
/**
|
||||
* Setup the necessary member field used by test methods.
|
||||
*/
|
||||
@Before
|
||||
public void setUp() {
|
||||
mParser = new CutoutSpecification.Parser(3.5f, 1080, 1920);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parse_nullString_shouldTriggerException() {
|
||||
assertThrows(NullPointerException.class, () -> mParser.parse(null));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parse_emptyString_pathShouldBeNull() {
|
||||
CutoutSpecification cutoutSpecification = mParser.parse("");
|
||||
assertThat(cutoutSpecification.getPath()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parse_withoutBindMarker_shouldHaveNoLeftBound() {
|
||||
CutoutSpecification cutoutSpecification = mParser.parse(WITHOUT_BIND_CUTOUT_SPECIFICATION);
|
||||
assertThat(cutoutSpecification.getLeftBound()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parse_withoutBindMarker_shouldHaveNoRightBound() {
|
||||
CutoutSpecification cutoutSpecification = mParser.parse(WITHOUT_BIND_CUTOUT_SPECIFICATION);
|
||||
assertThat(cutoutSpecification.getRightBound()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parse_withBindMarker_shouldHaveLeftBound() {
|
||||
CutoutSpecification cutoutSpecification = mParser.parse(WITH_BIND_CUTOUT_SPECIFICATION);
|
||||
assertThat(cutoutSpecification.getLeftBound()).isEqualTo(new Rect(0, 960, 168, 1128));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parse_withBindMarker_shouldHaveRightBound() {
|
||||
CutoutSpecification cutoutSpecification = mParser.parse(WITH_BIND_CUTOUT_SPECIFICATION);
|
||||
assertThat(cutoutSpecification.getRightBound()).isEqualTo(new Rect(912, 960, 1080, 1128));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parse_tallCutout_shouldBeDone() {
|
||||
CutoutSpecification cutoutSpecification = mParser.parse("M 0,0\n"
|
||||
+ "L -48, 0\n"
|
||||
+ "L -44.3940446283, 36.0595537175\n"
|
||||
+ "C -43.5582133885, 44.4178661152 -39.6, 48.0 -31.2, 48.0\n"
|
||||
+ "L 31.2, 48.0\n"
|
||||
+ "C 39.6, 48.0 43.5582133885, 44.4178661152 44.3940446283, 36.0595537175\n"
|
||||
+ "L 48, 0\n"
|
||||
+ "Z\n"
|
||||
+ "@dp");
|
||||
|
||||
assertThat(cutoutSpecification.getTopBound().height()).isEqualTo(168);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parse_wideCutout_shouldBeDone() {
|
||||
CutoutSpecification cutoutSpecification = mParser.parse("M 0,0\n"
|
||||
+ "L -72, 0\n"
|
||||
+ "L -69.9940446283, 20.0595537175\n"
|
||||
+ "C -69.1582133885, 28.4178661152 -65.2, 32.0 -56.8, 32.0\n"
|
||||
+ "L 56.8, 32.0\n"
|
||||
+ "C 65.2, 32.0 69.1582133885, 28.4178661152 69.9940446283, 20.0595537175\n"
|
||||
+ "L 72, 0\n"
|
||||
+ "Z\n"
|
||||
+ "@dp");
|
||||
|
||||
assertThat(cutoutSpecification.getTopBound().width()).isEqualTo(504);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parse_narrowCutout_shouldBeDone() {
|
||||
CutoutSpecification cutoutSpecification = mParser.parse("M 0,0\n"
|
||||
+ "L -24, 0\n"
|
||||
+ "L -21.9940446283, 20.0595537175\n"
|
||||
+ "C -21.1582133885, 28.4178661152 -17.2, 32.0 -8.8, 32.0\n"
|
||||
+ "L 8.8, 32.0\n"
|
||||
+ "C 17.2, 32.0 21.1582133885, 28.4178661152 21.9940446283, 20.0595537175\n"
|
||||
+ "L 24, 0\n"
|
||||
+ "Z\n"
|
||||
+ "@dp");
|
||||
|
||||
assertThat(cutoutSpecification.getTopBound().width()).isEqualTo(168);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parse_doubleCutout_shouldBeDone() {
|
||||
CutoutSpecification cutoutSpecification = mParser.parse("M 0,0\n"
|
||||
+ "L -72, 0\n"
|
||||
+ "L -69.9940446283, 20.0595537175\n"
|
||||
+ "C -69.1582133885, 28.4178661152 -65.2, 32.0 -56.8, 32.0\n"
|
||||
+ "L 56.8, 32.0\n"
|
||||
+ "C 65.2, 32.0 69.1582133885, 28.4178661152 69.9940446283, 20.0595537175\n"
|
||||
+ "L 72, 0\n"
|
||||
+ "Z\n"
|
||||
+ "@bottom\n"
|
||||
+ "M 0,0\n"
|
||||
+ "L -72, 0\n"
|
||||
+ "L -69.9940446283, -20.0595537175\n"
|
||||
+ "C -69.1582133885, -28.4178661152 -65.2, -32.0 -56.8, -32.0\n"
|
||||
+ "L 56.8, -32.0\n"
|
||||
+ "C 65.2, -32.0 69.1582133885, -28.4178661152 69.9940446283, -20"
|
||||
+ ".0595537175\n"
|
||||
+ "L 72, 0\n"
|
||||
+ "Z\n"
|
||||
+ "@dp");
|
||||
|
||||
assertThat(cutoutSpecification.getTopBound().height()).isEqualTo(112);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parse_cornerCutout_shouldBeDone() {
|
||||
CutoutSpecification cutoutSpecification = mParser.parse("M 0,0\n"
|
||||
+ "L -48, 0\n"
|
||||
+ "C -48,48 -48,48 0,48\n"
|
||||
+ "Z\n"
|
||||
+ "@dp\n"
|
||||
+ "@right");
|
||||
|
||||
assertThat(cutoutSpecification.getTopBound().height()).isEqualTo(168);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parse_holeCutout_shouldBeDone() {
|
||||
CutoutSpecification cutoutSpecification = mParser.parse("M 20.0,20.0\n"
|
||||
+ "h 136\n"
|
||||
+ "v 136\n"
|
||||
+ "h -136\n"
|
||||
+ "Z\n"
|
||||
+ "@left");
|
||||
|
||||
assertThat(cutoutSpecification.getSafeInset()).isEqualTo(new Rect(0, 156, 0, 0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getSafeInset_shortEdgeIsTopBottom_shouldMatchExpectedInset() {
|
||||
CutoutSpecification cutoutSpecification =
|
||||
new CutoutSpecification.Parser(2f, 200, 400)
|
||||
.parse(CORNER_CUTOUT_SPECIFICATION);
|
||||
|
||||
assertThat(cutoutSpecification.getSafeInset())
|
||||
.isEqualTo(new Rect(0, 4, 0, 8));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getSafeInset_shortEdgeIsLeftRight_shouldMatchExpectedInset() {
|
||||
CutoutSpecification cutoutSpecification =
|
||||
new CutoutSpecification.Parser(2f, 400, 200)
|
||||
.parse(CORNER_CUTOUT_SPECIFICATION);
|
||||
|
||||
assertThat(cutoutSpecification.getSafeInset())
|
||||
.isEqualTo(new Rect(6, 0, 8, 0));
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user