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