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

Commit 80c77b7a authored by Evan Laird's avatar Evan Laird
Browse files

[battery] add '?' state

This state represents the case when the battery is in an unknown state
which happens when `EXTRA_PRESENT` is `false` in the `BATTERY_CHANGED`
broadcast.

This is an existing feature of the old battery, so we should have it
here as well.

Test: adb shell am broadcast -a com.android.systemui.demo -e command battery -e present true
Test: BatteryInteractorTest
Test: BatteryViewModelBasedOnSettingTest
Fixes: 419820166
Flag: com.android.settingslib.flags.new_status_bar_icons
Flag: com.android.systemui.status_bar_root_modernization
Change-Id: Ia7b1ec01141c66b9cfe61c87db35534dfacb7a79
parent 50ecbee2
Loading
Loading
Loading
Loading
+22 −7
Original line number Diff line number Diff line
@@ -19,14 +19,22 @@ package com.android.systemui.statusbar.pipeline.battery.domain.interactor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.statusbar.pipeline.battery.data.repository.BatteryRepository
import javax.inject.Inject
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.map

@OptIn(ExperimentalCoroutinesApi::class)
@SysUISingleton
class BatteryInteractor @Inject constructor(repo: BatteryRepository) {
    /** The current level in the range of [0-100] */
    val level = repo.level.filterNotNull()
    /** The current level in the range of [0-100], or null if we don't know the level yet */
    val level =
        combine(repo.isStateUnknown, repo.level) { unknown, level ->
            if (unknown) {
                null
            } else {
                level
            }
        }

