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