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