1 /*
2  * Copyright (C) 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.systemui.battery.unified
18 
19 import android.graphics.Canvas
20 import android.graphics.ColorFilter
21 import android.graphics.PixelFormat
22 import android.graphics.Rect
23 import android.graphics.drawable.Drawable
24 import android.graphics.drawable.DrawableWrapper
25 import android.view.Gravity
26 import kotlin.math.ceil
27 import kotlin.math.min
28 import kotlin.math.roundToInt
29 
30 /**
31  * A battery attribution is defined as a drawable that can display either alongside the percent text
32  * or solely in the center of the battery frame.
33  *
34  * Attributions are given an explicit canvas of 18x8, or 6x6 depending on the display mode (centered
35  * or right-aligned). The size is configured in [BatteryLayersDrawable] by changing this drawable
36  * wrapper's bounds, and optionally setting the [gravity]
37  */
38 @Suppress("RtlHardcoded")
39 class BatteryAttributionDrawable(dr: Drawable?) : DrawableWrapper(dr) {
40     /** One of [CENTER, LEFT]. Note that number text does not RTL. */
41     var gravity = Gravity.CENTER
42         set(value) {
43             field = value
44             updateBoundsInner()
45         }
46 
47     // Must be called if bounds change, gravity changes, or the wrapped drawable changes
updateBoundsInnernull48     private fun updateBoundsInner() {
49         val dr = drawable ?: return
50 
51         val hScale = bounds.width().toFloat() / dr.intrinsicWidth.toFloat()
52         val vScale = bounds.height().toFloat() / dr.intrinsicHeight.toFloat()
53         val scale = min(hScale, vScale)
54 
55         val dw = scale * dr.intrinsicWidth
56         val dh = scale * dr.intrinsicHeight
57 
58         if (gravity == Gravity.CENTER) {
59             val padLeft = (bounds.width() - dw) / 2
60             val padTop = (bounds.height() - dh) / 2
61             dr.setBounds(
62                 (bounds.left + padLeft).roundToInt(),
63                 (bounds.top + padTop).roundToInt(),
64                 (bounds.left + padLeft + dw).roundToInt(),
65                 (bounds.top + padTop + dh).roundToInt()
66             )
67         } else if (gravity == Gravity.LEFT) {
68             dr.setBounds(
69                 bounds.left,
70                 bounds.top,
71                 ceil(bounds.left + dw).toInt(),
72                 ceil(bounds.top + dh).toInt()
73             )
74         }
75     }
76 
setDrawablenull77     override fun setDrawable(dr: Drawable?) {
78         super.setDrawable(dr)
79         updateBoundsInner()
80     }
81 
onBoundsChangenull82     override fun onBoundsChange(bounds: Rect) {
83         updateBoundsInner()
84     }
85 
86     /**
87      * DrawableWrapper allows for a null constructor, but this method assumes that the drawable is
88      * non-null. It is called by LayerDrawable on init, so we have to handle null here specifically
89      */
getChangingConfigurationsnull90     override fun getChangingConfigurations(): Int = drawable?.changingConfigurations ?: 0
91 
92     override fun draw(canvas: Canvas) {
93         drawable?.draw(canvas)
94     }
95 
96     // Deprecated, but needed for Drawable implementation
getOpacitynull97     override fun getOpacity() = PixelFormat.OPAQUE
98 
99     // We don't use this
100     override fun setAlpha(alpha: Int) {}
101 
setColorFilternull102     override fun setColorFilter(colorFilter: ColorFilter?) {}
103 }
104