Merge "Refactor Turbulence noise" into tm-qpr-dev

This commit is contained in:
Yein Jo
2023-01-06 02:18:49 +00:00
committed by Android (Google) Code Review
7 changed files with 420 additions and 100 deletions

View File

@@ -49,13 +49,16 @@ data class TurbulenceNoiseAnimationConfig(
val opacity: Int = DEFAULT_OPACITY, val opacity: Int = DEFAULT_OPACITY,
val width: Float = 0f, val width: Float = 0f,
val height: 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 pixelDensity: Float = 1f,
val blendMode: BlendMode = DEFAULT_BLEND_MODE, val blendMode: BlendMode = DEFAULT_BLEND_MODE,
val onAnimationEnd: Runnable? = null val onAnimationEnd: Runnable? = null
) { ) {
companion object { 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_LUMINOSITY_MULTIPLIER = 1f
const val DEFAULT_NOISE_GRID_COUNT = 1.2f const val DEFAULT_NOISE_GRID_COUNT = 1.2f
const val DEFAULT_NOISE_SPEED_Z = 0.3f const val DEFAULT_NOISE_SPEED_Z = 0.3f

View File

@@ -15,16 +15,106 @@
*/ */
package com.android.systemui.surfaceeffects.turbulencenoise 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) { 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. */ /** Updates the color of the noise. */
fun updateNoiseColor(color: Int) { fun updateNoiseColor(color: Int) {
if (state == AnimationState.NOT_PLAYING) {
return
}
turbulenceNoiseView.updateColor(color) turbulenceNoiseView.updateColor(color)
} }
// TODO: add cancel and/ or pause once design requirements become clear. /**
/** Plays [TurbulenceNoiseView] with the given config. */ * Plays [TurbulenceNoiseView] with the given config.
fun play(turbulenceNoiseAnimationConfig: TurbulenceNoiseAnimationConfig) { *
turbulenceNoiseView.play(turbulenceNoiseAnimationConfig) * <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 })
} }
} }

View File

@@ -114,8 +114,19 @@ class TurbulenceNoiseShader : RuntimeShader(TURBULENCE_NOISE_SHADER) {
setFloatUniform("in_aspectRatio", width / max(height, 0.001f)) 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) { 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)
} }
} }

View File

@@ -25,38 +25,29 @@ import android.util.AttributeSet
import android.view.View import android.view.View
import androidx.annotation.VisibleForTesting import androidx.annotation.VisibleForTesting
import androidx.core.graphics.ColorUtils 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) { class TurbulenceNoiseView(context: Context?, attrs: AttributeSet?) : View(context, attrs) {
companion object { companion object {
private const val MS_TO_SEC = 0.001f 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 paint = Paint().apply { this.shader = turbulenceNoiseShader }
private val random = Random() @VisibleForTesting var noiseConfig: TurbulenceNoiseAnimationConfig? = null
private val animator: ValueAnimator = ValueAnimator.ofFloat(0f, 1f) @VisibleForTesting var currentAnimator: ValueAnimator? = null
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)
}
}
override fun onDraw(canvas: Canvas?) { override fun onDraw(canvas: Canvas?) {
if (canvas == null || !canvas.isHardwareAccelerated) { if (canvas == null || !canvas.isHardwareAccelerated) {
@@ -68,32 +59,38 @@ class TurbulenceNoiseView(context: Context?, attrs: AttributeSet?) : View(contex
canvas.drawPaint(paint) canvas.drawPaint(paint)
} }
fun play(config: TurbulenceNoiseAnimationConfig) { /** Updates the color during the animation. No-op if there's no animation playing. */
if (isPlaying) { internal fun updateColor(color: Int) {
return // Ignore if the animation is playing. noiseConfig?.let {
turbulenceNoiseShader.setColor(ColorUtils.setAlphaComponent(color, it.opacity))
}
} }
visibility = VISIBLE
applyConfig(config)
// Add random offset to avoid same patterned noise. /** Plays the turbulence noise with no easing. */
val offsetX = random.nextFloat() @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
val offsetY = random.nextFloat() 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 -> animator.addUpdateListener { updateListener ->
val timeInSec = updateListener.currentPlayTime * MS_TO_SEC val timeInSec = updateListener.currentPlayTime * MS_TO_SEC
// Remap [0,1] to [0, 2*PI]
val progress = TWO_PI * updateListener.animatedValue as Float
turbulenceNoiseShader.setNoiseMove( turbulenceNoiseShader.setNoiseMove(
offsetX + timeInSec * config.noiseMoveSpeedX, initialX + timeInSec * config.noiseMoveSpeedX,
offsetY + timeInSec * config.noiseMoveSpeedY, initialY + timeInSec * config.noiseMoveSpeedY,
timeInSec * config.noiseMoveSpeedZ initialZ + timeInSec * config.noiseMoveSpeedZ
) )
// Fade in and out the noise as the animation progress. turbulenceNoiseShader.setOpacity(config.luminosityMultiplier)
// TODO: replace it with a better curve
turbulenceNoiseShader.setOpacity(sin(TWO_PI - progress) * config.luminosityMultiplier)
invalidate() invalidate()
} }
@@ -101,16 +98,121 @@ class TurbulenceNoiseView(context: Context?, attrs: AttributeSet?) : View(contex
animator.addListener( animator.addListener(
object : AnimatorListenerAdapter() { object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) { override fun onAnimationEnd(animation: Animator) {
visibility = INVISIBLE currentAnimator = null
config.onAnimationEnd?.run() onAnimationEnd?.run()
} }
} }
) )
animator.start() animator.start()
currentAnimator = animator
} }
private fun applyConfig(config: TurbulenceNoiseAnimationConfig) { /** Plays the turbulence noise with linear ease-in. */
this.config = config @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) { with(turbulenceNoiseShader) {
setGridCount(config.gridCount) setGridCount(config.gridCount)
setColor(ColorUtils.setAlphaComponent(config.color, config.opacity)) setColor(ColorUtils.setAlphaComponent(config.color, config.opacity))
@@ -120,4 +222,8 @@ class TurbulenceNoiseView(context: Context?, attrs: AttributeSet?) : View(contex
} }
paint.blendMode = config.blendMode paint.blendMode = config.blendMode
} }
internal fun clearConfig() {
noiseConfig = null
}
} }

