Fix persistent dot not removed due to race condition in scheduler
Bug: 290784635 Test: atest SystemStatusAnimationSchedulerImplTest Change-Id: I7380cb956b48c38bdd540b297287786cf35b94f2
This commit is contained in:
@@ -170,19 +170,19 @@ constructor(
|
||||
// Set hasPersistentDot to false. If the animationState is anything before ANIMATING_OUT,
|
||||
// the disappear animation will not animate into a dot but remove the chip entirely
|
||||
hasPersistentDot = false
|
||||
// if we are currently showing a persistent dot, hide it
|
||||
if (animationState.value == SHOWING_PERSISTENT_DOT) notifyHidePersistentDot()
|
||||
// if we are currently animating into a dot, wait for the animation to finish and then hide
|
||||
// the dot
|
||||
if (animationState.value == ANIMATING_OUT) {
|
||||
coroutineScope.launch {
|
||||
withTimeout(DISAPPEAR_ANIMATION_DURATION) {
|
||||
animationState.first {
|
||||
it == SHOWING_PERSISTENT_DOT || it == IDLE || it == ANIMATION_QUEUED
|
||||
}
|
||||
notifyHidePersistentDot()
|
||||
}
|
||||
|
||||
if (animationState.value == SHOWING_PERSISTENT_DOT) {
|
||||
// if we are currently showing a persistent dot, hide it and update the animationState
|
||||
notifyHidePersistentDot()
|
||||
if (scheduledEvent.value != null) {
|
||||
animationState.value = ANIMATION_QUEUED
|
||||
} else {
|
||||
animationState.value = IDLE
|
||||
}
|
||||
} else if (animationState.value == ANIMATING_OUT) {
|
||||
// if we are currently animating out, hide the dot. The animationState will be updated
|
||||
// once the animation has ended in the onAnimationEnd callback
|
||||
notifyHidePersistentDot()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -366,14 +366,6 @@ constructor(
|
||||
logger?.logHidePersistentDotCallbackInvoked()
|
||||
val anims: List<Animator> = listeners.mapNotNull { it.onHidePersistentDot() }
|
||||
|
||||
if (animationState.value == SHOWING_PERSISTENT_DOT) {
|
||||
if (scheduledEvent.value != null) {
|
||||
animationState.value = ANIMATION_QUEUED
|
||||
} else {
|
||||
animationState.value = IDLE
|
||||
}
|
||||
}
|
||||
|
||||
if (anims.isNotEmpty()) {
|
||||
val aSet = AnimatorSet()
|
||||
aSet.playTogether(anims)
|
||||
|
||||
@@ -400,15 +400,16 @@ class SystemStatusAnimationSchedulerImplTest : SysuiTestCase() {
|
||||
|
||||
// remove persistent dot
|
||||
systemStatusAnimationScheduler.removePersistentDot()
|
||||
testScheduler.runCurrent()
|
||||
|
||||
// verify that the onHidePersistentDot callback is invoked
|
||||
verify(listener, times(1)).onHidePersistentDot()
|
||||
|
||||
// skip disappear animation
|
||||
animatorTestRule.advanceTimeBy(DISAPPEAR_ANIMATION_DURATION)
|
||||
testScheduler.runCurrent()
|
||||
|
||||
// verify that animationState changes to IDLE and onHidePersistentDot callback is invoked
|
||||
// verify that animationState changes to IDLE
|
||||
assertEquals(IDLE, systemStatusAnimationScheduler.getAnimationState())
|
||||
verify(listener, times(1)).onHidePersistentDot()
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -473,7 +474,6 @@ class SystemStatusAnimationSchedulerImplTest : SysuiTestCase() {
|
||||
|
||||
// request removal of persistent dot
|
||||
systemStatusAnimationScheduler.removePersistentDot()
|
||||
testScheduler.runCurrent()
|
||||
|
||||
// schedule another high priority event while the event is animating out
|
||||
createAndScheduleFakePrivacyEvent()
|
||||
@@ -489,6 +489,42 @@ class SystemStatusAnimationSchedulerImplTest : SysuiTestCase() {
|
||||
verify(listener, times(1)).onHidePersistentDot()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testDotIsRemoved_evenIfAnimatorCallbackIsDelayed() = runTest {
|
||||
// Instantiate class under test with TestScope from runTest
|
||||
initializeSystemStatusAnimationScheduler(testScope = this)
|
||||
|
||||
// create and schedule high priority event
|
||||
createAndScheduleFakePrivacyEvent()
|
||||
|
||||
// skip chip animation lifecycle and fast forward to ANIMATING_OUT state
|
||||
fastForwardAnimationToState(ANIMATING_OUT)
|
||||
assertEquals(ANIMATING_OUT, systemStatusAnimationScheduler.getAnimationState())
|
||||
verify(listener, times(1)).onSystemStatusAnimationTransitionToPersistentDot(any())
|
||||
|
||||
// request removal of persistent dot
|
||||
systemStatusAnimationScheduler.removePersistentDot()
|
||||
|
||||
// verify that the state is still ANIMATING_OUT
|
||||
assertEquals(ANIMATING_OUT, systemStatusAnimationScheduler.getAnimationState())
|
||||
|
||||
// skip disappear animation duration
|
||||
testScheduler.advanceTimeBy(DISAPPEAR_ANIMATION_DURATION + 1)
|
||||
// In an old implementation this would trigger a coroutine timeout causing the
|
||||
// onHidePersistentDot callback to be missed.
|
||||
testScheduler.runCurrent()
|
||||
|
||||
// advance animator time to invoke onAnimationEnd callback
|
||||
animatorTestRule.advanceTimeBy(DISAPPEAR_ANIMATION_DURATION)
|
||||
testScheduler.runCurrent()
|
||||
|
||||
// verify that onHidePersistentDot is invoked despite the animator callback being delayed
|
||||
// (it's invoked more than DISAPPEAR_ANIMATION_DURATION after the dot removal was requested)
|
||||
verify(listener, times(1)).onHidePersistentDot()
|
||||
// verify that animationState is IDLE
|
||||
assertEquals(IDLE, systemStatusAnimationScheduler.getAnimationState())
|
||||
}
|
||||
|
||||
private fun TestScope.fastForwardAnimationToState(@SystemAnimationState animationState: Int) {
|
||||
// this function should only be called directly after posting a status event
|
||||
assertEquals(ANIMATION_QUEUED, systemStatusAnimationScheduler.getAnimationState())
|
||||
|
||||
Reference in New Issue
Block a user