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.view.View
19 import androidx.annotation.VisibleForTesting
20 import java.util.Random
21 
22 /** Plays [TurbulenceNoiseView] in ease-in, main (no easing), and ease-out order. */
23 class TurbulenceNoiseController(private val turbulenceNoiseView: TurbulenceNoiseView) {
24 
25     companion object {
26         /**
27          * States of the turbulence noise animation.
28          *
29          * <p>The state is designed to be follow the order below: [AnimationState.EASE_IN],
30          * [AnimationState.MAIN], [AnimationState.EASE_OUT].
31          */
32         enum class AnimationState {
33             EASE_IN,
34             MAIN,
35             EASE_OUT,
36             NOT_PLAYING
37         }
38     }
39 
40     private val random = Random()
41 
42     /** Current state of the animation. */
43     @VisibleForTesting
44     var state: AnimationState = AnimationState.NOT_PLAYING
45         set(value) {
46             field = value
47             if (state == AnimationState.NOT_PLAYING) {
48                 turbulenceNoiseView.visibility = View.INVISIBLE
49                 turbulenceNoiseView.clearConfig()
50             } else {
51                 turbulenceNoiseView.visibility = View.VISIBLE
52             }
53         }
54 
55     init {
56         turbulenceNoiseView.visibility = View.INVISIBLE
57     }
58 
59     /** Updates the color of the noise. */
60     fun updateNoiseColor(color: Int) {
61         if (state == AnimationState.NOT_PLAYING) {
62             return
63         }
64         turbulenceNoiseView.updateColor(color)
65     }
66 
67     /**
68      * Plays [TurbulenceNoiseView] with the given config.
69      *
70      * <p>It plays ease-in, main, and ease-out animations in sequence.
71      */
72     fun play(config: TurbulenceNoiseAnimationConfig) {
73         if (state != AnimationState.NOT_PLAYING) {
74             return // Ignore if any of the animation is playing.
75         }
76 
77         turbulenceNoiseView.applyConfig(config)
78         playEaseInAnimation()
79     }
80 
81     // TODO(b/237282226): Support force finish.
82     /** Finishes the main animation, which triggers the ease-out animation. */
83     fun finish() {
84         if (state == AnimationState.MAIN) {
85             turbulenceNoiseView.finish(nextAnimation = this::playEaseOutAnimation)
86         }
87     }
88 
89     private fun playEaseInAnimation() {
90         if (state != AnimationState.NOT_PLAYING) {
91             return
92         }
93         state = AnimationState.EASE_IN
94 
95         // Add offset to avoid repetitive noise.
96         turbulenceNoiseView.playEaseIn(
97             offsetX = random.nextFloat(),
98             offsetY = random.nextFloat(),
99             this::playMainAnimation
100         )
101     }
102 
103     private fun playMainAnimation() {
104         if (state != AnimationState.EASE_IN) {
105             return
106         }
107         state = AnimationState.MAIN
108 
109         turbulenceNoiseView.play(this::playEaseOutAnimation)
110     }
111 
112     private fun playEaseOutAnimation() {
113         if (state != AnimationState.MAIN) {
114             return
115         }
116         state = AnimationState.EASE_OUT
117 
118         turbulenceNoiseView.playEaseOut(onAnimationEnd = { state = AnimationState.NOT_PLAYING })
119     }
120 }
121