View File

@@ -1070,7 +1070,9 @@ public class MediaControlPanel {
TurbulenceNoiseAnimationConfig.DEFAULT_OPACITY, TurbulenceNoiseAnimationConfig.DEFAULT_OPACITY,
/* width= */ mMediaViewHolder.getMultiRippleView().getWidth(), /* width= */ mMediaViewHolder.getMultiRippleView().getWidth(),
/* height= */ mMediaViewHolder.getMultiRippleView().getHeight(), /* 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, this.getContext().getResources().getDisplayMetrics().density,
BlendMode.PLUS, BlendMode.PLUS,
/* onAnimationEnd= */ null /* onAnimationEnd= */ null

View File

@@ -17,8 +17,14 @@ package com.android.systemui.surfaceeffects.turbulencenoise
import android.graphics.Color import android.graphics.Color
import android.testing.AndroidTestingRunner import android.testing.AndroidTestingRunner
import android.view.View.INVISIBLE
import android.view.View.VISIBLE
import androidx.test.filters.SmallTest import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase 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.concurrency.FakeExecutor
import com.android.systemui.util.time.FakeSystemClock import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
@@ -33,26 +39,117 @@ class TurbulenceNoiseControllerTest : SysuiTestCase() {
private val fakeExecutor = FakeExecutor(fakeSystemClock) private val fakeExecutor = FakeExecutor(fakeSystemClock)
@Test @Test
fun play_playsTurbulenceNoise() { fun play_playsTurbulenceNoiseInOrder() {
val config = TurbulenceNoiseAnimationConfig(duration = 1000f) val config = TurbulenceNoiseAnimationConfig(maxDuration = 1000f)
val turbulenceNoiseView = TurbulenceNoiseView(context, null) val turbulenceNoiseView = TurbulenceNoiseView(context, null)
val turbulenceNoiseController = TurbulenceNoiseController(turbulenceNoiseView) val turbulenceNoiseController = TurbulenceNoiseController(turbulenceNoiseView)
assertThat(turbulenceNoiseController.state).isEqualTo(NOT_PLAYING)
fakeExecutor.execute { fakeExecutor.execute {
turbulenceNoiseController.play(config) 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 @Test
fun updateColor_updatesCorrectColor() { 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 turbulenceNoiseView = TurbulenceNoiseView(context, null)
val expectedColor = Color.RED val expectedColor = Color.RED
@@ -61,9 +158,9 @@ class TurbulenceNoiseControllerTest : SysuiTestCase() {
fakeExecutor.execute { fakeExecutor.execute {
turbulenceNoiseController.play(config) turbulenceNoiseController.play(config)
turbulenceNoiseView.updateColor(expectedColor) turbulenceNoiseController.updateNoiseColor(expectedColor)
fakeSystemClock.advanceTime(config.duration.toLong()) fakeSystemClock.advanceTime(config.maxDuration.toLong())
assertThat(config.color).isEqualTo(expectedColor) assertThat(config.color).isEqualTo(expectedColor)
} }

View File

@@ -16,7 +16,6 @@
package com.android.systemui.surfaceeffects.turbulencenoise package com.android.systemui.surfaceeffects.turbulencenoise
import android.testing.AndroidTestingRunner import android.testing.AndroidTestingRunner
import android.view.View
import androidx.test.filters.SmallTest import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase import com.android.systemui.SysuiTestCase
import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.concurrency.FakeExecutor
@@ -33,54 +32,66 @@ class TurbulenceNoiseViewTest : SysuiTestCase() {
// FakeExecutor is needed to run animator. // FakeExecutor is needed to run animator.
private val fakeExecutor = FakeExecutor(fakeSystemClock) 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 @Test
fun play_playsAnimation() { fun play_playsAnimation() {
val config = TurbulenceNoiseAnimationConfig(duration = 1000f) val config = TurbulenceNoiseAnimationConfig()
val turbulenceNoiseView = TurbulenceNoiseView(context, null) val turbulenceNoiseView = TurbulenceNoiseView(context, null).also { it.applyConfig(config) }
var onAnimationEndCalled = false
fakeExecutor.execute { fakeExecutor.execute {
turbulenceNoiseView.play(config) turbulenceNoiseView.play(onAnimationEnd = { onAnimationEndCalled = true })
assertThat(turbulenceNoiseView.isPlaying).isTrue() fakeSystemClock.advanceTime(config.maxDuration.toLong())
assertThat(onAnimationEndCalled).isTrue()
} }
} }
@Test @Test
fun play_onEnd_triggersOnAnimationEnd() { fun playEaseIn_playsEaseInAnimation() {
var animationEnd = false val config = TurbulenceNoiseAnimationConfig()
val config = val turbulenceNoiseView = TurbulenceNoiseView(context, null).also { it.applyConfig(config) }
TurbulenceNoiseAnimationConfig( var onAnimationEndCalled = false
duration = 1000f,
onAnimationEnd = { animationEnd = true }
)
val turbulenceNoiseView = TurbulenceNoiseView(context, null)
fakeExecutor.execute { 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()
} }
} }
} }