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 }