Merge changes Ia9d5b620,I49577bc7 into udc-qpr-dev
* changes: [flexiglass] Minor tweaks to support Trust Managers. [flexiglass] View-model integration test.
This commit is contained in:
@@ -102,7 +102,7 @@ constructor(
|
||||
.stateIn(
|
||||
scope = applicationScope,
|
||||
started = SharingStarted.Eagerly,
|
||||
initialValue = true,
|
||||
initialValue = false,
|
||||
)
|
||||
|
||||
/**
|
||||
|
||||
@@ -26,6 +26,7 @@ import com.android.systemui.dagger.qualifiers.Application
|
||||
import com.android.systemui.scene.shared.model.SceneKey
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.map
|
||||
@@ -51,15 +52,14 @@ constructor(
|
||||
)
|
||||
|
||||
/** The key of the scene we should switch to when swiping up. */
|
||||
val upDestinationSceneKey =
|
||||
authenticationInteractor.canSwipeToDismiss
|
||||
.map { canSwipeToDismiss -> upDestinationSceneKey(canSwipeToDismiss) }
|
||||
.stateIn(
|
||||
scope = applicationScope,
|
||||
started = SharingStarted.WhileSubscribed(),
|
||||
initialValue =
|
||||
upDestinationSceneKey(authenticationInteractor.canSwipeToDismiss.value),
|
||||
)
|
||||
val upDestinationSceneKey: Flow<SceneKey> =
|
||||
authenticationInteractor.isUnlocked.map { isUnlocked ->
|
||||
if (isUnlocked) {
|
||||
SceneKey.Gone
|
||||
} else {
|
||||
SceneKey.Bouncer
|
||||
}
|
||||
}
|
||||
|
||||
/** Notifies that the lock button on the lock screen was clicked. */
|
||||
fun onLockButtonClicked() {
|
||||
|
||||
@@ -178,12 +178,24 @@ constructor(
|
||||
}
|
||||
WakefulnessState.STARTING_TO_WAKE -> {
|
||||
val authMethod = authenticationInteractor.getAuthenticationMethod()
|
||||
if (authMethod == AuthenticationMethodModel.None) {
|
||||
switchToScene(
|
||||
targetSceneKey = SceneKey.Gone,
|
||||
loggingReason =
|
||||
"device is starting to wake up while auth method is None",
|
||||
)
|
||||
val isUnlocked = authenticationInteractor.isUnlocked.value
|
||||
when {
|
||||
authMethod == AuthenticationMethodModel.None -> {
|
||||
switchToScene(
|
||||
targetSceneKey = SceneKey.Gone,
|
||||
loggingReason =
|
||||
"device is starting to wake up while auth method is" +
|
||||
" none",
|
||||
)
|
||||
}
|
||||
authMethod.isSecure && isUnlocked -> {
|
||||
switchToScene(
|
||||
targetSceneKey = SceneKey.Gone,
|
||||
loggingReason =
|
||||
"device is starting to wake up while unlocked with a" +
|
||||
" secure auth method",
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> Unit
|
||||
|
||||
@@ -0,0 +1,496 @@
|
||||
/*
|
||||
* Copyright 2023 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
@file:OptIn(ExperimentalCoroutinesApi::class)
|
||||
|
||||
package com.android.systemui.scene
|
||||
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
|
||||
import com.android.systemui.authentication.domain.model.AuthenticationMethodModel as DomainLayerAuthenticationMethodModel
|
||||
import com.android.systemui.authentication.domain.model.AuthenticationMethodModel
|
||||
import com.android.systemui.bouncer.ui.viewmodel.PinBouncerViewModel
|
||||
import com.android.systemui.coroutines.collectLastValue
|
||||
import com.android.systemui.flags.FakeFeatureFlags
|
||||
import com.android.systemui.flags.Flags
|
||||
import com.android.systemui.keyguard.shared.model.WakefulnessState
|
||||
import com.android.systemui.keyguard.ui.viewmodel.LockscreenSceneViewModel
|
||||
import com.android.systemui.model.SysUiState
|
||||
import com.android.systemui.scene.SceneTestUtils.Companion.toDataLayer
|
||||
import com.android.systemui.scene.domain.startable.SceneContainerStartable
|
||||
import com.android.systemui.scene.shared.model.ObservableTransitionState
|
||||
import com.android.systemui.scene.shared.model.SceneKey
|
||||
import com.android.systemui.scene.shared.model.SceneModel
|
||||
import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel
|
||||
import com.android.systemui.settings.FakeDisplayTracker
|
||||
import com.android.systemui.util.mockito.mock
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import com.google.common.truth.Truth.assertWithMessage
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.test.TestScope
|
||||
import kotlinx.coroutines.test.runCurrent
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.junit.runners.JUnit4
|
||||
|
||||
/**
|
||||
* Integration test cases for the Scene Framework.
|
||||
*
|
||||
* **Principles**
|
||||
* * All test cases here should be done from the perspective of the view-models of the system.
|
||||
* * Focus on happy paths, let smaller unit tests focus on failure cases.
|
||||
* * These are _integration_ tests and, as such, are larger and harder to maintain than unit tests.
|
||||
* Therefore, when adding or modifying test cases, consider whether what you're testing is better
|
||||
* covered by a more granular unit test.
|
||||
* * Please reuse the helper methods in this class (for example, [putDeviceToSleep] or
|
||||
* [emulateUserDrivenTransition]).
|
||||
* * All tests start with the device locked and with a PIN auth method. The class offers useful
|
||||
* methods like [setAuthMethod], [unlockDevice], [lockDevice], etc. to help you set up a starting
|
||||
* state that makes more sense for your test case.
|
||||
* * All helper methods in this class make assertions that are meant to make sure that they're only
|
||||
* being used when the state is as required (e.g. cannot unlock an already unlocked device, cannot
|
||||
* put to sleep a device that's already asleep, etc.).
|
||||
*/
|
||||
@SmallTest
|
||||
@RunWith(JUnit4::class)
|
||||
class SceneFrameworkIntegrationTest : SysuiTestCase() {
|
||||
|
||||
private val utils = SceneTestUtils(this)
|
||||
private val testScope = utils.testScope
|
||||
|
||||
private val sceneContainerConfig = utils.fakeSceneContainerConfig()
|
||||
private val sceneRepository =
|
||||
utils.fakeSceneContainerRepository(
|
||||
containerConfig = sceneContainerConfig,
|
||||
)
|
||||
private val sceneInteractor =
|
||||
utils.sceneInteractor(
|
||||
repository = sceneRepository,
|
||||
)
|
||||
|
||||
private val authenticationRepository = utils.authenticationRepository()
|
||||
private val authenticationInteractor =
|
||||
utils.authenticationInteractor(
|
||||
repository = authenticationRepository,
|
||||
sceneInteractor = sceneInteractor,
|
||||
)
|
||||
|
||||
private val transitionState =
|
||||
MutableStateFlow<ObservableTransitionState>(
|
||||
ObservableTransitionState.Idle(sceneContainerConfig.initialSceneKey)
|
||||
)
|
||||
private val sceneContainerViewModel =
|
||||
SceneContainerViewModel(
|
||||
interactor = sceneInteractor,
|
||||
)
|
||||
.apply { setTransitionState(transitionState) }
|
||||
|
||||
private val bouncerInteractor =
|
||||
utils.bouncerInteractor(
|
||||
authenticationInteractor = authenticationInteractor,
|
||||
sceneInteractor = sceneInteractor,
|
||||
)
|
||||
private val bouncerViewModel =
|
||||
utils.bouncerViewModel(
|
||||
bouncerInteractor = bouncerInteractor,
|
||||
authenticationInteractor = authenticationInteractor,
|
||||
)
|
||||
|
||||
private val lockscreenSceneViewModel =
|
||||
LockscreenSceneViewModel(
|
||||
applicationScope = testScope.backgroundScope,
|
||||
authenticationInteractor = authenticationInteractor,
|
||||
bouncerInteractor = bouncerInteractor,
|
||||
)
|
||||
|
||||
private val keyguardRepository = utils.keyguardRepository()
|
||||
private val keyguardInteractor =
|
||||
utils.keyguardInteractor(
|
||||
repository = keyguardRepository,
|
||||
)
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
val featureFlags = FakeFeatureFlags().apply { set(Flags.SCENE_CONTAINER, true) }
|
||||
|
||||
authenticationRepository.setUnlocked(false)
|
||||
|
||||
val displayTracker = FakeDisplayTracker(context)
|
||||
val sysUiState = SysUiState(displayTracker)
|
||||
val startable =
|
||||
SceneContainerStartable(
|
||||
applicationScope = testScope.backgroundScope,
|
||||
sceneInteractor = sceneInteractor,
|
||||
authenticationInteractor = authenticationInteractor,
|
||||
keyguardInteractor = keyguardInteractor,
|
||||
featureFlags = featureFlags,
|
||||
sysUiState = sysUiState,
|
||||
displayId = displayTracker.defaultDisplayId,
|
||||
sceneLogger = mock(),
|
||||
)
|
||||
startable.start()
|
||||
|
||||
assertWithMessage("Initial scene key mismatch!")
|
||||
.that(sceneContainerViewModel.currentScene.value.key)
|
||||
.isEqualTo(sceneContainerConfig.initialSceneKey)
|
||||
assertWithMessage("Initial scene container visibility mismatch!")
|
||||
.that(sceneContainerViewModel.isVisible.value)
|
||||
.isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun clickLockButtonAndEnterCorrectPin_unlocksDevice() =
|
||||
testScope.runTest {
|
||||
lockscreenSceneViewModel.onLockButtonClicked()
|
||||
assertCurrentScene(SceneKey.Bouncer)
|
||||
emulateUiSceneTransition()
|
||||
|
||||
enterPin()
|
||||
assertCurrentScene(SceneKey.Gone)
|
||||
emulateUiSceneTransition(
|
||||
expectedVisible = false,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun swipeUpOnLockscreen_enterCorrectPin_unlocksDevice() =
|
||||
testScope.runTest {
|
||||
val upDestinationSceneKey by
|
||||
collectLastValue(lockscreenSceneViewModel.upDestinationSceneKey)
|
||||
assertThat(upDestinationSceneKey).isEqualTo(SceneKey.Bouncer)
|
||||
emulateUserDrivenTransition(
|
||||
to = upDestinationSceneKey,
|
||||
)
|
||||
|
||||
enterPin()
|
||||
assertCurrentScene(SceneKey.Gone)
|
||||
emulateUiSceneTransition(
|
||||
expectedVisible = false,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun swipeUpOnLockscreen_withAuthMethodSwipe_dismissesLockscreen() =
|
||||
testScope.runTest {
|
||||
setAuthMethod(DomainLayerAuthenticationMethodModel.Swipe)
|
||||
|
||||
val upDestinationSceneKey by
|
||||
collectLastValue(lockscreenSceneViewModel.upDestinationSceneKey)
|
||||
assertThat(upDestinationSceneKey).isEqualTo(SceneKey.Gone)
|
||||
emulateUserDrivenTransition(
|
||||
to = upDestinationSceneKey,
|
||||
expectedVisible = false,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun withAuthMethodNone_deviceWakeUp_skipsLockscreen() =
|
||||
testScope.runTest {
|
||||
setAuthMethod(AuthenticationMethodModel.None)
|
||||
putDeviceToSleep(instantlyLockDevice = false)
|
||||
assertCurrentScene(SceneKey.Lockscreen)
|
||||
|
||||
wakeUpDevice()
|
||||
assertCurrentScene(SceneKey.Gone)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun withAuthMethodSwipe_deviceWakeUp_doesNotSkipLockscreen() =
|
||||
testScope.runTest {
|
||||
setAuthMethod(AuthenticationMethodModel.Swipe)
|
||||
putDeviceToSleep(instantlyLockDevice = false)
|
||||
assertCurrentScene(SceneKey.Lockscreen)
|
||||
|
||||
wakeUpDevice()
|
||||
assertCurrentScene(SceneKey.Lockscreen)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deviceGoesToSleep_switchesToLockscreen() =
|
||||
testScope.runTest {
|
||||
unlockDevice()
|
||||
assertCurrentScene(SceneKey.Gone)
|
||||
|
||||
putDeviceToSleep()
|
||||
assertCurrentScene(SceneKey.Lockscreen)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deviceGoesToSleep_wakeUp_unlock() =
|
||||
testScope.runTest {
|
||||
unlockDevice()
|
||||
assertCurrentScene(SceneKey.Gone)
|
||||
putDeviceToSleep()
|
||||
assertCurrentScene(SceneKey.Lockscreen)
|
||||
wakeUpDevice()
|
||||
assertCurrentScene(SceneKey.Lockscreen)
|
||||
|
||||
unlockDevice()
|
||||
assertCurrentScene(SceneKey.Gone)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deviceWakesUpWhileUnlocked_dismissesLockscreen() =
|
||||
testScope.runTest {
|
||||
unlockDevice()
|
||||
assertCurrentScene(SceneKey.Gone)
|
||||
putDeviceToSleep(instantlyLockDevice = false)
|
||||
assertCurrentScene(SceneKey.Lockscreen)
|
||||
wakeUpDevice()
|
||||
assertCurrentScene(SceneKey.Gone)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun swipeUpOnLockscreenWhileUnlocked_dismissesLockscreen() =
|
||||
testScope.runTest {
|
||||
unlockDevice()
|
||||
val upDestinationSceneKey by
|
||||
collectLastValue(lockscreenSceneViewModel.upDestinationSceneKey)
|
||||
assertThat(upDestinationSceneKey).isEqualTo(SceneKey.Gone)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deviceGoesToSleep_withLockTimeout_staysOnLockscreen() =
|
||||
testScope.runTest {
|
||||
unlockDevice()
|
||||
assertCurrentScene(SceneKey.Gone)
|
||||
putDeviceToSleep(instantlyLockDevice = false)
|
||||
assertCurrentScene(SceneKey.Lockscreen)
|
||||
|
||||
// Pretend like the timeout elapsed and now lock the device.
|
||||
lockDevice()
|
||||
assertCurrentScene(SceneKey.Lockscreen)
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that the current scene in the view-model matches what's expected.
|
||||
*
|
||||
* Note that this doesn't assert what the current scene is in the UI.
|
||||
*/
|
||||
private fun TestScope.assertCurrentScene(expected: SceneKey) {
|
||||
runCurrent()
|
||||
assertWithMessage("Current scene mismatch!")
|
||||
.that(sceneContainerViewModel.currentScene.value.key)
|
||||
.isEqualTo(expected)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the [SceneKey] of the current scene as displayed in the UI.
|
||||
*
|
||||
* This can be different than the value in [SceneContainerViewModel.currentScene], by design, as
|
||||
* the UI must gradually transition between scenes.
|
||||
*/
|
||||
private fun getCurrentSceneInUi(): SceneKey {
|
||||
return when (val state = transitionState.value) {
|
||||
is ObservableTransitionState.Idle -> state.scene
|
||||
is ObservableTransitionState.Transition -> state.fromScene
|
||||
}
|
||||
}
|
||||
|
||||
/** Updates the current authentication method and related states in the data layer. */
|
||||
private fun TestScope.setAuthMethod(
|
||||
authMethod: DomainLayerAuthenticationMethodModel,
|
||||
) {
|
||||
// Set the lockscreen enabled bit _before_ set the auth method as the code picks up on the
|
||||
// lockscreen enabled bit _after_ the auth method is changed and the lockscreen enabled bit
|
||||
// is not an observable that can trigger a new evaluation.
|
||||
authenticationRepository.setLockscreenEnabled(authMethod !is AuthenticationMethodModel.None)
|
||||
authenticationRepository.setAuthenticationMethod(authMethod.toDataLayer())
|
||||
if (!authMethod.isSecure) {
|
||||
// When the auth method is not secure, the device is never considered locked.
|
||||
authenticationRepository.setUnlocked(true)
|
||||
}
|
||||
runCurrent()
|
||||
}
|
||||
|
||||
/**
|
||||
* Emulates a complete transition in the UI from whatever the current scene is in the UI to
|
||||
* whatever the current scene should be, based on the value in
|
||||
* [SceneContainerViewModel.onSceneChanged].
|
||||
*
|
||||
* This should post a series of values into [transitionState] to emulate a gradual scene
|
||||
* transition and culminate with a call to [SceneContainerViewModel.onSceneChanged].
|
||||
*
|
||||
* The method asserts that a transition is actually required. E.g. it will fail if the current
|
||||
* scene in [transitionState] is already caught up with the scene in
|
||||
* [SceneContainerViewModel.currentScene].
|
||||
*
|
||||
* @param expectedVisible Whether [SceneContainerViewModel.isVisible] should be set at the end
|
||||
* of the UI transition.
|
||||
*/
|
||||
private fun TestScope.emulateUiSceneTransition(
|
||||
expectedVisible: Boolean = true,
|
||||
) {
|
||||
val to = sceneContainerViewModel.currentScene.value
|
||||
val from = getCurrentSceneInUi()
|
||||
assertWithMessage("Cannot transition to ${to.key} as the UI is already on that scene!")
|
||||
.that(to.key)
|
||||
.isNotEqualTo(from)
|
||||
|
||||
// Begin to transition.
|
||||
val progressFlow = MutableStateFlow(0f)
|
||||
transitionState.value =
|
||||
ObservableTransitionState.Transition(
|
||||
fromScene = getCurrentSceneInUi(),
|
||||
toScene = to.key,
|
||||
progress = progressFlow,
|
||||
)
|
||||
runCurrent()
|
||||
|
||||
// Report progress of transition.
|
||||
while (progressFlow.value < 1f) {
|
||||
progressFlow.value += 0.2f
|
||||
runCurrent()
|
||||
}
|
||||
|
||||
// End the transition and report the change.
|
||||
transitionState.value = ObservableTransitionState.Idle(to.key)
|
||||
|
||||
sceneContainerViewModel.onSceneChanged(to)
|
||||
runCurrent()
|
||||
|
||||
assertWithMessage("Visibility mismatch after scene transition from $from to ${to.key}!")
|
||||
.that(sceneContainerViewModel.isVisible.value)
|
||||
.isEqualTo(expectedVisible)
|
||||
}
|
||||
|
||||
/**
|
||||
* Emulates a fire-and-forget user action (a fling or back, not a pointer-tracking swipe) that
|
||||
* causes a scene change to the [to] scene.
|
||||
*
|
||||
* This also includes the emulation of the resulting UI transition that culminates with the UI
|
||||
* catching up with the requested scene change (see [emulateUiSceneTransition]).
|
||||
*
|
||||
* @param to The scene to transition to.
|
||||
* @param expectedVisible Whether [SceneContainerViewModel.isVisible] should be set at the end
|
||||
* of the UI transition.
|
||||
*/
|
||||
private fun TestScope.emulateUserDrivenTransition(
|
||||
to: SceneKey?,
|
||||
expectedVisible: Boolean = true,
|
||||
) {
|
||||
checkNotNull(to)
|
||||
|
||||
sceneInteractor.changeScene(SceneModel(to), "reason")
|
||||
assertThat(sceneContainerViewModel.currentScene.value.key).isEqualTo(to)
|
||||
|
||||
emulateUiSceneTransition(
|
||||
expectedVisible = expectedVisible,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Locks the device immediately (without delay).
|
||||
*
|
||||
* Asserts the device to be lockable (e.g. that the current authentication is secure).
|
||||
*
|
||||
* Not to be confused with [putDeviceToSleep], which may also instantly lock the device.
|
||||
*/
|
||||
private suspend fun TestScope.lockDevice() {
|
||||
val authMethod = authenticationInteractor.getAuthenticationMethod()
|
||||
assertWithMessage("The authentication method of $authMethod is not secure, cannot lock!")
|
||||
.that(authMethod.isSecure)
|
||||
.isTrue()
|
||||
|
||||
authenticationRepository.setUnlocked(false)
|
||||
runCurrent()
|
||||
}
|
||||
|
||||
/** Unlocks the device by entering the correct PIN. Ends up in the Gone scene. */
|
||||
private fun TestScope.unlockDevice() {
|
||||
assertWithMessage("Cannot unlock a device that's already unlocked!")
|
||||
.that(authenticationInteractor.isUnlocked.value)
|
||||
.isFalse()
|
||||
|
||||
lockscreenSceneViewModel.onLockButtonClicked()
|
||||
runCurrent()
|
||||
emulateUiSceneTransition()
|
||||
|
||||
enterPin()
|
||||
emulateUiSceneTransition(
|
||||
expectedVisible = false,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Enters the correct PIN in the bouncer UI.
|
||||
*
|
||||
* Asserts that the current scene is [SceneKey.Bouncer] and that the current bouncer UI is a PIN
|
||||
* before proceeding.
|
||||
*
|
||||
* Does not assert that the device is locked or unlocked.
|
||||
*/
|
||||
private fun TestScope.enterPin() {
|
||||
assertWithMessage("Cannot enter PIN when not on the Bouncer scene!")
|
||||
.that(getCurrentSceneInUi())
|
||||
.isEqualTo(SceneKey.Bouncer)
|
||||
val authMethodViewModel by collectLastValue(bouncerViewModel.authMethod)
|
||||
assertWithMessage("Cannot enter PIN when not using a PIN authentication method!")
|
||||
.that(authMethodViewModel)
|
||||
.isInstanceOf(PinBouncerViewModel::class.java)
|
||||
|
||||
val pinBouncerViewModel = authMethodViewModel as PinBouncerViewModel
|
||||
FakeAuthenticationRepository.DEFAULT_PIN.forEach { digit ->
|
||||
pinBouncerViewModel.onPinButtonClicked(digit)
|
||||
}
|
||||
pinBouncerViewModel.onAuthenticateButtonClicked()
|
||||
runCurrent()
|
||||
}
|
||||
|
||||
/** Changes device wakefulness state from asleep to awake, going through intermediary states. */
|
||||
private fun TestScope.wakeUpDevice() {
|
||||
val wakefulnessModel = keyguardRepository.wakefulness.value
|
||||
assertWithMessage("Cannot wake up device as it's already awake!")
|
||||
.that(wakefulnessModel.isStartingToWakeOrAwake())
|
||||
.isFalse()
|
||||
|
||||
keyguardRepository.setWakefulnessModel(
|
||||
wakefulnessModel.copy(state = WakefulnessState.STARTING_TO_WAKE)
|
||||
)
|
||||
runCurrent()
|
||||
keyguardRepository.setWakefulnessModel(
|
||||
wakefulnessModel.copy(state = WakefulnessState.AWAKE)
|
||||
)
|
||||
runCurrent()
|
||||
}
|
||||
|
||||
/** Changes device wakefulness state from awake to asleep, going through intermediary states. */
|
||||
private suspend fun TestScope.putDeviceToSleep(
|
||||
instantlyLockDevice: Boolean = true,
|
||||
) {
|
||||
val wakefulnessModel = keyguardRepository.wakefulness.value
|
||||
assertWithMessage("Cannot put device to sleep as it's already asleep!")
|
||||
.that(wakefulnessModel.isStartingToWakeOrAwake())
|
||||
.isTrue()
|
||||
|
||||
keyguardRepository.setWakefulnessModel(
|
||||
wakefulnessModel.copy(state = WakefulnessState.STARTING_TO_SLEEP)
|
||||
)
|
||||
runCurrent()
|
||||
keyguardRepository.setWakefulnessModel(
|
||||
wakefulnessModel.copy(state = WakefulnessState.ASLEEP)
|
||||
)
|
||||
runCurrent()
|
||||
|
||||
if (instantlyLockDevice) {
|
||||
lockDevice()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,7 @@ package com.android.systemui.scene.domain.startable
|
||||
import android.view.Display
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.authentication.data.model.AuthenticationMethodModel
|
||||
import com.android.systemui.authentication.domain.model.AuthenticationMethodModel
|
||||
import com.android.systemui.coroutines.collectLastValue
|
||||
import com.android.systemui.flags.Flags
|
||||
import com.android.systemui.keyguard.shared.model.WakeSleepReason
|
||||
@@ -29,6 +29,7 @@ import com.android.systemui.keyguard.shared.model.WakefulnessModel
|
||||
import com.android.systemui.keyguard.shared.model.WakefulnessState
|
||||
import com.android.systemui.model.SysUiState
|
||||
import com.android.systemui.scene.SceneTestUtils
|
||||
import com.android.systemui.scene.SceneTestUtils.Companion.toDataLayer
|
||||
import com.android.systemui.scene.shared.model.ObservableTransitionState
|
||||
import com.android.systemui.scene.shared.model.SceneKey
|
||||
import com.android.systemui.scene.shared.model.SceneModel
|
||||
@@ -80,14 +81,13 @@ class SceneContainerStartableTest : SysuiTestCase() {
|
||||
)
|
||||
|
||||
@Test
|
||||
fun hydrateVisibility_featureEnabled() =
|
||||
fun hydrateVisibility() =
|
||||
testScope.runTest {
|
||||
val currentDesiredSceneKey by
|
||||
collectLastValue(sceneInteractor.desiredScene.map { it.key })
|
||||
val isVisible by collectLastValue(sceneInteractor.isVisible)
|
||||
val transitionStateFlow =
|
||||
prepareState(
|
||||
isFeatureEnabled = true,
|
||||
isDeviceUnlocked = true,
|
||||
initialSceneKey = SceneKey.Gone,
|
||||
)
|
||||
@@ -123,44 +123,10 @@ class SceneContainerStartableTest : SysuiTestCase() {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun hydrateVisibility_featureDisabled() =
|
||||
testScope.runTest {
|
||||
val currentDesiredSceneKey by
|
||||
collectLastValue(sceneInteractor.desiredScene.map { it.key })
|
||||
val isVisible by collectLastValue(sceneInteractor.isVisible)
|
||||
val transitionStateFlow =
|
||||
prepareState(
|
||||
isFeatureEnabled = false,
|
||||
isDeviceUnlocked = true,
|
||||
initialSceneKey = SceneKey.Gone,
|
||||
)
|
||||
assertThat(currentDesiredSceneKey).isEqualTo(SceneKey.Gone)
|
||||
assertThat(isVisible).isTrue()
|
||||
|
||||
underTest.start()
|
||||
|
||||
assertThat(isVisible).isTrue()
|
||||
|
||||
sceneInteractor.changeScene(SceneModel(SceneKey.Shade), "reason")
|
||||
transitionStateFlow.value =
|
||||
ObservableTransitionState.Transition(
|
||||
fromScene = SceneKey.Gone,
|
||||
toScene = SceneKey.Shade,
|
||||
progress = flowOf(0.5f),
|
||||
)
|
||||
assertThat(isVisible).isTrue()
|
||||
|
||||
sceneInteractor.onSceneChanged(SceneModel(SceneKey.Shade), "reason")
|
||||
transitionStateFlow.value = ObservableTransitionState.Idle(SceneKey.Shade)
|
||||
assertThat(isVisible).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun switchToLockscreenWhenDeviceLocks_featureEnabled() =
|
||||
fun switchToLockscreenWhenDeviceLocks() =
|
||||
testScope.runTest {
|
||||
val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key })
|
||||
prepareState(
|
||||
isFeatureEnabled = true,
|
||||
isDeviceUnlocked = true,
|
||||
initialSceneKey = SceneKey.Gone,
|
||||
)
|
||||
@@ -173,28 +139,10 @@ class SceneContainerStartableTest : SysuiTestCase() {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun switchToLockscreenWhenDeviceLocks_featureDisabled() =
|
||||
fun switchFromBouncerToGoneWhenDeviceUnlocked() =
|
||||
testScope.runTest {
|
||||
val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key })
|
||||
prepareState(
|
||||
isFeatureEnabled = false,
|
||||
isDeviceUnlocked = false,
|
||||
initialSceneKey = SceneKey.Gone,
|
||||
)
|
||||
assertThat(currentSceneKey).isEqualTo(SceneKey.Gone)
|
||||
underTest.start()
|
||||
|
||||
authenticationRepository.setUnlocked(false)
|
||||
|
||||
assertThat(currentSceneKey).isEqualTo(SceneKey.Gone)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun switchFromBouncerToGoneWhenDeviceUnlocked_featureEnabled() =
|
||||
testScope.runTest {
|
||||
val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key })
|
||||
prepareState(
|
||||
isFeatureEnabled = true,
|
||||
isDeviceUnlocked = false,
|
||||
initialSceneKey = SceneKey.Bouncer,
|
||||
)
|
||||
@@ -207,28 +155,10 @@ class SceneContainerStartableTest : SysuiTestCase() {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun switchFromBouncerToGoneWhenDeviceUnlocked_featureDisabled() =
|
||||
fun switchFromLockscreenToGoneWhenDeviceUnlocksWithBypassOn() =
|
||||
testScope.runTest {
|
||||
val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key })
|
||||
prepareState(
|
||||
isFeatureEnabled = false,
|
||||
isDeviceUnlocked = false,
|
||||
initialSceneKey = SceneKey.Bouncer,
|
||||
)
|
||||
assertThat(currentSceneKey).isEqualTo(SceneKey.Bouncer)
|
||||
underTest.start()
|
||||
|
||||
authenticationRepository.setUnlocked(true)
|
||||
|
||||
assertThat(currentSceneKey).isEqualTo(SceneKey.Bouncer)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun switchFromLockscreenToGoneWhenDeviceUnlocksWithBypassOn_featureOn_bypassOn() =
|
||||
testScope.runTest {
|
||||
val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key })
|
||||
prepareState(
|
||||
isFeatureEnabled = true,
|
||||
isBypassEnabled = true,
|
||||
initialSceneKey = SceneKey.Lockscreen,
|
||||
)
|
||||
@@ -241,11 +171,10 @@ class SceneContainerStartableTest : SysuiTestCase() {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun switchFromLockscreenToGoneWhenDeviceUnlocksWithBypassOn_featureOn_bypassOff() =
|
||||
fun stayOnLockscreenWhenDeviceUnlocksWithBypassOff() =
|
||||
testScope.runTest {
|
||||
val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key })
|
||||
prepareState(
|
||||
isFeatureEnabled = true,
|
||||
isBypassEnabled = false,
|
||||
initialSceneKey = SceneKey.Lockscreen,
|
||||
)
|
||||
@@ -258,28 +187,10 @@ class SceneContainerStartableTest : SysuiTestCase() {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun switchFromLockscreenToGoneWhenDeviceUnlocksWithBypassOn_featureOff_bypassOn() =
|
||||
fun switchToLockscreenWhenDeviceSleepsLocked() =
|
||||
testScope.runTest {
|
||||
val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key })
|
||||
prepareState(
|
||||
isFeatureEnabled = false,
|
||||
isBypassEnabled = true,
|
||||
initialSceneKey = SceneKey.Lockscreen,
|
||||
)
|
||||
assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
|
||||
underTest.start()
|
||||
|
||||
authenticationRepository.setUnlocked(true)
|
||||
|
||||
assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun switchToLockscreenWhenDeviceSleepsLocked_featureEnabled() =
|
||||
testScope.runTest {
|
||||
val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key })
|
||||
prepareState(
|
||||
isFeatureEnabled = true,
|
||||
isDeviceUnlocked = false,
|
||||
initialSceneKey = SceneKey.Shade,
|
||||
)
|
||||
@@ -291,23 +202,6 @@ class SceneContainerStartableTest : SysuiTestCase() {
|
||||
assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun switchToLockscreenWhenDeviceSleepsLocked_featureDisabled() =
|
||||
testScope.runTest {
|
||||
val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key })
|
||||
prepareState(
|
||||
isFeatureEnabled = false,
|
||||
isDeviceUnlocked = false,
|
||||
initialSceneKey = SceneKey.Shade,
|
||||
)
|
||||
assertThat(currentSceneKey).isEqualTo(SceneKey.Shade)
|
||||
underTest.start()
|
||||
|
||||
keyguardRepository.setWakefulnessModel(STARTING_TO_SLEEP)
|
||||
|
||||
assertThat(currentSceneKey).isEqualTo(SceneKey.Shade)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun hydrateSystemUiState() =
|
||||
testScope.runTest {
|
||||
@@ -339,11 +233,10 @@ class SceneContainerStartableTest : SysuiTestCase() {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun switchToGoneWhenDeviceStartsToWakeUp_authMethodNone_featureEnabled() =
|
||||
fun switchToGoneWhenDeviceStartsToWakeUp_authMethodNone() =
|
||||
testScope.runTest {
|
||||
val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key })
|
||||
prepareState(
|
||||
isFeatureEnabled = true,
|
||||
initialSceneKey = SceneKey.Lockscreen,
|
||||
authenticationMethod = AuthenticationMethodModel.None,
|
||||
)
|
||||
@@ -356,11 +249,26 @@ class SceneContainerStartableTest : SysuiTestCase() {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun switchToGoneWhenDeviceStartsToWakeUp_authMethodNotNone_featureEnabled() =
|
||||
fun stayOnLockscreenWhenDeviceStartsToWakeUp_authMethodSwipe() =
|
||||
testScope.runTest {
|
||||
val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key })
|
||||
prepareState(
|
||||
initialSceneKey = SceneKey.Lockscreen,
|
||||
authenticationMethod = AuthenticationMethodModel.Swipe,
|
||||
)
|
||||
assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
|
||||
underTest.start()
|
||||
|
||||
keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE)
|
||||
|
||||
assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun doesNotSwitchToGoneWhenDeviceStartsToWakeUp_authMethodSecure() =
|
||||
testScope.runTest {
|
||||
val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key })
|
||||
prepareState(
|
||||
isFeatureEnabled = true,
|
||||
initialSceneKey = SceneKey.Lockscreen,
|
||||
authenticationMethod = AuthenticationMethodModel.Pin,
|
||||
)
|
||||
@@ -373,30 +281,31 @@ class SceneContainerStartableTest : SysuiTestCase() {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun switchToGoneWhenDeviceStartsToWakeUp_authMethodNone_featureDisabled() =
|
||||
fun switchToGoneWhenDeviceStartsToWakeUp_authMethodSecure_deviceUnlocked() =
|
||||
testScope.runTest {
|
||||
val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key })
|
||||
prepareState(
|
||||
isFeatureEnabled = false,
|
||||
initialSceneKey = SceneKey.Lockscreen,
|
||||
authenticationMethod = AuthenticationMethodModel.None,
|
||||
authenticationMethod = AuthenticationMethodModel.Pin,
|
||||
isDeviceUnlocked = false,
|
||||
)
|
||||
assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
|
||||
underTest.start()
|
||||
|
||||
authenticationRepository.setUnlocked(true)
|
||||
runCurrent()
|
||||
keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE)
|
||||
|
||||
assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
|
||||
assertThat(currentSceneKey).isEqualTo(SceneKey.Gone)
|
||||
}
|
||||
|
||||
private fun prepareState(
|
||||
isFeatureEnabled: Boolean = true,
|
||||
isDeviceUnlocked: Boolean = false,
|
||||
isBypassEnabled: Boolean = false,
|
||||
initialSceneKey: SceneKey? = null,
|
||||
authenticationMethod: AuthenticationMethodModel? = null,
|
||||
): MutableStateFlow<ObservableTransitionState> {
|
||||
featureFlags.set(Flags.SCENE_CONTAINER, isFeatureEnabled)
|
||||
featureFlags.set(Flags.SCENE_CONTAINER, true)
|
||||
authenticationRepository.setUnlocked(isDeviceUnlocked)
|
||||
keyguardRepository.setBypassEnabled(isBypassEnabled)
|
||||
val transitionStateFlow =
|
||||
@@ -410,7 +319,7 @@ class SceneContainerStartableTest : SysuiTestCase() {
|
||||
sceneInteractor.onSceneChanged(SceneModel(it), "reason")
|
||||
}
|
||||
authenticationMethod?.let {
|
||||
authenticationRepository.setAuthenticationMethod(authenticationMethod)
|
||||
authenticationRepository.setAuthenticationMethod(authenticationMethod.toDataLayer())
|
||||
authenticationRepository.setLockscreenEnabled(
|
||||
authenticationMethod != AuthenticationMethodModel.None
|
||||
)
|
||||
|
||||
@@ -18,9 +18,11 @@ package com.android.systemui.scene
|
||||
|
||||
import android.content.pm.UserInfo
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.authentication.data.model.AuthenticationMethodModel as DataLayerAuthenticationMethodModel
|
||||
import com.android.systemui.authentication.data.repository.AuthenticationRepository
|
||||
import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
|
||||
import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
|
||||
import com.android.systemui.authentication.domain.model.AuthenticationMethodModel as DomainLayerAuthenticationMethodModel
|
||||
import com.android.systemui.bouncer.data.repository.BouncerRepository
|
||||
import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
|
||||
import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
|
||||
@@ -201,5 +203,18 @@ class SceneTestUtils(
|
||||
RemoteUserInput(10f, 40f, RemoteUserInputAction.MOVE),
|
||||
RemoteUserInput(10f, 40f, RemoteUserInputAction.UP),
|
||||
)
|
||||
|
||||
fun DomainLayerAuthenticationMethodModel.toDataLayer(): DataLayerAuthenticationMethodModel {
|
||||
return when (this) {
|
||||
DomainLayerAuthenticationMethodModel.None -> DataLayerAuthenticationMethodModel.None
|
||||
DomainLayerAuthenticationMethodModel.Swipe ->
|
||||
DataLayerAuthenticationMethodModel.None
|
||||
DomainLayerAuthenticationMethodModel.Pin -> DataLayerAuthenticationMethodModel.Pin
|
||||
DomainLayerAuthenticationMethodModel.Password ->
|
||||
DataLayerAuthenticationMethodModel.Password
|
||||
DomainLayerAuthenticationMethodModel.Pattern ->
|
||||
DataLayerAuthenticationMethodModel.Pattern
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user