Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 28b92c63 authored by Evan Laird's avatar Evan Laird Committed by Android (Google) Code Review
Browse files

Merge "[Battery] New icon updates" into main

parents 3143b284 1d312ed1
Loading
Loading
Loading
Loading
+3 −2
Original line number Diff line number Diff line
@@ -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
+35 −5
Original line number Diff line number Diff line
@@ -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;
@@ -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;
@@ -252,7 +254,7 @@ public class BatteryMeterView extends LinearLayout implements DarkReceiver {
                    new BatteryDrawableState(
                            level,
                            mUnifiedBatteryState.getShowPercent(),
                            level <= 20,
                            getCurrentColorProfile(),
                            attr
                    );

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

@@ -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;
@@ -293,7 +320,7 @@ public class BatteryMeterView extends LinearLayout implements DarkReceiver {
                    new BatteryDrawableState(
                            mUnifiedBatteryState.getLevel(),
                            mUnifiedBatteryState.getShowPercent(),
                            mUnifiedBatteryState.getShowErrorState(),
                            getCurrentColorProfile(),
                            getBatteryAttribution(isCharging())
                    )
            );
@@ -318,7 +345,7 @@ public class BatteryMeterView extends LinearLayout implements DarkReceiver {
                    new BatteryDrawableState(
                            mUnifiedBatteryState.getLevel(),
                            mUnifiedBatteryState.getShowPercent(),
                            mUnifiedBatteryState.getShowErrorState(),
                            getCurrentColorProfile(),
                            getBatteryAttribution(isCharging())
                    )
            );
@@ -334,7 +361,7 @@ public class BatteryMeterView extends LinearLayout implements DarkReceiver {
                        new BatteryDrawableState(
                                mUnifiedBatteryState.getLevel(),
                                mUnifiedBatteryState.getShowPercent(),
                                mUnifiedBatteryState.getShowErrorState(),
                                getCurrentColorProfile(),
                                getBatteryAttribution(isCharging())
                        )
                );
@@ -522,7 +549,7 @@ public class BatteryMeterView extends LinearLayout implements DarkReceiver {
                new BatteryDrawableState(
                        mUnifiedBatteryState.getLevel(),
                        shouldShow,
                        mUnifiedBatteryState.getShowErrorState(),
                        mUnifiedBatteryState.getColor(),
                        mUnifiedBatteryState.getAttribution()
                )
        );
@@ -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
+35 −23
Original line number Diff line number Diff line
@@ -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.
@@ -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
@@ -59,7 +71,6 @@ data class BatteryDrawableState(
            BatteryDrawableState(
                level = 50,
                showPercent = false,
                showErrorState = false,
                attribution = null,
            )
    }
@@ -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 {
@@ -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) */
@@ -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 {
+49 −19
Original line number Diff line number Diff line
@@ -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) {
@@ -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) {
@@ -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
    }
}
+44 −34
Original line number Diff line number Diff line
@@ -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(
@@ -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 {
@@ -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) {
@@ -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
@@ -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,