1 /*
<lambda>null2  * Copyright (C) 2023 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.wallpaper.picker.customization.animation.view
17 
18 import android.animation.Animator
19 import android.animation.AnimatorListenerAdapter
20 import android.animation.TimeAnimator
21 import android.animation.ValueAnimator
22 import android.graphics.Color
23 import android.graphics.RenderEffect
24 import android.graphics.Shader
25 import android.view.View
26 import com.android.systemui.monet.ColorScheme
27 import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseShader
28 import com.android.wallpaper.picker.customization.animation.Interpolators
29 import com.android.wallpaper.picker.customization.animation.shader.CircularRevealShader
30 import com.android.wallpaper.picker.customization.animation.shader.CompositeLoadingShader
31 import com.android.wallpaper.picker.customization.animation.shader.SparkleShader
32 import kotlin.math.max
33 
34 /** Renders loading and reveal animation. */
35 // TODO (b/281878827): remove this and use loading animation in SystemUIShaderLib when available
36 class LoadingAnimation(
37     /** The view used to play the loading and reveal animation */
38     private val revealOverlay: View,
39     /** The type of reveal animation to play */
40     private val revealType: RevealType = RevealType.CIRCULAR,
41     /**
42      * Amount of time before the loading animation times out and plays reveal animation, null
43      * represents no time out
44      */
45     private val timeOutDuration: Long? = null
46 ) {
47 
48     /** Type representing the reveal animation to be played in [LoadingAnimation] */
49     enum class RevealType {
50         /**
51          * Reveal animation that reveals the views beneath with an expanding circle starting from
52          * the center, ending with the loading view hidden
53          */
54         CIRCULAR,
55         /**
56          * Reveal animation that fades out the animation effects on the loading view, leaving the
57          * original loading view visible
58          */
59         FADE
60     }
61 
62     private val pixelDensity = revealOverlay.resources.displayMetrics.density
63 
64     private val loadingShader = CompositeLoadingShader()
65     private val colorTurbulenceNoiseShader =
66         TurbulenceNoiseShader().apply {
67             setPixelDensity(pixelDensity)
68             setGridCount(NOISE_SIZE)
69             setOpacity(1f)
70             setInverseNoiseLuminosity(inverse = true)
71             setBackgroundColor(Color.BLACK)
72         }
73     private val sparkleShader =
74         SparkleShader().apply {
75             setPixelDensity(pixelDensity)
76             setGridCount(NOISE_SIZE)
77         }
78     private val revealShader = CircularRevealShader()
79 
80     // Do not set blur radius to 0. It causes a crash.
81     private var blurRadius: Float = MIN_BLUR_PX
82 
83     private var elapsedTime = 0L
84     private var transitionProgress = 0f
85     // Responsible for fade in and blur on start of the loading.
86     private var fadeInAnimator: ValueAnimator? = null
87     private var timeAnimator: TimeAnimator? = null
88     private var revealAnimator: ValueAnimator? = null
89 
90     private var animationState = AnimationState.IDLE
91 
92     private var blurEffect =
93         RenderEffect.createBlurEffect(
94             blurRadius * pixelDensity,
95             blurRadius * pixelDensity,
96             Shader.TileMode.CLAMP
97         )
98 
99     fun playLoadingAnimation(seed: Long? = null) {
100         if (
101             animationState == AnimationState.FADE_IN_PLAYING ||
102                 animationState == AnimationState.FADE_IN_PLAYED
103         )
104             return
105 
106         if (animationState == AnimationState.REVEAL_PLAYING) revealAnimator?.cancel()
107 
108         animationState = AnimationState.FADE_IN_PLAYING
109 
110         revealOverlay.visibility = View.VISIBLE
111 
112         elapsedTime = seed ?: (0L..10000L).random()
113 
114         fadeInAnimator?.removeAllListeners()
115         fadeInAnimator?.removeAllUpdateListeners()
116         fadeInAnimator?.cancel()
117         timeAnimator?.cancel()
118         revealAnimator?.removeAllListeners()
119         revealAnimator?.removeAllUpdateListeners()
120         revealAnimator?.cancel()
121 
122         fadeInAnimator =
123             ValueAnimator.ofFloat(transitionProgress, 1f).apply {
124                 duration = FADE_IN_DURATION_MS
125                 interpolator = Interpolators.STANDARD_DECELERATE
126                 addUpdateListener {
127                     transitionProgress = it.animatedValue as Float
128                     loadingShader.setAlpha(transitionProgress)
129                     // Match the timing with the fade animations.
130                     blurRadius = maxOf(MAX_BLUR_PX * transitionProgress, MIN_BLUR_PX)
131                 }
132                 addListener(
133                     object : AnimatorListenerAdapter() {
134                         override fun onAnimationEnd(animation: Animator) {
135                             animationState = AnimationState.FADE_IN_PLAYED
136                         }
137                     }
138                 )
139                 start()
140             }
141 
142         // Keep clouds moving until we finish loading
143         timeAnimator =
144             TimeAnimator().apply {
145                 setTimeListener { _, totalTime, deltaTime -> flushUniforms(totalTime, deltaTime) }
146                 start()
147             }
148     }
149 
150     fun playRevealAnimation() {
151         when (revealType) {
152             RevealType.CIRCULAR -> playCircularRevealAnimation()
153             RevealType.FADE -> playFadeRevealAnimation()
154         }
155     }
156 
157     private fun playCircularRevealAnimation() {
158         if (
159             animationState == AnimationState.REVEAL_PLAYING ||
160                 animationState == AnimationState.REVEAL_PLAYED ||
161                 animationState == AnimationState.FADE_OUT_PLAYING ||
162                 animationState == AnimationState.FADE_OUT_PLAYED
163         )
164             return
165 
166         if (animationState == AnimationState.FADE_IN_PLAYING) {
167             fadeInAnimator?.removeAllListeners()
168             fadeInAnimator?.removeAllUpdateListeners()
169             fadeInAnimator?.cancel()
170         }
171 
172         animationState = AnimationState.REVEAL_PLAYING
173 
174         revealOverlay.visibility = View.VISIBLE
175 
176         revealShader.setCenter(revealOverlay.width * 0.5f, revealOverlay.height * 0.5f)
177 
178         revealAnimator?.cancel()
179         revealAnimator =
180             ValueAnimator.ofFloat(0f, 1f).apply {
181                 duration = REVEAL_DURATION_MS
182                 interpolator = Interpolators.STANDARD
183 
184                 addUpdateListener {
185                     val progress = it.animatedValue as Float
186 
187                     // Draw a circle slightly larger than the screen. Need some offset due to large
188                     // blur radius.
189                     revealShader.setRadius(
190                         progress * max(revealOverlay.width, revealOverlay.height) * 2f
191                     )
192                     // Map [0,1] to [MAX, MIN].
193                     val blurAmount =
194                         (1f - progress) * (MAX_REVEAL_BLUR_AMOUNT - MIN_REVEAL_BLUR_AMOUNT) +
195                             MIN_REVEAL_BLUR_AMOUNT
196                     revealShader.setBlur(blurAmount)
197                 }
198 
199                 addListener(
200                     object : AnimatorListenerAdapter() {
201                         override fun onAnimationEnd(animation: Animator) {
202                             resetCircularRevealAnimation()
203                         }
204                     }
205                 )
206 
207                 start()
208             }
209     }
210 
211     private fun resetCircularRevealAnimation() {
212         animationState = AnimationState.REVEAL_PLAYED
213 
214         revealOverlay.setRenderEffect(null)
215         revealOverlay.visibility = View.INVISIBLE
216 
217         // Stop turbulence and reset everything.
218         timeAnimator?.cancel()
219         blurRadius = MIN_BLUR_PX
220         transitionProgress = 0f
221     }
222 
223     private fun playFadeRevealAnimation() {
224         if (
225             animationState == AnimationState.REVEAL_PLAYING ||
226                 animationState == AnimationState.REVEAL_PLAYED ||
227                 animationState == AnimationState.FADE_OUT_PLAYING ||
228                 animationState == AnimationState.FADE_OUT_PLAYED
229         )
230             return
231 
232         if (animationState == AnimationState.FADE_IN_PLAYING) {
233             fadeInAnimator?.removeAllListeners()
234             fadeInAnimator?.removeAllUpdateListeners()
235             fadeInAnimator?.cancel()
236         }
237 
238         animationState = AnimationState.FADE_OUT_PLAYING
239 
240         revealOverlay.visibility = View.VISIBLE
241 
242         fadeInAnimator =
243             ValueAnimator.ofFloat(transitionProgress, 0f).apply {
244                 duration = FADE_OUT_DURATION_MS
245                 interpolator = Interpolators.STANDARD_DECELERATE
246                 addUpdateListener {
247                     transitionProgress = it.animatedValue as Float
248                     loadingShader.setAlpha(transitionProgress)
249                     // Match the timing with the fade animations.
250                     blurRadius = maxOf(MAX_BLUR_PX * transitionProgress, MIN_BLUR_PX)
251                 }
252                 addListener(
253                     object : AnimatorListenerAdapter() {
254                         override fun onAnimationEnd(animation: Animator) {
255                             resetFadeRevealAnimation()
256                         }
257                     }
258                 )
259                 start()
260             }
261     }
262 
263     private fun resetFadeRevealAnimation() {
264         animationState = AnimationState.FADE_OUT_PLAYED
265 
266         revealOverlay.setRenderEffect(null)
267 
268         // Stop turbulence and reset everything.
269         timeAnimator?.cancel()
270         blurRadius = MIN_BLUR_PX
271         transitionProgress = 0f
272     }
273 
274     fun updateColor(colorScheme: ColorScheme) {
275         colorTurbulenceNoiseShader.apply {
276             setColor(colorScheme.accent1.s600)
277             setBackgroundColor(colorScheme.accent1.s900)
278         }
279 
280         sparkleShader.setColor(colorScheme.accent1.s600)
281         loadingShader.setScreenColor(colorScheme.accent1.s900)
282     }
283 
284     private fun flushUniforms(totalTime: Long, deltaTime: Long) {
285         elapsedTime += deltaTime
286         val time = elapsedTime / 1000f
287         val viewWidth = revealOverlay.width.toFloat()
288         val viewHeight = revealOverlay.height.toFloat()
289 
290         colorTurbulenceNoiseShader.apply {
291             setSize(viewWidth, viewHeight)
292             setNoiseMove(time * NOISE_SPEED, 0f, time * NOISE_SPEED)
293         }
294 
295         sparkleShader.apply {
296             setSize(viewWidth, viewHeight)
297             setNoiseMove(time * NOISE_SPEED, 0f, time * NOISE_SPEED)
298             setTime(time)
299         }
300 
301         loadingShader.apply {
302             setSparkle(sparkleShader)
303             setColorTurbulenceMask(colorTurbulenceNoiseShader)
304         }
305 
306         val renderEffect = RenderEffect.createRuntimeShaderEffect(loadingShader, "in_background")
307 
308         // Update the blur effect only when loading animation is playing.
309         if (
310             animationState == AnimationState.FADE_IN_PLAYING ||
311                 animationState == AnimationState.FADE_OUT_PLAYING
312         ) {
313             blurEffect =
314                 RenderEffect.createBlurEffect(
315                     blurRadius * pixelDensity,
316                     blurRadius * pixelDensity,
317                     Shader.TileMode.MIRROR
318                 )
319         }
320 
321         // Animation time out
322         if (
323             timeOutDuration != null &&
324                 totalTime > timeOutDuration &&
325                 animationState == AnimationState.FADE_IN_PLAYED
326         ) {
327             playRevealAnimation()
328         }
329 
330         if (animationState == AnimationState.REVEAL_PLAYING) {
331             revealOverlay.setRenderEffect(
332                 RenderEffect.createChainEffect(
333                     RenderEffect.createRuntimeShaderEffect(revealShader, "in_src"),
334                     RenderEffect.createChainEffect(renderEffect, blurEffect)
335                 )
336             )
337         } else {
338             revealOverlay.setRenderEffect(RenderEffect.createChainEffect(renderEffect, blurEffect))
339         }
340     }
341 
342     /** Cancels the animation. Unlike end() , cancel() causes the animation to stop in its tracks */
343     fun cancel() {
344         fadeInAnimator?.removeAllListeners()
345         fadeInAnimator?.removeAllUpdateListeners()
346         fadeInAnimator?.cancel()
347         timeAnimator?.cancel()
348         revealAnimator?.removeAllListeners()
349         revealAnimator?.removeAllUpdateListeners()
350         revealAnimator?.cancel()
351     }
352 
353     /** Ends the animation, and causes the animation to skip to the end state */
354     fun end() {
355         fadeInAnimator?.removeAllListeners()
356         fadeInAnimator?.removeAllUpdateListeners()
357         fadeInAnimator?.end()
358         timeAnimator?.end()
359         revealAnimator?.removeAllListeners()
360         revealAnimator?.removeAllUpdateListeners()
361         revealAnimator?.end()
362         when (revealType) {
363             RevealType.CIRCULAR -> resetCircularRevealAnimation()
364             RevealType.FADE -> resetFadeRevealAnimation()
365         }
366     }
367 
368     fun setupRevealAnimation(seed: Long? = null, revealTransitionProgress: Float? = null) {
369         cancel()
370 
371         revealOverlay.visibility = View.VISIBLE
372 
373         elapsedTime = seed ?: (0L..10000L).random()
374         transitionProgress = revealTransitionProgress ?: 1f
375 
376         // Fast forward to state at the end of fade in animation
377         blurRadius = maxOf(MAX_BLUR_PX * transitionProgress, MIN_BLUR_PX)
378         blurEffect =
379             RenderEffect.createBlurEffect(
380                 blurRadius * pixelDensity,
381                 blurRadius * pixelDensity,
382                 Shader.TileMode.MIRROR
383             )
384         animationState = AnimationState.FADE_IN_PLAYED
385         loadingShader.setAlpha(transitionProgress)
386 
387         // Keep clouds moving until we finish loading
388         timeAnimator =
389             TimeAnimator().apply {
390                 setTimeListener { _, totalTime, deltaTime -> flushUniforms(totalTime, deltaTime) }
391                 start()
392             }
393     }
394 
395     fun getElapsedTime(): Long {
396         return elapsedTime
397     }
398 
399     fun getTransitionProgress(): Float {
400         return transitionProgress
401     }
402 
403     companion object {
404         private const val NOISE_SPEED = 0.2f
405         private const val NOISE_SIZE = 1.7f
406         private const val MAX_BLUR_PX = 80f
407         private const val MIN_BLUR_PX = 1f
408         private const val FADE_IN_DURATION_MS = 1100L
409         private const val FADE_OUT_DURATION_MS = 1500L
410         const val TIME_OUT_DURATION_MS = 10000L
411         private const val REVEAL_DURATION_MS = 3600L
412         private const val MIN_REVEAL_BLUR_AMOUNT = 1f
413         private const val MAX_REVEAL_BLUR_AMOUNT = 2.5f
414     }
415 
416     enum class AnimationState {
417         IDLE,
418         FADE_IN_PLAYING,
419         FADE_IN_PLAYED,
420         FADE_OUT_PLAYING,
421         FADE_OUT_PLAYED,
422         REVEAL_PLAYING,
423         REVEAL_PLAYED
424     }
425 }
426