526 lines
20 KiB
Java
526 lines
20 KiB
Java
![]() |
/*
|
||
|
* 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 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;
|
||
|
|
||
|
applyPhysicalPixelDisplaySizeRatio(parser.mPhysicalPixelDisplaySizeRatio);
|
||
|
|
||
|
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() : ""));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private void applyPhysicalPixelDisplaySizeRatio(float physicalPixelDisplaySizeRatio) {
|
||
|
if (physicalPixelDisplaySizeRatio == 1f) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (mPath != null && !mPath.isEmpty()) {
|
||
|
final Matrix matrix = new Matrix();
|
||
|
matrix.postScale(physicalPixelDisplaySizeRatio, physicalPixelDisplaySizeRatio);
|
||
|
mPath.transform(matrix);
|
||
|
}
|
||
|
|
||
|
scaleBounds(mLeftBound, physicalPixelDisplaySizeRatio);
|
||
|
scaleBounds(mTopBound, physicalPixelDisplaySizeRatio);
|
||
|
scaleBounds(mRightBound, physicalPixelDisplaySizeRatio);
|
||
|
scaleBounds(mBottomBound, physicalPixelDisplaySizeRatio);
|
||
|
mInsets = scaleInsets(mInsets, physicalPixelDisplaySizeRatio);
|
||
|
}
|
||
|
|
||
|
private void scaleBounds(Rect r, float ratio) {
|
||
|
if (r != null && !r.isEmpty()) {
|
||
|
r.scale(ratio);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private Insets scaleInsets(Insets insets, float ratio) {
|
||
|
return Insets.of(
|
||
|
(int) (insets.left * ratio + 0.5f),
|
||
|
(int) (insets.top * ratio + 0.5f),
|
||
|
(int) (insets.right * ratio + 0.5f),
|
||
|
(int) (insets.bottom * ratio + 0.5f));
|
||
|
}
|
||
|
|
||
|
@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 mStableDensity;
|
||
|
private final int mPhysicalDisplayWidth;
|
||
|
private final int mPhysicalDisplayHeight;
|
||
|
private final float mPhysicalPixelDisplaySizeRatio;
|
||
|
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;
|
||
|
|
||
|
@VisibleForTesting(visibility = PACKAGE)
|
||
|
public Parser(float stableDensity, int physicalDisplayWidth,
|
||
|
int physicalDisplayHeight) {
|
||
|
this(stableDensity, physicalDisplayWidth, physicalDisplayHeight, 1f);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* The constructor of the CutoutSpecification parser to parse the specification of cutout.
|
||
|
* @param stableDensity the display density.
|
||
|
* @param physicalDisplayWidth the display width.
|
||
|
* @param physicalDisplayHeight the display height.
|
||
|
* @param physicalPixelDisplaySizeRatio the display size ratio based on stable display size.
|
||
|
*/
|
||
|
Parser(float stableDensity, int physicalDisplayWidth, int physicalDisplayHeight,
|
||
|
float physicalPixelDisplaySizeRatio) {
|
||
|
mStableDensity = stableDensity;
|
||
|
mPhysicalDisplayWidth = physicalDisplayWidth;
|
||
|
mPhysicalDisplayHeight = physicalDisplayHeight;
|
||
|
mPhysicalPixelDisplaySizeRatio = physicalPixelDisplaySizeRatio;
|
||
|
mMatrix = new Matrix();
|
||
|
mIsShortEdgeOnTop = mPhysicalDisplayWidth < mPhysicalDisplayHeight;
|
||
|
}
|
||
|
|
||
|
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 = mPhysicalDisplayWidth;
|
||
|
} else if (mPositionFromLeft) {
|
||
|
offsetX = 0;
|
||
|
} else {
|
||
|
offsetX = mPhysicalDisplayWidth / 2f;
|
||
|
}
|
||
|
|
||
|
final float offsetY;
|
||
|
if (mPositionFromBottom) {
|
||
|
offsetY = mPhysicalDisplayHeight;
|
||
|
} else if (mPositionFromCenterVertical) {
|
||
|
offsetY = mPhysicalDisplayHeight / 2f;
|
||
|
} else {
|
||
|
offsetY = 0;
|
||
|
}
|
||
|
|
||
|
mMatrix.reset();
|
||
|
if (mInDp) {
|
||
|
mMatrix.postScale(mStableDensity, mStableDensity);
|
||
|
}
|
||
|
mMatrix.postTranslate(offsetX, offsetY);
|
||
|
}
|
||
|
|
||
|
private int computeSafeInsets(int gravity, Rect rect) {
|
||
|
if (gravity == LEFT && rect.right > 0 && rect.right < mPhysicalDisplayWidth) {
|
||
|
return rect.right;
|
||
|
} else if (gravity == TOP && rect.bottom > 0 && rect.bottom < mPhysicalDisplayHeight) {
|
||
|
return rect.bottom;
|
||
|
} else if (gravity == RIGHT && rect.left > 0 && rect.left < mPhysicalDisplayWidth) {
|
||
|
return mPhysicalDisplayWidth - rect.left;
|
||
|
} else if (gravity == BOTTOM && rect.top > 0 && rect.top < mPhysicalDisplayHeight) {
|
||
|
return mPhysicalDisplayHeight - 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;
|
||
|
}
|
||
|
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 >= mPhysicalDisplayHeight;
|
||
|
mIsCloserToStartSide = mTmpRect.centerY() < mPhysicalDisplayHeight / 2;
|
||
|
} else {
|
||
|
mIsTouchShortEdgeStart = mTmpRect.left <= 0;
|
||
|
mIsTouchShortEdgeEnd = mTmpRect.right >= mPhysicalDisplayWidth;
|
||
|
mIsCloserToStartSide = mTmpRect.centerX() < mPhysicalDisplayWidth / 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)) {
|
||
|
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)) {
|
||
|
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)) {
|
||
|
mBindBottomCutout = false;
|
||
|
mBindRightCutout = false;
|
||
|
mBindLeftCutout = true;
|
||
|
|
||
|
currentIndex += BIND_LEFT_CUTOUT_MARKER.length();
|
||
|
} else if (specWithoutDp.startsWith(BIND_RIGHT_CUTOUT_MARKER, currentIndex)) {
|
||
|
mBindBottomCutout = false;
|
||
|
mBindLeftCutout = false;
|
||
|
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);
|
||
|
}
|
||
|
}
|
||
|
}
|