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.testing.AndroidTestingRunner
20 import android.view.View
21 import android.view.ViewTreeObserver
22 import android.widget.FrameLayout
23 import androidx.test.filters.SmallTest
24 import com.android.systemui.SysuiTestCase
25 import com.android.systemui.broadcast.BroadcastDispatcher
26 import com.android.systemui.flags.Flags
27 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
28 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
29 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
30 import com.android.systemui.keyguard.shared.model.Edge
31 import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
32 import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING
33 import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
34 import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
35 import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED
36 import com.android.systemui.keyguard.shared.model.TransitionState
37 import com.android.systemui.keyguard.shared.model.TransitionStep
38 import com.android.systemui.log.core.LogLevel
39 import com.android.systemui.log.core.LogcatOnlyMessageBuffer
40 import com.android.systemui.plugins.clocks.ClockAnimations
41 import com.android.systemui.plugins.clocks.ClockController
42 import com.android.systemui.plugins.clocks.ClockEvents
43 import com.android.systemui.plugins.clocks.ClockFaceConfig
44 import com.android.systemui.plugins.clocks.ClockFaceController
45 import com.android.systemui.plugins.clocks.ClockFaceEvents
46 import com.android.systemui.plugins.clocks.ClockMessageBuffers
47 import com.android.systemui.plugins.clocks.ClockTickRate
48 import com.android.systemui.statusbar.policy.BatteryController
49 import com.android.systemui.statusbar.policy.ConfigurationController
50 import com.android.systemui.statusbar.policy.ZenModeController
51 import com.android.systemui.util.concurrency.DelayableExecutor
52 import com.android.systemui.util.mockito.any
53 import com.android.systemui.util.mockito.argumentCaptor
54 import com.android.systemui.util.mockito.capture
55 import com.android.systemui.util.mockito.eq
56 import com.android.systemui.util.mockito.mock
57 import java.util.TimeZone
58 import java.util.concurrent.Executor
59 import kotlinx.coroutines.Dispatchers
60 import kotlinx.coroutines.flow.MutableStateFlow
61 import kotlinx.coroutines.runBlocking
62 import kotlinx.coroutines.yield
63 import org.junit.Assert.assertEquals
64 import org.junit.Before
65 import org.junit.Rule
66 import org.junit.Test
67 import org.junit.runner.RunWith
68 import org.mockito.ArgumentMatchers.anyBoolean
69 import org.mockito.ArgumentMatchers.anyFloat
70 import org.mockito.ArgumentMatchers.anyInt
71 import org.mockito.Mock
72 import org.mockito.Mockito.never
73 import org.mockito.Mockito.times
74 import org.mockito.Mockito.verify
75 import org.mockito.junit.MockitoJUnit
76 import com.android.systemui.Flags as AConfigFlags
77 import org.mockito.Mockito.`when` as whenever
78 
79 @RunWith(AndroidTestingRunner::class)
80 @SmallTest
81 class ClockEventControllerTest : SysuiTestCase() {
82 
83     @JvmField @Rule val mockito = MockitoJUnit.rule()
84     @Mock private lateinit var broadcastDispatcher: BroadcastDispatcher
85     @Mock private lateinit var batteryController: BatteryController
86     @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
87     @Mock private lateinit var configurationController: ConfigurationController
88     @Mock private lateinit var animations: ClockAnimations
89     @Mock private lateinit var events: ClockEvents
90     @Mock private lateinit var clock: ClockController
91     @Mock private lateinit var mainExecutor: DelayableExecutor
92     @Mock private lateinit var bgExecutor: Executor
93     @Mock private lateinit var smallClockController: ClockFaceController
94     @Mock private lateinit var smallClockView: View
95     @Mock private lateinit var smallClockViewTreeObserver: ViewTreeObserver
96     @Mock private lateinit var smallClockFrame: FrameLayout
97     @Mock private lateinit var smallClockFrameViewTreeObserver: ViewTreeObserver
98     @Mock private lateinit var largeClockController: ClockFaceController
99     @Mock private lateinit var largeClockView: View
100     @Mock private lateinit var largeClockViewTreeObserver: ViewTreeObserver
101     @Mock private lateinit var smallClockEvents: ClockFaceEvents
102     @Mock private lateinit var largeClockEvents: ClockFaceEvents
103     @Mock private lateinit var parentView: View
104     private lateinit var repository: FakeKeyguardRepository
105     @Mock private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
106     private val messageBuffer = LogcatOnlyMessageBuffer(LogLevel.DEBUG)
107     private val clockBuffers = ClockMessageBuffers(messageBuffer, messageBuffer, messageBuffer)
108     private lateinit var underTest: ClockEventController
109     @Mock private lateinit var zenModeController: ZenModeController
110 
111     @Before
setUpnull112     fun setUp() {
113         whenever(clock.smallClock).thenReturn(smallClockController)
114         whenever(clock.largeClock).thenReturn(largeClockController)
115         whenever(smallClockController.view).thenReturn(smallClockView)
116         whenever(smallClockView.parent).thenReturn(smallClockFrame)
117         whenever(smallClockView.viewTreeObserver).thenReturn(smallClockViewTreeObserver)
118         whenever(smallClockFrame.viewTreeObserver).thenReturn(smallClockFrameViewTreeObserver)
119         whenever(largeClockController.view).thenReturn(largeClockView)
120         whenever(largeClockView.viewTreeObserver).thenReturn(largeClockViewTreeObserver)
121         whenever(smallClockController.events).thenReturn(smallClockEvents)
122         whenever(largeClockController.events).thenReturn(largeClockEvents)
123         whenever(clock.events).thenReturn(events)
124         whenever(smallClockController.animations).thenReturn(animations)
125         whenever(largeClockController.animations).thenReturn(animations)
126         whenever(smallClockController.config)
127             .thenReturn(ClockFaceConfig(tickRate = ClockTickRate.PER_MINUTE))
128         whenever(largeClockController.config)
129             .thenReturn(ClockFaceConfig(tickRate = ClockTickRate.PER_MINUTE))
130 
131         repository = FakeKeyguardRepository()
132 
133         val withDeps =
134             KeyguardInteractorFactory.create(
135                 repository = repository,
136             )
137 
138         withDeps.featureFlags.apply { set(Flags.REGION_SAMPLING, false) }
139         underTest =
140             ClockEventController(
141                 withDeps.keyguardInteractor,
142                 keyguardTransitionInteractor,
143                 broadcastDispatcher,
144                 batteryController,
145                 keyguardUpdateMonitor,
146                 configurationController,
147                 context.resources,
148                 context,
149                 mainExecutor,
150                 bgExecutor,
151                 clockBuffers,
152                 withDeps.featureFlags,
153                 zenModeController
154             )
155         underTest.clock = clock
156 
157         runBlocking(IMMEDIATE) {
158             underTest.registerListeners(parentView)
159 
160             repository.setIsDozing(true)
161             repository.setDozeAmount(1f)
162         }
163     }
164 
165     @Test
clockSet_validateInitializationnull166     fun clockSet_validateInitialization() {
167         verify(clock).initialize(any(), anyFloat(), anyFloat())
168     }
169 
170     @Test
clockUnset_validateStatenull171     fun clockUnset_validateState() {
172         underTest.clock = null
173 
174         assertEquals(underTest.clock, null)
175     }
176 
177     @Test
themeChanged_verifyClockPaletteUpdatednull178     fun themeChanged_verifyClockPaletteUpdated() =
179         runBlocking(IMMEDIATE) {
180             verify(smallClockEvents).onRegionDarknessChanged(anyBoolean())
181             verify(largeClockEvents).onRegionDarknessChanged(anyBoolean())
182 
183             val captor = argumentCaptor<ConfigurationController.ConfigurationListener>()
184             verify(configurationController).addCallback(capture(captor))
185             captor.value.onThemeChanged()
186 
187             verify(events).onColorPaletteChanged(any())
188         }
189 
190     @Test
fontChanged_verifyFontSizeUpdatednull191     fun fontChanged_verifyFontSizeUpdated() =
192         runBlocking(IMMEDIATE) {
193             val captor = argumentCaptor<ConfigurationController.ConfigurationListener>()
194             verify(configurationController).addCallback(capture(captor))
195             captor.value.onDensityOrFontScaleChanged()
196 
197             verify(smallClockEvents, times(2)).onFontSettingChanged(anyFloat())
198             verify(largeClockEvents, times(2)).onFontSettingChanged(anyFloat())
199         }
200 
201     @Test
batteryCallback_keyguardShowingCharging_verifyChargeAnimationnull202     fun batteryCallback_keyguardShowingCharging_verifyChargeAnimation() =
203         runBlocking(IMMEDIATE) {
204             val batteryCaptor = argumentCaptor<BatteryController.BatteryStateChangeCallback>()
205             verify(batteryController).addCallback(capture(batteryCaptor))
206             val keyguardCaptor = argumentCaptor<KeyguardUpdateMonitorCallback>()
207             verify(keyguardUpdateMonitor).registerCallback(capture(keyguardCaptor))
208             keyguardCaptor.value.onKeyguardVisibilityChanged(true)
209             batteryCaptor.value.onBatteryLevelChanged(10, false, true)
210 
211             verify(animations, times(2)).charge()
212         }
213 
214     @Test
batteryCallback_keyguardShowingCharging_Duplicate_verifyChargeAnimationnull215     fun batteryCallback_keyguardShowingCharging_Duplicate_verifyChargeAnimation() =
216         runBlocking(IMMEDIATE) {
217             val batteryCaptor = argumentCaptor<BatteryController.BatteryStateChangeCallback>()
218             verify(batteryController).addCallback(capture(batteryCaptor))
219             val keyguardCaptor = argumentCaptor<KeyguardUpdateMonitorCallback>()
220             verify(keyguardUpdateMonitor).registerCallback(capture(keyguardCaptor))
221             keyguardCaptor.value.onKeyguardVisibilityChanged(true)
222             batteryCaptor.value.onBatteryLevelChanged(10, false, true)
223             batteryCaptor.value.onBatteryLevelChanged(10, false, true)
224 
225             verify(animations, times(2)).charge()
226         }
227 
228     @Test
batteryCallback_keyguardHiddenCharging_verifyChargeAnimationnull229     fun batteryCallback_keyguardHiddenCharging_verifyChargeAnimation() =
230         runBlocking(IMMEDIATE) {
231             val batteryCaptor = argumentCaptor<BatteryController.BatteryStateChangeCallback>()
232             verify(batteryController).addCallback(capture(batteryCaptor))
233             val keyguardCaptor = argumentCaptor<KeyguardUpdateMonitorCallback>()
234             verify(keyguardUpdateMonitor).registerCallback(capture(keyguardCaptor))
235             keyguardCaptor.value.onKeyguardVisibilityChanged(false)
236             batteryCaptor.value.onBatteryLevelChanged(10, false, true)
237 
238             verify(animations, never()).charge()
239         }
240 
241     @Test
batteryCallback_keyguardShowingNotCharging_verifyChargeAnimationnull242     fun batteryCallback_keyguardShowingNotCharging_verifyChargeAnimation() =
243         runBlocking(IMMEDIATE) {
244             val batteryCaptor = argumentCaptor<BatteryController.BatteryStateChangeCallback>()
245             verify(batteryController).addCallback(capture(batteryCaptor))
246             val keyguardCaptor = argumentCaptor<KeyguardUpdateMonitorCallback>()
247             verify(keyguardUpdateMonitor).registerCallback(capture(keyguardCaptor))
248             keyguardCaptor.value.onKeyguardVisibilityChanged(true)
249             batteryCaptor.value.onBatteryLevelChanged(10, false, false)
250 
251             verify(animations, never()).charge()
252         }
253 
254     @Test
localeCallback_verifyClockNotifiednull255     fun localeCallback_verifyClockNotified() =
256         runBlocking(IMMEDIATE) {
257             val captor = argumentCaptor<BroadcastReceiver>()
258             verify(broadcastDispatcher)
259                 .registerReceiver(capture(captor), any(), eq(null), eq(null), anyInt(), eq(null))
260             captor.value.onReceive(context, mock())
261 
262             verify(events).onLocaleChanged(any())
263         }
264 
265     @Test
keyguardCallback_visibilityChanged_clockDozeCallednull266     fun keyguardCallback_visibilityChanged_clockDozeCalled() =
267         runBlocking(IMMEDIATE) {
268             mSetFlagsRule.disableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
269             val captor = argumentCaptor<KeyguardUpdateMonitorCallback>()
270             verify(keyguardUpdateMonitor).registerCallback(capture(captor))
271 
272             captor.value.onKeyguardVisibilityChanged(true)
273             verify(animations, never()).doze(0f)
274 
275             captor.value.onKeyguardVisibilityChanged(false)
276             verify(animations, times(2)).doze(0f)
277         }
278 
279     @Test
keyguardCallback_timeFormat_clockNotifiednull280     fun keyguardCallback_timeFormat_clockNotified() =
281         runBlocking(IMMEDIATE) {
282             val captor = argumentCaptor<KeyguardUpdateMonitorCallback>()
283             verify(keyguardUpdateMonitor).registerCallback(capture(captor))
284             captor.value.onTimeFormatChanged("12h")
285 
286             verify(events).onTimeFormatChanged(false)
287         }
288 
289     @Test
keyguardCallback_timezoneChanged_clockNotifiednull290     fun keyguardCallback_timezoneChanged_clockNotified() =
291         runBlocking(IMMEDIATE) {
292             val mockTimeZone = mock<TimeZone>()
293             val captor = argumentCaptor<KeyguardUpdateMonitorCallback>()
294             verify(keyguardUpdateMonitor).registerCallback(capture(captor))
295             captor.value.onTimeZoneChanged(mockTimeZone)
296 
297             verify(events).onTimeZoneChanged(mockTimeZone)
298         }
299 
300     @Test
keyguardCallback_userSwitched_clockNotifiednull301     fun keyguardCallback_userSwitched_clockNotified() =
302         runBlocking(IMMEDIATE) {
303             val captor = argumentCaptor<KeyguardUpdateMonitorCallback>()
304             verify(keyguardUpdateMonitor).registerCallback(capture(captor))
305             captor.value.onUserSwitchComplete(10)
306 
307             verify(events).onTimeFormatChanged(false)
308         }
309 
310     @Test
keyguardCallback_verifyKeyguardChangednull311     fun keyguardCallback_verifyKeyguardChanged() =
312         runBlocking(IMMEDIATE) {
313             val job = underTest.listenForDozeAmount(this)
314             repository.setDozeAmount(0.4f)
315 
316             yield()
317 
318             verify(animations, times(2)).doze(0.4f)
319 
320             job.cancel()
321         }
322 
323     @Test
listenForDozeAmountTransition_updatesClockDozeAmountnull324     fun listenForDozeAmountTransition_updatesClockDozeAmount() =
325         runBlocking(IMMEDIATE) {
326             val transitionStep = MutableStateFlow(TransitionStep())
327             whenever(keyguardTransitionInteractor.transition(Edge.create(LOCKSCREEN, AOD)))
328                 .thenReturn(transitionStep)
329             whenever(keyguardTransitionInteractor.transition(Edge.create(AOD, LOCKSCREEN)))
330                 .thenReturn(transitionStep)
331 
332             val job = underTest.listenForDozeAmountTransition(this)
333             transitionStep.value =
334                 TransitionStep(
335                     from = LOCKSCREEN,
336                     to = AOD,
337                     value = 0.4f,
338                     transitionState = TransitionState.RUNNING,
339                 )
340             yield()
341 
342             verify(animations, times(2)).doze(0.4f)
343 
344             job.cancel()
345         }
346 
347     @Test
listenForTransitionToAodFromGone_updatesClockDozeAmountToOnenull348     fun listenForTransitionToAodFromGone_updatesClockDozeAmountToOne() =
349         runBlocking(IMMEDIATE) {
350             val transitionStep = MutableStateFlow(TransitionStep())
351             whenever(keyguardTransitionInteractor
352                 .transition(Edge.create(to = AOD)))
353                 .thenReturn(transitionStep)
354 
355             val job = underTest.listenForAnyStateToAodTransition(this)
356             transitionStep.value =
357                 TransitionStep(
358                     from = GONE,
359                     to = AOD,
360                     transitionState = TransitionState.STARTED,
361                 )
362             yield()
363 
364             verify(animations, times(2)).doze(1f)
365 
366             job.cancel()
367         }
368 
369     @Test
listenForTransitionToLSFromOccluded_updatesClockDozeAmountToZeronull370     fun listenForTransitionToLSFromOccluded_updatesClockDozeAmountToZero() =
371         runBlocking(IMMEDIATE) {
372             val transitionStep = MutableStateFlow(TransitionStep())
373             whenever(keyguardTransitionInteractor
374                 .transition(Edge.create(to = LOCKSCREEN)))
375                 .thenReturn(transitionStep)
376 
377             val job = underTest.listenForAnyStateToLockscreenTransition(this)
378             transitionStep.value =
379                 TransitionStep(
380                     from = OCCLUDED,
381                     to = LOCKSCREEN,
382                     transitionState = TransitionState.STARTED,
383                 )
384             yield()
385 
386             verify(animations, times(2)).doze(0f)
387 
388             job.cancel()
389         }
390 
391     @Test
listenForTransitionToAodFromLockscreen_neverUpdatesClockDozeAmountnull392     fun listenForTransitionToAodFromLockscreen_neverUpdatesClockDozeAmount() =
393         runBlocking(IMMEDIATE) {
394             val transitionStep = MutableStateFlow(TransitionStep())
395             whenever(keyguardTransitionInteractor
396                 .transition(Edge.create(to = AOD)))
397                 .thenReturn(transitionStep)
398 
399             val job = underTest.listenForAnyStateToAodTransition(this)
400             transitionStep.value =
401                 TransitionStep(
402                     from = LOCKSCREEN,
403                     to = AOD,
404                     transitionState = TransitionState.STARTED,
405                 )
406             yield()
407 
408             verify(animations, never()).doze(1f)
409 
410             job.cancel()
411         }
412 
413     @Test
listenForAnyStateToLockscreenTransition_neverUpdatesClockDozeAmountnull414     fun listenForAnyStateToLockscreenTransition_neverUpdatesClockDozeAmount() =
415         runBlocking(IMMEDIATE) {
416             val transitionStep = MutableStateFlow(TransitionStep())
417             whenever(keyguardTransitionInteractor
418                 .transition(Edge.create(to = LOCKSCREEN)))
419                 .thenReturn(transitionStep)
420 
421             val job = underTest.listenForAnyStateToLockscreenTransition(this)
422             transitionStep.value =
423                 TransitionStep(
424                     from = AOD,
425                     to = LOCKSCREEN,
426                     transitionState = TransitionState.STARTED,
427                 )
428             yield()
429 
430             verify(animations, never()).doze(0f)
431 
432             job.cancel()
433         }
434 
435     @Test
listenForAnyStateToDozingTransition_UpdatesClockDozeAmountToOnenull436     fun listenForAnyStateToDozingTransition_UpdatesClockDozeAmountToOne() =
437         runBlocking(IMMEDIATE) {
438             val transitionStep = MutableStateFlow(TransitionStep())
439             whenever(keyguardTransitionInteractor
440                 .transition(Edge.create(to = DOZING)))
441                 .thenReturn(transitionStep)
442 
443             val job = underTest.listenForAnyStateToDozingTransition(this)
444             transitionStep.value =
445                 TransitionStep(
446                     from = LOCKSCREEN,
447                     to = DOZING,
448                     transitionState = TransitionState.STARTED,
449                 )
450             yield()
451 
452             verify(animations, times(2)).doze(1f)
453 
454             job.cancel()
455         }
456 
457     @Test
unregisterListeners_validatenull458     fun unregisterListeners_validate() =
459         runBlocking(IMMEDIATE) {
460             underTest.unregisterListeners()
461             verify(broadcastDispatcher).unregisterReceiver(any())
462             verify(configurationController).removeCallback(any())
463             verify(batteryController).removeCallback(any())
464             verify(keyguardUpdateMonitor).removeCallback(any())
465             verify(smallClockController.view)
466                 .removeOnAttachStateChangeListener(underTest.smallClockOnAttachStateChangeListener)
467             verify(largeClockController.view)
468                 .removeOnAttachStateChangeListener(underTest.largeClockOnAttachStateChangeListener)
469         }
470 
471     @Test
registerOnAttachStateChangeListener_validatenull472     fun registerOnAttachStateChangeListener_validate() =
473         runBlocking(IMMEDIATE) {
474             verify(smallClockController.view)
475                 .addOnAttachStateChangeListener(underTest.smallClockOnAttachStateChangeListener)
476             verify(largeClockController.view)
477                 .addOnAttachStateChangeListener(underTest.largeClockOnAttachStateChangeListener)
478         }
479 
480     @Test
registerAndRemoveOnGlobalLayoutListener_correctlynull481     fun registerAndRemoveOnGlobalLayoutListener_correctly() =
482         runBlocking(IMMEDIATE) {
483             underTest.smallClockOnAttachStateChangeListener!!.onViewAttachedToWindow(smallClockView)
484             verify(smallClockFrame.viewTreeObserver).addOnGlobalLayoutListener(any())
485             underTest.smallClockOnAttachStateChangeListener!!.onViewDetachedFromWindow(
486                 smallClockView
487             )
488             verify(smallClockFrame.viewTreeObserver).removeOnGlobalLayoutListener(any())
489         }
490 
491     @Test
registerOnGlobalLayoutListener_RemoveOnAttachStateChangeListener_correctlynull492     fun registerOnGlobalLayoutListener_RemoveOnAttachStateChangeListener_correctly() =
493         runBlocking(IMMEDIATE) {
494             underTest.smallClockOnAttachStateChangeListener!!.onViewAttachedToWindow(smallClockView)
495             verify(smallClockFrame.viewTreeObserver).addOnGlobalLayoutListener(any())
496             underTest.unregisterListeners()
497             verify(smallClockFrame.viewTreeObserver).removeOnGlobalLayoutListener(any())
498         }
499 
500     companion object {
501         private val IMMEDIATE = Dispatchers.Main.immediate
502     }
503 }
504