1 /*
2  * Copyright (C) 2020 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.media
18 
19 import android.animation.Animator
20 import android.animation.AnimatorListenerAdapter
21 import android.animation.ValueAnimator
22 import android.content.res.ColorStateList
23 import android.content.res.Resources
24 import android.content.res.TypedArray
25 import android.graphics.Canvas
26 import android.graphics.Color
27 import android.graphics.ColorFilter
28 import android.graphics.Outline
29 import android.graphics.Paint
30 import android.graphics.PixelFormat
31 import android.graphics.drawable.Drawable
32 import android.util.AttributeSet
33 import android.util.MathUtils
34 import android.view.View
35 import androidx.annotation.Keep
36 import com.android.internal.graphics.ColorUtils
37 import com.android.internal.graphics.ColorUtils.blendARGB
38 import com.android.systemui.Interpolators
39 import com.android.systemui.R
40 import org.xmlpull.v1.XmlPullParser
41 
42 private const val BACKGROUND_ANIM_DURATION = 370L
43 
44 /**
45  * Drawable that can draw an animated gradient when tapped.
46  */
47 @Keep
48 class IlluminationDrawable : Drawable() {
49 
50     private var themeAttrs: IntArray? = null
51     private var cornerRadius = 0f
52     private var highlightColor = Color.TRANSPARENT
53     private var tmpHsl = floatArrayOf(0f, 0f, 0f)
54     private var paint = Paint()
55     private var highlight = 0f
56     private val lightSources = arrayListOf<LightSourceDrawable>()
57 
58     private var backgroundColor = Color.TRANSPARENT
59     set(value) {
60         if (value == field) {
61             return
62         }
63         field = value
64         animateBackground()
65     }
66 
67     private var backgroundAnimation: ValueAnimator? = null
68 
69     /**
70      * Draw background and gradient.
71      */
drawnull72     override fun draw(canvas: Canvas) {
73         canvas.drawRoundRect(0f, 0f, bounds.width().toFloat(), bounds.height().toFloat(),
74                 cornerRadius, cornerRadius, paint)
75     }
76 
getOutlinenull77     override fun getOutline(outline: Outline) {
78         outline.setRoundRect(bounds, cornerRadius)
79     }
80 
getOpacitynull81     override fun getOpacity(): Int {
82         return PixelFormat.TRANSPARENT
83     }
84 
inflatenull85     override fun inflate(
86         r: Resources,
87         parser: XmlPullParser,
88         attrs: AttributeSet,
89         theme: Resources.Theme?
90     ) {
91         val a = obtainAttributes(r, theme, attrs, R.styleable.IlluminationDrawable)
92         themeAttrs = a.extractThemeAttrs()
93         updateStateFromTypedArray(a)
94         a.recycle()
95     }
96 
updateStateFromTypedArraynull97     private fun updateStateFromTypedArray(a: TypedArray) {
98         if (a.hasValue(R.styleable.IlluminationDrawable_cornerRadius)) {
99             cornerRadius = a.getDimension(R.styleable.IlluminationDrawable_cornerRadius,
100                     cornerRadius)
101         }
102         if (a.hasValue(R.styleable.IlluminationDrawable_highlight)) {
103             highlight = a.getInteger(R.styleable.IlluminationDrawable_highlight, 0) /
104                     100f
105         }
106     }
107 
canApplyThemenull108     override fun canApplyTheme(): Boolean {
109         return themeAttrs != null && themeAttrs!!.size > 0 || super.canApplyTheme()
110     }
111 
applyThemenull112     override fun applyTheme(t: Resources.Theme) {
113         super.applyTheme(t)
114         themeAttrs?.let {
115             val a = t.resolveAttributes(it, R.styleable.IlluminationDrawable)
116             updateStateFromTypedArray(a)
117             a.recycle()
118         }
119     }
120 
setColorFilternull121     override fun setColorFilter(p0: ColorFilter?) {
122         throw UnsupportedOperationException("Color filters are not supported")
123     }
124 
setAlphanull125     override fun setAlpha(value: Int) {
126         throw UnsupportedOperationException("Alpha is not supported")
127     }
128 
129     /**
130      * Cross fade background.
131      * @see setTintList
132      * @see backgroundColor
133      */
animateBackgroundnull134     private fun animateBackground() {
135         ColorUtils.colorToHSL(backgroundColor, tmpHsl)
136         val L = tmpHsl[2]
137         tmpHsl[2] = MathUtils.constrain(if (L < 1f - highlight) {
138             L + highlight
139         } else {
140             L - highlight
141         }, 0f, 1f)
142 
143         val initialBackground = paint.color
144         val initialHighlight = highlightColor
145         val finalHighlight = ColorUtils.HSLToColor(tmpHsl)
146 
147         backgroundAnimation?.cancel()
148         backgroundAnimation = ValueAnimator.ofFloat(0f, 1f).apply {
149             duration = BACKGROUND_ANIM_DURATION
150             interpolator = Interpolators.FAST_OUT_LINEAR_IN
151             addUpdateListener {
152                 val progress = it.animatedValue as Float
153                 paint.color = blendARGB(initialBackground, backgroundColor, progress)
154                 highlightColor = blendARGB(initialHighlight, finalHighlight, progress)
155                 lightSources.forEach { it.highlightColor = highlightColor }
156                 invalidateSelf()
157             }
158             addListener(object : AnimatorListenerAdapter() {
159                 override fun onAnimationEnd(animation: Animator?) {
160                     backgroundAnimation = null
161                 }
162             })
163             start()
164         }
165     }
166 
setTintListnull167     override fun setTintList(tint: ColorStateList?) {
168         super.setTintList(tint)
169         backgroundColor = tint!!.defaultColor
170     }
171 
registerLightSourcenull172     fun registerLightSource(lightSource: View) {
173         if (lightSource.background is LightSourceDrawable) {
174             lightSources.add(lightSource.background as LightSourceDrawable)
175         } else if (lightSource.foreground is LightSourceDrawable) {
176             lightSources.add(lightSource.foreground as LightSourceDrawable)
177         }
178     }
179 }