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