summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/SystemUI/src/com/android/systemui/ScreenDecorations.java62
-rw-r--r--packages/SystemUI/src/com/android/systemui/decor/DebugRoundedCornerDelegate.kt198
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java80
3 files changed, 327 insertions, 13 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index 892b9dc7f08e..67d4a2e25051 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -71,6 +71,7 @@ import com.android.systemui.biometrics.AuthController;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.decor.CutoutDecorProviderFactory;
+import com.android.systemui.decor.DebugRoundedCornerDelegate;
import com.android.systemui.decor.DecorProvider;
import com.android.systemui.decor.DecorProviderFactory;
import com.android.systemui.decor.DecorProviderKt;
@@ -145,6 +146,10 @@ public class ScreenDecorations implements CoreStartable, Dumpable {
protected RoundedCornerResDelegateImpl mRoundedCornerResDelegate;
@VisibleForTesting
protected DecorProviderFactory mRoundedCornerFactory;
+ @VisibleForTesting
+ protected DebugRoundedCornerDelegate mDebugRoundedCornerDelegate =
+ new DebugRoundedCornerDelegate();
+ protected DecorProviderFactory mDebugRoundedCornerFactory;
private CutoutDecorProviderFactory mCutoutFactory;
private int mProviderRefreshToken = 0;
@VisibleForTesting
@@ -363,11 +368,17 @@ public class ScreenDecorations implements CoreStartable, Dumpable {
* it requires essentially re-init-ing this screen decorations process with the debug
* information taken into account.
*/
- private void setDebug(boolean debug) {
+ @VisibleForTesting
+ protected void setDebug(boolean debug) {
if (mDebug == debug) {
return;
}
+ mDebug = debug;
+ if (!mDebug) {
+ mDebugRoundedCornerDelegate.removeDebugState();
+ }
+
mExecutor.execute(() -> {
// Re-trigger all of the screen decorations setup here so that the debug values
// can be picked up
@@ -383,11 +394,16 @@ public class ScreenDecorations implements CoreStartable, Dumpable {
}
@NonNull
- private List<DecorProvider> getProviders(boolean hasHwLayer) {
+ @VisibleForTesting
+ protected List<DecorProvider> getProviders(boolean hasHwLayer) {
List<DecorProvider> decorProviders = new ArrayList<>(mDotFactory.getProviders());
decorProviders.addAll(mFaceScanningFactory.getProviders());
if (!hasHwLayer) {
- decorProviders.addAll(mRoundedCornerFactory.getProviders());
+ if (mDebug && mDebugRoundedCornerFactory.getHasProviders()) {
+ decorProviders.addAll(mDebugRoundedCornerFactory.getProviders());
+ } else {
+ decorProviders.addAll(mRoundedCornerFactory.getProviders());
+ }
decorProviders.addAll(mCutoutFactory.getProviders());
}
return decorProviders;
@@ -434,6 +450,8 @@ public class ScreenDecorations implements CoreStartable, Dumpable {
mRoundedCornerResDelegate.setPhysicalPixelDisplaySizeRatio(
getPhysicalPixelDisplaySizeRatio());
mRoundedCornerFactory = new RoundedCornerDecorProviderFactory(mRoundedCornerResDelegate);
+ mDebugRoundedCornerFactory =
+ new RoundedCornerDecorProviderFactory(mDebugRoundedCornerDelegate);
mCutoutFactory = getCutoutFactory();
mHwcScreenDecorationSupport = mContext.getDisplay().getDisplayDecorationSupport();
updateHwLayerRoundedCornerDrawable();
@@ -966,6 +984,8 @@ public class ScreenDecorations implements CoreStartable, Dumpable {
mTintColor = colorsInvertedValue != 0 ? Color.WHITE : Color.BLACK;
if (mDebug) {
mTintColor = mDebugColor;
+ mDebugRoundedCornerDelegate.setColor(mTintColor);
+ //TODO(b/285941724): update the hwc layer color here too (or disable it in debug mode)
}
updateOverlayProviderViews(new Integer[] {
@@ -1038,6 +1058,7 @@ public class ScreenDecorations implements CoreStartable, Dumpable {
if (DEBUG_DISABLE_SCREEN_DECORATIONS) {
return;
}
+ ipw.println("mDebug:" + mDebug);
ipw.println("mIsPrivacyDotEnabled:" + isPrivacyDotEnabled());
ipw.println("shouldOptimizeOverlayVisibility:" + shouldOptimizeVisibility());
@@ -1093,6 +1114,7 @@ public class ScreenDecorations implements CoreStartable, Dumpable {
}
}
mRoundedCornerResDelegate.dump(pw, args);
+ mDebugRoundedCornerDelegate.dump(pw);
}
@VisibleForTesting
@@ -1115,8 +1137,9 @@ public class ScreenDecorations implements CoreStartable, Dumpable {
mRotation = newRotation;
mDisplayMode = newMod;
mDisplayCutout = newCutout;
- mRoundedCornerResDelegate.setPhysicalPixelDisplaySizeRatio(
- getPhysicalPixelDisplaySizeRatio());
+ float ratio = getPhysicalPixelDisplaySizeRatio();
+ mRoundedCornerResDelegate.setPhysicalPixelDisplaySizeRatio(ratio);
+ mDebugRoundedCornerDelegate.setPhysicalPixelDisplaySizeRatio(ratio);
if (mScreenDecorHwcLayer != null) {
mScreenDecorHwcLayer.pendingConfigChange = false;
mScreenDecorHwcLayer.updateConfiguration(mDisplayUniqueId);
@@ -1139,7 +1162,8 @@ public class ScreenDecorations implements CoreStartable, Dumpable {
}
private boolean hasRoundedCorners() {
- return mRoundedCornerFactory.getHasProviders();
+ return mRoundedCornerFactory.getHasProviders()
+ || mDebugRoundedCornerFactory.getHasProviders();
}
private boolean shouldOptimizeVisibility() {
@@ -1197,8 +1221,12 @@ public class ScreenDecorations implements CoreStartable, Dumpable {
return;
}
- final Drawable topDrawable = mRoundedCornerResDelegate.getTopRoundedDrawable();
- final Drawable bottomDrawable = mRoundedCornerResDelegate.getBottomRoundedDrawable();
+ Drawable topDrawable = mRoundedCornerResDelegate.getTopRoundedDrawable();
+ Drawable bottomDrawable = mRoundedCornerResDelegate.getBottomRoundedDrawable();
+ if (mDebug && (mDebugRoundedCornerFactory.getHasProviders())) {
+ topDrawable = mDebugRoundedCornerDelegate.getTopRoundedDrawable();
+ bottomDrawable = mDebugRoundedCornerDelegate.getBottomRoundedDrawable();
+ }
if (topDrawable == null || bottomDrawable == null) {
return;
@@ -1210,11 +1238,19 @@ public class ScreenDecorations implements CoreStartable, Dumpable {
if (mScreenDecorHwcLayer == null) {
return;
}
- mScreenDecorHwcLayer.updateRoundedCornerExistenceAndSize(
- mRoundedCornerResDelegate.getHasTop(),
- mRoundedCornerResDelegate.getHasBottom(),
- mRoundedCornerResDelegate.getTopRoundedSize().getWidth(),
- mRoundedCornerResDelegate.getBottomRoundedSize().getWidth());
+ if (mDebug && mDebugRoundedCornerFactory.getHasProviders()) {
+ mScreenDecorHwcLayer.updateRoundedCornerExistenceAndSize(
+ mDebugRoundedCornerDelegate.getHasTop(),
+ mDebugRoundedCornerDelegate.getHasBottom(),
+ mDebugRoundedCornerDelegate.getTopRoundedSize().getWidth(),
+ mDebugRoundedCornerDelegate.getBottomRoundedSize().getWidth());
+ } else {
+ mScreenDecorHwcLayer.updateRoundedCornerExistenceAndSize(
+ mRoundedCornerResDelegate.getHasTop(),
+ mRoundedCornerResDelegate.getHasBottom(),
+ mRoundedCornerResDelegate.getTopRoundedSize().getWidth(),
+ mRoundedCornerResDelegate.getBottomRoundedSize().getWidth());
+ }
}
@VisibleForTesting
diff --git a/packages/SystemUI/src/com/android/systemui/decor/DebugRoundedCornerDelegate.kt b/packages/SystemUI/src/com/android/systemui/decor/DebugRoundedCornerDelegate.kt
new file mode 100644
index 000000000000..4069bc7d73d0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/decor/DebugRoundedCornerDelegate.kt
@@ -0,0 +1,198 @@
+/*
+ * Copyright (C) 2023 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 com.android.systemui.decor
+
+import android.graphics.Canvas
+import android.graphics.Color
+import android.graphics.ColorFilter
+import android.graphics.Paint
+import android.graphics.Path
+import android.graphics.PixelFormat
+import android.graphics.drawable.Drawable
+import android.util.Size
+import java.io.PrintWriter
+
+/**
+ * Rounded corner delegate that handles incoming debug commands and can convert them to path
+ * drawables to be shown instead of the system-defined rounded corners.
+ *
+ * These debug corners are expected to supersede the system-defined corners
+ */
+class DebugRoundedCornerDelegate : RoundedCornerResDelegate {
+ override var hasTop: Boolean = false
+ private set
+ override var topRoundedDrawable: Drawable? = null
+ private set
+ override var topRoundedSize: Size = Size(0, 0)
+ private set
+
+ override var hasBottom: Boolean = false
+ private set
+ override var bottomRoundedDrawable: Drawable? = null
+ private set
+ override var bottomRoundedSize: Size = Size(0, 0)
+ private set
+
+ override var physicalPixelDisplaySizeRatio: Float = 1f
+ set(value) {
+ if (field == value) {
+ return
+ }
+ field = value
+ reloadMeasures()
+ }
+
+ var color: Int = Color.RED
+ set(value) {
+ if (field == value) {
+ return
+ }
+
+ field = value
+ paint.color = field
+ }
+
+ var paint =
+ Paint().apply {
+ color = Color.RED
+ style = Paint.Style.FILL
+ }
+
+ override fun updateDisplayUniqueId(newDisplayUniqueId: String?, newReloadToken: Int?) {
+ // nop -- debug corners draw the same on every display
+ }
+
+ fun applyNewDebugCorners(
+ topCorner: DebugRoundedCornerModel,
+ bottomCorner: DebugRoundedCornerModel,
+ ) {
+ hasTop = true
+ topRoundedDrawable = topCorner.toPathDrawable(paint)
+ topRoundedSize = topCorner.size()
+
+ hasBottom = true
+ bottomRoundedDrawable = bottomCorner.toPathDrawable(paint)
+ bottomRoundedSize = bottomCorner.size()
+ }
+
+ /**
+ * Remove accumulated debug state by clearing out the drawables and setting [hasTop] and
+ * [hasBottom] to false.
+ */
+ fun removeDebugState() {
+ hasTop = false
+ topRoundedDrawable = null
+ topRoundedSize = Size(0, 0)
+
+ hasBottom = false
+ bottomRoundedDrawable = null
+ bottomRoundedSize = Size(0, 0)
+ }
+
+ /**
+ * Scaling here happens when the display resolution is changed. This logic is exactly the same
+ * as in [RoundedCornerResDelegateImpl]
+ */
+ private fun reloadMeasures() {
+ topRoundedDrawable?.let { topRoundedSize = Size(it.intrinsicWidth, it.intrinsicHeight) }
+ bottomRoundedDrawable?.let {
+ bottomRoundedSize = Size(it.intrinsicWidth, it.intrinsicHeight)
+ }
+
+ if (physicalPixelDisplaySizeRatio != 1f) {
+ if (topRoundedSize.width != 0) {
+ topRoundedSize =
+ Size(
+ (physicalPixelDisplaySizeRatio * topRoundedSize.width + 0.5f).toInt(),
+ (physicalPixelDisplaySizeRatio * topRoundedSize.height + 0.5f).toInt()
+ )
+ }
+ if (bottomRoundedSize.width != 0) {
+ bottomRoundedSize =
+ Size(
+ (physicalPixelDisplaySizeRatio * bottomRoundedSize.width + 0.5f).toInt(),
+ (physicalPixelDisplaySizeRatio * bottomRoundedSize.height + 0.5f).toInt()
+ )
+ }
+ }
+ }
+
+ fun dump(pw: PrintWriter) {
+ pw.println("DebugRoundedCornerDelegate state:")
+ pw.println(" hasTop=$hasTop")
+ pw.println(" hasBottom=$hasBottom")
+ pw.println(" topRoundedSize(w,h)=(${topRoundedSize.width},${topRoundedSize.height})")
+ pw.println(
+ " bottomRoundedSize(w,h)=(${bottomRoundedSize.width},${bottomRoundedSize.height})"
+ )
+ pw.println(" physicalPixelDisplaySizeRatio=$physicalPixelDisplaySizeRatio")
+ }
+}
+
+/** Encapsulates the data coming in from the command line args and turns into a [PathDrawable] */
+data class DebugRoundedCornerModel(
+ val path: Path,
+ val width: Int,
+ val height: Int,
+ val scaleX: Float,
+ val scaleY: Float,
+) {
+ fun size() = Size(width, height)
+
+ fun toPathDrawable(paint: Paint) =
+ PathDrawable(
+ path,
+ width,
+ height,
+ scaleX,
+ scaleY,
+ paint,
+ )
+}
+
+/**
+ * PathDrawable accepts paths from the command line via [DebugRoundedCornerModel], and renders them
+ * in the canvas provided by the screen decor rounded corner provider
+ */
+class PathDrawable(
+ val path: Path,
+ val width: Int,
+ val height: Int,
+ val scaleX: Float = 1f,
+ val scaleY: Float = 1f,
+ val paint: Paint,
+) : Drawable() {
+ private var cf: ColorFilter? = null
+
+ override fun draw(canvas: Canvas) {
+ if (scaleX != 1f || scaleY != 1f) {
+ canvas.scale(scaleX, scaleY)
+ }
+ canvas.drawPath(path, paint)
+ }
+
+ override fun getIntrinsicHeight(): Int = height
+ override fun getIntrinsicWidth(): Int = width
+
+ override fun getOpacity(): Int = PixelFormat.OPAQUE
+
+ override fun setAlpha(alpha: Int) {}
+
+ override fun setColorFilter(colorFilter: ColorFilter?) {
+ cf = colorFilter
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
index a6006a1e6a48..79c87cfd1f3e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
@@ -62,6 +62,7 @@ import android.os.Handler;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;
+import android.util.PathParser;
import android.util.Size;
import android.view.Display;
import android.view.DisplayCutout;
@@ -82,6 +83,7 @@ import com.android.systemui.biometrics.AuthController;
import com.android.systemui.decor.CornerDecorProvider;
import com.android.systemui.decor.CutoutDecorProviderFactory;
import com.android.systemui.decor.CutoutDecorProviderImpl;
+import com.android.systemui.decor.DebugRoundedCornerModel;
import com.android.systemui.decor.DecorProvider;
import com.android.systemui.decor.DecorProviderFactory;
import com.android.systemui.decor.FaceScanningOverlayProviderImpl;
@@ -258,6 +260,8 @@ public class ScreenDecorationsTest extends SysuiTestCase {
}
});
mScreenDecorations.mDisplayInfo = mDisplayInfo;
+ // Make sure tests are never run starting in debug mode
+ mScreenDecorations.setDebug(false);
doReturn(1f).when(mScreenDecorations).getPhysicalPixelDisplaySizeRatio();
doNothing().when(mScreenDecorations).updateOverlayProviderViews(any());
@@ -1054,6 +1058,82 @@ public class ScreenDecorationsTest extends SysuiTestCase {
}
@Test
+ public void testDebugRoundedCorners_noDeviceCornersSet() {
+ setupResources(0 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */,
+ null /* roundedTopDrawable */, null /* roundedBottomDrawable */,
+ 0 /* roundedPadding */, false /* privacyDot */, false /* faceScanning */);
+
+ mScreenDecorations.start();
+ // No rounded corners exist at this point
+ verifyOverlaysExistAndAdded(false, false, false, false, View.VISIBLE);
+
+ // Path from rounded.xml, scaled by 10x to produce 80x80 corners
+ Path debugPath = PathParser.createPathFromPathData("M8,0H0v8C0,3.6,3.6,0,8,0z");
+ // WHEN debug corners are added to the delegate
+ DebugRoundedCornerModel debugCorner = new DebugRoundedCornerModel(
+ debugPath,
+ 80,
+ 80,
+ 10f,
+ 10f
+ );
+ mScreenDecorations.mDebugRoundedCornerDelegate
+ .applyNewDebugCorners(debugCorner, debugCorner);
+
+ // AND debug mode is entered
+ mScreenDecorations.setDebug(true);
+ mExecutor.runAllReady();
+
+ // THEN the debug corners provide decor
+ List<DecorProvider> providers = mScreenDecorations.getProviders(false);
+ assertEquals(4, providers.size());
+
+ // Top and bottom overlays contain the debug rounded corners
+ verifyOverlaysExistAndAdded(false, true, false, true, View.VISIBLE);
+ }
+
+ @Test
+ public void testDebugRoundedCornersRemoved_noDeviceCornersSet() {
+ // GIVEN a device with no rounded corners defined
+ setupResources(0 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */,
+ null /* roundedTopDrawable */, null /* roundedBottomDrawable */,
+ 0 /* roundedPadding */, false /* privacyDot */, false /* faceScanning */);
+
+ mScreenDecorations.start();
+ // No rounded corners exist at this point
+ verifyOverlaysExistAndAdded(false, false, false, false, View.VISIBLE);
+
+ // Path from rounded.xml, scaled by 10x to produce 80x80 corners
+ Path debugPath = PathParser.createPathFromPathData("M8,0H0v8C0,3.6,3.6,0,8,0z");
+ // WHEN debug corners are added to the delegate
+ DebugRoundedCornerModel debugCorner = new DebugRoundedCornerModel(
+ debugPath,
+ 80,
+ 80,
+ 10f,
+ 10f
+ );
+ mScreenDecorations.mDebugRoundedCornerDelegate
+ .applyNewDebugCorners(debugCorner, debugCorner);
+
+ // AND debug mode is entered
+ mScreenDecorations.setDebug(true);
+ mExecutor.runAllReady();
+
+ // Top and bottom overlays contain the debug rounded corners
+ verifyOverlaysExistAndAdded(false, true, false, true, View.VISIBLE);
+
+ // WHEN debug is exited
+ mScreenDecorations.setDebug(false);
+ mExecutor.runAllReady();
+
+ // THEN the decor is removed
+ verifyOverlaysExistAndAdded(false, false, false, false, View.VISIBLE);
+ assertThat(mScreenDecorations.mDebugRoundedCornerDelegate.getHasBottom()).isFalse();
+ assertThat(mScreenDecorations.mDebugRoundedCornerDelegate.getHasTop()).isFalse();
+ }
+
+ @Test
public void testRegistration_From_NoOverlay_To_HasOverlays() {
doReturn(false).when(mScreenDecorations).hasOverlays();
mScreenDecorations.start();