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

Commit 87971425 authored by Caitlin Shkuratov's avatar Caitlin Shkuratov Committed by Android (Google) Code Review
Browse files

Merge changes from topic "caitlinshk-dock-defend-initial" into tm-qpr-dev

* changes:
  [Dock Defend] Cache the density in the drawable so we don't re-fetch it constantly.
  [Dock Defend] Udpate the content description to account for dock defend if needed.
  [Battery] Ensure we always update the text and content description when necessary.
  [Dock Defend] Ensure the bottom of the battery icon is always aligned with the bottom of the other icons, even if the shield is displayed.
  [Dock Defend] Add an optional shield to the status bar battery drawable.
parents 2238b567 c181e3d0
Loading
Loading
Loading
Loading
+3 −4
Original line number Diff line number Diff line
@@ -412,14 +412,13 @@ open class ThemedBatteryDrawable(private val context: Context, frameColor: Int)
    }

    companion object {
        private const val TAG = "ThemedBatteryDrawable"
        private const val WIDTH = 12f
        private const val HEIGHT = 20f
        const val WIDTH = 12f
        const val HEIGHT = 20f
        private const val CRITICAL_LEVEL = 15
        // On a 12x20 grid, how wide to make the fill protection stroke.
        // Scales when our size changes
        private const val PROTECTION_STROKE_WIDTH = 3f
        // Arbitrarily chosen for visibility at small sizes
        private const val PROTECTION_MIN_STROKE_WIDTH = 6f
        const val PROTECTION_MIN_STROKE_WIDTH = 6f
    }
}
+6 −0
Original line number Diff line number Diff line
@@ -485,6 +485,12 @@
    <!-- Whether to show a severe low battery dialog. -->
    <bool name="config_severe_battery_dialog">false</bool>

    <!-- A path representing a shield. Will sometimes be displayed with the battery icon when
         needed. This path is a 10px wide and 13px tall. -->
    <string name="config_batterymeterShieldPath" translatable="false">
        M5 0L0 1.88V6.19C0 9.35 2.13 12.29 5 13.01C7.87 12.29 10 9.35 10 6.19V1.88L5 0Z
    </string>

    <!-- A path similar to frameworks/base/core/res/res/values/config.xml
      config_mainBuiltInDisplayCutout that describes a path larger than the exact path of a display
      cutout. If present as well as config_enableDisplayCutoutProtection is set to true, then
+6 −0
Original line number Diff line number Diff line
@@ -105,6 +105,12 @@
    so the width of the icon should be 13.0dp * (12.0 / 20.0) -->
    <dimen name="status_bar_battery_icon_width">7.8dp</dimen>

    <!-- The battery icon is 13dp tall, but the other system icons are 15dp tall (see
         @*android:dimen/status_bar_system_icon_size) with some top and bottom padding embedded in
         the drawables themselves. So, the battery icon may need an extra 1dp of spacing so that its
         bottom still aligns with the bottom of all the other system icons. See b/258672854. -->
    <dimen name="status_bar_battery_extra_vertical_spacing">1dp</dimen>

    <!-- The font size for the clock in the status bar. -->
    <dimen name="status_bar_clock_size">14sp</dimen>

