• 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.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