1 /* <lambda>null2 * Copyright (C) 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file 5 * except in compliance with the License. You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software distributed under the 10 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 11 * KIND, either express or implied. See the License for the specific language governing 12 * permissions and limitations under the License. 13 */ 14 15 package com.android.systemui.battery 16 17 import android.content.Context 18 import android.graphics.Canvas 19 import android.graphics.Color 20 import android.graphics.ColorFilter 21 import android.graphics.Matrix 22 import android.graphics.Paint 23 import android.graphics.Path 24 import android.graphics.PixelFormat 25 import android.graphics.PorterDuff 26 import android.graphics.PorterDuffXfermode 27 import android.graphics.Rect 28 import android.graphics.drawable.DrawableWrapper 29 import android.util.PathParser 30 import com.android.settingslib.graph.ThemedBatteryDrawable 31 import com.android.systemui.res.R 32 import com.android.systemui.battery.BatterySpecs.BATTERY_HEIGHT 33 import com.android.systemui.battery.BatterySpecs.BATTERY_HEIGHT_WITH_SHIELD 34 import com.android.systemui.battery.BatterySpecs.BATTERY_WIDTH 35 import com.android.systemui.battery.BatterySpecs.BATTERY_WIDTH_WITH_SHIELD 36 import com.android.systemui.battery.BatterySpecs.SHIELD_LEFT_OFFSET 37 import com.android.systemui.battery.BatterySpecs.SHIELD_STROKE 38 import com.android.systemui.battery.BatterySpecs.SHIELD_TOP_OFFSET 39 40 /** 41 * A battery drawable that accessorizes [ThemedBatteryDrawable] with additional information if 42 * necessary. 43 * 44 * For now, it adds a shield in the bottom-right corner when [displayShield] is true. 45 */ 46 class AccessorizedBatteryDrawable( 47 private val context: Context, 48 frameColor: Int, 49 ) : DrawableWrapper(ThemedBatteryDrawable(context, frameColor)) { 50 private val mainBatteryDrawable: ThemedBatteryDrawable 51 get() = drawable as ThemedBatteryDrawable 52 53 private val shieldPath = Path() 54 private val scaledShield = Path() 55 private val scaleMatrix = Matrix() 56 57 private var shieldLeftOffsetScaled = SHIELD_LEFT_OFFSET 58 private var shieldTopOffsetScaled = SHIELD_TOP_OFFSET 59 60 private var density = context.resources.displayMetrics.density 61 62 private val dualTone = 63 context.resources.getBoolean(com.android.internal.R.bool.config_batterymeterDualTone) 64 65 private val shieldTransparentOutlinePaint = 66 Paint(Paint.ANTI_ALIAS_FLAG).also { p -> 67 p.color = Color.TRANSPARENT 68 p.strokeWidth = ThemedBatteryDrawable.PROTECTION_MIN_STROKE_WIDTH 69 p.xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_IN) 70 p.style = Paint.Style.FILL_AND_STROKE 71 } 72 73 private val shieldPaint = 74 Paint(Paint.ANTI_ALIAS_FLAG).also { p -> 75 p.color = Color.MAGENTA 76 p.style = Paint.Style.FILL 77 p.isDither = true 78 } 79 80 init { 81 loadPaths() 82 } 83 84 override fun onBoundsChange(bounds: Rect) { 85 super.onBoundsChange(bounds) 86 updateSizes() 87 } 88 89 var displayShield: Boolean = false 90 set(value) { 91 field = value 92 postInvalidate() 93 } 94 95 private fun updateSizes() { 96 val b = bounds 97 if (b.isEmpty) { 98 return 99 } 100 101 val mainWidth = BatterySpecs.getMainBatteryWidth(b.width().toFloat(), displayShield) 102 val mainHeight = BatterySpecs.getMainBatteryHeight(b.height().toFloat(), displayShield) 103 104 drawable?.setBounds( 105 b.left, 106 b.top, 107 /* right= */ b.left + mainWidth.toInt(), 108 /* bottom= */ b.top + mainHeight.toInt() 109 ) 110 111 if (displayShield) { 112 val sx = b.right / BATTERY_WIDTH_WITH_SHIELD 113 val sy = b.bottom / BATTERY_HEIGHT_WITH_SHIELD 114 scaleMatrix.setScale(sx, sy) 115 shieldPath.transform(scaleMatrix, scaledShield) 116 117 shieldLeftOffsetScaled = sx * SHIELD_LEFT_OFFSET 118 shieldTopOffsetScaled = sy * SHIELD_TOP_OFFSET 119 120 val scaledStrokeWidth = 121 (sx * SHIELD_STROKE).coerceAtLeast( 122 ThemedBatteryDrawable.PROTECTION_MIN_STROKE_WIDTH 123 ) 124 shieldTransparentOutlinePaint.strokeWidth = scaledStrokeWidth 125 } 126 } 127 128 override fun getIntrinsicHeight(): Int { 129 val height = 130 if (displayShield) { 131 BATTERY_HEIGHT_WITH_SHIELD 132 } else { 133 BATTERY_HEIGHT 134 } 135 return (height * density).toInt() 136 } 137 138 override fun getIntrinsicWidth(): Int { 139 val width = 140 if (displayShield) { 141 BATTERY_WIDTH_WITH_SHIELD 142 } else { 143 BATTERY_WIDTH 144 } 145 return (width * density).toInt() 146 } 147 148 override fun draw(c: Canvas) { 149 c.saveLayer(null, null) 150 // Draw the main battery icon 151 super.draw(c) 152 153 if (displayShield) { 154 c.translate(shieldLeftOffsetScaled, shieldTopOffsetScaled) 155 // We need a transparent outline around the shield, so first draw the transparent-ness 156 // then draw the shield 157 c.drawPath(scaledShield, shieldTransparentOutlinePaint) 158 c.drawPath(scaledShield, shieldPaint) 159 } 160 c.restore() 161 } 162 163 override fun getOpacity(): Int { 164 return PixelFormat.OPAQUE 165 } 166 167 override fun setAlpha(p0: Int) { 168 // Unused internally -- see [ThemedBatteryDrawable.setAlpha]. 169 } 170 171 override fun setColorFilter(colorfilter: ColorFilter?) { 172 super.setColorFilter(colorFilter) 173 shieldPaint.colorFilter = colorFilter 174 } 175 176 /** Sets whether the battery is currently charging. */ 177 fun setCharging(charging: Boolean) { 178 mainBatteryDrawable.charging = charging 179 } 180 181 /** Returns whether the battery is currently charging. */ 182 fun getCharging(): Boolean { 183 return mainBatteryDrawable.charging 184 } 185 186 /** Sets the current level (out of 100) of the battery. */ 187 fun setBatteryLevel(level: Int) { 188 mainBatteryDrawable.setBatteryLevel(level) 189 } 190 191 /** Sets whether power save is enabled. */ 192 fun setPowerSaveEnabled(powerSaveEnabled: Boolean) { 193 mainBatteryDrawable.powerSaveEnabled = powerSaveEnabled 194 } 195 196 /** Returns whether power save is currently enabled. */ 197 fun getPowerSaveEnabled(): Boolean { 198 return mainBatteryDrawable.powerSaveEnabled 199 } 200 201 /** Sets the colors to use for the icon. */ 202 fun setColors(fgColor: Int, bgColor: Int, singleToneColor: Int) { 203 shieldPaint.color = if (dualTone) fgColor else singleToneColor 204 mainBatteryDrawable.setColors(fgColor, bgColor, singleToneColor) 205 } 206 207 /** Notifies this drawable that the density might have changed. */ 208 fun notifyDensityChanged() { 209 density = context.resources.displayMetrics.density 210 } 211 212 private fun loadPaths() { 213 val shieldPathString = context.resources.getString(R.string.config_batterymeterShieldPath) 214 shieldPath.set(PathParser.createPathFromPathData(shieldPathString)) 215 } 216 217 private val invalidateRunnable: () -> Unit = { invalidateSelf() } 218 219 private fun postInvalidate() { 220 unscheduleSelf(invalidateRunnable) 221 scheduleSelf(invalidateRunnable, 0) 222 } 223 } 224