+7 −1
Original line number Diff line number Diff line
@@ -439,11 +439,17 @@
    <string name="accessibility_battery_level">Battery <xliff:g id="number">%d</xliff:g> percent.</string>

    <!-- Content description of the battery level icon for accessibility, including the estimated time remaining before the phone runs out of battery (not shown on the screen). [CHAR LIMIT=NONE] -->
    <string name="accessibility_battery_level_with_estimate">Battery <xliff:g id="percentage" example="95%">%1$s</xliff:g> percent, about <xliff:g id="time" example="Until 3:15pm">%2$s</xliff:g> left based on your usage</string>
    <string name="accessibility_battery_level_with_estimate">Battery <xliff:g id="percentage" example="95%">%1$d</xliff:g> percent, about <xliff:g id="time" example="Until 3:15pm">%2$s</xliff:g> left based on your usage</string>

    <!-- Content description of the battery level icon for accessibility while the device is charging (not shown on the screen). [CHAR LIMIT=NONE] -->
    <string name="accessibility_battery_level_charging">Battery charging, <xliff:g id="battery_percentage">%d</xliff:g> percent.</string>

    <!-- Content description of the battery level icon for accessibility, with information that the device charging is paused in order to protect the lifetime of the battery (not shown on screen). [CHAR LIMIT=NONE] -->
    <string name="accessibility_battery_level_charging_paused">Battery <xliff:g id="percentage" example="90%">%d</xliff:g> percent. Charging paused for battery protection.</string>

    <!-- Content description of the battery level icon for accessibility, including the estimated time remaining before the phone runs out of battery *and* information that the device charging is paused in order to protect the lifetime of the battery (not shown on screen). [CHAR LIMIT=NONE] -->
    <string name="accessibility_battery_level_charging_paused_with_estimate">Battery <xliff:g id="percentage" example="90%">%1$d</xliff:g> percent, about <xliff:g id="time" example="Until 3:15pm">%2$s</xliff:g> left based on your usage. Charging paused for battery protection.</string>

    <!-- Content description of overflow icon container of the notifications for accessibility (not shown on the screen)[CHAR LIMIT=NONE] -->
    <string name="accessibility_overflow_action">See all notifications</string>

+207 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 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.battery

import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.ColorFilter
import android.graphics.Matrix
import android.graphics.Paint
import android.graphics.Path
import android.graphics.PixelFormat
import android.graphics.PorterDuff
import android.graphics.PorterDuffXfermode
import android.graphics.Rect
import android.graphics.drawable.DrawableWrapper
import android.util.PathParser
import com.android.settingslib.graph.ThemedBatteryDrawable
import com.android.systemui.R
import com.android.systemui.battery.BatterySpecs.BATTERY_HEIGHT
import com.android.systemui.battery.BatterySpecs.BATTERY_HEIGHT_WITH_SHIELD
import com.android.systemui.battery.BatterySpecs.BATTERY_WIDTH
import com.android.systemui.battery.BatterySpecs.BATTERY_WIDTH_WITH_SHIELD
import com.android.systemui.battery.BatterySpecs.SHIELD_LEFT_OFFSET
import com.android.systemui.battery.BatterySpecs.SHIELD_STROKE
import com.android.systemui.battery.BatterySpecs.SHIELD_TOP_OFFSET

/**
 * A battery drawable that accessorizes [ThemedBatteryDrawable] with additional information if
 * necessary.
 *
 * For now, it adds a shield in the bottom-right corner when [displayShield] is true.
 */
