1 /*
2  * Copyright 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 @file:OptIn(ExperimentalCoroutinesApi::class)
18 
19 package com.android.systemui.scene.domain.startable
20 
21 import android.view.Display
22 import androidx.test.filters.SmallTest
23 import com.android.systemui.SysuiTestCase
24 import com.android.systemui.authentication.domain.model.AuthenticationMethodModel
25 import com.android.systemui.coroutines.collectLastValue
26 import com.android.systemui.flags.Flags
27 import com.android.systemui.keyguard.shared.model.WakeSleepReason
28 import com.android.systemui.keyguard.shared.model.WakefulnessModel
29 import com.android.systemui.keyguard.shared.model.WakefulnessState
30 import com.android.systemui.model.SysUiState
31 import com.android.systemui.scene.SceneTestUtils
32 import com.android.systemui.scene.SceneTestUtils.Companion.toDataLayer
33 import com.android.systemui.scene.shared.model.ObservableTransitionState
34 import com.android.systemui.scene.shared.model.SceneKey
35 import com.android.systemui.scene.shared.model.SceneModel
36 import com.android.systemui.util.mockito.mock
37 import com.google.common.truth.Truth.assertThat
38 import kotlinx.coroutines.ExperimentalCoroutinesApi
39 import kotlinx.coroutines.flow.MutableStateFlow
40 import kotlinx.coroutines.flow.flowOf
41 import kotlinx.coroutines.flow.map
42 import kotlinx.coroutines.test.runCurrent
43 import kotlinx.coroutines.test.runTest
44 import org.junit.Test
45 import org.junit.runner.RunWith
46 import org.junit.runners.JUnit4
47 import org.mockito.Mockito.clearInvocations
48 import org.mockito.Mockito.times
49 import org.mockito.Mockito.verify
50 
51 @SmallTest
52 @RunWith(JUnit4::class)
53 class SceneContainerStartableTest : SysuiTestCase() {
54 
55     private val utils = SceneTestUtils(this)
56     private val testScope = utils.testScope
57     private val sceneInteractor = utils.sceneInteractor()
58     private val featureFlags = utils.featureFlags
59     private val authenticationRepository = utils.authenticationRepository()
60     private val authenticationInteractor =
61         utils.authenticationInteractor(
62             repository = authenticationRepository,
63         )
64     private val keyguardRepository = utils.keyguardRepository()
65     private val keyguardInteractor =
66         utils.keyguardInteractor(
67             repository = keyguardRepository,
68         )
69     private val sysUiState: SysUiState = mock()
70 
71     private val underTest =
72         SceneContainerStartable(
73             applicationScope = testScope.backgroundScope,
74             sceneInteractor = sceneInteractor,
75             authenticationInteractor = authenticationInteractor,
76             keyguardInteractor = keyguardInteractor,
77             featureFlags = featureFlags,
78             sysUiState = sysUiState,
79             displayId = Display.DEFAULT_DISPLAY,
80             sceneLogger = mock(),
81         )
82 
83     @Test
84     fun hydrateVisibility() =
85         testScope.runTest {
86             val currentDesiredSceneKey by
87                 collectLastValue(sceneInteractor.desiredScene.map { it.key })
88             val isVisible by collectLastValue(sceneInteractor.isVisible)
89             val transitionStateFlow =
90                 prepareState(
91                     isDeviceUnlocked = true,
92                     initialSceneKey = SceneKey.Gone,
93                 )
94             assertThat(currentDesiredSceneKey).isEqualTo(SceneKey.Gone)
95             assertThat(isVisible).isTrue()
96 
97             underTest.start()
98             assertThat(isVisible).isFalse()
99 
100             sceneInteractor.changeScene(SceneModel(SceneKey.Shade), "reason")
101             transitionStateFlow.value =
102                 ObservableTransitionState.Transition(
103                     fromScene = SceneKey.Gone,
104                     toScene = SceneKey.Shade,
105                     progress = flowOf(0.5f),
106                 )
107             assertThat(isVisible).isTrue()
108             sceneInteractor.onSceneChanged(SceneModel(SceneKey.Shade), "reason")
109             transitionStateFlow.value = ObservableTransitionState.Idle(SceneKey.Shade)
110             assertThat(isVisible).isTrue()
111 
112             sceneInteractor.changeScene(SceneModel(SceneKey.Gone), "reason")
113             transitionStateFlow.value =
114                 ObservableTransitionState.Transition(
115                     fromScene = SceneKey.Shade,
116                     toScene = SceneKey.Gone,
117                     progress = flowOf(0.5f),
118                 )
119             assertThat(isVisible).isTrue()
120             sceneInteractor.onSceneChanged(SceneModel(SceneKey.Gone), "reason")
121             transitionStateFlow.value = ObservableTransitionState.Idle(SceneKey.Gone)
122             assertThat(isVisible).isFalse()
123         }
124 
125     @Test
126     fun switchToLockscreenWhenDeviceLocks() =
127         testScope.runTest {
128             val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key })
129             prepareState(
130                 isDeviceUnlocked = true,
131                 initialSceneKey = SceneKey.Gone,
132             )
133             assertThat(currentSceneKey).isEqualTo(SceneKey.Gone)
134             underTest.start()
135 
136             authenticationRepository.setUnlocked(false)
137 
138             assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
139         }
140 
141     @Test
142     fun switchFromBouncerToGoneWhenDeviceUnlocked() =
143         testScope.runTest {
144             val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key })
145             prepareState(
146                 isDeviceUnlocked = false,
147                 initialSceneKey = SceneKey.Bouncer,
148             )
149             assertThat(currentSceneKey).isEqualTo(SceneKey.Bouncer)
150             underTest.start()
151 
152             authenticationRepository.setUnlocked(true)
153 
154             assertThat(currentSceneKey).isEqualTo(SceneKey.Gone)
155         }
156 
157     @Test
158     fun switchFromLockscreenToGoneWhenDeviceUnlocksWithBypassOn() =
159         testScope.runTest {
160             val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key })
161             prepareState(
162                 isBypassEnabled = true,
163                 initialSceneKey = SceneKey.Lockscreen,
164             )
165             assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
166             underTest.start()
167 
168             authenticationRepository.setUnlocked(true)
169 
170             assertThat(currentSceneKey).isEqualTo(SceneKey.Gone)
171         }
172 
173     @Test
174     fun stayOnLockscreenWhenDeviceUnlocksWithBypassOff() =
175         testScope.runTest {
176             val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key })
177             prepareState(
178                 isBypassEnabled = false,
179                 initialSceneKey = SceneKey.Lockscreen,
180             )
181             assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
182             underTest.start()
183 
184             authenticationRepository.setUnlocked(true)
185 
186             assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
187         }
188 
189     @Test
190     fun switchToLockscreenWhenDeviceSleepsLocked() =
191         testScope.runTest {
192             val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key })
193             prepareState(
194                 isDeviceUnlocked = false,
195                 initialSceneKey = SceneKey.Shade,
196             )
197             assertThat(currentSceneKey).isEqualTo(SceneKey.Shade)
198             underTest.start()
199 
200             keyguardRepository.setWakefulnessModel(STARTING_TO_SLEEP)
201 
202             assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
203         }
204 
205     @Test
206     fun hydrateSystemUiState() =
207         testScope.runTest {
208             val transitionStateFlow = prepareState()
209             underTest.start()
210             runCurrent()
211             clearInvocations(sysUiState)
212 
213             listOf(
214                     SceneKey.Gone,
215                     SceneKey.Lockscreen,
216                     SceneKey.Bouncer,
217                     SceneKey.Shade,
218                     SceneKey.QuickSettings,
219                 )
220                 .forEachIndexed { index, sceneKey ->
221                     sceneInteractor.changeScene(SceneModel(sceneKey), "reason")
222                     runCurrent()
223                     verify(sysUiState, times(index)).commitUpdate(Display.DEFAULT_DISPLAY)
224 
225                     sceneInteractor.onSceneChanged(SceneModel(sceneKey), "reason")
226                     runCurrent()
227                     verify(sysUiState, times(index)).commitUpdate(Display.DEFAULT_DISPLAY)
228 
229                     transitionStateFlow.value = ObservableTransitionState.Idle(sceneKey)
230                     runCurrent()
231                     verify(sysUiState, times(index + 1)).commitUpdate(Display.DEFAULT_DISPLAY)
232                 }
233         }
234 
235     @Test
236     fun switchToGoneWhenDeviceStartsToWakeUp_authMethodNone() =
237         testScope.runTest {
238             val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key })
239             prepareState(
240                 initialSceneKey = SceneKey.Lockscreen,
241                 authenticationMethod = AuthenticationMethodModel.None,
242             )
243             assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
244             underTest.start()
245 
246             keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE)
247 
248             assertThat(currentSceneKey).isEqualTo(SceneKey.Gone)
249         }
250 
251     @Test
252     fun stayOnLockscreenWhenDeviceStartsToWakeUp_authMethodSwipe() =
253         testScope.runTest {
254             val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key })
255             prepareState(
256                 initialSceneKey = SceneKey.Lockscreen,
257                 authenticationMethod = AuthenticationMethodModel.Swipe,
258             )
259             assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
260             underTest.start()
261 
262             keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE)
263 
264             assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
265         }
266 
267     @Test
268     fun doesNotSwitchToGoneWhenDeviceStartsToWakeUp_authMethodSecure() =
269         testScope.runTest {
270             val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key })
271             prepareState(
272                 initialSceneKey = SceneKey.Lockscreen,
273                 authenticationMethod = AuthenticationMethodModel.Pin,
274             )
275             assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
276             underTest.start()
277 
278             keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE)
279 
280             assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
281         }
282 
283     @Test
284     fun switchToGoneWhenDeviceStartsToWakeUp_authMethodSecure_deviceUnlocked() =
285         testScope.runTest {
286             val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key })
287             prepareState(
288                 initialSceneKey = SceneKey.Lockscreen,
289                 authenticationMethod = AuthenticationMethodModel.Pin,
290                 isDeviceUnlocked = false,
291             )
292             assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
293             underTest.start()
294 
295             authenticationRepository.setUnlocked(true)
296             runCurrent()
297             keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE)
298 
299             assertThat(currentSceneKey).isEqualTo(SceneKey.Gone)
300         }
301 
302     private fun prepareState(
303         isDeviceUnlocked: Boolean = false,
304         isBypassEnabled: Boolean = false,
305         initialSceneKey: SceneKey? = null,
306         authenticationMethod: AuthenticationMethodModel? = null,
307     ): MutableStateFlow<ObservableTransitionState> {
308         featureFlags.set(Flags.SCENE_CONTAINER, true)
309         authenticationRepository.setUnlocked(isDeviceUnlocked)
310         keyguardRepository.setBypassEnabled(isBypassEnabled)
311         val transitionStateFlow =
312             MutableStateFlow<ObservableTransitionState>(
313                 ObservableTransitionState.Idle(SceneKey.Lockscreen)
314             )
315         sceneInteractor.setTransitionState(transitionStateFlow)
316         initialSceneKey?.let {
317             transitionStateFlow.value = ObservableTransitionState.Idle(it)
318             sceneInteractor.changeScene(SceneModel(it), "reason")
319             sceneInteractor.onSceneChanged(SceneModel(it), "reason")
320         }
321         authenticationMethod?.let {
322             authenticationRepository.setAuthenticationMethod(authenticationMethod.toDataLayer())
323             authenticationRepository.setLockscreenEnabled(
324                 authenticationMethod != AuthenticationMethodModel.None
325             )
326         }
327         return transitionStateFlow
328     }
329 
330     companion object {
331         private val STARTING_TO_SLEEP =
332             WakefulnessModel(
333                 state = WakefulnessState.STARTING_TO_SLEEP,
334                 lastWakeReason = WakeSleepReason.POWER_BUTTON,
335                 lastSleepReason = WakeSleepReason.POWER_BUTTON
336             )
337         private val STARTING_TO_WAKE =
338             WakefulnessModel(
339                 state = WakefulnessState.STARTING_TO_WAKE,
340                 lastWakeReason = WakeSleepReason.POWER_BUTTON,
341                 lastSleepReason = WakeSleepReason.POWER_BUTTON
342             )
343     }
344 }
345