1 /* 2 * Copyright (C) 2023 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 17 package com.android.systemui.statusbar.events 18 19 import android.graphics.Insets 20 import android.graphics.Rect 21 import android.os.Process 22 import android.testing.TestableLooper.RunWithLooper 23 import android.view.View 24 import android.widget.FrameLayout 25 import androidx.test.ext.junit.runners.AndroidJUnit4 26 import androidx.test.filters.SmallTest 27 import com.android.systemui.SysuiTestCase 28 import com.android.systemui.animation.AnimatorTestRule 29 import com.android.systemui.dump.DumpManager 30 import com.android.systemui.privacy.OngoingPrivacyChip 31 import com.android.systemui.statusbar.BatteryStatusChip 32 import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider 33 import com.android.systemui.statusbar.window.StatusBarWindowController 34 import com.android.systemui.util.mockito.any 35 import com.android.systemui.util.mockito.eq 36 import com.android.systemui.util.mockito.mock 37 import com.android.systemui.util.mockito.whenever 38 import com.android.systemui.util.time.FakeSystemClock 39 import junit.framework.Assert.assertEquals 40 import kotlinx.coroutines.CoroutineScope 41 import kotlinx.coroutines.ExperimentalCoroutinesApi 42 import kotlinx.coroutines.test.StandardTestDispatcher 43 import kotlinx.coroutines.test.TestScope 44 import kotlinx.coroutines.test.advanceTimeBy 45 import kotlinx.coroutines.test.runTest 46 import org.junit.Before 47 import org.junit.Rule 48 import org.junit.Test 49 import org.junit.runner.RunWith 50 import org.mockito.Mock 51 import org.mockito.Mockito.anyBoolean 52 import org.mockito.Mockito.never 53 import org.mockito.Mockito.times 54 import org.mockito.Mockito.verify 55 import org.mockito.MockitoAnnotations 56 57 @RunWith(AndroidJUnit4::class) 58 @RunWithLooper(setAsMainLooper = true) 59 @OptIn(ExperimentalCoroutinesApi::class) 60 @SmallTest 61 class SystemStatusAnimationSchedulerImplTest : SysuiTestCase() { 62 63 @Mock private lateinit var systemEventCoordinator: SystemEventCoordinator 64 65 @Mock private lateinit var statusBarWindowController: StatusBarWindowController 66 67 @Mock private lateinit var statusBarContentInsetProvider: StatusBarContentInsetsProvider 68 69 @Mock private lateinit var dumpManager: DumpManager 70 71 @Mock private lateinit var listener: SystemStatusAnimationCallback 72 73 @Mock private lateinit var logger: SystemStatusAnimationSchedulerLogger 74 75 private lateinit var systemClock: FakeSystemClock 76 private lateinit var chipAnimationController: SystemEventChipAnimationController 77 private lateinit var systemStatusAnimationScheduler: SystemStatusAnimationScheduler 78 79 @get:Rule val animatorTestRule = AnimatorTestRule(this) 80 81 @Before setupnull82 fun setup() { 83 MockitoAnnotations.initMocks(this) 84 85 systemClock = FakeSystemClock() 86 chipAnimationController = 87 SystemEventChipAnimationController( 88 mContext, 89 statusBarWindowController, 90 statusBarContentInsetProvider 91 ) 92 93 // StatusBarContentInsetProvider is mocked. Ensure that it returns some mocked values. 94 whenever(statusBarContentInsetProvider.getStatusBarContentInsetsForCurrentRotation()) 95 .thenReturn( 96 Insets.of(/* left = */ 10, /* top = */ 10, /* right = */ 10, /* bottom = */ 0) 97 ) 98 whenever(statusBarContentInsetProvider.getStatusBarContentAreaForCurrentRotation()) 99 .thenReturn( 100 Rect(/* left = */ 10, /* top = */ 10, /* right = */ 990, /* bottom = */ 100) 101 ) 102 103 // StatusBarWindowController is mocked. The addViewToWindow function needs to be mocked to 104 // ensure that the chip view is added to a parent view 105 whenever(statusBarWindowController.addViewToWindow(any(), any())).then { 106 val statusbarFake = FrameLayout(mContext) 107 statusbarFake.layout(/* l = */ 0, /* t = */ 0, /* r = */ 1000, /* b = */ 100) 108 statusbarFake.addView( 109 it.arguments[0] as View, 110 it.arguments[1] as FrameLayout.LayoutParams 111 ) 112 } 113 } 114 115 @Test <lambda>null116 fun testBatteryStatusEvent_standardAnimationLifecycle() = runTest { 117 // Instantiate class under test with TestScope from runTest 118 initializeSystemStatusAnimationScheduler(testScope = this) 119 120 val batteryChip = createAndScheduleFakeBatteryEvent() 121 122 // assert that animation is queued 123 assertEquals(ANIMATION_QUEUED, systemStatusAnimationScheduler.getAnimationState()) 124 125 // skip debounce delay 126 advanceTimeBy(DEBOUNCE_DELAY + 1) 127 // status chip starts animating in after debounce delay 128 assertEquals(ANIMATING_IN, systemStatusAnimationScheduler.getAnimationState()) 129 assertEquals(0f, batteryChip.contentView.alpha) 130 assertEquals(0f, batteryChip.view.alpha) 131 verify(listener, times(1)).onSystemEventAnimationBegin() 132 133 // skip appear animation 134 animatorTestRule.advanceTimeBy(APPEAR_ANIMATION_DURATION) 135 advanceTimeBy(APPEAR_ANIMATION_DURATION) 136 // assert that status chip is visible 137 assertEquals(RUNNING_CHIP_ANIM, systemStatusAnimationScheduler.getAnimationState()) 138 assertEquals(1f, batteryChip.contentView.alpha) 139 assertEquals(1f, batteryChip.view.alpha) 140 141 // skip status chip display time 142 advanceTimeBy(DISPLAY_LENGTH + 1) 143 // assert that it is still visible but switched to the ANIMATING_OUT state 144 assertEquals(ANIMATING_OUT, systemStatusAnimationScheduler.getAnimationState()) 145 assertEquals(1f, batteryChip.contentView.alpha) 146 assertEquals(1f, batteryChip.view.alpha) 147 verify(listener, times(1)).onSystemEventAnimationFinish(false) 148 149 // skip disappear animation 150 animatorTestRule.advanceTimeBy(DISAPPEAR_ANIMATION_DURATION) 151 // assert that it is not visible anymore 152 assertEquals(IDLE, systemStatusAnimationScheduler.getAnimationState()) 153 assertEquals(0f, batteryChip.contentView.alpha) 154 assertEquals(0f, batteryChip.view.alpha) 155 } 156 157 /** Regression test for b/294104969. */ 158 @Test <lambda>null159 fun testPrivacyStatusEvent_beforeSystemUptime_stillDisplayed() = runTest { 160 initializeSystemStatusAnimationScheduler(testScope = this, advancePastMinUptime = false) 161 162 // WHEN the uptime hasn't quite passed the minimum required uptime... 163 systemClock.setUptimeMillis(Process.getStartUptimeMillis() + MIN_UPTIME / 2) 164 165 // BUT the event is a privacy event 166 createAndScheduleFakePrivacyEvent() 167 168 // THEN the privacy event still happens 169 assertEquals(ANIMATION_QUEUED, systemStatusAnimationScheduler.getAnimationState()) 170 } 171 172 @Test <lambda>null173 fun testPrivacyStatusEvent_standardAnimationLifecycle() = runTest { 174 // Instantiate class under test with TestScope from runTest 175 initializeSystemStatusAnimationScheduler(testScope = this) 176 177 val privacyChip = createAndScheduleFakePrivacyEvent() 178 179 // assert that animation is queued 180 assertEquals(ANIMATION_QUEUED, systemStatusAnimationScheduler.getAnimationState()) 181 182 // skip debounce delay 183 advanceTimeBy(DEBOUNCE_DELAY + 1) 184 // status chip starts animating in after debounce delay 185 assertEquals(ANIMATING_IN, systemStatusAnimationScheduler.getAnimationState()) 186 assertEquals(0f, privacyChip.view.alpha) 187 verify(listener, times(1)).onSystemEventAnimationBegin() 188 189 // skip appear animation 190 animatorTestRule.advanceTimeBy(APPEAR_ANIMATION_DURATION) 191 advanceTimeBy(APPEAR_ANIMATION_DURATION + 1) 192 // assert that status chip is visible 193 assertEquals(RUNNING_CHIP_ANIM, systemStatusAnimationScheduler.getAnimationState()) 194 assertEquals(1f, privacyChip.view.alpha) 195 196 // skip status chip display time 197 advanceTimeBy(DISPLAY_LENGTH + 1) 198 // assert that it is still visible but switched to the ANIMATING_OUT state 199 assertEquals(ANIMATING_OUT, systemStatusAnimationScheduler.getAnimationState()) 200 assertEquals(1f, privacyChip.view.alpha) 201 verify(listener, times(1)).onSystemEventAnimationFinish(true) 202 verify(listener, times(1)).onSystemStatusAnimationTransitionToPersistentDot(any()) 203 204 // skip transition to persistent dot 205 advanceTimeBy(DISAPPEAR_ANIMATION_DURATION + 1) 206 animatorTestRule.advanceTimeBy(DISAPPEAR_ANIMATION_DURATION) 207 // assert that it the dot is now visible 208 assertEquals(SHOWING_PERSISTENT_DOT, systemStatusAnimationScheduler.getAnimationState()) 209 assertEquals(1f, privacyChip.view.alpha) 210 211 // notify SystemStatusAnimationScheduler to remove persistent dot 212 systemStatusAnimationScheduler.removePersistentDot() 213 // assert that IDLE state is entered 214 assertEquals(IDLE, systemStatusAnimationScheduler.getAnimationState()) 215 verify(listener, times(1)).onHidePersistentDot() 216 } 217 218 @Test <lambda>null219 fun testHighPriorityEvent_takesPrecedenceOverScheduledLowPriorityEvent() = runTest { 220 // Instantiate class under test with TestScope from runTest 221 initializeSystemStatusAnimationScheduler(testScope = this) 222 223 // create and schedule low priority event 224 val batteryChip = createAndScheduleFakeBatteryEvent() 225 batteryChip.view.alpha = 0f 226 227 // assert that animation is queued 228 assertEquals(ANIMATION_QUEUED, systemStatusAnimationScheduler.getAnimationState()) 229 230 // create and schedule high priority event 231 val privacyChip = createAndScheduleFakePrivacyEvent() 232 233 // assert that animation is queued 234 assertEquals(ANIMATION_QUEUED, systemStatusAnimationScheduler.getAnimationState()) 235 236 // skip debounce delay and appear animation duration 237 fastForwardAnimationToState(RUNNING_CHIP_ANIM) 238 239 // high priority status chip is visible while low priority status chip is not visible 240 assertEquals(RUNNING_CHIP_ANIM, systemStatusAnimationScheduler.getAnimationState()) 241 assertEquals(1f, privacyChip.view.alpha) 242 assertEquals(0f, batteryChip.view.alpha) 243 } 244 245 @Test <lambda>null246 fun testHighPriorityEvent_cancelsCurrentlyDisplayedLowPriorityEvent() = runTest { 247 // Instantiate class under test with TestScope from runTest 248 initializeSystemStatusAnimationScheduler(testScope = this) 249 250 // create and schedule low priority event 251 val batteryChip = createAndScheduleFakeBatteryEvent() 252 253 // fast forward to RUNNING_CHIP_ANIM state 254 fastForwardAnimationToState(RUNNING_CHIP_ANIM) 255 256 // assert that chip is displayed 257 assertEquals(RUNNING_CHIP_ANIM, systemStatusAnimationScheduler.getAnimationState()) 258 assertEquals(1f, batteryChip.view.alpha) 259 260 // create and schedule high priority event 261 val privacyChip = createAndScheduleFakePrivacyEvent() 262 263 // ensure that the event cancellation coroutine is started by the test scope 264 testScheduler.runCurrent() 265 266 // assert that currently displayed chip is immediately animated out 267 assertEquals(ANIMATING_OUT, systemStatusAnimationScheduler.getAnimationState()) 268 269 // skip disappear animation 270 animatorTestRule.advanceTimeBy(DISAPPEAR_ANIMATION_DURATION) 271 272 // assert that high priority privacy chip animation is queued 273 assertEquals(ANIMATION_QUEUED, systemStatusAnimationScheduler.getAnimationState()) 274 275 // skip debounce delay and appear animation 276 advanceTimeBy(DEBOUNCE_DELAY + APPEAR_ANIMATION_DURATION + 1) 277 animatorTestRule.advanceTimeBy(APPEAR_ANIMATION_DURATION) 278 279 // high priority status chip is visible while low priority status chip is not visible 280 assertEquals(RUNNING_CHIP_ANIM, systemStatusAnimationScheduler.getAnimationState()) 281 assertEquals(1f, privacyChip.view.alpha) 282 assertEquals(0f, batteryChip.view.alpha) 283 } 284 285 @Test <lambda>null286 fun testHighPriorityEvent_cancelsCurrentlyAnimatedLowPriorityEvent() = runTest { 287 // Instantiate class under test with TestScope from runTest 288 initializeSystemStatusAnimationScheduler(testScope = this) 289 290 // create and schedule low priority event 291 val batteryChip = createAndScheduleFakeBatteryEvent() 292 293 // skip debounce delay 294 advanceTimeBy(DEBOUNCE_DELAY + 1) 295 296 // assert that chip is animated in 297 assertEquals(ANIMATING_IN, systemStatusAnimationScheduler.getAnimationState()) 298 299 // create and schedule high priority event 300 val privacyChip = createAndScheduleFakePrivacyEvent() 301 302 // ensure that the event cancellation coroutine is started by the test scope 303 testScheduler.runCurrent() 304 305 // assert that currently animated chip keeps animating 306 assertEquals(ANIMATING_IN, systemStatusAnimationScheduler.getAnimationState()) 307 308 // skip appear animation 309 animatorTestRule.advanceTimeBy(APPEAR_ANIMATION_DURATION) 310 advanceTimeBy(APPEAR_ANIMATION_DURATION + 1) 311 312 // assert that low priority chip is animated out immediately after finishing the appear 313 // animation 314 assertEquals(ANIMATING_OUT, systemStatusAnimationScheduler.getAnimationState()) 315 316 // skip disappear animation 317 animatorTestRule.advanceTimeBy(DISAPPEAR_ANIMATION_DURATION) 318 319 // assert that high priority privacy chip animation is queued 320 assertEquals(ANIMATION_QUEUED, systemStatusAnimationScheduler.getAnimationState()) 321 322 // skip debounce delay and appear animation 323 advanceTimeBy(DEBOUNCE_DELAY + APPEAR_ANIMATION_DURATION + 1) 324 animatorTestRule.advanceTimeBy(APPEAR_ANIMATION_DURATION) 325 326 // high priority status chip is visible while low priority status chip is not visible 327 assertEquals(RUNNING_CHIP_ANIM, systemStatusAnimationScheduler.getAnimationState()) 328 assertEquals(1f, privacyChip.view.alpha) 329 assertEquals(0f, batteryChip.view.alpha) 330 } 331 332 @Test <lambda>null333 fun testHighPriorityEvent_isNotReplacedByLowPriorityEvent() = runTest { 334 // Instantiate class under test with TestScope from runTest 335 initializeSystemStatusAnimationScheduler(testScope = this) 336 337 // create and schedule high priority event 338 val privacyChip = createAndScheduleFakePrivacyEvent() 339 340 // create and schedule low priority event 341 val batteryChip = createAndScheduleFakeBatteryEvent() 342 batteryChip.view.alpha = 0f 343 344 // skip debounce delay and appear animation 345 advanceTimeBy(DEBOUNCE_DELAY + APPEAR_ANIMATION_DURATION + 1) 346 animatorTestRule.advanceTimeBy(APPEAR_ANIMATION_DURATION) 347 348 // high priority status chip is visible while low priority status chip is not visible 349 assertEquals(RUNNING_CHIP_ANIM, systemStatusAnimationScheduler.getAnimationState()) 350 assertEquals(1f, privacyChip.view.alpha) 351 assertEquals(0f, batteryChip.view.alpha) 352 } 353 354 @Test <lambda>null355 fun testPrivacyDot_isRemoved() = runTest { 356 // Instantiate class under test with TestScope from runTest 357 initializeSystemStatusAnimationScheduler(testScope = this) 358 359 // create and schedule high priority event 360 createAndScheduleFakePrivacyEvent() 361 362 // skip chip animation lifecycle and fast forward to SHOWING_PERSISTENT_DOT state 363 fastForwardAnimationToState(SHOWING_PERSISTENT_DOT) 364 assertEquals(SHOWING_PERSISTENT_DOT, systemStatusAnimationScheduler.getAnimationState()) 365 verify(listener, times(1)).onSystemStatusAnimationTransitionToPersistentDot(any()) 366 367 // remove persistent dot and verify that animationState changes to IDLE 368 systemStatusAnimationScheduler.removePersistentDot() 369 assertEquals(IDLE, systemStatusAnimationScheduler.getAnimationState()) 370 verify(listener, times(1)).onHidePersistentDot() 371 } 372 373 @Test <lambda>null374 fun testAccessibilityAnnouncement_announced() = runTest { 375 // Instantiate class under test with TestScope from runTest 376 initializeSystemStatusAnimationScheduler(testScope = this) 377 val accessibilityDesc = "Some desc" 378 val mockView = mock<View>() 379 val mockAnimatableView = 380 mock<BackgroundAnimatableView> { whenever(view).thenReturn(mockView) } 381 382 scheduleFakeEventWithView( 383 accessibilityDesc, 384 mockAnimatableView, 385 shouldAnnounceAccessibilityEvent = true 386 ) 387 fastForwardAnimationToState(ANIMATING_OUT) 388 389 verify(mockView).announceForAccessibility(eq(accessibilityDesc)) 390 } 391 392 @Test <lambda>null393 fun testAccessibilityAnnouncement_nullDesc_noAnnouncement() = runTest { 394 // Instantiate class under test with TestScope from runTest 395 initializeSystemStatusAnimationScheduler(testScope = this) 396 val accessibilityDesc = null 397 val mockView = mock<View>() 398 val mockAnimatableView = 399 mock<BackgroundAnimatableView> { whenever(view).thenReturn(mockView) } 400 401 scheduleFakeEventWithView( 402 accessibilityDesc, 403 mockAnimatableView, 404 shouldAnnounceAccessibilityEvent = true 405 ) 406 fastForwardAnimationToState(ANIMATING_OUT) 407 408 verify(mockView, never()).announceForAccessibility(any()) 409 } 410 411 @Test <lambda>null412 fun testAccessibilityAnnouncement_notNeeded_noAnnouncement() = runTest { 413 // Instantiate class under test with TestScope from runTest 414 initializeSystemStatusAnimationScheduler(testScope = this) 415 val accessibilityDesc = "something" 416 val mockView = mock<View>() 417 val mockAnimatableView = 418 mock<BackgroundAnimatableView> { whenever(view).thenReturn(mockView) } 419 420 scheduleFakeEventWithView( 421 accessibilityDesc, 422 mockAnimatableView, 423 shouldAnnounceAccessibilityEvent = false 424 ) 425 fastForwardAnimationToState(ANIMATING_OUT) 426 427 verify(mockView, never()).announceForAccessibility(any()) 428 } 429 430 @Test <lambda>null431 fun testPrivacyDot_isRemovedDuringChipAnimation() = runTest { 432 // Instantiate class under test with TestScope from runTest 433 initializeSystemStatusAnimationScheduler(testScope = this) 434 435 // create and schedule high priority event 436 createAndScheduleFakePrivacyEvent() 437 438 // skip chip animation lifecycle and fast forward to RUNNING_CHIP_ANIM state 439 fastForwardAnimationToState(RUNNING_CHIP_ANIM) 440 assertEquals(RUNNING_CHIP_ANIM, systemStatusAnimationScheduler.getAnimationState()) 441 442 // request removal of persistent dot 443 systemStatusAnimationScheduler.removePersistentDot() 444 445 // skip display time and verify that disappear animation is run 446 advanceTimeBy(DISPLAY_LENGTH + 1) 447 assertEquals(ANIMATING_OUT, systemStatusAnimationScheduler.getAnimationState()) 448 449 // skip disappear animation and verify that animationState changes to IDLE instead of 450 // SHOWING_PERSISTENT_DOT 451 animatorTestRule.advanceTimeBy(DISAPPEAR_ANIMATION_DURATION) 452 assertEquals(IDLE, systemStatusAnimationScheduler.getAnimationState()) 453 // verify that the persistent dot callbacks are not invoked 454 verify(listener, never()).onSystemStatusAnimationTransitionToPersistentDot(any()) 455 verify(listener, never()).onHidePersistentDot() 456 } 457 458 @Test <lambda>null459 fun testPrivacyDot_isRemovedDuringChipDisappearAnimation() = runTest { 460 // Instantiate class under test with TestScope from runTest 461 initializeSystemStatusAnimationScheduler(testScope = this) 462 463 // create and schedule high priority event 464 createAndScheduleFakePrivacyEvent() 465 466 // fast forward to ANIMATING_OUT state 467 fastForwardAnimationToState(ANIMATING_OUT) 468 assertEquals(ANIMATING_OUT, systemStatusAnimationScheduler.getAnimationState()) 469 verify(listener, times(1)).onSystemStatusAnimationTransitionToPersistentDot(any()) 470 471 // remove persistent dot 472 systemStatusAnimationScheduler.removePersistentDot() 473 474 // verify that the onHidePersistentDot callback is invoked 475 verify(listener, times(1)).onHidePersistentDot() 476 477 // skip disappear animation 478 animatorTestRule.advanceTimeBy(DISAPPEAR_ANIMATION_DURATION) 479 testScheduler.runCurrent() 480 481 // verify that animationState changes to IDLE 482 assertEquals(IDLE, systemStatusAnimationScheduler.getAnimationState()) 483 } 484 485 @Test <lambda>null486 fun testPrivacyEvent_forceVisibleIsUpdated_whenRescheduledDuringQueuedState() = runTest { 487 // Instantiate class under test with TestScope from runTest 488 initializeSystemStatusAnimationScheduler(testScope = this) 489 490 // create and schedule privacy event 491 createAndScheduleFakePrivacyEvent() 492 // request removal of persistent dot (sets forceVisible to false) 493 systemStatusAnimationScheduler.removePersistentDot() 494 // create and schedule a privacy event again (resets forceVisible to true) 495 createAndScheduleFakePrivacyEvent() 496 497 // skip chip animation lifecycle and fast forward to SHOWING_PERSISTENT_DOT state 498 fastForwardAnimationToState(SHOWING_PERSISTENT_DOT) 499 500 // verify that we reach SHOWING_PERSISTENT_DOT and that listener callback is invoked 501 assertEquals(SHOWING_PERSISTENT_DOT, systemStatusAnimationScheduler.getAnimationState()) 502 verify(listener, times(1)).onSystemStatusAnimationTransitionToPersistentDot(any()) 503 } 504 505 @Test <lambda>null506 fun testPrivacyEvent_forceVisibleIsUpdated_whenRescheduledDuringAnimatingState() = runTest { 507 // Instantiate class under test with TestScope from runTest 508 initializeSystemStatusAnimationScheduler(testScope = this) 509 510 // create and schedule privacy event 511 createAndScheduleFakePrivacyEvent() 512 // request removal of persistent dot (sets forceVisible to false) 513 systemStatusAnimationScheduler.removePersistentDot() 514 fastForwardAnimationToState(RUNNING_CHIP_ANIM) 515 516 // create and schedule a privacy event again (resets forceVisible to true) 517 createAndScheduleFakePrivacyEvent() 518 519 // skip status chip display time 520 advanceTimeBy(DISPLAY_LENGTH + 1) 521 assertEquals(ANIMATING_OUT, systemStatusAnimationScheduler.getAnimationState()) 522 verify(listener, times(1)).onSystemEventAnimationFinish(anyBoolean()) 523 524 // skip disappear animation 525 animatorTestRule.advanceTimeBy(DISAPPEAR_ANIMATION_DURATION) 526 527 // verify that we reach SHOWING_PERSISTENT_DOT and that listener callback is invoked 528 assertEquals(SHOWING_PERSISTENT_DOT, systemStatusAnimationScheduler.getAnimationState()) 529 verify(listener, times(1)).onSystemStatusAnimationTransitionToPersistentDot(any()) 530 } 531 532 @Test <lambda>null533 fun testNewEvent_isScheduled_whenPostedDuringRemovalAnimation() = runTest { 534 // Instantiate class under test with TestScope from runTest 535 initializeSystemStatusAnimationScheduler(testScope = this) 536 537 // create and schedule high priority event 538 createAndScheduleFakePrivacyEvent() 539 540 // skip chip animation lifecycle and fast forward to ANIMATING_OUT state 541 fastForwardAnimationToState(ANIMATING_OUT) 542 assertEquals(ANIMATING_OUT, systemStatusAnimationScheduler.getAnimationState()) 543 verify(listener, times(1)).onSystemStatusAnimationTransitionToPersistentDot(any()) 544 545 // request removal of persistent dot 546 systemStatusAnimationScheduler.removePersistentDot() 547 548 // schedule another high priority event while the event is animating out 549 createAndScheduleFakePrivacyEvent() 550 551 // verify that the state is still ANIMATING_OUT 552 assertEquals(ANIMATING_OUT, systemStatusAnimationScheduler.getAnimationState()) 553 554 // skip disappear animation duration and verify that new state is ANIMATION_QUEUED 555 animatorTestRule.advanceTimeBy(DISAPPEAR_ANIMATION_DURATION) 556 testScheduler.runCurrent() 557 assertEquals(ANIMATION_QUEUED, systemStatusAnimationScheduler.getAnimationState()) 558 // also verify that onHidePersistentDot callback is called 559 verify(listener, times(1)).onHidePersistentDot() 560 } 561 562 @Test <lambda>null563 fun testDotIsRemoved_evenIfAnimatorCallbackIsDelayed() = runTest { 564 // Instantiate class under test with TestScope from runTest 565 initializeSystemStatusAnimationScheduler(testScope = this) 566 567 // create and schedule high priority event 568 createAndScheduleFakePrivacyEvent() 569 570 // skip chip animation lifecycle and fast forward to ANIMATING_OUT state 571 fastForwardAnimationToState(ANIMATING_OUT) 572 assertEquals(ANIMATING_OUT, systemStatusAnimationScheduler.getAnimationState()) 573 verify(listener, times(1)).onSystemStatusAnimationTransitionToPersistentDot(any()) 574 575 // request removal of persistent dot 576 systemStatusAnimationScheduler.removePersistentDot() 577 578 // verify that the state is still ANIMATING_OUT 579 assertEquals(ANIMATING_OUT, systemStatusAnimationScheduler.getAnimationState()) 580 581 // skip disappear animation duration 582 testScheduler.advanceTimeBy(DISAPPEAR_ANIMATION_DURATION + 1) 583 // In an old implementation this would trigger a coroutine timeout causing the 584 // onHidePersistentDot callback to be missed. 585 testScheduler.runCurrent() 586 587 // advance animator time to invoke onAnimationEnd callback 588 animatorTestRule.advanceTimeBy(DISAPPEAR_ANIMATION_DURATION) 589 testScheduler.runCurrent() 590 591 // verify that onHidePersistentDot is invoked despite the animator callback being delayed 592 // (it's invoked more than DISAPPEAR_ANIMATION_DURATION after the dot removal was requested) 593 verify(listener, times(1)).onHidePersistentDot() 594 // verify that animationState is IDLE 595 assertEquals(IDLE, systemStatusAnimationScheduler.getAnimationState()) 596 } 597 fastForwardAnimationToStatenull598 private fun TestScope.fastForwardAnimationToState(@SystemAnimationState animationState: Int) { 599 // this function should only be called directly after posting a status event 600 assertEquals(ANIMATION_QUEUED, systemStatusAnimationScheduler.getAnimationState()) 601 if (animationState == IDLE || animationState == ANIMATION_QUEUED) return 602 // skip debounce delay 603 advanceTimeBy(DEBOUNCE_DELAY + 1) 604 605 // status chip starts animating in after debounce delay 606 assertEquals(ANIMATING_IN, systemStatusAnimationScheduler.getAnimationState()) 607 verify(listener, times(1)).onSystemEventAnimationBegin() 608 if (animationState == ANIMATING_IN) return 609 610 // skip appear animation 611 animatorTestRule.advanceTimeBy(APPEAR_ANIMATION_DURATION) 612 advanceTimeBy(APPEAR_ANIMATION_DURATION) 613 assertEquals(RUNNING_CHIP_ANIM, systemStatusAnimationScheduler.getAnimationState()) 614 if (animationState == RUNNING_CHIP_ANIM) return 615 616 // skip status chip display time 617 advanceTimeBy(DISPLAY_LENGTH + 1) 618 assertEquals(ANIMATING_OUT, systemStatusAnimationScheduler.getAnimationState()) 619 verify(listener, times(1)).onSystemEventAnimationFinish(anyBoolean()) 620 if (animationState == ANIMATING_OUT) return 621 622 // skip disappear animation 623 animatorTestRule.advanceTimeBy(DISAPPEAR_ANIMATION_DURATION) 624 } 625 createAndScheduleFakePrivacyEventnull626 private fun createAndScheduleFakePrivacyEvent(): OngoingPrivacyChip { 627 val privacyChip = OngoingPrivacyChip(mContext) 628 val fakePrivacyStatusEvent = FakePrivacyStatusEvent(viewCreator = { privacyChip }) 629 systemStatusAnimationScheduler.onStatusEvent(fakePrivacyStatusEvent) 630 return privacyChip 631 } 632 scheduleFakeEventWithViewnull633 private fun scheduleFakeEventWithView( 634 desc: String?, 635 view: BackgroundAnimatableView, 636 shouldAnnounceAccessibilityEvent: Boolean 637 ) { 638 val fakeEvent = 639 FakeStatusEvent( 640 viewCreator = { view }, 641 contentDescription = desc, 642 shouldAnnounceAccessibilityEvent = shouldAnnounceAccessibilityEvent 643 ) 644 systemStatusAnimationScheduler.onStatusEvent(fakeEvent) 645 } 646 createAndScheduleFakeBatteryEventnull647 private fun createAndScheduleFakeBatteryEvent(): BatteryStatusChip { 648 val batteryChip = BatteryStatusChip(mContext) 649 val fakeBatteryEvent = 650 FakeStatusEvent(viewCreator = { batteryChip }, priority = 50, forceVisible = false) 651 systemStatusAnimationScheduler.onStatusEvent(fakeBatteryEvent) 652 return batteryChip 653 } 654 initializeSystemStatusAnimationSchedulernull655 private fun initializeSystemStatusAnimationScheduler( 656 testScope: TestScope, 657 advancePastMinUptime: Boolean = true, 658 ) { 659 systemStatusAnimationScheduler = 660 SystemStatusAnimationSchedulerImpl( 661 systemEventCoordinator, 662 chipAnimationController, 663 statusBarWindowController, 664 dumpManager, 665 systemClock, 666 CoroutineScope(StandardTestDispatcher(testScope.testScheduler)), 667 logger 668 ) 669 // add a mock listener 670 systemStatusAnimationScheduler.addCallback(listener) 671 672 if (advancePastMinUptime) { 673 // ensure that isTooEarly() check in SystemStatusAnimationScheduler does not return true 674 systemClock.advanceTime(Process.getStartUptimeMillis() + MIN_UPTIME) 675 } 676 } 677 } 678