• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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