1 /* <lambda>null2 * 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.BlendMode 23 import android.graphics.Canvas 24 import android.graphics.Paint 25 import android.util.AttributeSet 26 import android.view.View 27 import androidx.annotation.VisibleForTesting 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 paint = Paint() 48 @VisibleForTesting var turbulenceNoiseShader: TurbulenceNoiseShader? = null 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 turbulenceNoiseShader?.setColor(color) 65 } 66 67 /** Plays the turbulence noise with no easing. */ 68 @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) 69 fun play(onAnimationEnd: Runnable? = null) { 70 if (noiseConfig == null) { 71 return 72 } 73 val config = noiseConfig!! 74 val shader = turbulenceNoiseShader!! 75 76 val animator = ValueAnimator.ofFloat(0f, 1f) 77 animator.duration = config.maxDuration.toLong() 78 79 // Animation should start from the initial position to avoid abrupt transition. 80 val initialX = shader.noiseOffsetX 81 val initialY = shader.noiseOffsetY 82 val initialZ = shader.noiseOffsetZ 83 84 animator.addUpdateListener { updateListener -> 85 val timeInSec = updateListener.currentPlayTime * MS_TO_SEC 86 shader.setNoiseMove( 87 initialX + timeInSec * config.noiseMoveSpeedX, 88 initialY + timeInSec * config.noiseMoveSpeedY, 89 initialZ + timeInSec * config.noiseMoveSpeedZ 90 ) 91 92 shader.setOpacity(config.luminosityMultiplier) 93 94 invalidate() 95 } 96 97 animator.addListener( 98 object : AnimatorListenerAdapter() { 99 override fun onAnimationEnd(animation: Animator) { 100 currentAnimator = null 101 onAnimationEnd?.run() 102 } 103 } 104 ) 105 106 animator.start() 107 currentAnimator = animator 108 } 109 110 /** Plays the turbulence noise with linear ease-in. */ 111 @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) 112 fun playEaseIn(onAnimationEnd: Runnable? = null) { 113 if (noiseConfig == null) { 114 return 115 } 116 val config = noiseConfig!! 117 val shader = turbulenceNoiseShader!! 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 = shader.noiseOffsetX 124 val initialY = shader.noiseOffsetY 125 val initialZ = shader.noiseOffsetZ 126 127 animator.addUpdateListener { updateListener -> 128 val timeInSec = updateListener.currentPlayTime * MS_TO_SEC 129 val progress = updateListener.animatedValue as Float 130 131 shader.setNoiseMove( 132 initialX + timeInSec * config.noiseMoveSpeedX, 133 initialY + timeInSec * config.noiseMoveSpeedY, 134 initialZ + timeInSec * config.noiseMoveSpeedZ 135 ) 136 137 // TODO: Replace it with a better curve. 138 shader.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 val shader = turbulenceNoiseShader!! 164 165 val animator = ValueAnimator.ofFloat(0f, 1f) 166 animator.duration = config.easeOutDuration.toLong() 167 168 // Animation should start from the initial position to avoid abrupt transition. 169 val initialX = shader.noiseOffsetX 170 val initialY = shader.noiseOffsetY 171 val initialZ = shader.noiseOffsetZ 172 173 animator.addUpdateListener { updateListener -> 174 val timeInSec = updateListener.currentPlayTime * MS_TO_SEC 175 val progress = updateListener.animatedValue as Float 176 177 shader.setNoiseMove( 178 initialX + timeInSec * config.noiseMoveSpeedX, 179 initialY + timeInSec * config.noiseMoveSpeedY, 180 initialZ + timeInSec * config.noiseMoveSpeedZ 181 ) 182 183 // TODO: Replace it with a better curve. 184 shader.setOpacity((1f - progress) * config.luminosityMultiplier) 185 186 invalidate() 187 } 188 189 animator.addListener( 190 object : AnimatorListenerAdapter() { 191 override fun onAnimationEnd(animation: Animator) { 192 currentAnimator = null 193 onAnimationEnd?.run() 194 } 195 } 196 ) 197 198 animator.start() 199 currentAnimator = animator 200 } 201 202 /** Finishes the current animation if playing and plays the next animation if given. */ 203 @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) 204 fun finish(nextAnimation: Runnable? = null) { 205 // Calling Animator#end sets the animation state back to the initial state. Using pause to 206 // avoid visual artifacts. 207 currentAnimator?.pause() 208 currentAnimator = null 209 210 nextAnimation?.run() 211 } 212 213 /** Applies shader uniforms. Must be called before playing animation. */ 214 @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) 215 fun initShader( 216 baseType: TurbulenceNoiseShader.Companion.Type, 217 config: TurbulenceNoiseAnimationConfig 218 ) { 219 noiseConfig = config 220 if (turbulenceNoiseShader == null || turbulenceNoiseShader?.baseType != baseType) { 221 turbulenceNoiseShader = TurbulenceNoiseShader(baseType) 222 223 paint.shader = turbulenceNoiseShader!! 224 } 225 turbulenceNoiseShader!!.applyConfig(config) 226 } 227 228 /** Sets the blend mode of the View. */ 229 fun setBlendMode(blendMode: BlendMode) { 230 paint.blendMode = blendMode 231 } 232 233 internal fun clearConfig() { 234 noiseConfig = null 235 } 236 } 237