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.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 91 private fun updateSizes() { 92 val b = bounds 93 if (b.isEmpty) { 94 return 95 } 96 97 val mainWidth = BatterySpecs.getMainBatteryWidth(b.width().toFloat(), displayShield) 98 val mainHeight = BatterySpecs.getMainBatteryHeight(b.height().toFloat(), displayShield) 99 100 drawable?.setBounds( 101 b.left, 102 b.top, 103 /* right= */ b.left + mainWidth.toInt(), 104 /* bottom= */ b.top + mainHeight.toInt() 105 ) 106 107 if (displayShield) { 108 val sx = b.right / BATTERY_WIDTH_WITH_SHIELD 109 val sy = b.bottom / BATTERY_HEIGHT_WITH_SHIELD 110 scaleMatrix.setScale(sx, sy) 111 shieldPath.transform(scaleMatrix, scaledShield) 112 113 shieldLeftOffsetScaled = sx * SHIELD_LEFT_OFFSET 114 shieldTopOffsetScaled = sy * SHIELD_TOP_OFFSET 115 116 val scaledStrokeWidth = 117 (sx * SHIELD_STROKE).coerceAtLeast( 118 ThemedBatteryDrawable.PROTECTION_MIN_STROKE_WIDTH 119 ) 120 shieldTransparentOutlinePaint.strokeWidth = scaledStrokeWidth 121 } 122 } 123 124 override fun getIntrinsicHeight(): Int { 125 val height = 126 if (displayShield) { 127 BATTERY_HEIGHT_WITH_SHIELD 128 } else { 129 BATTERY_HEIGHT 130 } 131 return (height * density).toInt() 132 } 133 134 override fun getIntrinsicWidth(): Int { 135 val width = 136 if (displayShield) { 137 BATTERY_WIDTH_WITH_SHIELD 138 } else { 139 BATTERY_WIDTH 140 } 141 return (width * density).toInt() 142 } 143 144 override fun draw(c: Canvas) { 145 c.saveLayer(null, null) 146 // Draw the main battery icon 147 super.draw(c) 148 149 if (displayShield) { 150 c.translate(shieldLeftOffsetScaled, shieldTopOffsetScaled) 151 // We need a transparent outline around the shield, so first draw the transparent-ness 152 // then draw the shield 153 c.drawPath(scaledShield, shieldTransparentOutlinePaint) 154 c.drawPath(scaledShield, shieldPaint) 155 } 156 c.restore() 157 } 158 159 override fun getOpacity(): Int { 160 return PixelFormat.OPAQUE 161 } 162 163 override fun setAlpha(p0: Int) { 164 // Unused internally -- see [ThemedBatteryDrawable.setAlpha]. 165 } 166 167 override fun setColorFilter(colorfilter: ColorFilter?) { 168 super.setColorFilter(colorFilter) 169 shieldPaint.colorFilter = colorFilter 170 } 171 172 /** Sets whether the battery is currently charging. */ 173 fun setCharging(charging: Boolean) { 174 mainBatteryDrawable.charging = charging 175 } 176 177 /** Sets the current level (out of 100) of the battery. */ 178 fun setBatteryLevel(level: Int) { 179 mainBatteryDrawable.setBatteryLevel(level) 180 } 181 182 /** Sets whether power save is enabled. */ 183 fun setPowerSaveEnabled(powerSaveEnabled: Boolean) { 184 mainBatteryDrawable.powerSaveEnabled = powerSaveEnabled 185 } 186 187 /** Returns whether power save is currently enabled. */ 188 fun getPowerSaveEnabled(): Boolean { 189 return mainBatteryDrawable.powerSaveEnabled 190 } 191 192 /** Sets the colors to use for the icon. */ 193 fun setColors(fgColor: Int, bgColor: Int, singleToneColor: Int) { 194 shieldPaint.color = if (dualTone) fgColor else singleToneColor 195 mainBatteryDrawable.setColors(fgColor, bgColor, singleToneColor) 196 } 197 198 /** Notifies this drawable that the density might have changed. */ 199 fun notifyDensityChanged() { 200 density = context.resources.displayMetrics.density 201 } 202 203 private fun loadPaths() { 204 val shieldPathString = context.resources.getString(R.string.config_batterymeterShieldPath) 205 shieldPath.set(PathParser.createPathFromPathData(shieldPathString)) 206 } 207 } 208