/* * 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 set(value) { field = value postInvalidate() } 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 } /** Returns whether the battery is currently charging. */ fun getCharging(): Boolean { return mainBatteryDrawable.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)) } private val invalidateRunnable: () -> Unit = { invalidateSelf() } private fun postInvalidate() { unscheduleSelf(invalidateRunnable) scheduleSelf(invalidateRunnable, 0) } }