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.bouncer.data.repository.FakeKeyguardBouncerRepository
26 import com.android.systemui.broadcast.BroadcastDispatcher
27 import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
28 import com.android.systemui.flags.FeatureFlags
29 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
30 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
31 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
32 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
33 import com.android.systemui.log.LogBuffer
34 import com.android.systemui.plugins.ClockAnimations
35 import com.android.systemui.plugins.ClockController
36 import com.android.systemui.plugins.ClockEvents
37 import com.android.systemui.plugins.ClockFaceConfig
38 import com.android.systemui.plugins.ClockFaceController
39 import com.android.systemui.plugins.ClockFaceEvents
40 import com.android.systemui.plugins.ClockTickRate
41 import com.android.systemui.statusbar.CommandQueue
42 import com.android.systemui.statusbar.policy.BatteryController
43 import com.android.systemui.statusbar.policy.ConfigurationController
44 import com.android.systemui.util.concurrency.DelayableExecutor
45 import com.android.systemui.util.mockito.any
46 import com.android.systemui.util.mockito.argumentCaptor
47 import com.android.systemui.util.mockito.capture
48 import com.android.systemui.util.mockito.eq
49 import com.android.systemui.util.mockito.mock
50 import kotlinx.coroutines.Dispatchers
51 import kotlinx.coroutines.runBlocking
52 import kotlinx.coroutines.test.TestScope
53 import kotlinx.coroutines.yield
54 import org.junit.Assert.assertEquals
55 import org.junit.Before
56 import org.junit.Rule
57 import org.junit.Test
58 import org.junit.runner.RunWith
59 import org.mockito.ArgumentMatchers.anyBoolean
60 import org.mockito.ArgumentMatchers.anyFloat
61 import org.mockito.ArgumentMatchers.anyInt
62 import org.mockito.Mock
63 import org.mockito.Mockito.never
64 import org.mockito.Mockito.times
65 import org.mockito.Mockito.verify
66 import org.mockito.junit.MockitoJUnit
67 import java.util.TimeZone
68 import java.util.concurrent.Executor
69 import org.mockito.Mockito.`when` as whenever
70 
71 @RunWith(AndroidTestingRunner::class)
72 @SmallTest
73 class ClockEventControllerTest : SysuiTestCase() {
74 
75     @JvmField @Rule val mockito = MockitoJUnit.rule()
76     @Mock private lateinit var broadcastDispatcher: BroadcastDispatcher
77     @Mock private lateinit var batteryController: BatteryController
78     @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
79     @Mock private lateinit var configurationController: ConfigurationController
80     @Mock private lateinit var animations: ClockAnimations
81     @Mock private lateinit var events: ClockEvents
82     @Mock private lateinit var clock: ClockController
83     @Mock private lateinit var mainExecutor: DelayableExecutor
84     @Mock private lateinit var bgExecutor: Executor
85     @Mock private lateinit var featureFlags: FeatureFlags
86     @Mock private lateinit var smallClockController: ClockFaceController
87     @Mock private lateinit var smallClockView: View
88     @Mock private lateinit var smallClockViewTreeObserver: ViewTreeObserver
89     @Mock private lateinit var smallClockFrame: FrameLayout
90     @Mock private lateinit var smallClockFrameViewTreeObserver: ViewTreeObserver
91     @Mock private lateinit var largeClockController: ClockFaceController
92     @Mock private lateinit var largeClockView: View
93     @Mock private lateinit var largeClockViewTreeObserver: ViewTreeObserver
94     @Mock private lateinit var smallClockEvents: ClockFaceEvents
95     @Mock private lateinit var largeClockEvents: ClockFaceEvents
96     @Mock private lateinit var parentView: View
97     @Mock private lateinit var transitionRepository: KeyguardTransitionRepository
98     @Mock private lateinit var commandQueue: CommandQueue
99     private lateinit var repository: FakeKeyguardRepository
100     private lateinit var bouncerRepository: FakeKeyguardBouncerRepository
101     @Mock private lateinit var smallLogBuffer: LogBuffer
102     @Mock private lateinit var largeLogBuffer: LogBuffer
103     private lateinit var underTest: ClockEventController
104 
105     @Before
106     fun setUp() {
107         whenever(clock.smallClock).thenReturn(smallClockController)
108         whenever(clock.largeClock).thenReturn(largeClockController)
109         whenever(smallClockController.view).thenReturn(smallClockView)
110         whenever(smallClockView.parent).thenReturn(smallClockFrame)
111         whenever(smallClockView.viewTreeObserver).thenReturn(smallClockViewTreeObserver)
112         whenever(smallClockFrame.viewTreeObserver).thenReturn(smallClockFrameViewTreeObserver)
113         whenever(largeClockController.view).thenReturn(largeClockView)
114         whenever(largeClockView.viewTreeObserver).thenReturn(largeClockViewTreeObserver)
115         whenever(smallClockController.events).thenReturn(smallClockEvents)
116         whenever(largeClockController.events).thenReturn(largeClockEvents)
117         whenever(clock.events).thenReturn(events)
118         whenever(smallClockController.animations).thenReturn(animations)
119         whenever(largeClockController.animations).thenReturn(animations)
120         whenever(smallClockController.config)
121             .thenReturn(ClockFaceConfig(tickRate = ClockTickRate.PER_MINUTE))
122         whenever(largeClockController.config)
123             .thenReturn(ClockFaceConfig(tickRate = ClockTickRate.PER_MINUTE))
124 
125         repository = FakeKeyguardRepository()
126         bouncerRepository = FakeKeyguardBouncerRepository()
127 
128         underTest = ClockEventController(
129             KeyguardInteractor(
130                 repository = repository,
131                 commandQueue = commandQueue,
132                 featureFlags = featureFlags,
133                 bouncerRepository = bouncerRepository,
134                 configurationRepository = FakeConfigurationRepository(),
135             ),
136             KeyguardTransitionInteractorFactory.create(
137                     scope = TestScope().backgroundScope,
138             ).keyguardTransitionInteractor,
139             broadcastDispatcher,
140             batteryController,
141             keyguardUpdateMonitor,
142             configurationController,
143             context.resources,
144             context,
145             mainExecutor,
146             bgExecutor,
147             smallLogBuffer,
148             largeLogBuffer,
149             featureFlags
150         )
151         underTest.clock = clock
152 
153         runBlocking(IMMEDIATE) {
154             underTest.registerListeners(parentView)
155 
156             repository.setIsDozing(true)
157             repository.setDozeAmount(1f)
158         }
159     }
160 
161     @Test
162     fun clockSet_validateInitialization() {
163         verify(clock).initialize(any(), anyFloat(), anyFloat())
164     }
165 
166     @Test
167     fun clockUnset_validateState() {
168         underTest.clock = null
169 
170         assertEquals(underTest.clock, null)
171     }
172 
173     @Test
174     fun themeChanged_verifyClockPaletteUpdated() = runBlocking(IMMEDIATE) {
175          verify(smallClockEvents).onRegionDarknessChanged(anyBoolean())
176          verify(largeClockEvents).onRegionDarknessChanged(anyBoolean())
177 
178         val captor = argumentCaptor<ConfigurationController.ConfigurationListener>()
179         verify(configurationController).addCallback(capture(captor))
180         captor.value.onThemeChanged()
181 
182         verify(events).onColorPaletteChanged(any())
183     }
184 
185     @Test
186     fun fontChanged_verifyFontSizeUpdated() = runBlocking(IMMEDIATE) {
187         val captor = argumentCaptor<ConfigurationController.ConfigurationListener>()
188         verify(configurationController).addCallback(capture(captor))
189         captor.value.onDensityOrFontScaleChanged()
190 
191         verify(smallClockEvents, times(2)).onFontSettingChanged(anyFloat())
192         verify(largeClockEvents, times(2)).onFontSettingChanged(anyFloat())
193     }
194 
195     @Test
196     fun batteryCallback_keyguardShowingCharging_verifyChargeAnimation() = runBlocking(IMMEDIATE) {
197         val batteryCaptor = argumentCaptor<BatteryController.BatteryStateChangeCallback>()
198         verify(batteryController).addCallback(capture(batteryCaptor))
199         val keyguardCaptor = argumentCaptor<KeyguardUpdateMonitorCallback>()
200         verify(keyguardUpdateMonitor).registerCallback(capture(keyguardCaptor))
201         keyguardCaptor.value.onKeyguardVisibilityChanged(true)
202         batteryCaptor.value.onBatteryLevelChanged(10, false, true)
203 
204         verify(animations, times(2)).charge()
205     }
206 
207     @Test
208     fun batteryCallback_keyguardShowingCharging_Duplicate_verifyChargeAnimation() =
209         runBlocking(IMMEDIATE) {
210             val batteryCaptor = argumentCaptor<BatteryController.BatteryStateChangeCallback>()
211             verify(batteryController).addCallback(capture(batteryCaptor))
212             val keyguardCaptor = argumentCaptor<KeyguardUpdateMonitorCallback>()
213             verify(keyguardUpdateMonitor).registerCallback(capture(keyguardCaptor))
214             keyguardCaptor.value.onKeyguardVisibilityChanged(true)
215             batteryCaptor.value.onBatteryLevelChanged(10, false, true)
216             batteryCaptor.value.onBatteryLevelChanged(10, false, true)
217 
218             verify(animations, times(2)).charge()
219         }
220 
221     @Test
222     fun batteryCallback_keyguardHiddenCharging_verifyChargeAnimation() = runBlocking(IMMEDIATE) {
223         val batteryCaptor = argumentCaptor<BatteryController.BatteryStateChangeCallback>()
224         verify(batteryController).addCallback(capture(batteryCaptor))
225         val keyguardCaptor = argumentCaptor<KeyguardUpdateMonitorCallback>()
226         verify(keyguardUpdateMonitor).registerCallback(capture(keyguardCaptor))
227         keyguardCaptor.value.onKeyguardVisibilityChanged(false)
228         batteryCaptor.value.onBatteryLevelChanged(10, false, true)
229 
230         verify(animations, never()).charge()
231     }
232 
233     @Test
234     fun batteryCallback_keyguardShowingNotCharging_verifyChargeAnimation() =
235         runBlocking(IMMEDIATE) {
236             val batteryCaptor = argumentCaptor<BatteryController.BatteryStateChangeCallback>()
237             verify(batteryController).addCallback(capture(batteryCaptor))
238             val keyguardCaptor = argumentCaptor<KeyguardUpdateMonitorCallback>()
239             verify(keyguardUpdateMonitor).registerCallback(capture(keyguardCaptor))
240             keyguardCaptor.value.onKeyguardVisibilityChanged(true)
241             batteryCaptor.value.onBatteryLevelChanged(10, false, false)
242 
243             verify(animations, never()).charge()
244         }
245 
246     @Test
247     fun localeCallback_verifyClockNotified() = runBlocking(IMMEDIATE) {
248         val captor = argumentCaptor<BroadcastReceiver>()
249         verify(broadcastDispatcher).registerReceiver(
250             capture(captor), any(), eq(null), eq(null), anyInt(), eq(null)
251         )
252         captor.value.onReceive(context, mock())
253 
254         verify(events).onLocaleChanged(any())
255     }
256 
257     @Test
258     fun keyguardCallback_visibilityChanged_clockDozeCalled() = runBlocking(IMMEDIATE) {
259         val captor = argumentCaptor<KeyguardUpdateMonitorCallback>()
260         verify(keyguardUpdateMonitor).registerCallback(capture(captor))
261 
262         captor.value.onKeyguardVisibilityChanged(true)
263         verify(animations, never()).doze(0f)
264 
265         captor.value.onKeyguardVisibilityChanged(false)
266         verify(animations, times(2)).doze(0f)
267     }
268 
269     @Test
270     fun keyguardCallback_timeFormat_clockNotified() = runBlocking(IMMEDIATE) {
271         val captor = argumentCaptor<KeyguardUpdateMonitorCallback>()
272         verify(keyguardUpdateMonitor).registerCallback(capture(captor))
273         captor.value.onTimeFormatChanged("12h")
274 
275         verify(events).onTimeFormatChanged(false)
276     }
277 
278     @Test
279     fun keyguardCallback_timezoneChanged_clockNotified() = runBlocking(IMMEDIATE) {
280         val mockTimeZone = mock<TimeZone>()
281         val captor = argumentCaptor<KeyguardUpdateMonitorCallback>()
282         verify(keyguardUpdateMonitor).registerCallback(capture(captor))
283         captor.value.onTimeZoneChanged(mockTimeZone)
284 
285         verify(events).onTimeZoneChanged(mockTimeZone)
286     }
287 
288     @Test
289     fun keyguardCallback_userSwitched_clockNotified() = runBlocking(IMMEDIATE) {
290         val captor = argumentCaptor<KeyguardUpdateMonitorCallback>()
291         verify(keyguardUpdateMonitor).registerCallback(capture(captor))
292         captor.value.onUserSwitchComplete(10)
293 
294         verify(events).onTimeFormatChanged(false)
295     }
296 
297     @Test
298     fun keyguardCallback_verifyKeyguardChanged() = runBlocking(IMMEDIATE) {
299         val job = underTest.listenForDozeAmount(this)
300         repository.setDozeAmount(0.4f)
301 
302         yield()
303 
304         verify(animations, times(2)).doze(0.4f)
305 
306         job.cancel()
307     }
308 
309     @Test
310     fun unregisterListeners_validate() = runBlocking(IMMEDIATE) {
311         underTest.unregisterListeners()
312         verify(broadcastDispatcher).unregisterReceiver(any())
313         verify(configurationController).removeCallback(any())
314         verify(batteryController).removeCallback(any())
315         verify(keyguardUpdateMonitor).removeCallback(any())
316         verify(smallClockController.view)
317                 .removeOnAttachStateChangeListener(underTest.smallClockOnAttachStateChangeListener)
318         verify(largeClockController.view)
319                 .removeOnAttachStateChangeListener(underTest.largeClockOnAttachStateChangeListener)
320     }
321 
322     @Test
323     fun registerOnAttachStateChangeListener_validate() = runBlocking(IMMEDIATE) {
324         verify(smallClockController.view)
325             .addOnAttachStateChangeListener(underTest.smallClockOnAttachStateChangeListener)
326         verify(largeClockController.view)
327             .addOnAttachStateChangeListener(underTest.largeClockOnAttachStateChangeListener)
328     }
329 
330     @Test
331     fun registerAndRemoveOnGlobalLayoutListener_correctly() = runBlocking(IMMEDIATE) {
332         underTest.smallClockOnAttachStateChangeListener!!.onViewAttachedToWindow(smallClockView)
333         verify(smallClockFrame.viewTreeObserver).addOnGlobalLayoutListener(any())
334         underTest.smallClockOnAttachStateChangeListener!!.onViewDetachedFromWindow(smallClockView)
335         verify(smallClockFrame.viewTreeObserver).removeOnGlobalLayoutListener(any())
336     }
337 
338     @Test
339     fun registerOnGlobalLayoutListener_RemoveOnAttachStateChangeListener_correctly() =
340         runBlocking(IMMEDIATE) {
341             underTest.smallClockOnAttachStateChangeListener!!
342                 .onViewAttachedToWindow(smallClockView)
343             verify(smallClockFrame.viewTreeObserver).addOnGlobalLayoutListener(any())
344             underTest.unregisterListeners()
345             verify(smallClockFrame.viewTreeObserver).removeOnGlobalLayoutListener(any())
346         }
347 
348     companion object {
349         private val IMMEDIATE = Dispatchers.Main.immediate
350     }
351 }
352