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?.cancel() 115 timeAnimator?.cancel() 116 revealAnimator?.cancel() 117 118 fadeInAnimator = 119 ValueAnimator.ofFloat(transitionProgress, 1f).apply { 120 duration = FADE_IN_DURATION_MS 121 interpolator = Interpolators.STANDARD_DECELERATE 122 addUpdateListener { 123 transitionProgress = it.animatedValue as Float 124 loadingShader.setAlpha(transitionProgress) 125 // Match the timing with the fade animations. 126 blurRadius = maxOf(MAX_BLUR_PX * transitionProgress, MIN_BLUR_PX) 127 } 128 addListener( 129 object : AnimatorListenerAdapter() { 130 override fun onAnimationEnd(animation: Animator) { 131 animationState = AnimationState.FADE_IN_PLAYED 132 } 133 } 134 ) 135 start() 136 } 137 138 // Keep clouds moving until we finish loading 139 timeAnimator = 140 TimeAnimator().apply { 141 setTimeListener { _, totalTime, deltaTime -> flushUniforms(totalTime, deltaTime) } 142 start() 143 } 144 } 145 146 fun playRevealAnimation() { 147 when (revealType) { 148 RevealType.CIRCULAR -> playCircularRevealAnimation() 149 RevealType.FADE -> playFadeRevealAnimation() 150 } 151 } 152 153 private fun playCircularRevealAnimation() { 154 if ( 155 animationState == AnimationState.REVEAL_PLAYING || 156 animationState == AnimationState.REVEAL_PLAYED || 157 animationState == AnimationState.FADE_OUT_PLAYING || 158 animationState == AnimationState.FADE_OUT_PLAYED 159 ) 160 return 161 162 if (animationState == AnimationState.FADE_IN_PLAYING) fadeInAnimator?.cancel() 163 164 animationState = AnimationState.REVEAL_PLAYING 165 166 revealOverlay.visibility = View.VISIBLE 167 168 revealShader.setCenter(revealOverlay.width * 0.5f, revealOverlay.height * 0.5f) 169 170 revealAnimator?.cancel() 171 revealAnimator = 172 ValueAnimator.ofFloat(0f, 1f).apply { 173 duration = REVEAL_DURATION_MS 174 interpolator = Interpolators.STANDARD 175 176 addUpdateListener { 177 val progress = it.animatedValue as Float 178 179 // Draw a circle slightly larger than the screen. Need some offset due to large 180 // blur radius. 181 revealShader.setRadius( 182 progress * max(revealOverlay.width, revealOverlay.height) * 2f 183 ) 184 // Map [0,1] to [MAX, MIN]. 185 val blurAmount = 186 (1f - progress) * (MAX_REVEAL_BLUR_AMOUNT - MIN_REVEAL_BLUR_AMOUNT) + 187 MIN_REVEAL_BLUR_AMOUNT 188 revealShader.setBlur(blurAmount) 189 } 190 191 addListener( 192 object : AnimatorListenerAdapter() { 193 override fun onAnimationEnd(animation: Animator) { 194 resetCircularRevealAnimation() 195 } 196 } 197 ) 198 199 start() 200 } 201 } 202 203 private fun resetCircularRevealAnimation() { 204 animationState = AnimationState.REVEAL_PLAYED 205 206 revealOverlay.setRenderEffect(null) 207 revealOverlay.visibility = View.INVISIBLE 208 209 // Stop turbulence and reset everything. 210 timeAnimator?.cancel() 211 blurRadius = MIN_BLUR_PX 212 transitionProgress = 0f 213 } 214 215 private fun playFadeRevealAnimation() { 216 if ( 217 animationState == AnimationState.REVEAL_PLAYING || 218 animationState == AnimationState.REVEAL_PLAYED || 219 animationState == AnimationState.FADE_OUT_PLAYING || 220 animationState == AnimationState.FADE_OUT_PLAYED 221 ) 222 return 223 224 if (animationState == AnimationState.FADE_IN_PLAYING) fadeInAnimator?.cancel() 225 226 animationState = AnimationState.FADE_OUT_PLAYING 227 228 revealOverlay.visibility = View.VISIBLE 229 230 fadeInAnimator = 231 ValueAnimator.ofFloat(transitionProgress, 0f).apply { 232 duration = FADE_OUT_DURATION_MS 233 interpolator = Interpolators.STANDARD_DECELERATE 234 addUpdateListener { 235 transitionProgress = it.animatedValue as Float 236 loadingShader.setAlpha(transitionProgress) 237 // Match the timing with the fade animations. 238 blurRadius = maxOf(MAX_BLUR_PX * transitionProgress, MIN_BLUR_PX) 239 } 240 addListener( 241 object : AnimatorListenerAdapter() { 242 override fun onAnimationEnd(animation: Animator) { 243 resetFadeRevealAnimation() 244 } 245 } 246 ) 247 start() 248 } 249 } 250 251 private fun resetFadeRevealAnimation() { 252 animationState = AnimationState.FADE_OUT_PLAYED 253 254 revealOverlay.setRenderEffect(null) 255 256 // Stop turbulence and reset everything. 257 timeAnimator?.cancel() 258 blurRadius = MIN_BLUR_PX 259 transitionProgress = 0f 260 } 261 262 fun updateColor(colorScheme: ColorScheme) { 263 colorTurbulenceNoiseShader.apply { 264 setColor(colorScheme.accent1.s600) 265 setBackgroundColor(colorScheme.accent1.s900) 266 } 267 268 sparkleShader.setColor(colorScheme.accent1.s600) 269 loadingShader.setScreenColor(colorScheme.accent1.s900) 270 } 271 272 private fun flushUniforms(totalTime: Long, deltaTime: Long) { 273 elapsedTime += deltaTime 274 val time = elapsedTime / 1000f 275 val viewWidth = revealOverlay.width.toFloat() 276 val viewHeight = revealOverlay.height.toFloat() 277 278 colorTurbulenceNoiseShader.apply { 279 setSize(viewWidth, viewHeight) 280 setNoiseMove(time * NOISE_SPEED, 0f, time * NOISE_SPEED) 281 } 282 283 sparkleShader.apply { 284 setSize(viewWidth, viewHeight) 285 setNoiseMove(time * NOISE_SPEED, 0f, time * NOISE_SPEED) 286 setTime(time) 287 } 288 289 loadingShader.apply { 290 setSparkle(sparkleShader) 291 setColorTurbulenceMask(colorTurbulenceNoiseShader) 292 } 293 294 val renderEffect = RenderEffect.createRuntimeShaderEffect(loadingShader, "in_background") 295 296 // Update the blur effect only when loading animation is playing. 297 if ( 298 animationState == AnimationState.FADE_IN_PLAYING || 299 animationState == AnimationState.FADE_OUT_PLAYING 300 ) { 301 blurEffect = 302 RenderEffect.createBlurEffect( 303 blurRadius * pixelDensity, 304 blurRadius * pixelDensity, 305 Shader.TileMode.MIRROR 306 ) 307 } 308 309 // Animation time out 310 if ( 311 timeOutDuration != null && 312 totalTime > timeOutDuration && 313 animationState == AnimationState.FADE_IN_PLAYED 314 ) { 315 playRevealAnimation() 316 } 317 318 if (animationState == AnimationState.REVEAL_PLAYING) { 319 revealOverlay.setRenderEffect( 320 RenderEffect.createChainEffect( 321 RenderEffect.createRuntimeShaderEffect(revealShader, "in_src"), 322 RenderEffect.createChainEffect(renderEffect, blurEffect) 323 ) 324 ) 325 } else { 326 revealOverlay.setRenderEffect(RenderEffect.createChainEffect(renderEffect, blurEffect)) 327 } 328 } 329 330 /** Cancels the animation. Unlike end() , cancel() causes the animation to stop in its tracks */ 331 fun cancel() { 332 fadeInAnimator?.cancel() 333 timeAnimator?.cancel() 334 revealAnimator?.removeAllListeners() 335 revealAnimator?.removeAllUpdateListeners() 336 revealAnimator?.cancel() 337 } 338 339 /** Ends the animation, and causes the animation to skip to the end state */ 340 fun end() { 341 fadeInAnimator?.end() 342 timeAnimator?.end() 343 revealAnimator?.removeAllListeners() 344 revealAnimator?.removeAllUpdateListeners() 345 revealAnimator?.end() 346 when (revealType) { 347 RevealType.CIRCULAR -> resetCircularRevealAnimation() 348 RevealType.FADE -> resetFadeRevealAnimation() 349 } 350 } 351 352 fun setupRevealAnimation(seed: Long? = null, revealTransitionProgress: Float? = null) { 353 cancel() 354 355 revealOverlay.visibility = View.VISIBLE 356 357 elapsedTime = seed ?: (0L..10000L).random() 358 transitionProgress = revealTransitionProgress ?: 1f 359 360 // Fast forward to state at the end of fade in animation 361 blurRadius = maxOf(MAX_BLUR_PX * transitionProgress, MIN_BLUR_PX) 362 blurEffect = 363 RenderEffect.createBlurEffect( 364 blurRadius * pixelDensity, 365 blurRadius * pixelDensity, 366 Shader.TileMode.MIRROR 367 ) 368 animationState = AnimationState.FADE_IN_PLAYED 369 loadingShader.setAlpha(transitionProgress) 370 371 // Keep clouds moving until we finish loading 372 timeAnimator = 373 TimeAnimator().apply { 374 setTimeListener { _, totalTime, deltaTime -> flushUniforms(totalTime, deltaTime) } 375 start() 376 } 377 } 378 379 fun getElapsedTime(): Long { 380 return elapsedTime 381 } 382 383 fun getTransitionProgress(): Float { 384 return transitionProgress 385 } 386 387 companion object { 388 private const val NOISE_SPEED = 0.2f 389 private const val NOISE_SIZE = 1.7f 390 private const val MAX_BLUR_PX = 80f 391 private const val MIN_BLUR_PX = 1f 392 private const val FADE_IN_DURATION_MS = 1100L 393 private const val FADE_OUT_DURATION_MS = 1500L 394 const val TIME_OUT_DURATION_MS = 10000L 395 private const val REVEAL_DURATION_MS = 3600L 396 private const val MIN_REVEAL_BLUR_AMOUNT = 1f 397 private const val MAX_REVEAL_BLUR_AMOUNT = 2.5f 398 } 399 400 enum class AnimationState { 401 IDLE, 402 FADE_IN_PLAYING, 403 FADE_IN_PLAYED, 404 FADE_OUT_PLAYING, 405 FADE_OUT_PLAYED, 406 REVEAL_PLAYING, 407 REVEAL_PLAYED 408 } 409 } 410