Loading packages/SystemUI/res/values/dimens.xml +3 −2 Original line number Diff line number Diff line Loading @@ -164,8 +164,9 @@ so the width of the icon should be 13.0sp * (12.0 / 20.0) --> <dimen name="status_bar_battery_icon_width">7.8sp</dimen> <dimen name="status_bar_battery_unified_icon_width">24sp</dimen> <dimen name="status_bar_battery_unified_icon_height">14sp</dimen> <!-- Original canvas is 24x14. These dimens reflect that ratio, with 12sp height instead --> <dimen name="status_bar_battery_unified_icon_width">20.6sp</dimen> <dimen name="status_bar_battery_unified_icon_height">12sp</dimen> <!-- The battery icon is 13sp tall, but the other system icons are 15sp tall (see @*android:dimen/status_bar_system_icon_size) with some top and bottom padding embedded in Loading packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java +35 −5 Original line number Diff line number Diff line Loading @@ -27,6 +27,7 @@ import android.animation.ObjectAnimator; import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.Nullable; import android.annotation.SuppressLint; import android.content.Context; import android.content.res.Configuration; import android.content.res.Resources; Loading @@ -52,6 +53,7 @@ import com.android.systemui.DualToneHandler; import com.android.systemui.battery.unified.BatteryColors; import com.android.systemui.battery.unified.BatteryDrawableState; import com.android.systemui.battery.unified.BatteryLayersDrawable; import com.android.systemui.battery.unified.ColorProfile; import com.android.systemui.plugins.DarkIconDispatcher; import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver; import com.android.systemui.res.R; Loading Loading @@ -252,7 +254,7 @@ public class BatteryMeterView extends LinearLayout implements DarkReceiver { new BatteryDrawableState( level, mUnifiedBatteryState.getShowPercent(), level <= 20, getCurrentColorProfile(), attr ); Loading @@ -261,6 +263,7 @@ public class BatteryMeterView extends LinearLayout implements DarkReceiver { } // Potentially reloads any attribution. Should not be called if the state hasn't changed @SuppressLint("UseCompatLoadingForDrawables") private Drawable getBatteryAttribution(boolean isCharging) { if (!newStatusBarIcons()) return null; Loading @@ -281,6 +284,30 @@ public class BatteryMeterView extends LinearLayout implements DarkReceiver { return attr; } /** Calculate the appropriate color for the current state */ private ColorProfile getCurrentColorProfile() { return getColorProfile( mPowerSaveEnabled, mIsBatteryDefender && mDisplayShieldEnabled, mPluggedIn, mLevel <= 20); } /** pure function to compute the correct color profile for our battery icon */ private ColorProfile getColorProfile( boolean isPowerSave, boolean isBatteryDefender, boolean isCharging, boolean isLowBattery ) { if (isCharging) return ColorProfile.Active; if (isPowerSave) return ColorProfile.Warning; if (isBatteryDefender) return ColorProfile.None; if (isLowBattery) return ColorProfile.Error; return ColorProfile.None; } void onPowerSaveChanged(boolean isPowerSave) { if (isPowerSave == mPowerSaveEnabled) { return; Loading @@ -293,7 +320,7 @@ public class BatteryMeterView extends LinearLayout implements DarkReceiver { new BatteryDrawableState( mUnifiedBatteryState.getLevel(), mUnifiedBatteryState.getShowPercent(), mUnifiedBatteryState.getShowErrorState(), getCurrentColorProfile(), getBatteryAttribution(isCharging()) ) ); Loading @@ -318,7 +345,7 @@ public class BatteryMeterView extends LinearLayout implements DarkReceiver { new BatteryDrawableState( mUnifiedBatteryState.getLevel(), mUnifiedBatteryState.getShowPercent(), mUnifiedBatteryState.getShowErrorState(), getCurrentColorProfile(), getBatteryAttribution(isCharging()) ) ); Loading @@ -334,7 +361,7 @@ public class BatteryMeterView extends LinearLayout implements DarkReceiver { new BatteryDrawableState( mUnifiedBatteryState.getLevel(), mUnifiedBatteryState.getShowPercent(), mUnifiedBatteryState.getShowErrorState(), getCurrentColorProfile(), getBatteryAttribution(isCharging()) ) ); Loading Loading @@ -522,7 +549,7 @@ public class BatteryMeterView extends LinearLayout implements DarkReceiver { new BatteryDrawableState( mUnifiedBatteryState.getLevel(), shouldShow, mUnifiedBatteryState.getShowErrorState(), mUnifiedBatteryState.getColor(), mUnifiedBatteryState.getAttribution() ) ); Loading Loading @@ -755,6 +782,9 @@ public class BatteryMeterView extends LinearLayout implements DarkReceiver { pw.println(" mPluggedIn: " + mPluggedIn); pw.println(" mLevel: " + mLevel); pw.println(" mMode: " + mShowPercentMode); if (newStatusBarIcons()) { pw.println(" mUnifiedBatteryState: " + mUnifiedBatteryState); } } @VisibleForTesting Loading packages/SystemUI/src/com/android/systemui/battery/unified/BatteryDrawableState.kt +35 −23 Original line number Diff line number Diff line Loading @@ -19,6 +19,21 @@ package com.android.systemui.battery.unified import android.graphics.Color import android.graphics.drawable.Drawable /** * States that might set a color profile (e.g., red for low battery) and are mutually exclusive. * This enum allows us to address which colors we want to use based on their function. */ enum class ColorProfile { // Grayscale is the default color None, // Green for e.g., charging Active, // Yellow for e.g., battery saver Warning, // Red for e.t., low battery Error, } /** * Encapsulates all drawing information needed by BatteryMeterDrawable to render properly. Rendered * state will be equivalent to the most recent state passed in. Loading @@ -28,12 +43,9 @@ data class BatteryDrawableState( val level: Int, /** Whether or not to render the percent as a foreground text layer */ val showPercent: Boolean, /** * In an error state, the drawable will use the error colors and removes the third layer. If * [showPercent] is false, then the fill will be rendered in the foreground error color. Else * the fill is not rendered. */ val showErrorState: Boolean, /** Set the [ColorProfile] to get the appropriate fill colors */ val color: ColorProfile = ColorProfile.None, /** * An attribution is a drawable that shows either alongside the percent, or centered in the Loading @@ -59,7 +71,6 @@ data class BatteryDrawableState( BatteryDrawableState( level = 50, showPercent = false, showErrorState = false, attribution = null, ) } Loading @@ -82,12 +93,14 @@ sealed interface BatteryColors { */ val fillOnly: Int /** Error colors are used for low battery states typically */ val errorForeground: Int val errorBackground: Int /** Used when charging */ val activeFill: Int /** Currently unused */ val warnBackground: Int /** Warning color is used for battery saver mode */ val warnFill: Int /** Error colors are used for low battery states typically */ val errorFill: Int /** Color scheme appropriate for light mode (dark icons) */ data object LightThemeColors : BatteryColors { Loading @@ -100,13 +113,12 @@ sealed interface BatteryColors { // GM Gray 700 override val fillOnly = Color.parseColor("#5F6368") // GM Red 600 override val errorForeground = Color.parseColor("#D93025") // GM Red 100 override val errorBackground = Color.parseColor("#FAD2CF") // GM Green 700 override val activeFill = Color.parseColor("#188038") // GM Yellow 500 override val warnBackground = Color.parseColor("#FBBC04") override val warnFill = Color.parseColor("#FBBC04") // GM Red 600 override val errorFill = Color.parseColor("#D93025") } /** Color scheme appropriate for dark mode (light icons) */ Loading @@ -120,12 +132,12 @@ sealed interface BatteryColors { // GM Gray 400 override val fillOnly = Color.parseColor("#BDC1C6") // GM Red 600 override val errorForeground = Color.parseColor("#D93025") // GM Red 200 override val errorBackground = Color.parseColor("#F6AEA9") // GM Green 500 override val activeFill = Color.parseColor("#34A853") // GM Yellow override val warnBackground = Color.parseColor("#FBBC04") override val warnFill = Color.parseColor("#FBBC04") // GM Red 600 override val errorFill = Color.parseColor("#D93025") } companion object { Loading packages/SystemUI/src/com/android/systemui/battery/unified/BatteryFillDrawable.kt +49 −19 Original line number Diff line number Diff line Loading @@ -44,6 +44,29 @@ class BatteryFillDrawable(private val framePath: Path) : Drawable() { private var scaledLeftOffset = 0f private var scaledRightInset = 0f /** Scale this to the viewport so we fill correctly! */ private val fillRectNotScaled = RectF() private var leftInsetNotScaled = 0f private var rightInsetNotScaled = 0f /** * Configure how much space between the battery frame (drawn at 1.5dp stroke width) and the * inner fill. This is accomplished by tracing the exact same path as the frame, but using * [BlendMode.CLEAR] as the blend mode. * * This value also affects the overall width of the fill, so it requires us to re-draw * everything */ var fillInsetAmount = -1f set(value) { if (field != value) { field = value updateInsets() updateScale() invalidateSelf() } } // Drawable.level cannot be overloaded var batteryLevel = 0 set(value) { Loading Loading @@ -87,15 +110,32 @@ class BatteryFillDrawable(private val framePath: Path) : Drawable() { updateScale() } /** * To support dynamic insets, we have to keep mutable references to the left/right unscaled * insets, as well as the fill rect. */ private fun updateInsets() { leftInsetNotScaled = LeftFillOffsetExcludingPadding + fillInsetAmount rightInsetNotScaled = RightFillInsetExcludingPadding + fillInsetAmount fillRectNotScaled.set( leftInsetNotScaled, 0f, Metrics.ViewportWidth - rightInsetNotScaled, Metrics.ViewportHeight ) } private fun updateScale() { framePath.transform(/* matrix = */ scaleMatrix, /* dst = */ scaledPath) scaleMatrix.mapRect(/* dst = */ scaledFillRect, /* src = */ FillRect) scaleMatrix.mapRect(/* dst = */ scaledFillRect, /* src = */ fillRectNotScaled) scaledLeftOffset = LeftFillOffset * hScale scaledRightInset = RightFillInset * hScale scaledLeftOffset = leftInsetNotScaled * hScale scaledRightInset = rightInsetNotScaled * hScale // Ensure 0.5dp space between the frame stroke and the fill clearPaint.strokeWidth = 2.5f * hScale // stroke width = 1.5 (same as the outer frame) + 2x fillInsetAmount, since N px of padding // requires the entire stroke to be 2N px wider clearPaint.strokeWidth = (1.5f + 2 * fillInsetAmount) * hScale } override fun draw(canvas: Canvas) { Loading Loading @@ -157,23 +197,13 @@ class BatteryFillDrawable(private val framePath: Path) : Drawable() { override fun setAlpha(alpha: Int) {} companion object { // 4f = // 3.5f = // 2.75 (left-most edge of the frame path) // + 0.75 (1/2 of the stroke width) // + 0.5 (padding between stroke and fill edge) private const val LeftFillOffset = 4f private const val LeftFillOffsetExcludingPadding = 3.5f // 2, calculated the same way, but from the right edge (without the battery cap), which // 1.5, calculated the same way, but from the right edge (without the battery cap), which // consumes 2 units of width. private const val RightFillInset = 2f /** Scale this to the viewport so we fill correctly! */ private val FillRect = RectF( LeftFillOffset, 0f, Metrics.ViewportWidth - RightFillInset, Metrics.ViewportHeight ) private const val RightFillInsetExcludingPadding = 1.5f } } packages/SystemUI/src/com/android/systemui/battery/unified/BatteryLayersDrawable.kt +44 −34 Original line number Diff line number Diff line Loading @@ -56,9 +56,6 @@ import kotlin.math.roundToInt * - The internal space is divided into 12x10 and 6x6 rectangles * - The attribution is aligned left * - The percent text is scaled based on the number of characters (1,2, or 3) in the string * * When [BatteryDrawableState.showErrorState] is true, we will only show either the percent text OR * the battery fill, in order to maximize contrast when using the error colors. */ @Suppress("RtlHardcoded") class BatteryLayersDrawable( Loading Loading @@ -91,7 +88,7 @@ class BatteryLayersDrawable( var colors: BatteryColors = BatteryColors.LightThemeColors set(value) { field = value updateColors(batteryState.showErrorState, value) updateColorProfile(batteryState.hasForegroundContent(), batteryState.color, value) } init { Loading @@ -101,51 +98,64 @@ class BatteryLayersDrawable( } private fun handleUpdateState(old: BatteryDrawableState, new: BatteryDrawableState) { if (new.showErrorState != old.showErrorState) { updateColors(new.showErrorState, colors) } if (new.level != old.level) { fill.batteryLevel = new.level textOnly.batteryLevel = new.level spaceSharingText.batteryLevel = new.level } val shouldUpdateColors = new.color != old.color || new.attribution != attribution.drawable || new.hasForegroundContent() != old.hasForegroundContent() if (new.attribution != null && new.attribution != attribution.drawable) { attribution.drawable = new.attribution updateColors(new.showErrorState, colors) } if (new.hasForegroundContent() != old.hasForegroundContent()) { setFillColor(new.hasForegroundContent(), new.showErrorState, colors) } setFillInsets(new.hasForegroundContent()) } /** In error states, we don't draw fill unless there is no foreground content (e.g., percent) */ private fun updateColors(showErrorState: Boolean, colorInfo: BatteryColors) { frameBg.setTint(if (showErrorState) colorInfo.errorBackground else colorInfo.bg) frame.setTint(colorInfo.fg) attribution.setTint(if (showErrorState) colorInfo.errorForeground else colorInfo.fg) textOnly.setTint(if (showErrorState) colorInfo.errorForeground else colorInfo.fg) spaceSharingText.setTint(if (showErrorState) colorInfo.errorForeground else colorInfo.fg) setFillColor(batteryState.hasForegroundContent(), showErrorState, colorInfo) // Finally, update colors last if any of the above conditions were met, so that everything // is properly tinted if (shouldUpdateColors) { updateColorProfile(new.hasForegroundContent(), new.color, colors) } } /** * If there is a foreground layer, then we draw the fill with the low opacity * [BatteryColors.fill] color. Otherwise, if there is no other foreground layer, we will use * either the error or fillOnly colors for more contrast */ private fun setFillColor( private fun updateColorProfile( hasFg: Boolean, error: Boolean, color: ColorProfile, colorInfo: BatteryColors, ) { if (hasFg) { fill.fillColor = colorInfo.fill } else { fill.fillColor = if (error) colorInfo.errorForeground else colorInfo.fillOnly frame.setTint(colorInfo.fg) frameBg.setTint(colorInfo.bg) textOnly.setTint(colorInfo.fg) spaceSharingText.setTint(colorInfo.fg) attribution.setTint(colorInfo.fg) when (color) { ColorProfile.None -> { fill.fillColor = if (hasFg) colorInfo.fill else colorInfo.fillOnly } ColorProfile.Active -> { fill.fillColor = colorInfo.activeFill } ColorProfile.Warning -> { fill.fillColor = colorInfo.warnFill } ColorProfile.Error -> { fill.fillColor = colorInfo.errorFill } } } private fun setFillInsets( hasFg: Boolean, ) { // Extra padding around the fill if there is nothing in the foreground fill.fillInsetAmount = if (hasFg) 0f else 1.5f } override fun onBoundsChange(bounds: Rect) { Loading Loading @@ -200,10 +210,9 @@ class BatteryLayersDrawable( // 2. Then the frame itself frame.draw(canvas) // 3. Fill it the appropriate amount if non-error state or error + no attribute if (!batteryState.showErrorState || !batteryState.hasForegroundContent()) { // 3. Fill it the appropriate amount fill.draw(canvas) } // 4. Decide what goes inside if (batteryState.showPercent && batteryState.attribution != null) { // 4a. percent & attribution. Implies space-sharing Loading Loading @@ -309,6 +318,7 @@ class BatteryLayersDrawable( * * See [BatteryDrawableState] for how to set the properties of the resulting class */ @Suppress("UseCompatLoadingForDrawables") fun newBatteryDrawable( context: Context, initialState: BatteryDrawableState = BatteryDrawableState.DefaultInitialState, Loading Loading
packages/SystemUI/res/values/dimens.xml +3 −2 Original line number Diff line number Diff line Loading @@ -164,8 +164,9 @@ so the width of the icon should be 13.0sp * (12.0 / 20.0) --> <dimen name="status_bar_battery_icon_width">7.8sp</dimen> <dimen name="status_bar_battery_unified_icon_width">24sp</dimen> <dimen name="status_bar_battery_unified_icon_height">14sp</dimen> <!-- Original canvas is 24x14. These dimens reflect that ratio, with 12sp height instead --> <dimen name="status_bar_battery_unified_icon_width">20.6sp</dimen> <dimen name="status_bar_battery_unified_icon_height">12sp</dimen> <!-- The battery icon is 13sp tall, but the other system icons are 15sp tall (see @*android:dimen/status_bar_system_icon_size) with some top and bottom padding embedded in Loading
packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java +35 −5 Original line number Diff line number Diff line Loading @@ -27,6 +27,7 @@ import android.animation.ObjectAnimator; import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.Nullable; import android.annotation.SuppressLint; import android.content.Context; import android.content.res.Configuration; import android.content.res.Resources; Loading @@ -52,6 +53,7 @@ import com.android.systemui.DualToneHandler; import com.android.systemui.battery.unified.BatteryColors; import com.android.systemui.battery.unified.BatteryDrawableState; import com.android.systemui.battery.unified.BatteryLayersDrawable; import com.android.systemui.battery.unified.ColorProfile; import com.android.systemui.plugins.DarkIconDispatcher; import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver; import com.android.systemui.res.R; Loading Loading @@ -252,7 +254,7 @@ public class BatteryMeterView extends LinearLayout implements DarkReceiver { new BatteryDrawableState( level, mUnifiedBatteryState.getShowPercent(), level <= 20, getCurrentColorProfile(), attr ); Loading @@ -261,6 +263,7 @@ public class BatteryMeterView extends LinearLayout implements DarkReceiver { } // Potentially reloads any attribution. Should not be called if the state hasn't changed @SuppressLint("UseCompatLoadingForDrawables") private Drawable getBatteryAttribution(boolean isCharging) { if (!newStatusBarIcons()) return null; Loading @@ -281,6 +284,30 @@ public class BatteryMeterView extends LinearLayout implements DarkReceiver { return attr; } /** Calculate the appropriate color for the current state */ private ColorProfile getCurrentColorProfile() { return getColorProfile( mPowerSaveEnabled, mIsBatteryDefender && mDisplayShieldEnabled, mPluggedIn, mLevel <= 20); } /** pure function to compute the correct color profile for our battery icon */ private ColorProfile getColorProfile( boolean isPowerSave, boolean isBatteryDefender, boolean isCharging, boolean isLowBattery ) { if (isCharging) return ColorProfile.Active; if (isPowerSave) return ColorProfile.Warning; if (isBatteryDefender) return ColorProfile.None; if (isLowBattery) return ColorProfile.Error; return ColorProfile.None; } void onPowerSaveChanged(boolean isPowerSave) { if (isPowerSave == mPowerSaveEnabled) { return; Loading @@ -293,7 +320,7 @@ public class BatteryMeterView extends LinearLayout implements DarkReceiver { new BatteryDrawableState( mUnifiedBatteryState.getLevel(), mUnifiedBatteryState.getShowPercent(), mUnifiedBatteryState.getShowErrorState(), getCurrentColorProfile(), getBatteryAttribution(isCharging()) ) ); Loading @@ -318,7 +345,7 @@ public class BatteryMeterView extends LinearLayout implements DarkReceiver { new BatteryDrawableState( mUnifiedBatteryState.getLevel(), mUnifiedBatteryState.getShowPercent(), mUnifiedBatteryState.getShowErrorState(), getCurrentColorProfile(), getBatteryAttribution(isCharging()) ) ); Loading @@ -334,7 +361,7 @@ public class BatteryMeterView extends LinearLayout implements DarkReceiver { new BatteryDrawableState( mUnifiedBatteryState.getLevel(), mUnifiedBatteryState.getShowPercent(), mUnifiedBatteryState.getShowErrorState(), getCurrentColorProfile(), getBatteryAttribution(isCharging()) ) ); Loading Loading @@ -522,7 +549,7 @@ public class BatteryMeterView extends LinearLayout implements DarkReceiver { new BatteryDrawableState( mUnifiedBatteryState.getLevel(), shouldShow, mUnifiedBatteryState.getShowErrorState(), mUnifiedBatteryState.getColor(), mUnifiedBatteryState.getAttribution() ) ); Loading Loading @@ -755,6 +782,9 @@ public class BatteryMeterView extends LinearLayout implements DarkReceiver { pw.println(" mPluggedIn: " + mPluggedIn); pw.println(" mLevel: " + mLevel); pw.println(" mMode: " + mShowPercentMode); if (newStatusBarIcons()) { pw.println(" mUnifiedBatteryState: " + mUnifiedBatteryState); } } @VisibleForTesting Loading
packages/SystemUI/src/com/android/systemui/battery/unified/BatteryDrawableState.kt +35 −23 Original line number Diff line number Diff line Loading @@ -19,6 +19,21 @@ package com.android.systemui.battery.unified import android.graphics.Color import android.graphics.drawable.Drawable /** * States that might set a color profile (e.g., red for low battery) and are mutually exclusive. * This enum allows us to address which colors we want to use based on their function. */ enum class ColorProfile { // Grayscale is the default color None, // Green for e.g., charging Active, // Yellow for e.g., battery saver Warning, // Red for e.t., low battery Error, } /** * Encapsulates all drawing information needed by BatteryMeterDrawable to render properly. Rendered * state will be equivalent to the most recent state passed in. Loading @@ -28,12 +43,9 @@ data class BatteryDrawableState( val level: Int, /** Whether or not to render the percent as a foreground text layer */ val showPercent: Boolean, /** * In an error state, the drawable will use the error colors and removes the third layer. If * [showPercent] is false, then the fill will be rendered in the foreground error color. Else * the fill is not rendered. */ val showErrorState: Boolean, /** Set the [ColorProfile] to get the appropriate fill colors */ val color: ColorProfile = ColorProfile.None, /** * An attribution is a drawable that shows either alongside the percent, or centered in the Loading @@ -59,7 +71,6 @@ data class BatteryDrawableState( BatteryDrawableState( level = 50, showPercent = false, showErrorState = false, attribution = null, ) } Loading @@ -82,12 +93,14 @@ sealed interface BatteryColors { */ val fillOnly: Int /** Error colors are used for low battery states typically */ val errorForeground: Int val errorBackground: Int /** Used when charging */ val activeFill: Int /** Currently unused */ val warnBackground: Int /** Warning color is used for battery saver mode */ val warnFill: Int /** Error colors are used for low battery states typically */ val errorFill: Int /** Color scheme appropriate for light mode (dark icons) */ data object LightThemeColors : BatteryColors { Loading @@ -100,13 +113,12 @@ sealed interface BatteryColors { // GM Gray 700 override val fillOnly = Color.parseColor("#5F6368") // GM Red 600 override val errorForeground = Color.parseColor("#D93025") // GM Red 100 override val errorBackground = Color.parseColor("#FAD2CF") // GM Green 700 override val activeFill = Color.parseColor("#188038") // GM Yellow 500 override val warnBackground = Color.parseColor("#FBBC04") override val warnFill = Color.parseColor("#FBBC04") // GM Red 600 override val errorFill = Color.parseColor("#D93025") } /** Color scheme appropriate for dark mode (light icons) */ Loading @@ -120,12 +132,12 @@ sealed interface BatteryColors { // GM Gray 400 override val fillOnly = Color.parseColor("#BDC1C6") // GM Red 600 override val errorForeground = Color.parseColor("#D93025") // GM Red 200 override val errorBackground = Color.parseColor("#F6AEA9") // GM Green 500 override val activeFill = Color.parseColor("#34A853") // GM Yellow override val warnBackground = Color.parseColor("#FBBC04") override val warnFill = Color.parseColor("#FBBC04") // GM Red 600 override val errorFill = Color.parseColor("#D93025") } companion object { Loading
packages/SystemUI/src/com/android/systemui/battery/unified/BatteryFillDrawable.kt +49 −19 Original line number Diff line number Diff line Loading @@ -44,6 +44,29 @@ class BatteryFillDrawable(private val framePath: Path) : Drawable() { private var scaledLeftOffset = 0f private var scaledRightInset = 0f /** Scale this to the viewport so we fill correctly! */ private val fillRectNotScaled = RectF() private var leftInsetNotScaled = 0f private var rightInsetNotScaled = 0f /** * Configure how much space between the battery frame (drawn at 1.5dp stroke width) and the * inner fill. This is accomplished by tracing the exact same path as the frame, but using * [BlendMode.CLEAR] as the blend mode. * * This value also affects the overall width of the fill, so it requires us to re-draw * everything */ var fillInsetAmount = -1f set(value) { if (field != value) { field = value updateInsets() updateScale() invalidateSelf() } } // Drawable.level cannot be overloaded var batteryLevel = 0 set(value) { Loading Loading @@ -87,15 +110,32 @@ class BatteryFillDrawable(private val framePath: Path) : Drawable() { updateScale() } /** * To support dynamic insets, we have to keep mutable references to the left/right unscaled * insets, as well as the fill rect. */ private fun updateInsets() { leftInsetNotScaled = LeftFillOffsetExcludingPadding + fillInsetAmount rightInsetNotScaled = RightFillInsetExcludingPadding + fillInsetAmount fillRectNotScaled.set( leftInsetNotScaled, 0f, Metrics.ViewportWidth - rightInsetNotScaled, Metrics.ViewportHeight ) } private fun updateScale() { framePath.transform(/* matrix = */ scaleMatrix, /* dst = */ scaledPath) scaleMatrix.mapRect(/* dst = */ scaledFillRect, /* src = */ FillRect) scaleMatrix.mapRect(/* dst = */ scaledFillRect, /* src = */ fillRectNotScaled) scaledLeftOffset = LeftFillOffset * hScale scaledRightInset = RightFillInset * hScale scaledLeftOffset = leftInsetNotScaled * hScale scaledRightInset = rightInsetNotScaled * hScale // Ensure 0.5dp space between the frame stroke and the fill clearPaint.strokeWidth = 2.5f * hScale // stroke width = 1.5 (same as the outer frame) + 2x fillInsetAmount, since N px of padding // requires the entire stroke to be 2N px wider clearPaint.strokeWidth = (1.5f + 2 * fillInsetAmount) * hScale } override fun draw(canvas: Canvas) { Loading Loading @@ -157,23 +197,13 @@ class BatteryFillDrawable(private val framePath: Path) : Drawable() { override fun setAlpha(alpha: Int) {} companion object { // 4f = // 3.5f = // 2.75 (left-most edge of the frame path) // + 0.75 (1/2 of the stroke width) // + 0.5 (padding between stroke and fill edge) private const val LeftFillOffset = 4f private const val LeftFillOffsetExcludingPadding = 3.5f // 2, calculated the same way, but from the right edge (without the battery cap), which // 1.5, calculated the same way, but from the right edge (without the battery cap), which // consumes 2 units of width. private const val RightFillInset = 2f /** Scale this to the viewport so we fill correctly! */ private val FillRect = RectF( LeftFillOffset, 0f, Metrics.ViewportWidth - RightFillInset, Metrics.ViewportHeight ) private const val RightFillInsetExcludingPadding = 1.5f } }
packages/SystemUI/src/com/android/systemui/battery/unified/BatteryLayersDrawable.kt +44 −34 Original line number Diff line number Diff line Loading @@ -56,9 +56,6 @@ import kotlin.math.roundToInt * - The internal space is divided into 12x10 and 6x6 rectangles * - The attribution is aligned left * - The percent text is scaled based on the number of characters (1,2, or 3) in the string * * When [BatteryDrawableState.showErrorState] is true, we will only show either the percent text OR * the battery fill, in order to maximize contrast when using the error colors. */ @Suppress("RtlHardcoded") class BatteryLayersDrawable( Loading Loading @@ -91,7 +88,7 @@ class BatteryLayersDrawable( var colors: BatteryColors = BatteryColors.LightThemeColors set(value) { field = value updateColors(batteryState.showErrorState, value) updateColorProfile(batteryState.hasForegroundContent(), batteryState.color, value) } init { Loading @@ -101,51 +98,64 @@ class BatteryLayersDrawable( } private fun handleUpdateState(old: BatteryDrawableState, new: BatteryDrawableState) { if (new.showErrorState != old.showErrorState) { updateColors(new.showErrorState, colors) } if (new.level != old.level) { fill.batteryLevel = new.level textOnly.batteryLevel = new.level spaceSharingText.batteryLevel = new.level } val shouldUpdateColors = new.color != old.color || new.attribution != attribution.drawable || new.hasForegroundContent() != old.hasForegroundContent() if (new.attribution != null && new.attribution != attribution.drawable) { attribution.drawable = new.attribution updateColors(new.showErrorState, colors) } if (new.hasForegroundContent() != old.hasForegroundContent()) { setFillColor(new.hasForegroundContent(), new.showErrorState, colors) } setFillInsets(new.hasForegroundContent()) } /** In error states, we don't draw fill unless there is no foreground content (e.g., percent) */ private fun updateColors(showErrorState: Boolean, colorInfo: BatteryColors) { frameBg.setTint(if (showErrorState) colorInfo.errorBackground else colorInfo.bg) frame.setTint(colorInfo.fg) attribution.setTint(if (showErrorState) colorInfo.errorForeground else colorInfo.fg) textOnly.setTint(if (showErrorState) colorInfo.errorForeground else colorInfo.fg) spaceSharingText.setTint(if (showErrorState) colorInfo.errorForeground else colorInfo.fg) setFillColor(batteryState.hasForegroundContent(), showErrorState, colorInfo) // Finally, update colors last if any of the above conditions were met, so that everything // is properly tinted if (shouldUpdateColors) { updateColorProfile(new.hasForegroundContent(), new.color, colors) } } /** * If there is a foreground layer, then we draw the fill with the low opacity * [BatteryColors.fill] color. Otherwise, if there is no other foreground layer, we will use * either the error or fillOnly colors for more contrast */ private fun setFillColor( private fun updateColorProfile( hasFg: Boolean, error: Boolean, color: ColorProfile, colorInfo: BatteryColors, ) { if (hasFg) { fill.fillColor = colorInfo.fill } else { fill.fillColor = if (error) colorInfo.errorForeground else colorInfo.fillOnly frame.setTint(colorInfo.fg) frameBg.setTint(colorInfo.bg) textOnly.setTint(colorInfo.fg) spaceSharingText.setTint(colorInfo.fg) attribution.setTint(colorInfo.fg) when (color) { ColorProfile.None -> { fill.fillColor = if (hasFg) colorInfo.fill else colorInfo.fillOnly } ColorProfile.Active -> { fill.fillColor = colorInfo.activeFill } ColorProfile.Warning -> { fill.fillColor = colorInfo.warnFill } ColorProfile.Error -> { fill.fillColor = colorInfo.errorFill } } } private fun setFillInsets( hasFg: Boolean, ) { // Extra padding around the fill if there is nothing in the foreground fill.fillInsetAmount = if (hasFg) 0f else 1.5f } override fun onBoundsChange(bounds: Rect) { Loading Loading @@ -200,10 +210,9 @@ class BatteryLayersDrawable( // 2. Then the frame itself frame.draw(canvas) // 3. Fill it the appropriate amount if non-error state or error + no attribute if (!batteryState.showErrorState || !batteryState.hasForegroundContent()) { // 3. Fill it the appropriate amount fill.draw(canvas) } // 4. Decide what goes inside if (batteryState.showPercent && batteryState.attribution != null) { // 4a. percent & attribution. Implies space-sharing Loading Loading @@ -309,6 +318,7 @@ class BatteryLayersDrawable( * * See [BatteryDrawableState] for how to set the properties of the resulting class */ @Suppress("UseCompatLoadingForDrawables") fun newBatteryDrawable( context: Context, initialState: BatteryDrawableState = BatteryDrawableState.DefaultInitialState, Loading