xref: /aosp_15_r20/frameworks/base/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt (revision d57664e9bc4670b3ecf6748a746a57c557b6bc9e)
1 /*
2  * Copyright (C) 2022 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 package com.android.keyguard
17 
18 import android.content.BroadcastReceiver
19 import android.platform.test.annotations.DisableFlags
20 import android.platform.test.annotations.EnableFlags
21 import android.provider.Settings
22 import android.view.View
23 import android.view.ViewTreeObserver
24 import android.widget.FrameLayout
25 import androidx.test.ext.junit.runners.AndroidJUnit4
26 import androidx.test.filters.SmallTest
27 import com.android.settingslib.notification.modes.TestModeBuilder.MANUAL_DND_INACTIVE
28 import com.android.systemui.Flags as AConfigFlags
29 import com.android.systemui.SysuiTestCase
30 import com.android.systemui.broadcast.BroadcastDispatcher
31 import com.android.systemui.flags.Flags
32 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
33 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
34 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
35 import com.android.systemui.keyguard.shared.model.Edge
36 import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
37 import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING
38 import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
39 import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
40 import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED
41 import com.android.systemui.keyguard.shared.model.TransitionState
42 import com.android.systemui.keyguard.shared.model.TransitionStep
43 import com.android.systemui.kosmos.testScope
44 import com.android.systemui.log.core.LogLevel
45 import com.android.systemui.log.core.LogcatOnlyMessageBuffer
46 import com.android.systemui.plugins.clocks.ClockAnimations
47 import com.android.systemui.plugins.clocks.ClockController
48 import com.android.systemui.plugins.clocks.ClockEvents
49 import com.android.systemui.plugins.clocks.ClockFaceConfig
50 import com.android.systemui.plugins.clocks.ClockFaceController
51 import com.android.systemui.plugins.clocks.ClockFaceEvents
52 import com.android.systemui.plugins.clocks.ClockMessageBuffers
53 import com.android.systemui.plugins.clocks.ClockTickRate
54 import com.android.systemui.plugins.clocks.ThemeConfig
55 import com.android.systemui.plugins.clocks.ZenData
56 import com.android.systemui.plugins.clocks.ZenData.ZenMode
57 import com.android.systemui.res.R
58 import com.android.systemui.settings.UserTracker
59 import com.android.systemui.statusbar.policy.BatteryController
60 import com.android.systemui.statusbar.policy.ConfigurationController
61 import com.android.systemui.statusbar.policy.ZenModeController
62 import com.android.systemui.statusbar.policy.data.repository.fakeZenModeRepository
63 import com.android.systemui.statusbar.policy.domain.interactor.zenModeInteractor
64 import com.android.systemui.testKosmos
65 import com.android.systemui.util.concurrency.DelayableExecutor
66 import com.android.systemui.util.mockito.any
67 import com.android.systemui.util.mockito.argumentCaptor
68 import com.android.systemui.util.mockito.capture
69 import com.android.systemui.util.mockito.eq
70 import com.android.systemui.util.mockito.mock
71 import java.util.TimeZone
72 import java.util.concurrent.Executor
73 import java.util.concurrent.TimeUnit
74 import kotlinx.coroutines.Dispatchers
75 import kotlinx.coroutines.flow.MutableStateFlow
76 import kotlinx.coroutines.runBlocking
77 import kotlinx.coroutines.test.runCurrent
78 import kotlinx.coroutines.test.runTest
79 import kotlinx.coroutines.yield
80 import org.junit.Assert.assertEquals
81 import org.junit.Before
82 import org.junit.Rule
83 import org.junit.Test
84 import org.junit.runner.RunWith
85 import org.mockito.ArgumentMatchers.anyFloat
86 import org.mockito.ArgumentMatchers.anyInt
87 import org.mockito.Mock
88 import org.mockito.Mockito.never
89 import org.mockito.Mockito.times
90 import org.mockito.Mockito.verify
91 import org.mockito.Mockito.`when` as whenever
92 import org.mockito.junit.MockitoJUnit
93 import org.mockito.kotlin.clearInvocations
94 
95 @RunWith(AndroidJUnit4::class)
96 @SmallTest
97 @OptIn(kotlinx.coroutines.ExperimentalCoroutinesApi::class)
98 class ClockEventControllerTest : SysuiTestCase() {
99 
100     private val kosmos = testKosmos()
101     private val zenModeRepository = kosmos.fakeZenModeRepository
102     private val testScope = kosmos.testScope
103 
104     @JvmField @Rule val mockito = MockitoJUnit.rule()
105 
106     private val mainExecutor = ImmediateExecutor()
107     private lateinit var repository: FakeKeyguardRepository
108     private val clockBuffers = ClockMessageBuffers(LogcatOnlyMessageBuffer(LogLevel.DEBUG))
109     private lateinit var underTest: ClockEventController
110     private lateinit var dndModeId: String
111 
112     @Mock private lateinit var broadcastDispatcher: BroadcastDispatcher
113     @Mock private lateinit var batteryController: BatteryController
114     @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
115     @Mock private lateinit var configurationController: ConfigurationController
116     @Mock private lateinit var animations: ClockAnimations
117     @Mock private lateinit var events: ClockEvents
118     @Mock private lateinit var clock: ClockController
119     @Mock private lateinit var bgExecutor: Executor
120     @Mock private lateinit var smallClockController: ClockFaceController
121     @Mock private lateinit var smallClockView: View
122     @Mock private lateinit var smallClockViewTreeObserver: ViewTreeObserver
123     @Mock private lateinit var smallClockFrame: FrameLayout
124     @Mock private lateinit var smallClockFrameViewTreeObserver: ViewTreeObserver
125     @Mock private lateinit var largeClockController: ClockFaceController
126     @Mock private lateinit var largeClockView: View
127     @Mock private lateinit var largeClockViewTreeObserver: ViewTreeObserver
128     @Mock private lateinit var smallClockEvents: ClockFaceEvents
129     @Mock private lateinit var largeClockEvents: ClockFaceEvents
130     @Mock private lateinit var parentView: View
131     @Mock private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
132     @Mock private lateinit var userTracker: UserTracker
133 
134     @Mock private lateinit var zenModeController: ZenModeController
135     private var zenModeControllerCallback: ZenModeController.Callback? = null
136 
137     @Before
setUpnull138     fun setUp() {
139         whenever(clock.smallClock).thenReturn(smallClockController)
140         whenever(clock.largeClock).thenReturn(largeClockController)
141         whenever(smallClockController.view).thenReturn(smallClockView)
142         whenever(smallClockView.parent).thenReturn(smallClockFrame)
143         whenever(smallClockView.viewTreeObserver).thenReturn(smallClockViewTreeObserver)
144         whenever(smallClockFrame.viewTreeObserver).thenReturn(smallClockFrameViewTreeObserver)
145         whenever(largeClockController.view).thenReturn(largeClockView)
146         whenever(largeClockView.viewTreeObserver).thenReturn(largeClockViewTreeObserver)
147         whenever(smallClockController.events).thenReturn(smallClockEvents)
148         whenever(largeClockController.events).thenReturn(largeClockEvents)
149         whenever(clock.events).thenReturn(events)
150         whenever(smallClockController.animations).thenReturn(animations)
151         whenever(largeClockController.animations).thenReturn(animations)
152         whenever(smallClockController.config)
153             .thenReturn(ClockFaceConfig(tickRate = ClockTickRate.PER_MINUTE))
154         whenever(largeClockController.config)
155             .thenReturn(ClockFaceConfig(tickRate = ClockTickRate.PER_MINUTE))
156         whenever(smallClockController.theme).thenReturn(ThemeConfig(true, null))
157         whenever(largeClockController.theme).thenReturn(ThemeConfig(true, null))
158         whenever(userTracker.userId).thenReturn(1)
159 
160         dndModeId = MANUAL_DND_INACTIVE.id
161         zenModeRepository.addMode(MANUAL_DND_INACTIVE)
162 
163         repository = FakeKeyguardRepository()
164 
165         val withDeps = KeyguardInteractorFactory.create(repository = repository)
166 
167         withDeps.featureFlags.apply { set(Flags.REGION_SAMPLING, false) }
168         underTest =
169             ClockEventController(
170                 withDeps.keyguardInteractor,
171                 keyguardTransitionInteractor,
172                 broadcastDispatcher,
173                 batteryController,
174                 keyguardUpdateMonitor,
175                 configurationController,
176                 context.resources,
177                 context,
178                 mainExecutor,
179                 bgExecutor,
180                 clockBuffers,
181                 withDeps.featureFlags,
182                 zenModeController,
183                 kosmos.zenModeInteractor,
184                 userTracker,
185             )
186         underTest.clock = clock
187 
188         runBlocking(IMMEDIATE) {
189             underTest.registerListeners(parentView)
190 
191             repository.setIsDozing(true)
192             repository.setDozeAmount(1f)
193         }
194 
195         val zenCallbackCaptor = argumentCaptor<ZenModeController.Callback>()
196         verify(zenModeController).addCallback(zenCallbackCaptor.capture())
197         zenModeControllerCallback = zenCallbackCaptor.value
198     }
199 
200     @Test
clockSet_validateInitializationnull201     fun clockSet_validateInitialization() {
202         verify(clock).initialize(any(), anyFloat(), anyFloat())
203     }
204 
205     @Test
clockUnset_validateStatenull206     fun clockUnset_validateState() {
207         underTest.clock = null
208 
209         assertEquals(underTest.clock, null)
210     }
211 
212     @Test
themeChanged_verifyClockPaletteUpdatednull213     fun themeChanged_verifyClockPaletteUpdated() =
214         runBlocking(IMMEDIATE) {
215             verify(smallClockEvents).onThemeChanged(any())
216             verify(largeClockEvents).onThemeChanged(any())
217 
218             val captor = argumentCaptor<ConfigurationController.ConfigurationListener>()
219             verify(configurationController).addCallback(capture(captor))
220             captor.value.onThemeChanged()
221 
222             verify(smallClockEvents, times(2)).onThemeChanged(any())
223             verify(largeClockEvents, times(2)).onThemeChanged(any())
224         }
225 
226     @Test
fontChanged_verifyFontSizeUpdatednull227     fun fontChanged_verifyFontSizeUpdated() =
228         runBlocking(IMMEDIATE) {
229             val captor = argumentCaptor<ConfigurationController.ConfigurationListener>()
230             verify(configurationController).addCallback(capture(captor))
231             captor.value.onDensityOrFontScaleChanged()
232 
233             verify(smallClockEvents, times(2)).onFontSettingChanged(anyFloat())
234             verify(largeClockEvents, times(2)).onFontSettingChanged(anyFloat())
235         }
236 
237     @Test
batteryCallback_keyguardShowingCharging_verifyChargeAnimationnull238     fun batteryCallback_keyguardShowingCharging_verifyChargeAnimation() =
239         runBlocking(IMMEDIATE) {
240             val batteryCaptor = argumentCaptor<BatteryController.BatteryStateChangeCallback>()
241             verify(batteryController).addCallback(capture(batteryCaptor))
242             val keyguardCaptor = argumentCaptor<KeyguardUpdateMonitorCallback>()
243             verify(keyguardUpdateMonitor).registerCallback(capture(keyguardCaptor))
244             keyguardCaptor.value.onKeyguardVisibilityChanged(true)
245             batteryCaptor.value.onBatteryLevelChanged(10, false, true)
246 
247             verify(animations, times(2)).charge()
248         }
249 
250     @Test
batteryCallback_keyguardShowingCharging_Duplicate_verifyChargeAnimationnull251     fun batteryCallback_keyguardShowingCharging_Duplicate_verifyChargeAnimation() =
252         runBlocking(IMMEDIATE) {
253             val batteryCaptor = argumentCaptor<BatteryController.BatteryStateChangeCallback>()
254             verify(batteryController).addCallback(capture(batteryCaptor))
255             val keyguardCaptor = argumentCaptor<KeyguardUpdateMonitorCallback>()
256             verify(keyguardUpdateMonitor).registerCallback(capture(keyguardCaptor))
257             keyguardCaptor.value.onKeyguardVisibilityChanged(true)
258             batteryCaptor.value.onBatteryLevelChanged(10, false, true)
259             batteryCaptor.value.onBatteryLevelChanged(10, false, true)
260 
261             verify(animations, times(2)).charge()
262         }
263 
264     @Test
batteryCallback_keyguardHiddenCharging_verifyChargeAnimationnull265     fun batteryCallback_keyguardHiddenCharging_verifyChargeAnimation() =
266         runBlocking(IMMEDIATE) {
267             val batteryCaptor = argumentCaptor<BatteryController.BatteryStateChangeCallback>()
268             verify(batteryController).addCallback(capture(batteryCaptor))
269             val keyguardCaptor = argumentCaptor<KeyguardUpdateMonitorCallback>()
270             verify(keyguardUpdateMonitor).registerCallback(capture(keyguardCaptor))
271             keyguardCaptor.value.onKeyguardVisibilityChanged(false)
272             batteryCaptor.value.onBatteryLevelChanged(10, false, true)
273 
274             verify(animations, never()).charge()
275         }
276 
277     @Test
batteryCallback_keyguardShowingNotCharging_verifyChargeAnimationnull278     fun batteryCallback_keyguardShowingNotCharging_verifyChargeAnimation() =
279         runBlocking(IMMEDIATE) {
280             val batteryCaptor = argumentCaptor<BatteryController.BatteryStateChangeCallback>()
281             verify(batteryController).addCallback(capture(batteryCaptor))
282             val keyguardCaptor = argumentCaptor<KeyguardUpdateMonitorCallback>()
283             verify(keyguardUpdateMonitor).registerCallback(capture(keyguardCaptor))
284             keyguardCaptor.value.onKeyguardVisibilityChanged(true)
285             batteryCaptor.value.onBatteryLevelChanged(10, false, false)
286 
287             verify(animations, never()).charge()
288         }
289 
290     @Test
localeCallback_verifyClockNotifiednull291     fun localeCallback_verifyClockNotified() =
292         runBlocking(IMMEDIATE) {
293             val captor = argumentCaptor<BroadcastReceiver>()
294             verify(broadcastDispatcher)
295                 .registerReceiver(capture(captor), any(), eq(null), eq(null), anyInt(), eq(null))
296             captor.value.onReceive(context, mock())
297 
298             verify(events).onLocaleChanged(any())
299         }
300 
301     @Test
302     @DisableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
keyguardCallback_visibilityChanged_clockDozeCallednull303     fun keyguardCallback_visibilityChanged_clockDozeCalled() =
304         runBlocking(IMMEDIATE) {
305             val captor = argumentCaptor<KeyguardUpdateMonitorCallback>()
306             verify(keyguardUpdateMonitor).registerCallback(capture(captor))
307 
308             captor.value.onKeyguardVisibilityChanged(true)
309             verify(animations, never()).doze(0f)
310 
311             captor.value.onKeyguardVisibilityChanged(false)
312             verify(animations, times(2)).doze(0f)
313         }
314 
315     @Test
keyguardCallback_timeFormat_clockNotifiednull316     fun keyguardCallback_timeFormat_clockNotified() =
317         runBlocking(IMMEDIATE) {
318             val captor = argumentCaptor<KeyguardUpdateMonitorCallback>()
319             verify(keyguardUpdateMonitor).registerCallback(capture(captor))
320             captor.value.onTimeFormatChanged("12h")
321 
322             verify(events).onTimeFormatChanged(false)
323         }
324 
325     @Test
keyguardCallback_timezoneChanged_clockNotifiednull326     fun keyguardCallback_timezoneChanged_clockNotified() =
327         runBlocking(IMMEDIATE) {
328             val mockTimeZone = mock<TimeZone>()
329             val captor = argumentCaptor<KeyguardUpdateMonitorCallback>()
330             verify(keyguardUpdateMonitor).registerCallback(capture(captor))
331             captor.value.onTimeZoneChanged(mockTimeZone)
332 
333             verify(events).onTimeZoneChanged(mockTimeZone)
334         }
335 
336     @Test
keyguardCallback_userSwitched_clockNotifiednull337     fun keyguardCallback_userSwitched_clockNotified() =
338         runBlocking(IMMEDIATE) {
339             val captor = argumentCaptor<KeyguardUpdateMonitorCallback>()
340             verify(keyguardUpdateMonitor).registerCallback(capture(captor))
341             captor.value.onUserSwitchComplete(10)
342 
343             verify(events).onTimeFormatChanged(false)
344         }
345 
346     @Test
keyguardCallback_verifyKeyguardChangednull347     fun keyguardCallback_verifyKeyguardChanged() =
348         runBlocking(IMMEDIATE) {
349             val job = underTest.listenForDozeAmount(this)
350             repository.setDozeAmount(0.4f)
351 
352             yield()
353 
354             verify(animations, times(2)).doze(0.4f)
355 
356             job.cancel()
357         }
358 
359     @Test
listenForDozeAmountTransition_updatesClockDozeAmountnull360     fun listenForDozeAmountTransition_updatesClockDozeAmount() =
361         runBlocking(IMMEDIATE) {
362             val transitionStep = MutableStateFlow(TransitionStep())
363             whenever(keyguardTransitionInteractor.transition(Edge.create(LOCKSCREEN, AOD)))
364                 .thenReturn(transitionStep)
365             whenever(keyguardTransitionInteractor.transition(Edge.create(AOD, LOCKSCREEN)))
366                 .thenReturn(transitionStep)
367 
368             val job = underTest.listenForDozeAmountTransition(this)
369             transitionStep.value =
370                 TransitionStep(
371                     from = LOCKSCREEN,
372                     to = AOD,
373                     value = 0.4f,
374                     transitionState = TransitionState.RUNNING,
375                 )
376             yield()
377 
378             verify(animations, times(2)).doze(0.4f)
379 
380             job.cancel()
381         }
382 
383     @Test
listenForTransitionToAodFromGone_updatesClockDozeAmountToOnenull384     fun listenForTransitionToAodFromGone_updatesClockDozeAmountToOne() =
385         runBlocking(IMMEDIATE) {
386             val transitionStep = MutableStateFlow(TransitionStep())
387             whenever(keyguardTransitionInteractor.transition(Edge.create(to = AOD)))
388                 .thenReturn(transitionStep)
389 
390             val job = underTest.listenForAnyStateToAodTransition(this)
391             transitionStep.value =
392                 TransitionStep(from = GONE, to = AOD, transitionState = TransitionState.STARTED)
393             yield()
394 
395             verify(animations, times(2)).doze(1f)
396 
397             job.cancel()
398         }
399 
400     @Test
listenForTransitionToLSFromOccluded_updatesClockDozeAmountToZeronull401     fun listenForTransitionToLSFromOccluded_updatesClockDozeAmountToZero() =
402         runBlocking(IMMEDIATE) {
403             val transitionStep = MutableStateFlow(TransitionStep())
404             whenever(keyguardTransitionInteractor.transition(Edge.create(to = LOCKSCREEN)))
405                 .thenReturn(transitionStep)
406 
407             val job = underTest.listenForAnyStateToLockscreenTransition(this)
408             transitionStep.value =
409                 TransitionStep(
410                     from = OCCLUDED,
411                     to = LOCKSCREEN,
412                     transitionState = TransitionState.STARTED,
413                 )
414             yield()
415 
416             verify(animations, times(2)).doze(0f)
417 
418             job.cancel()
419         }
420 
421     @Test
listenForTransitionToAodFromLockscreen_neverUpdatesClockDozeAmountnull422     fun listenForTransitionToAodFromLockscreen_neverUpdatesClockDozeAmount() =
423         runBlocking(IMMEDIATE) {
424             val transitionStep = MutableStateFlow(TransitionStep())
425             whenever(keyguardTransitionInteractor.transition(Edge.create(to = AOD)))
426                 .thenReturn(transitionStep)
427 
428             val job = underTest.listenForAnyStateToAodTransition(this)
429             transitionStep.value =
430                 TransitionStep(
431                     from = LOCKSCREEN,
432                     to = AOD,
433                     transitionState = TransitionState.STARTED,
434                 )
435             yield()
436 
437             verify(animations, never()).doze(1f)
438 
439             job.cancel()
440         }
441 
442     @Test
listenForAnyStateToLockscreenTransition_neverUpdatesClockDozeAmountnull443     fun listenForAnyStateToLockscreenTransition_neverUpdatesClockDozeAmount() =
444         runBlocking(IMMEDIATE) {
445             val transitionStep = MutableStateFlow(TransitionStep())
446             whenever(keyguardTransitionInteractor.transition(Edge.create(to = LOCKSCREEN)))
447                 .thenReturn(transitionStep)
448 
449             val job = underTest.listenForAnyStateToLockscreenTransition(this)
450             transitionStep.value =
451                 TransitionStep(
452                     from = AOD,
453                     to = LOCKSCREEN,
454                     transitionState = TransitionState.STARTED,
455                 )
456             yield()
457 
458             verify(animations, never()).doze(0f)
459 
460             job.cancel()
461         }
462 
463     @Test
listenForAnyStateToDozingTransition_UpdatesClockDozeAmountToOnenull464     fun listenForAnyStateToDozingTransition_UpdatesClockDozeAmountToOne() =
465         runBlocking(IMMEDIATE) {
466             val transitionStep = MutableStateFlow(TransitionStep())
467             whenever(keyguardTransitionInteractor.transition(Edge.create(to = DOZING)))
468                 .thenReturn(transitionStep)
469 
470             val job = underTest.listenForAnyStateToDozingTransition(this)
471             transitionStep.value =
472                 TransitionStep(
473                     from = LOCKSCREEN,
474                     to = DOZING,
475                     transitionState = TransitionState.STARTED,
476                 )
477             yield()
478 
479             verify(animations, times(2)).doze(1f)
480 
481             job.cancel()
482         }
483 
484     @Test
unregisterListeners_validatenull485     fun unregisterListeners_validate() =
486         runBlocking(IMMEDIATE) {
487             underTest.unregisterListeners()
488             verify(broadcastDispatcher).unregisterReceiver(any())
489             verify(configurationController).removeCallback(any())
490             verify(batteryController).removeCallback(any())
491             verify(keyguardUpdateMonitor).removeCallback(any())
492             verify(smallClockController.view)
493                 .removeOnAttachStateChangeListener(underTest.smallClockOnAttachStateChangeListener)
494             verify(largeClockController.view)
495                 .removeOnAttachStateChangeListener(underTest.largeClockOnAttachStateChangeListener)
496         }
497 
498     @Test
registerOnAttachStateChangeListener_validatenull499     fun registerOnAttachStateChangeListener_validate() =
500         runBlocking(IMMEDIATE) {
501             verify(smallClockController.view)
502                 .addOnAttachStateChangeListener(underTest.smallClockOnAttachStateChangeListener)
503             verify(largeClockController.view)
504                 .addOnAttachStateChangeListener(underTest.largeClockOnAttachStateChangeListener)
505         }
506 
507     @Test
registerAndRemoveOnGlobalLayoutListener_correctlynull508     fun registerAndRemoveOnGlobalLayoutListener_correctly() =
509         runBlocking(IMMEDIATE) {
510             underTest.smallClockOnAttachStateChangeListener!!.onViewAttachedToWindow(smallClockView)
511             verify(smallClockFrame.viewTreeObserver).addOnGlobalLayoutListener(any())
512             underTest.smallClockOnAttachStateChangeListener!!.onViewDetachedFromWindow(
513                 smallClockView
514             )
515             verify(smallClockFrame.viewTreeObserver).removeOnGlobalLayoutListener(any())
516         }
517 
518     @Test
registerOnGlobalLayoutListener_RemoveOnAttachStateChangeListener_correctlynull519     fun registerOnGlobalLayoutListener_RemoveOnAttachStateChangeListener_correctly() =
520         runBlocking(IMMEDIATE) {
521             underTest.smallClockOnAttachStateChangeListener!!.onViewAttachedToWindow(smallClockView)
522             verify(smallClockFrame.viewTreeObserver).addOnGlobalLayoutListener(any())
523             underTest.unregisterListeners()
524             verify(smallClockFrame.viewTreeObserver).removeOnGlobalLayoutListener(any())
525         }
526 
527     @Test
528     @EnableFlags(android.app.Flags.FLAG_MODES_UI)
listenForDnd_onDndChange_updatesClockZenModenull529     fun listenForDnd_onDndChange_updatesClockZenMode() =
530         testScope.runTest {
531             underTest.listenForDnd(testScope.backgroundScope)
532             runCurrent()
533             clearInvocations(events)
534 
535             zenModeRepository.activateMode(dndModeId)
536             runCurrent()
537 
538             verify(events)
539                 .onZenDataChanged(
540                     eq(ZenData(ZenMode.IMPORTANT_INTERRUPTIONS, R.string::dnd_is_on.name))
541                 )
542 
543             zenModeRepository.deactivateMode(dndModeId)
544             runCurrent()
545 
546             verify(events).onZenDataChanged(eq(ZenData(ZenMode.OFF, R.string::dnd_is_off.name)))
547         }
548 
549     @Test
550     @DisableFlags(android.app.Flags.FLAG_MODES_UI)
zenModeControllerCallback_onDndChange_updatesClockZenModenull551     fun zenModeControllerCallback_onDndChange_updatesClockZenMode() =
552         runBlocking(IMMEDIATE) {
553             zenModeControllerCallback!!.onZenChanged(
554                 Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS
555             )
556 
557             verify(events)
558                 .onZenDataChanged(
559                     eq(ZenData(ZenMode.IMPORTANT_INTERRUPTIONS, R.string::dnd_is_on.name))
560                 )
561 
562             zenModeControllerCallback!!.onZenChanged(Settings.Global.ZEN_MODE_OFF)
563 
564             verify(events).onZenDataChanged(eq(ZenData(ZenMode.OFF, R.string::dnd_is_off.name)))
565         }
566 
567     companion object {
568         private val IMMEDIATE = Dispatchers.Main.immediate
569     }
570 }
571 
572 private class ImmediateExecutor : DelayableExecutor {
executenull573     override fun execute(runnable: Runnable) {
574         runnable.run()
575     }
576 
executeDelayednull577     override fun executeDelayed(runnable: Runnable, delay: Long, unit: TimeUnit) =
578         runnable.apply { run() }
579 
executeAtTimenull580     override fun executeAtTime(runnable: Runnable, uptimeMillis: Long, unit: TimeUnit) =
581         runnable.apply { run() }
582 }
583