Merge changes Ia0ce4c06,I68fbe1a7,Ibc19ae82,If9f3e28c into udc-qpr-dev

* changes:
  [Decor] Add DebugRoundedCornerDelegate
  [Decor] Split RoundedCornerResDelegate into impl and interface
  [Decor] Make ScreenDecorations.mDebug non-final
  [Decor] Remove unused properties and Tunable
This commit is contained in:
Evan Laird
2023-06-16 19:08:35 +00:00
committed by Android (Google) Code Review
9 changed files with 490 additions and 199 deletions

View File

@@ -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
}
}

View File

@@ -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;
@@ -78,14 +79,12 @@ 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;
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,19 +104,17 @@ import javax.inject.Inject;
* for antialiasing and emulation purposes.
*/
@SysUISingleton
public class ScreenDecorations implements CoreStartable, Tunable , Dumpable {
private static final boolean DEBUG = false;
public class ScreenDecorations implements CoreStartable, Dumpable {
private static final boolean DEBUG_LOGGING = 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 boolean mDebug = DEBUG_SCREENSHOT_ROUNDED_CORNERS;
private int mDebugColor = Color.RED;
private static final int[] DISPLAY_CUTOUT_IDS = {
R.id.display_cutout,
@@ -134,7 +131,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;
@@ -147,9 +143,13 @@ public class ScreenDecorations implements CoreStartable, Tunable , Dumpable {
public final int mFaceScanningViewId;
@VisibleForTesting
protected RoundedCornerResDelegate mRoundedCornerResDelegate;
protected RoundedCornerResDelegateImpl mRoundedCornerResDelegate;
@VisibleForTesting
protected DecorProviderFactory mRoundedCornerFactory;
@VisibleForTesting
protected DebugRoundedCornerDelegate mDebugRoundedCornerDelegate =
new DebugRoundedCornerDelegate();
protected DecorProviderFactory mDebugRoundedCornerFactory;
private CutoutDecorProviderFactory mCutoutFactory;
private int mProviderRefreshToken = 0;
@VisibleForTesting
@@ -315,7 +315,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 +326,6 @@ public class ScreenDecorations implements CoreStartable, Tunable , Dumpable {
mContext = context;
mMainExecutor = mainExecutor;
mSecureSettings = secureSettings;
mTunerService = tunerService;
mUserTracker = userTracker;
mDisplayTracker = displayTracker;
mDotViewController = dotViewController;
@@ -365,16 +363,47 @@ public class ScreenDecorations implements CoreStartable, Tunable , 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.
*/
@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
removeAllOverlays();
removeHwcOverlay();
startOnScreenDecorationsThread();
updateColorInversionDefault();
});
}
private boolean isPrivacyDotEnabled() {
return mDotFactory.getHasProviders();
}
@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;
@@ -416,11 +445,13 @@ public class ScreenDecorations implements CoreStartable, Tunable , 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);
mDebugRoundedCornerFactory =
new RoundedCornerDecorProviderFactory(mDebugRoundedCornerDelegate);
mCutoutFactory = getCutoutFactory();
mHwcScreenDecorationSupport = mContext.getDisplay().getDisplayDecorationSupport();
updateHwLayerRoundedCornerDrawable();
@@ -444,15 +475,12 @@ public class ScreenDecorations implements CoreStartable, Tunable , 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) {
@@ -608,12 +636,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 +654,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);
}
@@ -777,7 +793,8 @@ public class ScreenDecorations implements CoreStartable, Tunable , 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());
@@ -825,7 +842,7 @@ public class ScreenDecorations implements CoreStartable, Tunable , 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;
@@ -933,19 +950,42 @@ public class ScreenDecorations implements CoreStartable, Tunable , 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;
mDebugRoundedCornerDelegate.setColor(mTintColor);
//TODO(b/285941724): update the hwc layer color here too (or disable it in debug mode)
}
updateOverlayProviderViews(new Integer[] {
@@ -986,7 +1026,9 @@ public class ScreenDecorations implements CoreStartable, Tunable , 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(),
@@ -1016,6 +1058,7 @@ public class ScreenDecorations implements CoreStartable, Tunable , Dumpable {
if (DEBUG_DISABLE_SCREEN_DECORATIONS) {
return;
}
ipw.println("mDebug:" + mDebug);
ipw.println("mIsPrivacyDotEnabled:" + isPrivacyDotEnabled());
ipw.println("shouldOptimizeOverlayVisibility:" + shouldOptimizeVisibility());
@@ -1071,6 +1114,7 @@ public class ScreenDecorations implements CoreStartable, Tunable , Dumpable {
}
}
mRoundedCornerResDelegate.dump(pw, args);
mDebugRoundedCornerDelegate.dump(pw);
}
@VisibleForTesting
@@ -1093,8 +1137,9 @@ public class ScreenDecorations implements CoreStartable, Tunable , 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);
@@ -1117,7 +1162,8 @@ public class ScreenDecorations implements CoreStartable, Tunable , Dumpable {
}
private boolean hasRoundedCorners() {
return mRoundedCornerFactory.getHasProviders();
return mRoundedCornerFactory.getHasProviders()
|| mDebugRoundedCornerFactory.getHasProviders();
}
private boolean shouldOptimizeVisibility() {
@@ -1170,41 +1216,17 @@ 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;
}
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;
@@ -1216,11 +1238,19 @@ public class ScreenDecorations implements CoreStartable, Tunable , 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
@@ -1247,7 +1277,7 @@ public class ScreenDecorations implements CoreStartable, Tunable , 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));
}
@@ -1440,7 +1470,7 @@ public class ScreenDecorations implements CoreStartable, Tunable , 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 "
@@ -1456,7 +1486,7 @@ public class ScreenDecorations implements CoreStartable, Tunable , 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
@@ -1491,7 +1521,7 @@ public class ScreenDecorations implements CoreStartable, Tunable , 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");

View File

@@ -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
}
}

View File

@@ -27,44 +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 tuningSizeFactor: Int? = null
set(value) {
if (field == value) {
return
}
field = value
reloadMeasures()
}
var physicalPixelDisplaySizeRatio: Float = 1f
override var physicalPixelDisplaySizeRatio: Float = 1f
set(value) {
if (field == value) {
return
@@ -78,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 }
@@ -122,19 +128,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(

View File

@@ -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" },
)
}
}

View File

@@ -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)

View File

@@ -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;
@@ -63,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;
@@ -83,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;
@@ -96,7 +97,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 +139,6 @@ public class ScreenDecorationsTest extends SysuiTestCase {
@Mock
private Display mDisplay;
@Mock
private TunerService mTunerService;
@Mock
private UserTracker mUserTracker;
@Mock
private PrivacyDotViewController mDotViewController;
@@ -234,7 +232,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 +248,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);
@@ -268,9 +260,10 @@ 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());
reset(mTunerService);
try {
mPrivacyDotShowingListener = mScreenDecorations.mPrivacyDotShowingListener.getClass()
@@ -464,8 +457,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 +488,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 +517,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 +547,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));
@@ -1072,19 +1057,90 @@ 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<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();
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 +1149,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 +1160,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 +1227,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();

View File

@@ -39,7 +39,7 @@ class RoundedCornerDecorProviderFactoryTest : SysuiTestCase() {
@Before
fun setUp() {
roundedCornerResDelegate = spy(RoundedCornerResDelegate(mContext.resources, null))
roundedCornerResDelegate = spy(RoundedCornerResDelegateImpl(mContext.resources, null))
}
@Test

View File

@@ -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)
@@ -108,34 +108,13 @@ 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(
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)