1 /* 2 * Copyright (C) 2022 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 package com.android.systemui.surfaceeffects.turbulencenoise 17 18 import android.animation.Animator 19 import android.animation.AnimatorListenerAdapter 20 import android.animation.ValueAnimator 21 import android.content.Context 22 import android.graphics.Canvas 23 import android.graphics.Paint 24 import android.util.AttributeSet 25 import android.view.View 26 import androidx.annotation.VisibleForTesting 27 import androidx.core.graphics.ColorUtils 28 29 /** 30 * View that renders turbulence noise effect. 31 * 32 * <p>Use [TurbulenceNoiseController] to control the turbulence animation. If you want to make some 33 * other turbulence noise effects, either add functionality to [TurbulenceNoiseController] or create 34 * another controller instead of extend or modify the [TurbulenceNoiseView]. 35 * 36 * <p>Please keep the [TurbulenceNoiseView] (or View in general) not aware of the state. 37 * 38 * <p>Please avoid inheriting the View if possible. Instead, reconsider adding a controller for a 39 * new case. 40 */ 41 class TurbulenceNoiseView(context: Context?, attrs: AttributeSet?) : View(context, attrs) { 42 43 companion object { 44 private const val MS_TO_SEC = 0.001f 45 } 46 47 private val turbulenceNoiseShader = TurbulenceNoiseShader() 48 private val paint = Paint().apply { this.shader = turbulenceNoiseShader } 49 @VisibleForTesting var noiseConfig: TurbulenceNoiseAnimationConfig? = null 50 @VisibleForTesting var currentAnimator: ValueAnimator? = null 51 52 override fun onDraw(canvas: Canvas) { 53 if (!canvas.isHardwareAccelerated) { 54 // Drawing with the turbulence noise shader requires hardware acceleration, so skip 55 // if it's unsupported. 56 return 57 } 58 59 canvas.drawPaint(paint) 60 } 61 62 /** Updates the color during the animation. No-op if there's no animation playing. */ 63 internal fun updateColor(color: Int) { 64 noiseConfig?.let { 65 turbulenceNoiseShader.setColor(ColorUtils.setAlphaComponent(color, it.opacity)) 66 } 67 } 68 69 /** Plays the turbulence noise with no easing. */ 70 @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) 71 fun play(onAnimationEnd: Runnable? = null) { 72 if (noiseConfig == null) { 73 return 74 } 75 val config = noiseConfig!! 76 77 val animator = ValueAnimator.ofFloat(0f, 1f) 78 animator.duration = config.maxDuration.toLong() 79 80 // Animation should start from the initial position to avoid abrupt transition. 81 val initialX = turbulenceNoiseShader.noiseOffsetX 82 val initialY = turbulenceNoiseShader.noiseOffsetY 83 val initialZ = turbulenceNoiseShader.noiseOffsetZ 84 85 animator.addUpdateListener { updateListener -> 86 val timeInSec = updateListener.currentPlayTime * MS_TO_SEC 87 turbulenceNoiseShader.setNoiseMove( 88 initialX + timeInSec * config.noiseMoveSpeedX, 89 initialY + timeInSec * config.noiseMoveSpeedY, 90 initialZ + timeInSec * config.noiseMoveSpeedZ 91 ) 92 93 turbulenceNoiseShader.setOpacity(config.luminosityMultiplier) 94 95 invalidate() 96 } 97 98 animator.addListener( 99 object : AnimatorListenerAdapter() { 100 override fun onAnimationEnd(animation: Animator) { 101 currentAnimator = null 102 onAnimationEnd?.run() 103 } 104 } 105 ) 106 107 animator.start() 108 currentAnimator = animator 109 } 110 111 /** Plays the turbulence noise with linear ease-in. */ 112 @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) 113 fun playEaseIn(offsetX: Float = 0f, offsetY: Float = 0f, onAnimationEnd: Runnable? = null) { 114 if (noiseConfig == null) { 115 return 116 } 117 val config = noiseConfig!! 118 119 val animator = ValueAnimator.ofFloat(0f, 1f) 120 animator.duration = config.easeInDuration.toLong() 121 122 // Animation should start from the initial position to avoid abrupt transition. 123 val initialX = turbulenceNoiseShader.noiseOffsetX 124 val initialY = turbulenceNoiseShader.noiseOffsetY 125 val initialZ = turbulenceNoiseShader.noiseOffsetZ 126 127 animator.addUpdateListener { updateListener -> 128 val timeInSec = updateListener.currentPlayTime * MS_TO_SEC 129 val progress = updateListener.animatedValue as Float 130 131 turbulenceNoiseShader.setNoiseMove( 132 offsetX + initialX + timeInSec * config.noiseMoveSpeedX, 133 offsetY + initialY + timeInSec * config.noiseMoveSpeedY, 134 initialZ + timeInSec * config.noiseMoveSpeedZ 135 ) 136 137 // TODO: Replace it with a better curve. 138 turbulenceNoiseShader.setOpacity(progress * config.luminosityMultiplier) 139 140 invalidate() 141 } 142 143 animator.addListener( 144 object : AnimatorListenerAdapter() { 145 override fun onAnimationEnd(animation: Animator) { 146 currentAnimator = null 147 onAnimationEnd?.run() 148 } 149 } 150 ) 151 152 animator.start() 153 currentAnimator = animator 154 } 155 156 /** Plays the turbulence noise with linear ease-out. */ 157 @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) 158 fun playEaseOut(onAnimationEnd: Runnable? = null) { 159 if (noiseConfig == null) { 160 return 161 } 162 val config = noiseConfig!! 163 164 val animator = ValueAnimator.ofFloat(0f, 1f) 165 animator.duration = config.easeOutDuration.toLong() 166 167 // Animation should start from the initial position to avoid abrupt transition. 168 val initialX = turbulenceNoiseShader.noiseOffsetX 169 val initialY = turbulenceNoiseShader.noiseOffsetY 170 val initialZ = turbulenceNoiseShader.noiseOffsetZ 171 172 animator.addUpdateListener { updateListener -> 173 val timeInSec = updateListener.currentPlayTime * MS_TO_SEC 174 val progress = updateListener.animatedValue as Float 175 176 turbulenceNoiseShader.setNoiseMove( 177 initialX + timeInSec * config.noiseMoveSpeedX, 178 initialY + timeInSec * config.noiseMoveSpeedY, 179 initialZ + timeInSec * config.noiseMoveSpeedZ 180 ) 181 182 // TODO: Replace it with a better curve. 183 turbulenceNoiseShader.setOpacity((1f - progress) * config.luminosityMultiplier) 184 185 invalidate() 186 } 187 188 animator.addListener( 189 object : AnimatorListenerAdapter() { 190 override fun onAnimationEnd(animation: Animator) { 191 currentAnimator = null 192 onAnimationEnd?.run() 193 } 194 } 195 ) 196 197 animator.start() 198 currentAnimator = animator 199 } 200 201 /** Finishes the current animation if playing and plays the next animation if given. */ 202 @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) 203 fun finish(nextAnimation: Runnable? = null) { 204 // Calling Animator#end sets the animation state back to the initial state. Using pause to 205 // avoid visual artifacts. 206 currentAnimator?.pause() 207 currentAnimator = null 208 209 nextAnimation?.run() 210 } 211 212 /** Applies shader uniforms. Must be called before playing animation. */ 213 @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) 214 fun applyConfig(config: TurbulenceNoiseAnimationConfig) { 215 noiseConfig = config 216 with(turbulenceNoiseShader) { 217 setGridCount(config.gridCount) 218 setColor(config.color) 219 setBackgroundColor(config.backgroundColor) 220 setSize(config.width, config.height) 221 setPixelDensity(config.pixelDensity) 222 setInverseNoiseLuminosity(inverse = false) 223 setLumaMatteFactors(config.lumaMatteBlendFactor, config.lumaMatteOverallBrightness) 224 } 225 paint.blendMode = config.blendMode 226 } 227 228 internal fun clearConfig() { 229 noiseConfig = null 230 } 231 } 232