• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.Xfermode
32 import android.graphics.drawable.Drawable
33 import android.util.AttributeSet
34 import android.util.MathUtils
35 import android.view.View
36 import androidx.annotation.Keep
37 import com.android.internal.graphics.ColorUtils
38 import com.android.internal.graphics.ColorUtils.blendARGB
39 import com.android.systemui.R
40 import com.android.systemui.animation.Interpolators
41 import org.xmlpull.v1.XmlPullParser
42 
43 private const val BACKGROUND_ANIM_DURATION = 370L
44 
45 /**
46  * Drawable that can draw an animated gradient when tapped.
47  */
48 @Keep
49 class IlluminationDrawable : Drawable() {
50 
51     private var themeAttrs: IntArray? = null
52     private var cornerRadiusOverride = -1f
53     var cornerRadius = 0f
54     get() {
55         return if (cornerRadiusOverride >= 0) {
56             cornerRadiusOverride
57         } else {
58             field
59         }
60     }
61     private var highlightColor = Color.TRANSPARENT
62     private var tmpHsl = floatArrayOf(0f, 0f, 0f)
63     private var paint = Paint()
64     private var highlight = 0f
65     private val lightSources = arrayListOf<LightSourceDrawable>()
66 
67     private var backgroundColor = Color.TRANSPARENT
68     set(value) {
69         if (value == field) {
70             return
71         }
72         field = value
73         animateBackground()
74     }
75 
76     private var backgroundAnimation: ValueAnimator? = null
77 
78     /**
79      * Draw background and gradient.
80      */
drawnull81     override fun draw(canvas: Canvas) {
82         canvas.drawRoundRect(0f, 0f, bounds.width().toFloat(), bounds.height().toFloat(),
83                 cornerRadius, cornerRadius, paint)
84     }
85 
getOutlinenull86     override fun getOutline(outline: Outline) {
87         outline.setRoundRect(bounds, cornerRadius)
88     }
89 
getOpacitynull90     override fun getOpacity(): Int {
91         return PixelFormat.TRANSPARENT
92     }
93 
inflatenull94     override fun inflate(
95         r: Resources,
96         parser: XmlPullParser,
97         attrs: AttributeSet,
98         theme: Resources.Theme?
99     ) {
100         val a = obtainAttributes(r, theme, attrs, R.styleable.IlluminationDrawable)
101         themeAttrs = a.extractThemeAttrs()
102         updateStateFromTypedArray(a)
103         a.recycle()
104     }
105 
updateStateFromTypedArraynull106     private fun updateStateFromTypedArray(a: TypedArray) {
107         if (a.hasValue(R.styleable.IlluminationDrawable_cornerRadius)) {
108             cornerRadius = a.getDimension(R.styleable.IlluminationDrawable_cornerRadius,
109                     cornerRadius)
110         }
111         if (a.hasValue(R.styleable.IlluminationDrawable_highlight)) {
112             highlight = a.getInteger(R.styleable.IlluminationDrawable_highlight, 0) /
113                     100f
114         }
115     }
116 
canApplyThemenull117     override fun canApplyTheme(): Boolean {
118         return themeAttrs != null && themeAttrs!!.size > 0 || super.canApplyTheme()
119     }
120 
applyThemenull121     override fun applyTheme(t: Resources.Theme) {
122         super.applyTheme(t)
123         themeAttrs?.let {
124             val a = t.resolveAttributes(it, R.styleable.IlluminationDrawable)
125             updateStateFromTypedArray(a)
126             a.recycle()
127         }
128     }
129 
setColorFilternull130     override fun setColorFilter(p0: ColorFilter?) {
131         throw UnsupportedOperationException("Color filters are not supported")
132     }
133 
setAlphanull134     override fun setAlpha(alpha: Int) {
135         if (alpha == paint.alpha) {
136             return
137         }
138 
139         paint.alpha = alpha
140         invalidateSelf()
141 
142         lightSources.forEach { it.alpha = alpha }
143     }
144 
getAlphanull145     override fun getAlpha(): Int {
146         return paint.alpha
147     }
148 
setXfermodenull149     override fun setXfermode(mode: Xfermode?) {
150         if (mode == paint.xfermode) {
151             return
152         }
153 
154         paint.xfermode = mode
155         invalidateSelf()
156     }
157 
158     /**
159      * Cross fade background.
160      * @see setTintList
161      * @see backgroundColor
162      */
animateBackgroundnull163     private fun animateBackground() {
164         ColorUtils.colorToHSL(backgroundColor, tmpHsl)
165         val L = tmpHsl[2]
166         tmpHsl[2] = MathUtils.constrain(if (L < 1f - highlight) {
167             L + highlight
168         } else {
169             L - highlight
170         }, 0f, 1f)
171 
172         val initialBackground = paint.color
173         val initialHighlight = highlightColor
174         val finalHighlight = ColorUtils.HSLToColor(tmpHsl)
175 
176         backgroundAnimation?.cancel()
177         backgroundAnimation = ValueAnimator.ofFloat(0f, 1f).apply {
178             duration = BACKGROUND_ANIM_DURATION
179             interpolator = Interpolators.FAST_OUT_LINEAR_IN
180             addUpdateListener {
181                 val progress = it.animatedValue as Float
182                 paint.color = blendARGB(initialBackground, backgroundColor, progress)
183                 highlightColor = blendARGB(initialHighlight, finalHighlight, progress)
184                 lightSources.forEach { it.highlightColor = highlightColor }
185                 invalidateSelf()
186             }
187             addListener(object : AnimatorListenerAdapter() {
188                 override fun onAnimationEnd(animation: Animator?) {
189                     backgroundAnimation = null
190                 }
191             })
192             start()
193         }
194     }
195 
setTintListnull196     override fun setTintList(tint: ColorStateList?) {
197         super.setTintList(tint)
198         backgroundColor = tint!!.defaultColor
199     }
200 
registerLightSourcenull201     fun registerLightSource(lightSource: View) {
202         if (lightSource.background is LightSourceDrawable) {
203             registerLightSource(lightSource.background as LightSourceDrawable)
204         } else if (lightSource.foreground is LightSourceDrawable) {
205             registerLightSource(lightSource.foreground as LightSourceDrawable)
206         }
207     }
208 
registerLightSourcenull209     private fun registerLightSource(lightSource: LightSourceDrawable) {
210         lightSource.alpha = paint.alpha
211         lightSources.add(lightSource)
212     }
213 
214     /** Set or remove the corner radius override. This is typically set during animations. */
setCornerRadiusOverridenull215     fun setCornerRadiusOverride(cornerRadius: Float?) {
216         cornerRadiusOverride = cornerRadius ?: -1f
217     }
218 }