From 68270b2198c6613bc0df15c6dea0a8ea073efc84 Mon Sep 17 00:00:00 2001 From: Evan Laird Date: Tue, 6 Jun 2023 16:06:55 -0400 Subject: [PATCH 1/4] [Decor] Remove unused properties and Tunable Before adding support for runtime debugging, we can remove the `Tunable` implementation from ScreenDecorations, as well as the unused fields declared. Tunable was added to the screen decor when the assumption was that a single path was suitable for all rounded corners. Therefore, the only supported tuning was `SIZE`, which simply changes the size of the rounded corners. With the proposed changes, this tuning size is no longer relevant. The CLI that will be implemented can handle arbitrary paths, viewport sizes, and colors, and does not require registering entries with the TunerService. Test: ScreenDecorationsTest Test: RoundedCornerResDelegateTest Bug: 285941724 Change-Id: If9f3e28c641590c04d19315970359bed04521bc6 --- .../android/systemui/ScreenDecorations.java | 50 +------------------ .../decor/RoundedCornerResDelegate.kt | 22 -------- .../systemui/ScreenDecorationsTest.java | 38 +------------- .../decor/RoundedCornerResDelegateTest.kt | 21 -------- 4 files changed, 3 insertions(+), 128 deletions(-) diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java index ea0f343e80f45..0b8d8563802d9 100644 --- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java +++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java @@ -84,8 +84,6 @@ import com.android.systemui.qs.SettingObserver; import com.android.systemui.settings.DisplayTracker; import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.events.PrivacyDotViewController; -import com.android.systemui.tuner.TunerService; -import com.android.systemui.tuner.TunerService.Tunable; import com.android.systemui.util.concurrency.DelayableExecutor; import com.android.systemui.util.concurrency.ThreadFactory; import com.android.systemui.util.settings.SecureSettings; @@ -105,18 +103,15 @@ import javax.inject.Inject; * for antialiasing and emulation purposes. */ @SysUISingleton -public class ScreenDecorations implements CoreStartable, Tunable , Dumpable { +public class ScreenDecorations implements CoreStartable, Dumpable { private static final boolean DEBUG = false; private static final String TAG = "ScreenDecorations"; - public static final String SIZE = "sysui_rounded_size"; - public static final String PADDING = "sysui_rounded_content_padding"; // Provide a way for factory to disable ScreenDecorations to run the Display tests. private static final boolean DEBUG_DISABLE_SCREEN_DECORATIONS = SystemProperties.getBoolean("debug.disable_screen_decorations", false); private static final boolean DEBUG_SCREENSHOT_ROUNDED_CORNERS = SystemProperties.getBoolean("debug.screenshot_rounded_corners", false); - private static final boolean VERBOSE = false; static final boolean DEBUG_COLOR = DEBUG_SCREENSHOT_ROUNDED_CORNERS; private static final int[] DISPLAY_CUTOUT_IDS = { @@ -134,7 +129,6 @@ public class ScreenDecorations implements CoreStartable, Tunable , Dumpable { protected boolean mIsRegistered; private final Context mContext; private final Executor mMainExecutor; - private final TunerService mTunerService; private final SecureSettings mSecureSettings; @VisibleForTesting DisplayTracker.Callback mDisplayListener; @@ -315,7 +309,6 @@ public class ScreenDecorations implements CoreStartable, Tunable , Dumpable { public ScreenDecorations(Context context, @Main Executor mainExecutor, SecureSettings secureSettings, - TunerService tunerService, UserTracker userTracker, DisplayTracker displayTracker, PrivacyDotViewController dotViewController, @@ -327,7 +320,6 @@ public class ScreenDecorations implements CoreStartable, Tunable , Dumpable { mContext = context; mMainExecutor = mainExecutor; mSecureSettings = secureSettings; - mTunerService = tunerService; mUserTracker = userTracker; mDisplayTracker = displayTracker; mDotViewController = dotViewController; @@ -608,12 +600,6 @@ public class ScreenDecorations implements CoreStartable, Tunable , Dumpable { return; } - mMainExecutor.execute(() -> { - Trace.beginSection("ScreenDecorations#addTunable"); - mTunerService.addTunable(this, SIZE); - Trace.endSection(); - }); - // Watch color inversion and invert the overlay as needed. if (mColorInversionSetting == null) { mColorInversionSetting = new SettingObserver(mSecureSettings, mHandler, @@ -632,12 +618,6 @@ public class ScreenDecorations implements CoreStartable, Tunable , Dumpable { mUserTracker.addCallback(mUserChangedCallback, mExecutor); mIsRegistered = true; } else { - mMainExecutor.execute(() -> { - Trace.beginSection("ScreenDecorations#removeTunable"); - mTunerService.removeTunable(this); - Trace.endSection(); - }); - if (mColorInversionSetting != null) { mColorInversionSetting.setListening(false); } @@ -1170,34 +1150,6 @@ public class ScreenDecorations implements CoreStartable, Tunable , Dumpable { Trace.endSection(); } - @Override - public void onTuningChanged(String key, String newValue) { - if (DEBUG_DISABLE_SCREEN_DECORATIONS) { - Log.i(TAG, "ScreenDecorations is disabled"); - return; - } - mExecutor.execute(() -> { - if (mOverlays == null || !SIZE.equals(key)) { - return; - } - Trace.beginSection("ScreenDecorations#onTuningChanged"); - try { - final int sizeFactor = Integer.parseInt(newValue); - mRoundedCornerResDelegate.setTuningSizeFactor(sizeFactor); - } catch (NumberFormatException e) { - mRoundedCornerResDelegate.setTuningSizeFactor(null); - } - updateOverlayProviderViews(new Integer[] { - R.id.rounded_corner_top_left, - R.id.rounded_corner_top_right, - R.id.rounded_corner_bottom_left, - R.id.rounded_corner_bottom_right - }); - updateHwLayerRoundedCornerExistAndSize(); - Trace.endSection(); - }); - } - private void updateHwLayerRoundedCornerDrawable() { if (mScreenDecorHwcLayer == null) { return; diff --git a/packages/SystemUI/src/com/android/systemui/decor/RoundedCornerResDelegate.kt b/packages/SystemUI/src/com/android/systemui/decor/RoundedCornerResDelegate.kt index 8b4aeefb6ed43..5f5ca54bd01f3 100644 --- a/packages/SystemUI/src/com/android/systemui/decor/RoundedCornerResDelegate.kt +++ b/packages/SystemUI/src/com/android/systemui/decor/RoundedCornerResDelegate.kt @@ -55,15 +55,6 @@ class RoundedCornerResDelegate( var bottomRoundedSize = Size(0, 0) private set - var tuningSizeFactor: Int? = null - set(value) { - if (field == value) { - return - } - field = value - reloadMeasures() - } - var physicalPixelDisplaySizeRatio: Float = 1f set(value) { if (field == value) { @@ -122,19 +113,6 @@ class RoundedCornerResDelegate( bottomRoundedSize = Size(it.intrinsicWidth, it.intrinsicHeight) } - tuningSizeFactor?.let { - if (it <= 0) { - return - } - val length: Int = (it * density).toInt() - if (topRoundedSize.width > 0) { - topRoundedSize = Size(length, length) - } - if (bottomRoundedSize.width > 0) { - bottomRoundedSize = Size(length, length) - } - } - if (physicalPixelDisplaySizeRatio != 1f) { if (topRoundedSize.width != 0) { topRoundedSize = Size( diff --git a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java index 4cf5a4be0b601..a6006a1e6a48b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java @@ -43,7 +43,6 @@ import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; -import static org.mockito.Mockito.reset; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -96,7 +95,6 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.settings.FakeDisplayTracker; import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.events.PrivacyDotViewController; -import com.android.systemui.tuner.TunerService; import com.android.systemui.util.concurrency.FakeExecutor; import com.android.systemui.util.concurrency.FakeThreadFactory; import com.android.systemui.util.settings.FakeSettings; @@ -139,8 +137,6 @@ public class ScreenDecorationsTest extends SysuiTestCase { @Mock private Display mDisplay; @Mock - private TunerService mTunerService; - @Mock private UserTracker mUserTracker; @Mock private PrivacyDotViewController mDotViewController; @@ -234,7 +230,7 @@ public class ScreenDecorationsTest extends SysuiTestCase { new ScreenDecorationsLogger(logcatLogBuffer("TestLogBuffer")))); mScreenDecorations = spy(new ScreenDecorations(mContext, mExecutor, mSecureSettings, - mTunerService, mUserTracker, mDisplayTracker, mDotViewController, mThreadFactory, + mUserTracker, mDisplayTracker, mDotViewController, mThreadFactory, mPrivacyDotDecorProviderFactory, mFaceScanningProviderFactory, new ScreenDecorationsLogger(logcatLogBuffer("TestLogBuffer")), mAuthController) { @@ -250,12 +246,6 @@ public class ScreenDecorationsTest extends SysuiTestCase { mExecutor.runAllReady(); } - @Override - public void onTuningChanged(String key, String newValue) { - super.onTuningChanged(key, newValue); - mExecutor.runAllReady(); - } - @Override protected void updateOverlayWindowVisibilityIfViewExists(@Nullable View view) { super.updateOverlayWindowVisibilityIfViewExists(view); @@ -270,7 +260,6 @@ public class ScreenDecorationsTest extends SysuiTestCase { mScreenDecorations.mDisplayInfo = mDisplayInfo; doReturn(1f).when(mScreenDecorations).getPhysicalPixelDisplaySizeRatio(); doNothing().when(mScreenDecorations).updateOverlayProviderViews(any()); - reset(mTunerService); try { mPrivacyDotShowingListener = mScreenDecorations.mPrivacyDotShowingListener.getClass() @@ -464,8 +453,6 @@ public class ScreenDecorationsTest extends SysuiTestCase { mScreenDecorations.start(); // No views added. verifyOverlaysExistAndAdded(false, false, false, false, null); - // No Tuners tuned. - verify(mTunerService, never()).addTunable(any(), any()); // No dot controller init verify(mDotViewController, never()).initialize(any(), any(), any(), any()); } @@ -497,8 +484,6 @@ public class ScreenDecorationsTest extends SysuiTestCase { // Face scanning doesn't exist verifyFaceScanningViewExists(false); - // One tunable. - verify(mTunerService, times(1)).addTunable(any(), any()); // Dot controller init verify(mDotViewController, times(1)).initialize( isA(View.class), isA(View.class), isA(View.class), isA(View.class)); @@ -528,8 +513,6 @@ public class ScreenDecorationsTest extends SysuiTestCase { // Face scanning doesn't exist verifyFaceScanningViewExists(false); - // One tunable. - verify(mTunerService, times(1)).addTunable(any(), any()); // No dot controller init verify(mDotViewController, never()).initialize(any(), any(), any(), any()); } @@ -560,8 +543,6 @@ public class ScreenDecorationsTest extends SysuiTestCase { // Face scanning doesn't exist verifyFaceScanningViewExists(false); - // One tunable. - verify(mTunerService, times(1)).addTunable(any(), any()); // Dot controller init verify(mDotViewController, times(1)).initialize( isA(View.class), isA(View.class), isA(View.class), isA(View.class)); @@ -1076,15 +1057,10 @@ public class ScreenDecorationsTest extends SysuiTestCase { public void testRegistration_From_NoOverlay_To_HasOverlays() { doReturn(false).when(mScreenDecorations).hasOverlays(); mScreenDecorations.start(); - verify(mTunerService, times(0)).addTunable(any(), any()); - verify(mTunerService, times(1)).removeTunable(any()); assertThat(mScreenDecorations.mIsRegistered, is(false)); - reset(mTunerService); doReturn(true).when(mScreenDecorations).hasOverlays(); mScreenDecorations.onConfigurationChanged(new Configuration()); - verify(mTunerService, times(1)).addTunable(any(), any()); - verify(mTunerService, times(0)).removeTunable(any()); assertThat(mScreenDecorations.mIsRegistered, is(true)); } @@ -1093,14 +1069,9 @@ public class ScreenDecorationsTest extends SysuiTestCase { doReturn(true).when(mScreenDecorations).hasOverlays(); mScreenDecorations.start(); - verify(mTunerService, times(1)).addTunable(any(), any()); - verify(mTunerService, times(0)).removeTunable(any()); assertThat(mScreenDecorations.mIsRegistered, is(true)); - reset(mTunerService); mScreenDecorations.onConfigurationChanged(new Configuration()); - verify(mTunerService, times(0)).addTunable(any(), any()); - verify(mTunerService, times(0)).removeTunable(any()); assertThat(mScreenDecorations.mIsRegistered, is(true)); } @@ -1109,15 +1080,10 @@ public class ScreenDecorationsTest extends SysuiTestCase { doReturn(true).when(mScreenDecorations).hasOverlays(); mScreenDecorations.start(); - verify(mTunerService, times(1)).addTunable(any(), any()); - verify(mTunerService, times(0)).removeTunable(any()); assertThat(mScreenDecorations.mIsRegistered, is(true)); - reset(mTunerService); doReturn(false).when(mScreenDecorations).hasOverlays(); mScreenDecorations.onConfigurationChanged(new Configuration()); - verify(mTunerService, times(0)).addTunable(any(), any()); - verify(mTunerService, times(1)).removeTunable(any()); assertThat(mScreenDecorations.mIsRegistered, is(false)); } @@ -1181,7 +1147,7 @@ public class ScreenDecorationsTest extends SysuiTestCase { when(mFaceScanningProviderFactory.getProviders()).thenReturn(mFaceScanningProviders); when(mFaceScanningProviderFactory.getHasProviders()).thenReturn(true); ScreenDecorations screenDecorations = new ScreenDecorations(mContext, mExecutor, - mSecureSettings, mTunerService, mUserTracker, mDisplayTracker, mDotViewController, + mSecureSettings, mUserTracker, mDisplayTracker, mDotViewController, mThreadFactory, mPrivacyDotDecorProviderFactory, mFaceScanningProviderFactory, new ScreenDecorationsLogger(logcatLogBuffer("TestLogBuffer")), mAuthController); screenDecorations.start(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/decor/RoundedCornerResDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/decor/RoundedCornerResDelegateTest.kt index 93a1868b72f51..3912bc0e354e3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/decor/RoundedCornerResDelegateTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/decor/RoundedCornerResDelegateTest.kt @@ -108,27 +108,6 @@ class RoundedCornerResDelegateTest : SysuiTestCase() { assertEquals(Size(8, 8), roundedCornerResDelegate.bottomRoundedSize) } - @Test - fun testUpdateTuningSizeFactor() { - setupResources(radius = 100, - roundedTopDrawable = getTestsDrawable(R.drawable.rounded3px), - roundedBottomDrawable = getTestsDrawable(R.drawable.rounded4px)) - - roundedCornerResDelegate = RoundedCornerResDelegate(mContext.resources, null) - - val factor = 5 - roundedCornerResDelegate.tuningSizeFactor = factor - val length = (factor * mContext.resources.displayMetrics.density).toInt() - - assertEquals(Size(length, length), roundedCornerResDelegate.topRoundedSize) - assertEquals(Size(length, length), roundedCornerResDelegate.bottomRoundedSize) - - roundedCornerResDelegate.tuningSizeFactor = null - - assertEquals(Size(3, 3), roundedCornerResDelegate.topRoundedSize) - assertEquals(Size(4, 4), roundedCornerResDelegate.bottomRoundedSize) - } - @Test fun testPhysicalPixelDisplaySizeChanged() { setupResources( From 76a732f8e527554cdf7f36bb6eec71c5c6213556 Mon Sep 17 00:00:00 2001 From: Evan Laird Date: Wed, 7 Jun 2023 11:58:10 -0400 Subject: [PATCH 2/4] [Decor] Make ScreenDecorations.mDebug non-final Most of the code behind DEBUG was logging-related. This CL converts most logs to use the LogBuffer implementation, but keeps a few `onPreDraw` logs behind a new `DEBUG_LOGGING` flag, which is still statically defined. This CL also moves `DEBUG` to a new `mDebug` field, which is private and modifiable at runtime. The `setDebug` method is unused in this CL, but will be supported in follow-up. Setting debug is still supported through the old sysprop `debug.screenshot_rounded_corners`. In the future, setting debug will be supported over the command line, and will essentially re-setup the screen decor wih the flag enabled. Test: adb shell setprop debug.screenshot_rounded_corners 1 && restart-sysui Test: ScreenDecorationsTest Bug: 285941724 Change-Id: Ibc19ae82257d8e5fa4c45582d7d83067bbf67548 --- .../android/systemui/ScreenDecorHwcLayer.kt | 19 ++-- .../android/systemui/ScreenDecorations.java | 88 ++++++++++++++----- .../systemui/log/ScreenDecorationsLogger.kt | 47 +++++++++- .../systemui/ScreenDecorHwcLayerTest.kt | 3 +- 4 files changed, 122 insertions(+), 35 deletions(-) diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorHwcLayer.kt b/packages/SystemUI/src/com/android/systemui/ScreenDecorHwcLayer.kt index 99dd6b6cd6730..670c1fa45e5ca 100644 --- a/packages/SystemUI/src/com/android/systemui/ScreenDecorHwcLayer.kt +++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorHwcLayer.kt @@ -49,8 +49,11 @@ import kotlin.math.floor * When the HWC of the device supports Composition.DISPLAY_DECORATION, we use this layer to draw * screen decorations. */ -class ScreenDecorHwcLayer(context: Context, displayDecorationSupport: DisplayDecorationSupport) : - DisplayCutoutBaseView(context) { +class ScreenDecorHwcLayer( + context: Context, + displayDecorationSupport: DisplayDecorationSupport, + private val debug: Boolean, +) : DisplayCutoutBaseView(context) { val colorMode: Int private val useInvertedAlphaColor: Boolean private val color: Int @@ -74,7 +77,7 @@ class ScreenDecorHwcLayer(context: Context, displayDecorationSupport: DisplayDec throw IllegalArgumentException("Attempting to use unsupported mode " + "${PixelFormat.formatToString(displayDecorationSupport.format)}") } - if (DEBUG_COLOR) { + if (debug) { color = Color.GREEN bgColor = Color.TRANSPARENT colorMode = ActivityInfo.COLOR_MODE_DEFAULT @@ -106,7 +109,7 @@ class ScreenDecorHwcLayer(context: Context, displayDecorationSupport: DisplayDec override fun onAttachedToWindow() { super.onAttachedToWindow() parent.requestTransparentRegion(this) - if (!DEBUG_COLOR) { + if (!debug) { viewRootImpl.setDisplayDecoration(true) } @@ -143,12 +146,12 @@ class ScreenDecorHwcLayer(context: Context, displayDecorationSupport: DisplayDec override fun gatherTransparentRegion(region: Region?): Boolean { region?.let { calculateTransparentRect() - if (DEBUG_COLOR) { + if (debug) { // Since we're going to draw a rectangle where the layer would // normally be transparent, treat the transparent region as // empty. We still want this method to be called, though, so // that it calculates the transparent rect at the right time - // to match !DEBUG_COLOR. + // to match ![debug] region.setEmpty() } else { region.op(transparentRect, Region.Op.INTERSECT) @@ -421,8 +424,4 @@ class ScreenDecorHwcLayer(context: Context, displayDecorationSupport: DisplayDec ipw.println("roundedCornerBottomSize=$roundedCornerBottomSize") ipw.decreaseIndent() } - - companion object { - private val DEBUG_COLOR = ScreenDecorations.DEBUG_COLOR - } } diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java index 0b8d8563802d9..29f7a5e42778a 100644 --- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java +++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java @@ -104,7 +104,7 @@ import javax.inject.Inject; */ @SysUISingleton public class ScreenDecorations implements CoreStartable, Dumpable { - private static final boolean DEBUG = false; + private static final boolean DEBUG_LOGGING = false; private static final String TAG = "ScreenDecorations"; // Provide a way for factory to disable ScreenDecorations to run the Display tests. @@ -112,7 +112,8 @@ public class ScreenDecorations implements CoreStartable, Dumpable { SystemProperties.getBoolean("debug.disable_screen_decorations", false); private static final boolean DEBUG_SCREENSHOT_ROUNDED_CORNERS = SystemProperties.getBoolean("debug.screenshot_rounded_corners", false); - static final boolean DEBUG_COLOR = DEBUG_SCREENSHOT_ROUNDED_CORNERS; + private boolean mDebug = DEBUG_SCREENSHOT_ROUNDED_CORNERS; + private int mDebugColor = Color.RED; private static final int[] DISPLAY_CUTOUT_IDS = { R.id.display_cutout, @@ -357,6 +358,26 @@ public class ScreenDecorations implements CoreStartable, Dumpable { mAuthController.addCallback(mAuthControllerCallback); } + /** + * Change the value of {@link ScreenDecorations#mDebug}. This operation is heavyweight, since + * it requires essentially re-init-ing this screen decorations process with the debug + * information taken into account. + */ + private void setDebug(boolean debug) { + if (mDebug == debug) { + return; + } + + mExecutor.execute(() -> { + // Re-trigger all of the screen decorations setup here so that the debug values + // can be picked up + removeAllOverlays(); + removeHwcOverlay(); + startOnScreenDecorationsThread(); + updateColorInversionDefault(); + }); + } + private boolean isPrivacyDotEnabled() { return mDotFactory.getHasProviders(); } @@ -436,15 +457,12 @@ public class ScreenDecorations implements CoreStartable, Dumpable { // - we are trying to redraw. This because WM resized our window and told us to. // - the config change has been dispatched, so WM is no longer deferring layout. mPendingConfigChange = true; - if (DEBUG) { - if (mRotation != newRotation) { - Log.i(TAG, "Rotation changed, deferring " + newRotation - + ", staying at " + mRotation); - } - if (displayModeChanged(mDisplayMode, newDisplayMode)) { - Log.i(TAG, "Resolution changed, deferring " + newDisplayMode - + ", staying at " + mDisplayMode); - } + if (mRotation != newRotation) { + mLogger.logRotationChangeDeferred(mRotation, newRotation); + } + if (displayModeChanged(mDisplayMode, newDisplayMode)) { + mLogger.logDisplayModeChanged( + newDisplayMode.getModeId(), mDisplayMode.getModeId()); } if (mOverlays != null) { @@ -757,7 +775,8 @@ public class ScreenDecorations implements CoreStartable, Dumpable { } mScreenDecorHwcWindow = (ViewGroup) LayoutInflater.from(mContext).inflate( R.layout.screen_decor_hwc_layer, null); - mScreenDecorHwcLayer = new ScreenDecorHwcLayer(mContext, mHwcScreenDecorationSupport); + mScreenDecorHwcLayer = + new ScreenDecorHwcLayer(mContext, mHwcScreenDecorationSupport, mDebug); mScreenDecorHwcWindow.addView(mScreenDecorHwcLayer, new FrameLayout.LayoutParams( MATCH_PARENT, MATCH_PARENT, Gravity.TOP | Gravity.START)); mWindowManager.addView(mScreenDecorHwcWindow, getHwcWindowLayoutParams()); @@ -805,7 +824,7 @@ public class ScreenDecorations implements CoreStartable, Dumpable { lp.height = MATCH_PARENT; lp.setTitle("ScreenDecorHwcOverlay"); lp.gravity = Gravity.TOP | Gravity.START; - if (!DEBUG_COLOR) { + if (!mDebug) { lp.setColorMode(ActivityInfo.COLOR_MODE_A8); } return lp; @@ -913,19 +932,40 @@ public class ScreenDecorations implements CoreStartable, Dumpable { new UserTracker.Callback() { @Override public void onUserChanged(int newUser, @NonNull Context userContext) { - if (DEBUG) { - Log.d(TAG, "UserSwitched newUserId=" + newUser); - } + mLogger.logUserSwitched(newUser); // update color inversion setting to the new user mColorInversionSetting.setUserId(newUser); updateColorInversion(mColorInversionSetting.getValue()); } }; + /** + * Use the current value of {@link ScreenDecorations#mColorInversionSetting} and passes it + * to {@link ScreenDecorations#updateColorInversion} + */ + private void updateColorInversionDefault() { + int inversion = 0; + if (mColorInversionSetting != null) { + inversion = mColorInversionSetting.getValue(); + } + + updateColorInversion(inversion); + } + + /** + * Update the tint color of screen decoration assets. Defaults to Color.BLACK. In the case of + * a color inversion being set, use Color.WHITE (which inverts to black). + * + * When {@link ScreenDecorations#mDebug} is {@code true}, this value is updated to use + * {@link ScreenDecorations#mDebugColor}, and does not handle inversion. + * + * @param colorsInvertedValue if non-zero, assume that colors are inverted, and use Color.WHITE + * for screen decoration tint + */ private void updateColorInversion(int colorsInvertedValue) { mTintColor = colorsInvertedValue != 0 ? Color.WHITE : Color.BLACK; - if (DEBUG_COLOR) { - mTintColor = Color.RED; + if (mDebug) { + mTintColor = mDebugColor; } updateOverlayProviderViews(new Integer[] { @@ -966,7 +1006,9 @@ public class ScreenDecorations implements CoreStartable, Dumpable { int oldRotation = mRotation; mPendingConfigChange = false; updateConfiguration(); - if (DEBUG) Log.i(TAG, "onConfigChanged from rot " + oldRotation + " to " + mRotation); + if (oldRotation != mRotation) { + mLogger.logRotationChanged(oldRotation, mRotation); + } setupDecorations(); if (mOverlays != null) { // Updating the layout params ensures that ViewRootImpl will call relayoutWindow(), @@ -1199,7 +1241,7 @@ public class ScreenDecorations implements CoreStartable, Dumpable { paint.setColor(mColor); paint.setStyle(Paint.Style.FILL); - if (DEBUG) { + if (DEBUG_LOGGING) { getViewTreeObserver().addOnDrawListener(() -> Log.i(TAG, getWindowTitleByPos(pos) + " drawn in rot " + mRotation)); } @@ -1392,7 +1434,7 @@ public class ScreenDecorations implements CoreStartable, Dumpable { mView.getViewTreeObserver().removeOnPreDrawListener(this); if (mTargetRotation == mRotation && !displayModeChanged(mDisplayMode, mTargetDisplayMode)) { - if (DEBUG) { + if (DEBUG_LOGGING) { final String title = mPosition < 0 ? "ScreenDecorHwcLayer" : getWindowTitleByPos(mPosition); Log.i(TAG, title + " already in target rot " @@ -1408,7 +1450,7 @@ public class ScreenDecorations implements CoreStartable, Dumpable { // This changes the window attributes - we need to restart the traversal for them to // take effect. updateConfiguration(); - if (DEBUG) { + if (DEBUG_LOGGING) { final String title = mPosition < 0 ? "ScreenDecorHwcLayer" : getWindowTitleByPos(mPosition); Log.i(TAG, title @@ -1443,7 +1485,7 @@ public class ScreenDecorations implements CoreStartable, Dumpable { final Display.Mode displayMode = mDisplayInfo.getMode(); if ((displayRotation != mRotation || displayModeChanged(mDisplayMode, displayMode)) && !mPendingConfigChange) { - if (DEBUG) { + if (DEBUG_LOGGING) { if (displayRotation != mRotation) { Log.i(TAG, "Drawing rot " + mRotation + ", but display is at rot " + displayRotation + ". Restarting draw"); diff --git a/packages/SystemUI/src/com/android/systemui/log/ScreenDecorationsLogger.kt b/packages/SystemUI/src/com/android/systemui/log/ScreenDecorationsLogger.kt index f7277842c0265..27301e92eca22 100644 --- a/packages/SystemUI/src/com/android/systemui/log/ScreenDecorationsLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/log/ScreenDecorationsLogger.kt @@ -21,9 +21,9 @@ import android.graphics.Rect import android.graphics.RectF import androidx.core.graphics.toRectF import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.log.LogBuffer import com.android.systemui.log.LogLevel.DEBUG import com.android.systemui.log.LogLevel.ERROR +import com.android.systemui.log.LogLevel.INFO import com.android.systemui.log.dagger.ScreenDecorationsLog import com.google.errorprone.annotations.CompileTimeConstant import javax.inject.Inject @@ -164,4 +164,49 @@ constructor( fun cameraProtectionEvent(@CompileTimeConstant cameraProtectionEvent: String) { logBuffer.log(TAG, DEBUG, cameraProtectionEvent) } + + fun logRotationChangeDeferred(currentRot: Int, newRot: Int) { + logBuffer.log( + TAG, + INFO, + { + int1 = currentRot + int2 = newRot + }, + { "Rotation changed, deferring $int2, staying at $int2" }, + ) + } + + fun logRotationChanged(oldRot: Int, newRot: Int) { + logBuffer.log( + TAG, + INFO, + { + int1 = oldRot + int2 = newRot + }, + { "Rotation changed from $int1 to $int2" } + ) + } + + fun logDisplayModeChanged(currentMode: Int, newMode: Int) { + logBuffer.log( + TAG, + INFO, + { + int1 = currentMode + int2 = newMode + }, + { "Resolution changed, deferring mode change to $int2, staying at $int1" }, + ) + } + + fun logUserSwitched(newUser: Int) { + logBuffer.log( + TAG, + DEBUG, + { int1 = newUser }, + { "UserSwitched newUserId=$int1. Updating color inversion setting" }, + ) + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorHwcLayerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorHwcLayerTest.kt index 8207fa6958f33..d500b5aebcbfd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorHwcLayerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorHwcLayerTest.kt @@ -74,7 +74,8 @@ class ScreenDecorHwcLayerTest : SysuiTestCase() { val decorationSupport = DisplayDecorationSupport() decorationSupport.format = PixelFormat.R_8 - decorHwcLayer = Mockito.spy(ScreenDecorHwcLayer(mContext, decorationSupport)) + decorHwcLayer = + Mockito.spy(ScreenDecorHwcLayer(mContext, decorationSupport, /* debug */ false)) whenever(decorHwcLayer.width).thenReturn(displayWidth) whenever(decorHwcLayer.height).thenReturn(displayHeight) whenever(decorHwcLayer.context).thenReturn(mockContext) From eba0b0f0dfd77caa7bcda7dd2994e972418d4ab9 Mon Sep 17 00:00:00 2001 From: Evan Laird Date: Wed, 7 Jun 2023 12:33:37 -0400 Subject: [PATCH 3/4] [Decor] Split RoundedCornerResDelegate into impl and interface In order to support a new debug corner res delegate, we should split out the RoundedCornerResDelegate into an interface + implementation. This will allow us to create a DebugCornerDelegate that can be reused by the RoundedCornerDecorProvider without needing to duplicate that class definition. Test: RoundedCornerDecorProviderFactoryTest Test: RoundedCornerResDelegateTest Test: adb shell setprop debug.screenshot_rounded_corners 1 && restart-sysui Bug: 285941724 Change-Id: I68fbe1a7d0662781b611c84ae81dd526381aad22 --- .../android/systemui/ScreenDecorations.java | 8 ++-- .../decor/RoundedCornerResDelegate.kt | 41 +++++++++++++------ .../RoundedCornerDecorProviderFactoryTest.kt | 2 +- .../decor/RoundedCornerResDelegateTest.kt | 12 +++--- 4 files changed, 39 insertions(+), 24 deletions(-) diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java index 29f7a5e42778a..892b9dc7f08e8 100644 --- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java +++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java @@ -78,7 +78,7 @@ import com.android.systemui.decor.FaceScanningProviderFactory; import com.android.systemui.decor.OverlayWindow; import com.android.systemui.decor.PrivacyDotDecorProviderFactory; import com.android.systemui.decor.RoundedCornerDecorProviderFactory; -import com.android.systemui.decor.RoundedCornerResDelegate; +import com.android.systemui.decor.RoundedCornerResDelegateImpl; import com.android.systemui.log.ScreenDecorationsLogger; import com.android.systemui.qs.SettingObserver; import com.android.systemui.settings.DisplayTracker; @@ -142,7 +142,7 @@ public class ScreenDecorations implements CoreStartable, Dumpable { public final int mFaceScanningViewId; @VisibleForTesting - protected RoundedCornerResDelegate mRoundedCornerResDelegate; + protected RoundedCornerResDelegateImpl mRoundedCornerResDelegate; @VisibleForTesting protected DecorProviderFactory mRoundedCornerFactory; private CutoutDecorProviderFactory mCutoutFactory; @@ -429,8 +429,8 @@ public class ScreenDecorations implements CoreStartable, Dumpable { mDisplayMode = mDisplayInfo.getMode(); mDisplayUniqueId = mDisplayInfo.uniqueId; mDisplayCutout = mDisplayInfo.displayCutout; - mRoundedCornerResDelegate = new RoundedCornerResDelegate(mContext.getResources(), - mDisplayUniqueId); + mRoundedCornerResDelegate = + new RoundedCornerResDelegateImpl(mContext.getResources(), mDisplayUniqueId); mRoundedCornerResDelegate.setPhysicalPixelDisplaySizeRatio( getPhysicalPixelDisplaySizeRatio()); mRoundedCornerFactory = new RoundedCornerDecorProviderFactory(mRoundedCornerResDelegate); diff --git a/packages/SystemUI/src/com/android/systemui/decor/RoundedCornerResDelegate.kt b/packages/SystemUI/src/com/android/systemui/decor/RoundedCornerResDelegate.kt index 5f5ca54bd01f3..c64766a3eb3ce 100644 --- a/packages/SystemUI/src/com/android/systemui/decor/RoundedCornerResDelegate.kt +++ b/packages/SystemUI/src/com/android/systemui/decor/RoundedCornerResDelegate.kt @@ -27,35 +27,50 @@ import com.android.systemui.Dumpable import com.android.systemui.R import java.io.PrintWriter -class RoundedCornerResDelegate( +interface RoundedCornerResDelegate { + val hasTop: Boolean + val topRoundedDrawable: Drawable? + val topRoundedSize: Size + + val hasBottom: Boolean + val bottomRoundedDrawable: Drawable? + val bottomRoundedSize: Size + + var physicalPixelDisplaySizeRatio: Float + + fun updateDisplayUniqueId(newDisplayUniqueId: String?, newReloadToken: Int?) +} + +/** + * Delegate for the device-default rounded corners. These will always be loaded from the config + * values `R.array.config_roundedCornerTopDrawableArray` and `R.drawable.rounded_corner_top` + */ +class RoundedCornerResDelegateImpl( private val res: Resources, private var displayUniqueId: String? -) : Dumpable { - - private val density: Float - get() = res.displayMetrics.density +) : RoundedCornerResDelegate, Dumpable { private var reloadToken: Int = 0 - var hasTop: Boolean = false + override var hasTop: Boolean = false private set - var hasBottom: Boolean = false + override var hasBottom: Boolean = false private set - var topRoundedDrawable: Drawable? = null + override var topRoundedDrawable: Drawable? = null private set - var bottomRoundedDrawable: Drawable? = null + override var bottomRoundedDrawable: Drawable? = null private set - var topRoundedSize = Size(0, 0) + override var topRoundedSize = Size(0, 0) private set - var bottomRoundedSize = Size(0, 0) + override var bottomRoundedSize = Size(0, 0) private set - var physicalPixelDisplaySizeRatio: Float = 1f + override var physicalPixelDisplaySizeRatio: Float = 1f set(value) { if (field == value) { return @@ -69,7 +84,7 @@ class RoundedCornerResDelegate( reloadMeasures() } - fun updateDisplayUniqueId(newDisplayUniqueId: String?, newReloadToken: Int?) { + override fun updateDisplayUniqueId(newDisplayUniqueId: String?, newReloadToken: Int?) { if (displayUniqueId != newDisplayUniqueId) { displayUniqueId = newDisplayUniqueId newReloadToken ?.let { reloadToken = it } diff --git a/packages/SystemUI/tests/src/com/android/systemui/decor/RoundedCornerDecorProviderFactoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/decor/RoundedCornerDecorProviderFactoryTest.kt index fcc358982e6c4..8f0b193070001 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/decor/RoundedCornerDecorProviderFactoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/decor/RoundedCornerDecorProviderFactoryTest.kt @@ -39,7 +39,7 @@ class RoundedCornerDecorProviderFactoryTest : SysuiTestCase() { @Before fun setUp() { - roundedCornerResDelegate = spy(RoundedCornerResDelegate(mContext.resources, null)) + roundedCornerResDelegate = spy(RoundedCornerResDelegateImpl(mContext.resources, null)) } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/decor/RoundedCornerResDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/decor/RoundedCornerResDelegateTest.kt index 3912bc0e354e3..4feba7bfd3596 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/decor/RoundedCornerResDelegateTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/decor/RoundedCornerResDelegateTest.kt @@ -48,7 +48,7 @@ class RoundedCornerResDelegateTest : SysuiTestCase() { @Test fun testTopAndBottomRoundedCornerExist() { setupResources(radius = 5) - roundedCornerResDelegate = RoundedCornerResDelegate(mContext.resources, null) + roundedCornerResDelegate = RoundedCornerResDelegateImpl(mContext.resources, null) assertEquals(true, roundedCornerResDelegate.hasTop) assertEquals(true, roundedCornerResDelegate.hasBottom) } @@ -56,7 +56,7 @@ class RoundedCornerResDelegateTest : SysuiTestCase() { @Test fun testTopRoundedCornerExist() { setupResources(radiusTop = 10) - roundedCornerResDelegate = RoundedCornerResDelegate(mContext.resources, null) + roundedCornerResDelegate = RoundedCornerResDelegateImpl(mContext.resources, null) assertEquals(true, roundedCornerResDelegate.hasTop) assertEquals(false, roundedCornerResDelegate.hasBottom) } @@ -64,7 +64,7 @@ class RoundedCornerResDelegateTest : SysuiTestCase() { @Test fun testBottomRoundedCornerExist() { setupResources(radiusBottom = 15) - roundedCornerResDelegate = RoundedCornerResDelegate(mContext.resources, null) + roundedCornerResDelegate = RoundedCornerResDelegateImpl(mContext.resources, null) assertEquals(false, roundedCornerResDelegate.hasTop) assertEquals(true, roundedCornerResDelegate.hasBottom) } @@ -75,7 +75,7 @@ class RoundedCornerResDelegateTest : SysuiTestCase() { roundedTopDrawable = getTestsDrawable(R.drawable.rounded3px), roundedBottomDrawable = getTestsDrawable(R.drawable.rounded4px)) - roundedCornerResDelegate = RoundedCornerResDelegate(mContext.resources, null) + roundedCornerResDelegate = RoundedCornerResDelegateImpl(mContext.resources, null) assertEquals(Size(3, 3), roundedCornerResDelegate.topRoundedSize) assertEquals(Size(4, 4), roundedCornerResDelegate.bottomRoundedSize) @@ -96,7 +96,7 @@ class RoundedCornerResDelegateTest : SysuiTestCase() { roundedTopDrawable = getTestsDrawable(R.drawable.rounded3px), roundedBottomDrawable = getTestsDrawable(R.drawable.rounded4px)) - roundedCornerResDelegate = RoundedCornerResDelegate(mContext.resources, null) + roundedCornerResDelegate = RoundedCornerResDelegateImpl(mContext.resources, null) assertEquals(Size(3, 3), roundedCornerResDelegate.topRoundedSize) assertEquals(Size(4, 4), roundedCornerResDelegate.bottomRoundedSize) @@ -114,7 +114,7 @@ class RoundedCornerResDelegateTest : SysuiTestCase() { roundedTopDrawable = getTestsDrawable(R.drawable.rounded4px), roundedBottomDrawable = getTestsDrawable(R.drawable.rounded4px)) - roundedCornerResDelegate = RoundedCornerResDelegate(mContext.resources, null) + roundedCornerResDelegate = RoundedCornerResDelegateImpl(mContext.resources, null) assertEquals(Size(4, 4), roundedCornerResDelegate.topRoundedSize) assertEquals(Size(4, 4), roundedCornerResDelegate.bottomRoundedSize) From d8e648ee834a2dd2a424fec7591efab77ab6c69c Mon Sep 17 00:00:00 2001 From: Evan Laird Date: Thu, 8 Jun 2023 13:34:20 -0400 Subject: [PATCH 4/4] [Decor] Add DebugRoundedCornerDelegate This CL introduces the ability to have debug rounded corners in ScreenDecorations. It does this by creating a DebugRoundedCornerDelegate that maintains the debug state for rounded corners, and modifying ScreenDecorations to read from that delegate when in debug mode AND the delegate has providers. This means that debug mode actually has 2 stages: 1. simply turning on debug mode and providing no extra rounded corner information will change the device-default corners to show up in screenshots and display in color. 2. Secondly, providing a debug corner path spec will switch from using the rounded corner delegate to the new debug delegate. This switching is to-be-defined in a future CL, but it will involve re-solving for the providers and overlays. Test: manual Test: ScreenDecorationsTest Bug: 285941724 Change-Id: Ia0ce4c06117a2877cc930a1c1683912a383a968c --- .../android/systemui/ScreenDecorations.java | 62 ++++-- .../decor/DebugRoundedCornerDelegate.kt | 198 ++++++++++++++++++ .../systemui/ScreenDecorationsTest.java | 80 +++++++ 3 files changed, 327 insertions(+), 13 deletions(-) create mode 100644 packages/SystemUI/src/com/android/systemui/decor/DebugRoundedCornerDelegate.kt diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java index 892b9dc7f08e8..67d4a2e250510 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 getProviders(boolean hasHwLayer) { + @VisibleForTesting + protected List getProviders(boolean hasHwLayer) { List 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 0000000000000..4069bc7d73d0b --- /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 a6006a1e6a48b..79c87cfd1f3ef 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()); @@ -1053,6 +1057,82 @@ public class ScreenDecorationsTest extends SysuiTestCase { assertEquals(true, providers.get(1).getAlignedBounds().contains(BOUNDS_POSITION_BOTTOM)); } + @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 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();