    /** Whether the battery has been fully charged */
    val isFull = level.map { isBatteryFull(it) }
@@ -47,7 +55,7 @@ class BatteryInteractor @Inject constructor(repo: BatteryRepository) {
     * The critical level (see [CRITICAL_LEVEL]) defines the level below which we might want to
     * display an error UI. E.g., show the battery as red.
     */
    val isCritical = level.map { it <= CRITICAL_LEVEL }
    val isCritical = level.map { it != null && it <= CRITICAL_LEVEL }

    /** @see [BatteryRepository.isStateUnknown] for docs. The battery cannot be detected */
    val isStateUnknown = repo.isStateUnknown
@@ -69,8 +77,14 @@ class BatteryInteractor @Inject constructor(repo: BatteryRepository) {
     * This flow can be used to canonically describe the battery state charging state.
     */
    val batteryAttributionType =
        combine(isCharging, powerSave, isBatteryDefenderEnabled) { charging, powerSave, defend ->
            if (powerSave) {
        combine(isCharging, powerSave, isBatteryDefenderEnabled, isStateUnknown) {
            charging,
            powerSave,
            defend,
            unknown ->
            if (unknown) {
                BatteryAttributionModel.Unknown
            } else if (powerSave) {
                BatteryAttributionModel.PowerSave
            } else if (defend) {
                BatteryAttributionModel.Defend
@@ -88,7 +102,7 @@ class BatteryInteractor @Inject constructor(repo: BatteryRepository) {
        /** Level below which we consider to be critically low */
        private const val CRITICAL_LEVEL = 20

        fun isBatteryFull(level: Int) = level >= 100
        fun isBatteryFull(level: Int?) = level != null && level >= 100
    }
}

@@ -97,4 +111,5 @@ enum class BatteryAttributionModel {
    Defend,
    PowerSave,
    Charging,
    Unknown,
}
+3 −0
Original line number Diff line number Diff line
@@ -75,6 +75,9 @@ object BatteryFrame {

    /** The height of the drawable that is usable for inside elements */
    const val innerHeight = 13f

    /** Corner radius for the battery body */
    const val cornerRadius = 4f
}

/**
+12 −0
Original line number Diff line number Diff line
@@ -134,6 +134,18 @@ sealed interface BatteryGlyph : Glyph {
        override val height: Float = 9.00f
    }

    data object Question : BatteryGlyph {
        override val path: Path =
            Path().apply {
                addSvg(
                    "M2.85,6.438C2.591,6.438 2.363,6.356 2.167,6.193C1.975,6.025 1.879,5.823 1.879,5.588V5.557C1.879,5.209 1.958,4.911 2.117,4.663C2.276,4.416 2.545,4.143 2.925,3.845C3.276,3.572 3.537,3.346 3.708,3.166C3.883,2.985 3.971,2.792 3.971,2.587C3.971,2.31 3.869,2.091 3.664,1.932C3.464,1.768 3.188,1.687 2.837,1.687C2.616,1.687 2.418,1.722 2.242,1.794C2.067,1.865 1.919,1.961 1.798,2.083C1.677,2.205 1.568,2.322 1.472,2.435C1.38,2.545 1.242,2.62 1.059,2.662C0.879,2.7 0.687,2.67 0.482,2.574C0.282,2.477 0.14,2.32 0.057,2.102C-0.023,1.884 -0.019,1.668 0.069,1.454C0.161,1.24 0.34,1.015 0.608,0.78C0.879,0.541 1.207,0.352 1.591,0.214C1.975,0.071 2.426,0 2.944,0C3.866,0 4.605,0.231 5.161,0.692C5.72,1.15 6,1.739 6,2.461C6,2.897 5.889,3.287 5.668,3.631C5.451,3.971 5.098,4.315 4.61,4.663C4.326,4.869 4.127,5.049 4.015,5.205C3.902,5.36 3.835,5.546 3.814,5.765V5.777C3.781,5.945 3.679,6.098 3.507,6.237C3.336,6.371 3.117,6.438 2.85,6.438ZM2.837,10C2.495,10 2.205,9.885 1.967,9.654C1.733,9.423 1.616,9.14 1.616,8.804C1.616,8.477 1.733,8.2 1.967,7.974C2.205,7.747 2.495,7.634 2.837,7.634C3.18,7.634 3.47,7.747 3.708,7.974C3.95,8.2 4.071,8.477 4.071,8.804C4.071,9.14 3.952,9.423 3.714,9.654C3.476,9.885 3.184,10 2.837,10Z"
                )
            }

        override val width: Float = 6.00f
        override val height: Float = 10.00f
    }

    data object Zero : BatteryGlyph {
        override val path: Path =
            Path().apply {
+32 −28
Original line number Diff line number Diff line
@@ -68,7 +68,7 @@ fun BatteryCanvas(
    innerWidth: Float,
    innerHeight: Float,
    glyphs: List<BatteryGlyph>,
    level: Int,
    level: Int?,
    isFull: Boolean,
    colorsProvider: () -> BatteryColors,
    modifier: Modifier = Modifier,
@@ -110,6 +110,7 @@ fun BatteryCanvas(
                    }
                drawPath(path.path, bgColor)
                // Then draw the body, clipped to the fill level
                if (level != null && level > 0) {
                    clipRect(0f, 0f, level.scaledLevel(), innerHeight) {
                        drawRoundRect(
                            color = colors.fill,
@@ -119,6 +120,7 @@ fun BatteryCanvas(
                        )
                    }
                }
            }

            // Now draw the glyphs
            var horizontalOffset = (BatteryFrame.innerWidth - totalWidth) / 2
@@ -190,7 +192,7 @@ fun UnifiedBattery(
@Composable
fun BatteryLayout(
    attribution: BatteryGlyph?,
    levelProvider: () -> Int,
    levelProvider: () -> Int?,
    isFullProvider: () -> Boolean,
    glyphsProvider: () -> List<BatteryGlyph>,
    colorsProvider: () -> BatteryColors,
@@ -329,7 +331,7 @@ class BatteryMeasurePolicy : MeasurePolicy {
@Composable
fun BatteryBody(
    pathSpec: PathSpec,
    levelProvider: () -> Int,
    levelProvider: () -> Int?,
    glyphsProvider: () -> List<BatteryGlyph>,
    isFullProvider: () -> Boolean,
    colorsProvider: () -> BatteryColors,
@@ -368,14 +370,15 @@ fun BatteryBody(
                // 2. draw body
                drawPath(pathSpec.path, color)

                // 3. clip the fill to the level
                // 3. clip the fill to the level if we have it
                if (level != null && level > 0) {
                    clipRect(
                        left = 0f,
                        top = 0f,
                        right = level.scaledLevel(),
                        bottom = BatteryFrame.innerHeight,
                    ) {
                    // 4 Draw the rounded rect fill fully, it'll be clipped above
                        // 4. Draw the rounded rect fill fully, it'll be clipped above
                        drawRoundRect(
                            color = colors.fill,
                            topLeft = Offset.Zero,
@@ -384,10 +387,11 @@ fun BatteryBody(
                                    width = BatteryFrame.innerWidth,
                                    height = BatteryFrame.innerHeight,
                                ),
                        CornerRadius(x = 4f),
                            CornerRadius(x = BatteryFrame.cornerRadius),
                        )
                    }
                }
            }

            // Next: draw the glyphs
            var horizontalOffset = (BatteryFrame.innerWidth - totalGlyphWidth) / 2f
+21 −17
Original line number Diff line number Diff line
@@ -27,6 +27,7 @@ import com.android.systemui.res.R
import com.android.systemui.statusbar.pipeline.battery.domain.interactor.BatteryAttributionModel.Charging
import com.android.systemui.statusbar.pipeline.battery.domain.interactor.BatteryAttributionModel.Defend
import com.android.systemui.statusbar.pipeline.battery.domain.interactor.BatteryAttributionModel.PowerSave
import com.android.systemui.statusbar.pipeline.battery.domain.interactor.BatteryAttributionModel.Unknown
import com.android.systemui.statusbar.pipeline.battery.domain.interactor.BatteryInteractor
import com.android.systemui.statusbar.pipeline.battery.shared.ui.BatteryColors
import com.android.systemui.statusbar.pipeline.battery.shared.ui.BatteryFrame
@@ -79,7 +80,7 @@ sealed class BatteryViewModel(

    /** A [List<BatteryGlyph>] representation of the current [level] */
    private val levelGlyphs: Flow<List<BatteryGlyph>> =
        interactor.level.map { it.glyphRepresentation() }
        interactor.level.map { it?.glyphRepresentation() ?: emptyList() }

    private val _glyphList: Flow<List<BatteryGlyph>> =
        shouldShowPercent.flatMapLatest {
@@ -111,6 +112,8 @@ sealed class BatteryViewModel(

                Defend -> BatteryGlyph.Defend

                Unknown -> BatteryGlyph.Question

                else -> null
            }
        }
@@ -170,16 +173,9 @@ sealed class BatteryViewModel(
            traceName = "contentDescription",
            initialValue = ContentDescription.Loaded(null),
            source =
                combine(
                    interactor.batteryAttributionType,
                    interactor.isStateUnknown,
                    interactor.level,
                ) { attr, isUnknown, level ->
                    when {
                        isUnknown ->
                            ContentDescription.Resource(R.string.accessibility_battery_unknown)

                        attr == Defend -> {
                combine(interactor.batteryAttributionType, interactor.level) { attr, level ->
                    when (attr) {
                        Defend -> {
                            val descr =
                                context.getString(
                                    R.string.accessibility_battery_level_charging_paused,
@@ -188,8 +184,7 @@ sealed class BatteryViewModel(

                            ContentDescription.Loaded(descr)
                        }

                        attr == Charging -> {
                        Charging -> {
                            val descr =
                                context.getString(
                                    R.string.accessibility_battery_level_charging,
@@ -197,8 +192,7 @@ sealed class BatteryViewModel(
                                )
                            ContentDescription.Loaded(descr)
                        }

                        attr == PowerSave -> {
                        PowerSave -> {
                            val descr =
                                context.getString(
                                    R.string.accessibility_battery_level_battery_saver_with_percent,
@@ -206,7 +200,10 @@ sealed class BatteryViewModel(
                                )
                            ContentDescription.Loaded(descr)
                        }

                        Unknown -> {
                            val descr = context.getString(R.string.accessibility_battery_unknown)
                            ContentDescription.Loaded(descr)
                        }
                        else -> {
                            val descr =
                                context.getString(R.string.accessibility_battery_level, level)
@@ -338,7 +335,14 @@ constructor(interactor: BatteryInteractor, @Application context: Context) :
        hydrator.hydratedStateOf(
            traceName = "batteryPercent",
            initialValue = null,
            source = interactor.level.map { NumberFormat.getPercentInstance().format(it / 100f) },
            source =
                interactor.level.map { level ->
                    if (level == null) {
                        null
                    } else {
                        NumberFormat.getPercentInstance().format(level / 100f)
                    }
                },
        )

    private val _attributionAsList: Flow<List<BatteryGlyph>> =
Loading