Merge "Fix persistent dot not removed due to race condition in scheduler" into udc-dev am: 31a8777cbc

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/25379523

Change-Id: I120a18d795ab59bd03beb67f1beed8d0d8a807b3
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
Johannes Gallmann 2023-11-20 08:51:11 +00:00 committed by Automerger Merge Worker
commit 9d43da4760
2 changed files with 52 additions and 24 deletions

View File

@ -182,19 +182,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()
}
}
@ -376,14 +376,6 @@ constructor(
Assert.isMainThread()
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)

View File

@ -410,15 +410,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
@ -483,7 +484,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()
@ -499,6 +499,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())