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 }