class AccessorizedBatteryDrawable(
    private val context: Context,
    frameColor: Int,
) : DrawableWrapper(ThemedBatteryDrawable(context, frameColor)) {
    private val mainBatteryDrawable: ThemedBatteryDrawable
        get() = drawable as ThemedBatteryDrawable

    private val shieldPath = Path()
    private val scaledShield = Path()
    private val scaleMatrix = Matrix()

    private var shieldLeftOffsetScaled = SHIELD_LEFT_OFFSET
    private var shieldTopOffsetScaled = SHIELD_TOP_OFFSET

    private var density = context.resources.displayMetrics.density

    private val dualTone =
        context.resources.getBoolean(com.android.internal.R.bool.config_batterymeterDualTone)

    private val shieldTransparentOutlinePaint =
        Paint(Paint.ANTI_ALIAS_FLAG).also { p ->
            p.color = Color.TRANSPARENT
            p.strokeWidth = ThemedBatteryDrawable.PROTECTION_MIN_STROKE_WIDTH
            p.xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_IN)
            p.style = Paint.Style.FILL_AND_STROKE
        }

    private val shieldPaint =
        Paint(Paint.ANTI_ALIAS_FLAG).also { p ->
            p.color = Color.MAGENTA
            p.style = Paint.Style.FILL
            p.isDither = true
        }

    init {
        loadPaths()
    }

    override fun onBoundsChange(bounds: Rect) {
        super.onBoundsChange(bounds)
        updateSizes()
    }

    var displayShield: Boolean = false

    private fun updateSizes() {
        val b = bounds
        if (b.isEmpty) {
            return
        }

        val mainWidth = BatterySpecs.getMainBatteryWidth(b.width().toFloat(), displayShield)
        val mainHeight = BatterySpecs.getMainBatteryHeight(b.height().toFloat(), displayShield)

        drawable?.setBounds(
            b.left,
            b.top,
            /* right= */ b.left + mainWidth.toInt(),
            /* bottom= */ b.top + mainHeight.toInt()
        )

        if (displayShield) {
            val sx = b.right / BATTERY_WIDTH_WITH_SHIELD
            val sy = b.bottom / BATTERY_HEIGHT_WITH_SHIELD
            scaleMatrix.setScale(sx, sy)
            shieldPath.transform(scaleMatrix, scaledShield)

            shieldLeftOffsetScaled = sx * SHIELD_LEFT_OFFSET
            shieldTopOffsetScaled = sy * SHIELD_TOP_OFFSET

            val scaledStrokeWidth =
                (sx * SHIELD_STROKE).coerceAtLeast(
                    ThemedBatteryDrawable.PROTECTION_MIN_STROKE_WIDTH
                )
            shieldTransparentOutlinePaint.strokeWidth = scaledStrokeWidth
        }
    }

    override fun getIntrinsicHeight(): Int {
        val height =
            if (displayShield) {
                BATTERY_HEIGHT_WITH_SHIELD
            } else {
                BATTERY_HEIGHT
            }
        return (height * density).toInt()
    }

    override fun getIntrinsicWidth(): Int {
        val width =
            if (displayShield) {
                BATTERY_WIDTH_WITH_SHIELD
            } else {
                BATTERY_WIDTH
            }
        return (width * density).toInt()
    }

    override fun draw(c: Canvas) {
        c.saveLayer(null, null)
        // Draw the main battery icon
        super.draw(c)

        if (displayShield) {
            c.translate(shieldLeftOffsetScaled, shieldTopOffsetScaled)
            // We need a transparent outline around the shield, so first draw the transparent-ness
            // then draw the shield
            c.drawPath(scaledShield, shieldTransparentOutlinePaint)
            c.drawPath(scaledShield, shieldPaint)
        }
        c.restore()
    }

    override fun getOpacity(): Int {
        return PixelFormat.OPAQUE
    }

    override fun setAlpha(p0: Int) {
        // Unused internally -- see [ThemedBatteryDrawable.setAlpha].
    }

    override fun setColorFilter(colorfilter: ColorFilter?) {
        super.setColorFilter(colorFilter)
        shieldPaint.colorFilter = colorFilter
    }

    /** Sets whether the battery is currently charging. */
    fun setCharging(charging: Boolean) {
        mainBatteryDrawable.charging = charging
    }

    /** Sets the current level (out of 100) of the battery. */
    fun setBatteryLevel(level: Int) {
        mainBatteryDrawable.setBatteryLevel(level)
    }

    /** Sets whether power save is enabled. */
    fun setPowerSaveEnabled(powerSaveEnabled: Boolean) {
        mainBatteryDrawable.powerSaveEnabled = powerSaveEnabled
    }

    /** Returns whether power save is currently enabled. */
    fun getPowerSaveEnabled(): Boolean {
        return mainBatteryDrawable.powerSaveEnabled
    }

    /** Sets the colors to use for the icon. */
    fun setColors(fgColor: Int, bgColor: Int, singleToneColor: Int) {
        shieldPaint.color = if (dualTone) fgColor else singleToneColor
        mainBatteryDrawable.setColors(fgColor, bgColor, singleToneColor)
    }

    /** Notifies this drawable that the density might have changed. */
    fun notifyDensityChanged() {
        density = context.resources.displayMetrics.density
    }

    private fun loadPaths() {
        val shieldPathString = context.resources.getString(R.string.config_batterymeterShieldPath)
        shieldPath.set(PathParser.createPathFromPathData(shieldPathString))
    }
}
Loading