Loading apct-tests/perftests/core/src/android/view/CutoutSpecificationBenchmark.java 0 → 100644 +240 −0 Original line number Original line Diff line number Diff line /* * 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); } } } core/java/android/view/CutoutSpecification.java 0 → 100644 +486 −0 File added.Preview size limit exceeded, changes collapsed. Show changes core/java/android/view/DisplayCutout.java +11 −90 Original line number Original line Diff line number Diff line Loading @@ -31,18 +31,12 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.Nullable; import android.content.res.Resources; import android.content.res.Resources; import android.graphics.Insets; import android.graphics.Insets; import android.graphics.Matrix; import android.graphics.Path; import android.graphics.Path; import android.graphics.Rect; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.Region; import android.graphics.Region.Op; import android.os.Parcel; import android.os.Parcel; import android.os.Parcelable; import android.os.Parcelable; import android.text.TextUtils; import android.text.TextUtils; import android.util.Log; import android.util.Pair; import android.util.Pair; import android.util.PathParser; import android.util.proto.ProtoOutputStream; import android.util.proto.ProtoOutputStream; import com.android.internal.R; import com.android.internal.R; Loading @@ -63,10 +57,6 @@ import java.util.List; public final class DisplayCutout { public final class DisplayCutout { private static final String TAG = "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 * Category for overlays that allow emulating a display cutout on devices that don't have Loading Loading @@ -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(); 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(); CutoutSpecification cutoutSpec = new CutoutSpecification.Parser(density, final Region r = Region.obtain(); displayWidth, displayHeight).parse(spec); if (!spec.isEmpty()) { Rect safeInset = cutoutSpec.getSafeInset(); try { final Rect boundLeft = cutoutSpec.getLeftBound(); p = PathParser.createPathFromPathData(spec); final Rect boundTop = cutoutSpec.getTopBound(); } catch (Throwable e) { final Rect boundRight = cutoutSpec.getRightBound(); Log.wtf(TAG, "Could not inflate cutout: ", e); final Rect boundBottom = cutoutSpec.getBottomBound(); } 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)) { if (!waterfallInsets.equals(Insets.NONE)) { safeInset.set( safeInset.set( Loading @@ -784,9 +713,9 @@ public final class DisplayCutout { } } final DisplayCutout cutout = new DisplayCutout( final DisplayCutout cutout = new DisplayCutout( safeInset, waterfallInsets, null /* boundLeft */, boundTop, safeInset, waterfallInsets, boundLeft, boundTop, null /* boundRight */, boundBottom, false /* copyArguments */); boundRight, boundBottom, false /* copyArguments */); final Pair<Path, DisplayCutout> result = new Pair<>(p, cutout); final Pair<Path, DisplayCutout> result = new Pair<>(cutoutSpec.getPath(), cutout); synchronized (CACHE_LOCK) { synchronized (CACHE_LOCK) { sCachedSpec = spec; sCachedSpec = spec; sCachedDisplayWidth = displayWidth; sCachedDisplayWidth = displayWidth; Loading @@ -798,14 +727,6 @@ public final class DisplayCutout { return result; 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) { private static Insets loadWaterfallInset(Resources res) { return Insets.of( return Insets.of( res.getDimensionPixelSize(R.dimen.waterfall_display_left_edge_size), res.getDimensionPixelSize(R.dimen.waterfall_display_left_edge_size), Loading core/tests/coretests/src/android/view/CutoutSpecificationTest.java 0 → 100644 +262 −0 Original line number Original line Diff line number Diff line /* * 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)); } } Loading
apct-tests/perftests/core/src/android/view/CutoutSpecificationBenchmark.java 0 → 100644 +240 −0 Original line number Original line Diff line number Diff line /* * 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); } } }
core/java/android/view/CutoutSpecification.java 0 → 100644 +486 −0 File added.Preview size limit exceeded, changes collapsed. Show changes
core/java/android/view/DisplayCutout.java +11 −90 Original line number Original line Diff line number Diff line Loading @@ -31,18 +31,12 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.Nullable; import android.content.res.Resources; import android.content.res.Resources; import android.graphics.Insets; import android.graphics.Insets; import android.graphics.Matrix; import android.graphics.Path; import android.graphics.Path; import android.graphics.Rect; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.Region; import android.graphics.Region.Op; import android.os.Parcel; import android.os.Parcel; import android.os.Parcelable; import android.os.Parcelable; import android.text.TextUtils; import android.text.TextUtils; import android.util.Log; import android.util.Pair; import android.util.Pair; import android.util.PathParser; import android.util.proto.ProtoOutputStream; import android.util.proto.ProtoOutputStream; import com.android.internal.R; import com.android.internal.R; Loading @@ -63,10 +57,6 @@ import java.util.List; public final class DisplayCutout { public final class DisplayCutout { private static final String TAG = "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 * Category for overlays that allow emulating a display cutout on devices that don't have Loading Loading @@ -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(); 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(); CutoutSpecification cutoutSpec = new CutoutSpecification.Parser(density, final Region r = Region.obtain(); displayWidth, displayHeight).parse(spec); if (!spec.isEmpty()) { Rect safeInset = cutoutSpec.getSafeInset(); try { final Rect boundLeft = cutoutSpec.getLeftBound(); p = PathParser.createPathFromPathData(spec); final Rect boundTop = cutoutSpec.getTopBound(); } catch (Throwable e) { final Rect boundRight = cutoutSpec.getRightBound(); Log.wtf(TAG, "Could not inflate cutout: ", e); final Rect boundBottom = cutoutSpec.getBottomBound(); } 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)) { if (!waterfallInsets.equals(Insets.NONE)) { safeInset.set( safeInset.set( Loading @@ -784,9 +713,9 @@ public final class DisplayCutout { } } final DisplayCutout cutout = new DisplayCutout( final DisplayCutout cutout = new DisplayCutout( safeInset, waterfallInsets, null /* boundLeft */, boundTop, safeInset, waterfallInsets, boundLeft, boundTop, null /* boundRight */, boundBottom, false /* copyArguments */); boundRight, boundBottom, false /* copyArguments */); final Pair<Path, DisplayCutout> result = new Pair<>(p, cutout); final Pair<Path, DisplayCutout> result = new Pair<>(cutoutSpec.getPath(), cutout); synchronized (CACHE_LOCK) { synchronized (CACHE_LOCK) { sCachedSpec = spec; sCachedSpec = spec; sCachedDisplayWidth = displayWidth; sCachedDisplayWidth = displayWidth; Loading @@ -798,14 +727,6 @@ public final class DisplayCutout { return result; 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) { private static Insets loadWaterfallInset(Resources res) { return Insets.of( return Insets.of( res.getDimensionPixelSize(R.dimen.waterfall_display_left_edge_size), res.getDimensionPixelSize(R.dimen.waterfall_display_left_edge_size), Loading
core/tests/coretests/src/android/view/CutoutSpecificationTest.java 0 → 100644 +262 −0 Original line number Original line Diff line number Diff line /* * 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)); } }