Refactor Turbulence noise
1. introduced three states (ease-in, main, and ease-out) to better control each animation & support non-fixed duration animation (such as loading). 2. move business logic to the controller. Follow up tasks (ordered in priority): - have media player send a signal to end the animation. (currently it finishes after the max duration.) - update animation curve (currently it's all linear) - support force finish if needed (currently finish only works when the state is MAIN) Bug: 237282226 Test: TurbulenceNoiseViewTest, TurbulenceNoiseControllerTest Change-Id: I711c87096c7c9af6e322104ad0138c4fd78c4953
This commit is contained in:
@@ -49,13 +49,16 @@ data class TurbulenceNoiseAnimationConfig(
|
||||
val opacity: Int = DEFAULT_OPACITY,
|
||||
val width: Float = 0f,
|
||||
val height: Float = 0f,
|
||||
val duration: Float = DEFAULT_NOISE_DURATION_IN_MILLIS,
|
||||
val maxDuration: Float = DEFAULT_MAX_DURATION_IN_MILLIS,
|
||||
val easeInDuration: Float = DEFAULT_EASING_DURATION_IN_MILLIS,
|
||||
val easeOutDuration: Float = DEFAULT_EASING_DURATION_IN_MILLIS,
|
||||
val pixelDensity: Float = 1f,
|
||||
val blendMode: BlendMode = DEFAULT_BLEND_MODE,
|
||||
val onAnimationEnd: Runnable? = null
|
||||
) {
|
||||
companion object {
|
||||
const val DEFAULT_NOISE_DURATION_IN_MILLIS = 7500F
|
||||
const val DEFAULT_MAX_DURATION_IN_MILLIS = 7500f
|
||||
const val DEFAULT_EASING_DURATION_IN_MILLIS = 750f
|
||||
const val DEFAULT_LUMINOSITY_MULTIPLIER = 1f
|
||||
const val DEFAULT_NOISE_GRID_COUNT = 1.2f
|
||||
const val DEFAULT_NOISE_SPEED_Z = 0.3f
|
||||
|
||||
@@ -15,16 +15,106 @@
|
||||
*/
|
||||
package com.android.systemui.surfaceeffects.turbulencenoise
|
||||
|
||||
/** A controller that plays [TurbulenceNoiseView]. */
|
||||
import android.view.View
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import java.util.Random
|
||||
|
||||
/** Plays [TurbulenceNoiseView] in ease-in, main (no easing), and ease-out order. */
|
||||
class TurbulenceNoiseController(private val turbulenceNoiseView: TurbulenceNoiseView) {
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* States of the turbulence noise animation.
|
||||
*
|
||||
* <p>The state is designed to be follow the order below: [AnimationState.EASE_IN],
|
||||
* [AnimationState.MAIN], [AnimationState.EASE_OUT].
|
||||
*/
|
||||
enum class AnimationState {
|
||||
EASE_IN,
|
||||
MAIN,
|
||||
EASE_OUT,
|
||||
NOT_PLAYING
|
||||
}
|
||||
}
|
||||
|
||||
private val random = Random()
|
||||
|
||||
/** Current state of the animation. */
|
||||
@VisibleForTesting
|
||||
var state: AnimationState = AnimationState.NOT_PLAYING
|
||||
set(value) {
|
||||
field = value
|
||||
if (state == AnimationState.NOT_PLAYING) {
|
||||
turbulenceNoiseView.visibility = View.INVISIBLE
|
||||
turbulenceNoiseView.clearConfig()
|
||||
} else {
|
||||
turbulenceNoiseView.visibility = View.VISIBLE
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
turbulenceNoiseView.visibility = View.INVISIBLE
|
||||
}
|
||||
|
||||
/** Updates the color of the noise. */
|
||||
fun updateNoiseColor(color: Int) {
|
||||
if (state == AnimationState.NOT_PLAYING) {
|
||||
return
|
||||
}
|
||||
turbulenceNoiseView.updateColor(color)
|
||||
}
|
||||
|
||||
// TODO: add cancel and/ or pause once design requirements become clear.
|
||||
/** Plays [TurbulenceNoiseView] with the given config. */
|
||||
fun play(turbulenceNoiseAnimationConfig: TurbulenceNoiseAnimationConfig) {
|
||||
turbulenceNoiseView.play(turbulenceNoiseAnimationConfig)
|
||||
/**
|
||||
* Plays [TurbulenceNoiseView] with the given config.
|
||||
*
|
||||
* <p>It plays ease-in, main, and ease-out animations in sequence.
|
||||
*/
|
||||
fun play(config: TurbulenceNoiseAnimationConfig) {
|
||||
if (state != AnimationState.NOT_PLAYING) {
|
||||
return // Ignore if any of the animation is playing.
|
||||
}
|
||||
|
||||
turbulenceNoiseView.applyConfig(config)
|
||||
playEaseInAnimation()
|
||||
}
|
||||
|
||||
// TODO(b/237282226): Support force finish.
|
||||
/** Finishes the main animation, which triggers the ease-out animation. */
|
||||
fun finish() {
|
||||
if (state == AnimationState.MAIN) {
|
||||
turbulenceNoiseView.finish(nextAnimation = this::playEaseOutAnimation)
|
||||
}
|
||||
}
|
||||
|
||||
private fun playEaseInAnimation() {
|
||||
if (state != AnimationState.NOT_PLAYING) {
|
||||
return
|
||||
}
|
||||
state = AnimationState.EASE_IN
|
||||
|
||||
// Add offset to avoid repetitive noise.
|
||||
turbulenceNoiseView.playEaseIn(
|
||||
offsetX = random.nextFloat(),
|
||||
offsetY = random.nextFloat(),
|
||||
this::playMainAnimation
|
||||
)
|
||||
}
|
||||
|
||||
private fun playMainAnimation() {
|
||||
if (state != AnimationState.EASE_IN) {
|
||||
return
|
||||
}
|
||||
state = AnimationState.MAIN
|
||||
|
||||
turbulenceNoiseView.play(this::playEaseOutAnimation)
|
||||
}
|
||||
|
||||
private fun playEaseOutAnimation() {
|
||||
if (state != AnimationState.MAIN) {
|
||||
return
|
||||
}
|
||||
state = AnimationState.EASE_OUT
|
||||
|
||||
turbulenceNoiseView.playEaseOut(onAnimationEnd = { state = AnimationState.NOT_PLAYING })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,8 +114,19 @@ class TurbulenceNoiseShader : RuntimeShader(TURBULENCE_NOISE_SHADER) {
|
||||
setFloatUniform("in_aspectRatio", width / max(height, 0.001f))
|
||||
}
|
||||
|
||||
/** Sets noise move speed in x, y, and z direction. */
|
||||
/** Current noise movements in x, y, and z axes. */
|
||||
var noiseOffsetX: Float = 0f
|
||||
private set
|
||||
var noiseOffsetY: Float = 0f
|
||||
private set
|
||||
var noiseOffsetZ: Float = 0f
|
||||
private set
|
||||
|
||||
/** Sets noise move offset in x, y, and z direction. */
|
||||
fun setNoiseMove(x: Float, y: Float, z: Float) {
|
||||
setFloatUniform("in_noiseMove", x, y, z)
|
||||
noiseOffsetX = x
|
||||
noiseOffsetY = y
|
||||
noiseOffsetZ = z
|
||||
setFloatUniform("in_noiseMove", noiseOffsetX, noiseOffsetY, noiseOffsetZ)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,38 +25,29 @@ import android.util.AttributeSet
|
||||
import android.view.View
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import androidx.core.graphics.ColorUtils
|
||||
import java.util.Random
|
||||
import kotlin.math.sin
|
||||
|
||||
/** View that renders turbulence noise effect. */
|
||||
/**
|
||||
* View that renders turbulence noise effect.
|
||||
*
|
||||
* <p>Use [TurbulenceNoiseController] to control the turbulence animation. If you want to make some
|
||||
* other turbulence noise effects, either add functionality to [TurbulenceNoiseController] or create
|
||||
* another controller instead of extend or modify the [TurbulenceNoiseView].
|
||||
*
|
||||
* <p>Please keep the [TurbulenceNoiseView] (or View in general) not aware of the state.
|
||||
*
|
||||
* <p>Please avoid inheriting the View if possible. Instead, reconsider adding a controller for a
|
||||
* new case.
|
||||
*/
|
||||
class TurbulenceNoiseView(context: Context?, attrs: AttributeSet?) : View(context, attrs) {
|
||||
|
||||
companion object {
|
||||
private const val MS_TO_SEC = 0.001f
|
||||
private const val TWO_PI = Math.PI.toFloat() * 2f
|
||||
}
|
||||
|
||||
@VisibleForTesting val turbulenceNoiseShader = TurbulenceNoiseShader()
|
||||
private val turbulenceNoiseShader = TurbulenceNoiseShader()
|
||||
private val paint = Paint().apply { this.shader = turbulenceNoiseShader }
|
||||
private val random = Random()
|
||||
private val animator: ValueAnimator = ValueAnimator.ofFloat(0f, 1f)
|
||||
private var config: TurbulenceNoiseAnimationConfig? = null
|
||||
|
||||
val isPlaying: Boolean
|
||||
get() = animator.isRunning
|
||||
|
||||
init {
|
||||
// Only visible during the animation.
|
||||
visibility = INVISIBLE
|
||||
}
|
||||
|
||||
/** Updates the color during the animation. No-op if there's no animation playing. */
|
||||
fun updateColor(color: Int) {
|
||||
config?.let {
|
||||
it.color = color
|
||||
applyConfig(it)
|
||||
}
|
||||
}
|
||||
@VisibleForTesting var noiseConfig: TurbulenceNoiseAnimationConfig? = null
|
||||
@VisibleForTesting var currentAnimator: ValueAnimator? = null
|
||||
|
||||
override fun onDraw(canvas: Canvas?) {
|
||||
if (canvas == null || !canvas.isHardwareAccelerated) {
|
||||
@@ -68,32 +59,38 @@ class TurbulenceNoiseView(context: Context?, attrs: AttributeSet?) : View(contex
|
||||
canvas.drawPaint(paint)
|
||||
}
|
||||
|
||||
fun play(config: TurbulenceNoiseAnimationConfig) {
|
||||
if (isPlaying) {
|
||||
return // Ignore if the animation is playing.
|
||||
/** Updates the color during the animation. No-op if there's no animation playing. */
|
||||
internal fun updateColor(color: Int) {
|
||||
noiseConfig?.let {
|
||||
turbulenceNoiseShader.setColor(ColorUtils.setAlphaComponent(color, it.opacity))
|
||||
}
|
||||
visibility = VISIBLE
|
||||
applyConfig(config)
|
||||
}
|
||||
|
||||
// Add random offset to avoid same patterned noise.
|
||||
val offsetX = random.nextFloat()
|
||||
val offsetY = random.nextFloat()
|
||||
/** Plays the turbulence noise with no easing. */
|
||||
@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
|
||||
fun play(onAnimationEnd: Runnable? = null) {
|
||||
if (noiseConfig == null) {
|
||||
return
|
||||
}
|
||||
val config = noiseConfig!!
|
||||
|
||||
val animator = ValueAnimator.ofFloat(0f, 1f)
|
||||
animator.duration = config.maxDuration.toLong()
|
||||
|
||||
// Animation should start from the initial position to avoid abrupt transition.
|
||||
val initialX = turbulenceNoiseShader.noiseOffsetX
|
||||
val initialY = turbulenceNoiseShader.noiseOffsetY
|
||||
val initialZ = turbulenceNoiseShader.noiseOffsetZ
|
||||
|
||||
animator.duration = config.duration.toLong()
|
||||
animator.addUpdateListener { updateListener ->
|
||||
val timeInSec = updateListener.currentPlayTime * MS_TO_SEC
|
||||
// Remap [0,1] to [0, 2*PI]
|
||||
val progress = TWO_PI * updateListener.animatedValue as Float
|
||||
|
||||
turbulenceNoiseShader.setNoiseMove(
|
||||
offsetX + timeInSec * config.noiseMoveSpeedX,
|
||||
offsetY + timeInSec * config.noiseMoveSpeedY,
|
||||
timeInSec * config.noiseMoveSpeedZ
|
||||
initialX + timeInSec * config.noiseMoveSpeedX,
|
||||
initialY + timeInSec * config.noiseMoveSpeedY,
|
||||
initialZ + timeInSec * config.noiseMoveSpeedZ
|
||||
)
|
||||
|
||||
// Fade in and out the noise as the animation progress.
|
||||
// TODO: replace it with a better curve
|
||||
turbulenceNoiseShader.setOpacity(sin(TWO_PI - progress) * config.luminosityMultiplier)
|
||||
turbulenceNoiseShader.setOpacity(config.luminosityMultiplier)
|
||||
|
||||
invalidate()
|
||||
}
|
||||
@@ -101,16 +98,121 @@ class TurbulenceNoiseView(context: Context?, attrs: AttributeSet?) : View(contex
|
||||
animator.addListener(
|
||||
object : AnimatorListenerAdapter() {
|
||||
override fun onAnimationEnd(animation: Animator) {
|
||||
visibility = INVISIBLE
|
||||
config.onAnimationEnd?.run()
|
||||
currentAnimator = null
|
||||
onAnimationEnd?.run()
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
animator.start()
|
||||
currentAnimator = animator
|
||||
}
|
||||
|
||||
private fun applyConfig(config: TurbulenceNoiseAnimationConfig) {
|
||||
this.config = config
|
||||
/** Plays the turbulence noise with linear ease-in. */
|
||||
@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
|
||||
fun playEaseIn(offsetX: Float = 0f, offsetY: Float = 0f, onAnimationEnd: Runnable? = null) {
|
||||
if (noiseConfig == null) {
|
||||
return
|
||||
}
|
||||
val config = noiseConfig!!
|
||||
|
||||
val animator = ValueAnimator.ofFloat(0f, 1f)
|
||||
animator.duration = config.easeInDuration.toLong()
|
||||
|
||||
// Animation should start from the initial position to avoid abrupt transition.
|
||||
val initialX = turbulenceNoiseShader.noiseOffsetX
|
||||
val initialY = turbulenceNoiseShader.noiseOffsetY
|
||||
val initialZ = turbulenceNoiseShader.noiseOffsetZ
|
||||
|
||||
animator.addUpdateListener { updateListener ->
|
||||
val timeInSec = updateListener.currentPlayTime * MS_TO_SEC
|
||||
val progress = updateListener.animatedValue as Float
|
||||
|
||||
turbulenceNoiseShader.setNoiseMove(
|
||||
offsetX + initialX + timeInSec * config.noiseMoveSpeedX,
|
||||
offsetY + initialY + timeInSec * config.noiseMoveSpeedY,
|
||||
initialZ + timeInSec * config.noiseMoveSpeedZ
|
||||
)
|
||||
|
||||
// TODO: Replace it with a better curve.
|
||||
turbulenceNoiseShader.setOpacity(progress * config.luminosityMultiplier)
|
||||
|
||||
invalidate()
|
||||
}
|
||||
|
||||
animator.addListener(
|
||||
object : AnimatorListenerAdapter() {
|
||||
override fun onAnimationEnd(animation: Animator) {
|
||||
currentAnimator = null
|
||||
onAnimationEnd?.run()
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
animator.start()
|
||||
currentAnimator = animator
|
||||
}
|
||||
|
||||
/** Plays the turbulence noise with linear ease-out. */
|
||||
@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
|
||||
fun playEaseOut(onAnimationEnd: Runnable? = null) {
|
||||
if (noiseConfig == null) {
|
||||
return
|
||||
}
|
||||
val config = noiseConfig!!
|
||||
|
||||
val animator = ValueAnimator.ofFloat(0f, 1f)
|
||||
animator.duration = config.easeOutDuration.toLong()
|
||||
|
||||
// Animation should start from the initial position to avoid abrupt transition.
|
||||
val initialX = turbulenceNoiseShader.noiseOffsetX
|
||||
val initialY = turbulenceNoiseShader.noiseOffsetY
|
||||
val initialZ = turbulenceNoiseShader.noiseOffsetZ
|
||||
|
||||
animator.addUpdateListener { updateListener ->
|
||||
val timeInSec = updateListener.currentPlayTime * MS_TO_SEC
|
||||
val progress = updateListener.animatedValue as Float
|
||||
|
||||
turbulenceNoiseShader.setNoiseMove(
|
||||
initialX + timeInSec * config.noiseMoveSpeedX,
|
||||
initialY + timeInSec * config.noiseMoveSpeedY,
|
||||
initialZ + timeInSec * config.noiseMoveSpeedZ
|
||||
)
|
||||
|
||||
// TODO: Replace it with a better curve.
|
||||
turbulenceNoiseShader.setOpacity((1f - progress) * config.luminosityMultiplier)
|
||||
|
||||
invalidate()
|
||||
}
|
||||
|
||||
animator.addListener(
|
||||
object : AnimatorListenerAdapter() {
|
||||
override fun onAnimationEnd(animation: Animator) {
|
||||
currentAnimator = null
|
||||
onAnimationEnd?.run()
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
animator.start()
|
||||
currentAnimator = animator
|
||||
}
|
||||
|
||||
/** Finishes the current animation if playing and plays the next animation if given. */
|
||||
@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
|
||||
fun finish(nextAnimation: Runnable? = null) {
|
||||
// Calling Animator#end sets the animation state back to the initial state. Using pause to
|
||||
// avoid visual artifacts.
|
||||
currentAnimator?.pause()
|
||||
currentAnimator = null
|
||||
|
||||
nextAnimation?.run()
|
||||
}
|
||||
|
||||
/** Applies shader uniforms. Must be called before playing animation. */
|
||||
@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
|
||||
fun applyConfig(config: TurbulenceNoiseAnimationConfig) {
|
||||
noiseConfig = config
|
||||
with(turbulenceNoiseShader) {
|
||||
setGridCount(config.gridCount)
|
||||
setColor(ColorUtils.setAlphaComponent(config.color, config.opacity))
|
||||
@@ -120,4 +222,8 @@ class TurbulenceNoiseView(context: Context?, attrs: AttributeSet?) : View(contex
|
||||
}
|
||||
paint.blendMode = config.blendMode
|
||||
}
|
||||
|
||||
internal fun clearConfig() {
|
||||
noiseConfig = null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1070,7 +1070,9 @@ public class MediaControlPanel {
|
||||
TurbulenceNoiseAnimationConfig.DEFAULT_OPACITY,
|
||||
/* width= */ mMediaViewHolder.getMultiRippleView().getWidth(),
|
||||
/* height= */ mMediaViewHolder.getMultiRippleView().getHeight(),
|
||||
TurbulenceNoiseAnimationConfig.DEFAULT_NOISE_DURATION_IN_MILLIS,
|
||||
TurbulenceNoiseAnimationConfig.DEFAULT_MAX_DURATION_IN_MILLIS,
|
||||
TurbulenceNoiseAnimationConfig.DEFAULT_EASING_DURATION_IN_MILLIS,
|
||||
TurbulenceNoiseAnimationConfig.DEFAULT_EASING_DURATION_IN_MILLIS,
|
||||
this.getContext().getResources().getDisplayMetrics().density,
|
||||
BlendMode.PLUS,
|
||||
/* onAnimationEnd= */ null
|
||||
|
||||
@@ -17,8 +17,14 @@ package com.android.systemui.surfaceeffects.turbulencenoise
|
||||
|
||||
import android.graphics.Color
|
||||
import android.testing.AndroidTestingRunner
|
||||
import android.view.View.INVISIBLE
|
||||
import android.view.View.VISIBLE
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseController.Companion.AnimationState.EASE_IN
|
||||
import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseController.Companion.AnimationState.EASE_OUT
|
||||
import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseController.Companion.AnimationState.MAIN
|
||||
import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseController.Companion.AnimationState.NOT_PLAYING
|
||||
import com.android.systemui.util.concurrency.FakeExecutor
|
||||
import com.android.systemui.util.time.FakeSystemClock
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
@@ -33,26 +39,117 @@ class TurbulenceNoiseControllerTest : SysuiTestCase() {
|
||||
private val fakeExecutor = FakeExecutor(fakeSystemClock)
|
||||
|
||||
@Test
|
||||
fun play_playsTurbulenceNoise() {
|
||||
val config = TurbulenceNoiseAnimationConfig(duration = 1000f)
|
||||
fun play_playsTurbulenceNoiseInOrder() {
|
||||
val config = TurbulenceNoiseAnimationConfig(maxDuration = 1000f)
|
||||
val turbulenceNoiseView = TurbulenceNoiseView(context, null)
|
||||
|
||||
val turbulenceNoiseController = TurbulenceNoiseController(turbulenceNoiseView)
|
||||
|
||||
assertThat(turbulenceNoiseController.state).isEqualTo(NOT_PLAYING)
|
||||
|
||||
fakeExecutor.execute {
|
||||
turbulenceNoiseController.play(config)
|
||||
|
||||
assertThat(turbulenceNoiseView.isPlaying).isTrue()
|
||||
assertThat(turbulenceNoiseController.state).isEqualTo(EASE_IN)
|
||||
|
||||
fakeSystemClock.advanceTime(config.duration.toLong())
|
||||
fakeSystemClock.advanceTime(config.easeInDuration.toLong())
|
||||
|
||||
assertThat(turbulenceNoiseView.isPlaying).isFalse()
|
||||
assertThat(turbulenceNoiseController.state).isEqualTo(MAIN)
|
||||
|
||||
fakeSystemClock.advanceTime(config.maxDuration.toLong())
|
||||
|
||||
assertThat(turbulenceNoiseController.state).isEqualTo(EASE_OUT)
|
||||
|
||||
fakeSystemClock.advanceTime(config.easeOutDuration.toLong())
|
||||
|
||||
assertThat(turbulenceNoiseController.state).isEqualTo(NOT_PLAYING)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun play_alreadyPlaying_ignoresNewAnimationRequest() {
|
||||
val config = TurbulenceNoiseAnimationConfig(maxDuration = 1000f)
|
||||
val turbulenceNoiseView = TurbulenceNoiseView(context, null)
|
||||
// Currently playing the main animation.
|
||||
val turbulenceNoiseController =
|
||||
TurbulenceNoiseController(turbulenceNoiseView).also { it.state = MAIN }
|
||||
|
||||
fakeExecutor.execute {
|
||||
// Request another animation
|
||||
turbulenceNoiseController.play(config)
|
||||
|
||||
assertThat(turbulenceNoiseController.state).isEqualTo(MAIN)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun finish_mainAnimationPlaying_playsEaseOutAnimation() {
|
||||
val config = TurbulenceNoiseAnimationConfig(maxDuration = 1000f)
|
||||
val turbulenceNoiseView = TurbulenceNoiseView(context, null)
|
||||
val turbulenceNoiseController =
|
||||
TurbulenceNoiseController(turbulenceNoiseView).also { it.state = MAIN }
|
||||
|
||||
fakeExecutor.execute {
|
||||
turbulenceNoiseController.play(config)
|
||||
|
||||
fakeSystemClock.advanceTime(config.maxDuration.toLong() / 2)
|
||||
|
||||
turbulenceNoiseController.finish()
|
||||
|
||||
assertThat(turbulenceNoiseController.state).isEqualTo(EASE_OUT)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun finish_nonMainAnimationPlaying_doesNotFinishAnimation() {
|
||||
val config = TurbulenceNoiseAnimationConfig(maxDuration = 1000f)
|
||||
val turbulenceNoiseView = TurbulenceNoiseView(context, null)
|
||||
val turbulenceNoiseController =
|
||||
TurbulenceNoiseController(turbulenceNoiseView).also { it.state = EASE_IN }
|
||||
|
||||
fakeExecutor.execute {
|
||||
turbulenceNoiseController.play(config)
|
||||
|
||||
fakeSystemClock.advanceTime(config.maxDuration.toLong() / 2)
|
||||
|
||||
turbulenceNoiseController.finish()
|
||||
|
||||
assertThat(turbulenceNoiseController.state).isEqualTo(EASE_IN)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun onAnimationFinished_resetsStateCorrectly() {
|
||||
val config = TurbulenceNoiseAnimationConfig(maxDuration = 1000f)
|
||||
val turbulenceNoiseView = TurbulenceNoiseView(context, null)
|
||||
val turbulenceNoiseController = TurbulenceNoiseController(turbulenceNoiseView)
|
||||
|
||||
assertThat(turbulenceNoiseController.state).isEqualTo(NOT_PLAYING)
|
||||
assertThat(turbulenceNoiseView.visibility).isEqualTo(INVISIBLE)
|
||||
assertThat(turbulenceNoiseView.noiseConfig).isNull()
|
||||
|
||||
fakeExecutor.execute {
|
||||
turbulenceNoiseController.play(config)
|
||||
|
||||
assertThat(turbulenceNoiseController.state).isEqualTo(EASE_IN)
|
||||
assertThat(turbulenceNoiseView.visibility).isEqualTo(VISIBLE)
|
||||
assertThat(turbulenceNoiseView.noiseConfig).isEqualTo(config)
|
||||
|
||||
// Play all the animations.
|
||||
fakeSystemClock.advanceTime(
|
||||
config.easeInDuration.toLong() +
|
||||
config.maxDuration.toLong() +
|
||||
config.easeOutDuration.toLong()
|
||||
)
|
||||
|
||||
assertThat(turbulenceNoiseController.state).isEqualTo(NOT_PLAYING)
|
||||
assertThat(turbulenceNoiseView.visibility).isEqualTo(INVISIBLE)
|
||||
assertThat(turbulenceNoiseView.noiseConfig).isNull()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun updateColor_updatesCorrectColor() {
|
||||
val config = TurbulenceNoiseAnimationConfig(duration = 1000f, color = Color.WHITE)
|
||||
val config = TurbulenceNoiseAnimationConfig(maxDuration = 1000f, color = Color.WHITE)
|
||||
val turbulenceNoiseView = TurbulenceNoiseView(context, null)
|
||||
val expectedColor = Color.RED
|
||||
|
||||
@@ -61,9 +158,9 @@ class TurbulenceNoiseControllerTest : SysuiTestCase() {
|
||||
fakeExecutor.execute {
|
||||
turbulenceNoiseController.play(config)
|
||||
|
||||
turbulenceNoiseView.updateColor(expectedColor)
|
||||
turbulenceNoiseController.updateNoiseColor(expectedColor)
|
||||
|
||||
fakeSystemClock.advanceTime(config.duration.toLong())
|
||||
fakeSystemClock.advanceTime(config.maxDuration.toLong())
|
||||
|
||||
assertThat(config.color).isEqualTo(expectedColor)
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
package com.android.systemui.surfaceeffects.turbulencenoise
|
||||
|
||||
import android.testing.AndroidTestingRunner
|
||||
import android.view.View
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.util.concurrency.FakeExecutor
|
||||
@@ -33,54 +32,66 @@ class TurbulenceNoiseViewTest : SysuiTestCase() {
|
||||
// FakeExecutor is needed to run animator.
|
||||
private val fakeExecutor = FakeExecutor(fakeSystemClock)
|
||||
|
||||
@Test
|
||||
fun play_viewHasCorrectVisibility() {
|
||||
val config = TurbulenceNoiseAnimationConfig(duration = 1000f)
|
||||
val turbulenceNoiseView = TurbulenceNoiseView(context, null)
|
||||
|
||||
assertThat(turbulenceNoiseView.visibility).isEqualTo(View.INVISIBLE)
|
||||
|
||||
fakeExecutor.execute {
|
||||
turbulenceNoiseView.play(config)
|
||||
|
||||
assertThat(turbulenceNoiseView.visibility).isEqualTo(View.VISIBLE)
|
||||
|
||||
fakeSystemClock.advanceTime(config.duration.toLong())
|
||||
|
||||
assertThat(turbulenceNoiseView.visibility).isEqualTo(View.INVISIBLE)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun play_playsAnimation() {
|
||||
val config = TurbulenceNoiseAnimationConfig(duration = 1000f)
|
||||
val turbulenceNoiseView = TurbulenceNoiseView(context, null)
|
||||
val config = TurbulenceNoiseAnimationConfig()
|
||||
val turbulenceNoiseView = TurbulenceNoiseView(context, null).also { it.applyConfig(config) }
|
||||
var onAnimationEndCalled = false
|
||||
|
||||
fakeExecutor.execute {
|
||||
turbulenceNoiseView.play(config)
|
||||
turbulenceNoiseView.play(onAnimationEnd = { onAnimationEndCalled = true })
|
||||
|
||||
assertThat(turbulenceNoiseView.isPlaying).isTrue()
|
||||
fakeSystemClock.advanceTime(config.maxDuration.toLong())
|
||||
|
||||
assertThat(onAnimationEndCalled).isTrue()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun play_onEnd_triggersOnAnimationEnd() {
|
||||
var animationEnd = false
|
||||
val config =
|
||||
TurbulenceNoiseAnimationConfig(
|
||||
duration = 1000f,
|
||||
onAnimationEnd = { animationEnd = true }
|
||||
)
|
||||
val turbulenceNoiseView = TurbulenceNoiseView(context, null)
|
||||
fun playEaseIn_playsEaseInAnimation() {
|
||||
val config = TurbulenceNoiseAnimationConfig()
|
||||
val turbulenceNoiseView = TurbulenceNoiseView(context, null).also { it.applyConfig(config) }
|
||||
var onAnimationEndCalled = false
|
||||
|
||||
fakeExecutor.execute {
|
||||
turbulenceNoiseView.play(config)
|
||||
turbulenceNoiseView.playEaseIn(onAnimationEnd = { onAnimationEndCalled = true })
|
||||
|
||||
assertThat(turbulenceNoiseView.isPlaying).isTrue()
|
||||
fakeSystemClock.advanceTime(config.easeInDuration.toLong())
|
||||
|
||||
fakeSystemClock.advanceTime(config.duration.toLong())
|
||||
assertThat(onAnimationEndCalled).isTrue()
|
||||
}
|
||||
}
|
||||
|
||||
assertThat(animationEnd).isTrue()
|
||||
@Test
|
||||
fun playEaseOut_playsEaseOutAnimation() {
|
||||
val config = TurbulenceNoiseAnimationConfig()
|
||||
val turbulenceNoiseView = TurbulenceNoiseView(context, null).also { it.applyConfig(config) }
|
||||
var onAnimationEndCalled = false
|
||||
|
||||
fakeExecutor.execute {
|
||||
turbulenceNoiseView.playEaseOut(onAnimationEnd = { onAnimationEndCalled = true })
|
||||
|
||||
fakeSystemClock.advanceTime(config.easeOutDuration.toLong())
|
||||
|
||||
assertThat(onAnimationEndCalled).isTrue()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun finish_animationPlaying_finishesAnimation() {
|
||||
val config = TurbulenceNoiseAnimationConfig()
|
||||
val turbulenceNoiseView = TurbulenceNoiseView(context, null).also { it.applyConfig(config) }
|
||||
var onAnimationEndCalled = false
|
||||
|
||||
fakeExecutor.execute {
|
||||
turbulenceNoiseView.play(onAnimationEnd = { onAnimationEndCalled = true })
|
||||
|
||||
assertThat(turbulenceNoiseView.currentAnimator).isNotNull()
|
||||
|
||||
turbulenceNoiseView.finish()
|
||||
|
||||
assertThat(onAnimationEndCalled).isTrue()
|
||||
assertThat(turbulenceNoiseView.currentAnimator).isNull()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user