[flexiglass] Centralizes automatic scene changing business logic.

We had automatic scene changing business logic spread out amongst
various scene-specific interactors. This was suboptimal for a few reasons:
1. Hard to reason about what changes the scenes and when, when it's all
   distributed
2. Hard to gate this logic based on the feature flag; causing memory and
   boot time regressions as seen in the attached files, because more
   resources were being used at startup even when the feature was off

This CL moves business logic from LockScreenSceneInteractor and
BouncerInteractor into SystemUiDefaultSceneContainerStartable and makes
sure it's all gated behind the feature flag.

Fix: 280883900, 289353099, 289726647
Test: new unit tests added
Test: manually verified in system UI that locking the device moves back
to the Lockscreen scene and unlocking the device moves to the Gone scene

Change-Id: I1dc46fc2d1d71f1b9baddc822ef69f5ea7b21cfc
This commit is contained in:
Alejandro Nijamkin
2023-07-10 16:43:15 -07:00
parent 0407508cfa
commit 778b918db2
13 changed files with 328 additions and 188 deletions

View File

@@ -25,6 +25,8 @@ import com.android.systemui.authentication.shared.model.AuthenticationMethodMode
import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel
import com.android.systemui.bouncer.data.repository.BouncerRepository
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
@@ -38,9 +40,6 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
@@ -53,6 +52,7 @@ constructor(
private val repository: BouncerRepository,
private val authenticationInteractor: AuthenticationInteractor,
private val sceneInteractor: SceneInteractor,
featureFlags: FeatureFlags,
@Assisted private val containerName: String,
) {
@@ -95,36 +95,14 @@ constructor(
val isPatternVisible: StateFlow<Boolean> = authenticationInteractor.isPatternVisible
init {
// UNLOCKING SHOWS Gone.
//
// Move to the gone scene if the device becomes unlocked while on the bouncer scene.
applicationScope.launch {
sceneInteractor
.currentScene(containerName)
.flatMapLatest { currentScene ->
if (currentScene.key == SceneKey.Bouncer) {
authenticationInteractor.isUnlocked
} else {
flowOf(false)
if (featureFlags.isEnabled(Flags.SCENE_CONTAINER)) {
// Clear the message if moved from throttling to no-longer throttling.
applicationScope.launch {
isThrottled.pairwise().collect { (wasThrottled, currentlyThrottled) ->
if (wasThrottled && !currentlyThrottled) {
clearMessage()
}
}
.distinctUntilChanged()
.collect { isUnlocked ->
if (isUnlocked) {
sceneInteractor.setCurrentScene(
containerName = containerName,
scene = SceneModel(SceneKey.Gone),
)
}
}
}
// Clear the message if moved from throttling to no-longer throttling.
applicationScope.launch {
isThrottled.pairwise().collect { (wasThrottled, currentlyThrottled) ->
if (wasThrottled && !currentlyThrottled) {
clearMessage()
}
}
}
}

View File

@@ -23,6 +23,8 @@ import com.android.systemui.R
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.util.kotlin.pairwise
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
@@ -49,6 +51,7 @@ constructor(
@Application private val applicationContext: Context,
@Application private val applicationScope: CoroutineScope,
interactorFactory: BouncerInteractor.Factory,
featureFlags: FeatureFlags,
@Assisted containerName: String,
) {
private val interactor: BouncerInteractor = interactorFactory.create(containerName)
@@ -102,15 +105,48 @@ constructor(
)
init {
applicationScope.launch {
_authMethod.subscriptionCount
.pairwise()
.map { (previousCount, currentCount) -> currentCount > previousCount }
.collect { subscriberAdded ->
if (subscriberAdded) {
reloadAuthMethod()
if (featureFlags.isEnabled(Flags.SCENE_CONTAINER)) {
applicationScope.launch {
interactor.isThrottled
.map { isThrottled ->
if (isThrottled) {
when (interactor.getAuthenticationMethod()) {
is AuthenticationMethodModel.Pin ->
R.string.kg_too_many_failed_pin_attempts_dialog_message
is AuthenticationMethodModel.Password ->
R.string.kg_too_many_failed_password_attempts_dialog_message
is AuthenticationMethodModel.Pattern ->
R.string.kg_too_many_failed_pattern_attempts_dialog_message
else -> null
}?.let { stringResourceId ->
applicationContext.getString(
stringResourceId,
interactor.throttling.value.failedAttemptCount,
ceil(interactor.throttling.value.remainingMs / 1000f).toInt(),
)
}
} else {
null
}
}
}
.distinctUntilChanged()
.collect { dialogMessageOrNull ->
if (dialogMessageOrNull != null) {
_throttlingDialogMessage.value = dialogMessageOrNull
}
}
}
applicationScope.launch {
_authMethod.subscriptionCount
.pairwise()
.map { (previousCount, currentCount) -> currentCount > previousCount }
.collect { subscriberAdded ->
if (subscriberAdded) {
reloadAuthMethod()
}
}
}
}
}
@@ -144,39 +180,6 @@ constructor(
*/
val throttlingDialogMessage: StateFlow<String?> = _throttlingDialogMessage.asStateFlow()
init {
applicationScope.launch {
interactor.isThrottled
.map { isThrottled ->
if (isThrottled) {
when (interactor.getAuthenticationMethod()) {
is AuthenticationMethodModel.Pin ->
R.string.kg_too_many_failed_pin_attempts_dialog_message
is AuthenticationMethodModel.Password ->
R.string.kg_too_many_failed_password_attempts_dialog_message
is AuthenticationMethodModel.Pattern ->
R.string.kg_too_many_failed_pattern_attempts_dialog_message
else -> null
}?.let { stringResourceId ->
applicationContext.getString(
stringResourceId,
interactor.throttling.value.failedAttemptCount,
ceil(interactor.throttling.value.remainingMs / 1000f).toInt(),
)
}
} else {
null
}
}
.distinctUntilChanged()
.collect { dialogMessageOrNull ->
if (dialogMessageOrNull != null) {
_throttlingDialogMessage.value = dialogMessageOrNull
}
}
}
}
/** Notifies that the emergency services button was clicked. */
fun onEmergencyServicesButtonClicked() {
// TODO(b/280877228): implement this

View File

@@ -20,20 +20,14 @@ import com.android.systemui.authentication.domain.interactor.AuthenticationInter
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
/** Hosts business and application state accessing logic for the lockscreen scene. */
class LockscreenSceneInteractor
@@ -42,7 +36,6 @@ constructor(
@Application applicationScope: CoroutineScope,
private val authenticationInteractor: AuthenticationInteractor,
bouncerInteractorFactory: BouncerInteractor.Factory,
private val sceneInteractor: SceneInteractor,
@Assisted private val containerName: String,
) {
private val bouncerInteractor: BouncerInteractor =
@@ -72,46 +65,6 @@ constructor(
initialValue = false,
)
init {
// LOCKING SHOWS Lockscreen.
//
// Move to the lockscreen scene if the device becomes locked while in any scene.
applicationScope.launch {
authenticationInteractor.isUnlocked
.map { !it }
.distinctUntilChanged()
.collect { isLocked ->
if (isLocked) {
sceneInteractor.setCurrentScene(
containerName = containerName,
scene = SceneModel(SceneKey.Lockscreen),
)
}
}
}
// BYPASS UNLOCK.
//
// Moves to the gone scene if bypass is enabled and the device becomes unlocked while in the
// lockscreen scene.
applicationScope.launch {
combine(
authenticationInteractor.isBypassEnabled,
authenticationInteractor.isUnlocked,
sceneInteractor.currentScene(containerName),
::Triple,
)
.collect { (isBypassEnabled, isUnlocked, currentScene) ->
if (isBypassEnabled && isUnlocked && currentScene.key == SceneKey.Lockscreen) {
sceneInteractor.setCurrentScene(
containerName = containerName,
scene = SceneModel(SceneKey.Gone),
)
}
}
}
}
/** Attempts to dismiss the lockscreen. This will cause the bouncer to show, if needed. */
fun dismissLockscreen() {
bouncerInteractor.showOrUnlockDevice(containerName = containerName)

View File

@@ -17,6 +17,7 @@
package com.android.systemui.scene.domain.startable
import com.android.systemui.CoreStartable
import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.flags.FeatureFlags
@@ -24,9 +25,11 @@ import com.android.systemui.flags.Flags
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.model.SceneContainerNames
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
@@ -41,17 +44,19 @@ class SystemUiDefaultSceneContainerStartable
constructor(
@Application private val applicationScope: CoroutineScope,
private val sceneInteractor: SceneInteractor,
private val authenticationInteractor: AuthenticationInteractor,
private val featureFlags: FeatureFlags,
) : CoreStartable {
override fun start() {
if (featureFlags.isEnabled(Flags.SCENE_CONTAINER)) {
keepVisibilityUpdated()
hydrateVisibility()
automaticallySwitchScenes()
}
}
/** Drives visibility of the scene container. */
private fun keepVisibilityUpdated() {
/** Updates the visibility of the scene container based on the current scene. */
private fun hydrateVisibility() {
applicationScope.launch {
sceneInteractor
.currentScene(CONTAINER_NAME)
@@ -63,6 +68,48 @@ constructor(
}
}
/** Switches between scenes based on ever-changing application state. */
private fun automaticallySwitchScenes() {
applicationScope.launch {
authenticationInteractor.isUnlocked
.map { isUnlocked ->
val currentSceneKey = sceneInteractor.currentScene(CONTAINER_NAME).value.key
val isBypassEnabled = authenticationInteractor.isBypassEnabled.value
when {
isUnlocked ->
when (currentSceneKey) {
// When the device becomes unlocked in Bouncer, go to the Gone.
is SceneKey.Bouncer -> SceneKey.Gone
// When the device becomes unlocked in Lockscreen, go to Gone if
// bypass is enabled.
is SceneKey.Lockscreen -> SceneKey.Gone.takeIf { isBypassEnabled }
// We got unlocked while on a scene that's not Lockscreen or
// Bouncer, no need to change scenes.
else -> null
}
// When the device becomes locked, to Lockscreen.
!isUnlocked ->
when (currentSceneKey) {
// Already on lockscreen or bouncer, no need to change scenes.
is SceneKey.Lockscreen,
is SceneKey.Bouncer -> null
// We got locked while on a scene that's not Lockscreen or Bouncer,
// go to Lockscreen.
else -> SceneKey.Lockscreen
}
else -> null
}
}
.filterNotNull()
.collect { targetSceneKey ->
sceneInteractor.setCurrentScene(
containerName = CONTAINER_NAME,
scene = SceneModel(targetSceneKey),
)
}
}
}
companion object {
private const val CONTAINER_NAME = SceneContainerNames.SYSTEM_UI_DEFAULT
}

View File

@@ -345,23 +345,6 @@ class BouncerInteractorTest : SysuiTestCase() {
assertThat(throttling).isEqualTo(AuthenticationThrottlingModel())
}
@Test
fun switchesToGone_whenUnlocked() =
testScope.runTest {
utils.authenticationRepository.setUnlocked(false)
sceneInteractor.setCurrentScene(
SceneTestUtils.CONTAINER_1,
SceneModel(SceneKey.Bouncer)
)
val currentScene by
collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
utils.authenticationRepository.setUnlocked(true)
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
}
private fun assertTryAgainMessage(
message: String?,
time: Int,

View File

@@ -52,7 +52,10 @@ class BouncerViewModelTest : SysuiTestCase() {
authenticationInteractor = authenticationInteractor,
sceneInteractor = utils.sceneInteractor(),
)
private val underTest = utils.bouncerViewModel(bouncerInteractor)
private val underTest =
utils.bouncerViewModel(
bouncerInteractor = bouncerInteractor,
)
@Test
fun authMethod_nonNullForSecureMethods_nullForNotSecureMethods() =

View File

@@ -20,7 +20,6 @@ import androidx.test.filters.SmallTest
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.scene.SceneTestUtils
import com.android.systemui.scene.shared.model.SceneKey
@@ -54,16 +53,8 @@ class PinBouncerViewModelTest : SysuiTestCase() {
sceneInteractor = sceneInteractor,
)
private val bouncerViewModel =
BouncerViewModel(
applicationContext = context,
applicationScope = testScope.backgroundScope,
interactorFactory =
object : BouncerInteractor.Factory {
override fun create(containerName: String): BouncerInteractor {
return bouncerInteractor
}
},
containerName = SceneTestUtils.CONTAINER_1,
utils.bouncerViewModel(
bouncerInteractor = bouncerInteractor,
)
private val underTest =
PinBouncerViewModel(

View File

@@ -47,7 +47,6 @@ class LockscreenSceneInteractorTest : SysuiTestCase() {
private val underTest =
utils.lockScreenSceneInteractor(
authenticationInteractor = authenticationInteractor,
sceneInteractor = sceneInteractor,
bouncerInteractor =
utils.bouncerInteractor(
authenticationInteractor = authenticationInteractor,
@@ -128,22 +127,6 @@ class LockscreenSceneInteractorTest : SysuiTestCase() {
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
}
@Test
fun deviceLockedInNonLockScreenScene_switchesToLockScreenScene() =
testScope.runTest {
val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
runCurrent()
sceneInteractor.setCurrentScene(CONTAINER_1, SceneModel(SceneKey.Gone))
runCurrent()
utils.authenticationRepository.setUnlocked(true)
runCurrent()
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
utils.authenticationRepository.setUnlocked(false)
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
}
@Test
fun switchFromLockScreenToGone_authMethodNotSwipe_doesNotUnlockDevice() =
testScope.runTest {

View File

@@ -56,7 +56,6 @@ class LockscreenSceneViewModelTest : SysuiTestCase() {
override fun create(containerName: String): LockscreenSceneInteractor {
return utils.lockScreenSceneInteractor(
authenticationInteractor = authenticationInteractor,
sceneInteractor = sceneInteractor,
bouncerInteractor =
utils.bouncerInteractor(
authenticationInteractor = authenticationInteractor,

View File

@@ -53,7 +53,6 @@ class QuickSettingsSceneViewModelTest : SysuiTestCase() {
override fun create(containerName: String): LockscreenSceneInteractor {
return utils.lockScreenSceneInteractor(
authenticationInteractor = authenticationInteractor,
sceneInteractor = sceneInteractor,
bouncerInteractor =
utils.bouncerInteractor(
authenticationInteractor = authenticationInteractor,

View File

@@ -14,14 +14,11 @@
* limitations under the License.
*/
@file:OptIn(ExperimentalCoroutinesApi::class, ExperimentalCoroutinesApi::class)
package com.android.systemui.scene.domain.startable
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.scene.SceneTestUtils
import com.android.systemui.scene.shared.model.SceneContainerNames
@@ -29,11 +26,14 @@ import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(JUnit4::class)
class SystemUiDefaultSceneContainerStartableTest : SysuiTestCase() {
@@ -41,29 +41,47 @@ class SystemUiDefaultSceneContainerStartableTest : SysuiTestCase() {
private val utils = SceneTestUtils(this)
private val testScope = utils.testScope
private val sceneInteractor = utils.sceneInteractor()
private val featureFlags = FakeFeatureFlags()
private val featureFlags = utils.featureFlags
private val authenticationRepository = utils.authenticationRepository()
private val authenticationInteractor =
utils.authenticationInteractor(
repository = authenticationRepository,
)
private val underTest =
SystemUiDefaultSceneContainerStartable(
applicationScope = testScope.backgroundScope,
sceneInteractor = sceneInteractor,
authenticationInteractor = authenticationInteractor,
featureFlags = featureFlags,
)
@Before
fun setUp() {
prepareState()
}
@Test
fun start_featureEnabled_keepsVisibilityUpdated() =
fun hydrateVisibility_featureEnabled() =
testScope.runTest {
featureFlags.set(Flags.SCENE_CONTAINER, true)
val currentSceneKey by
collectLastValue(
sceneInteractor.currentScene(SceneContainerNames.SYSTEM_UI_DEFAULT).map {
it.key
}
)
val isVisible by
collectLastValue(sceneInteractor.isVisible(SceneContainerNames.SYSTEM_UI_DEFAULT))
prepareState(
isFeatureEnabled = true,
isDeviceUnlocked = true,
initialSceneKey = SceneKey.Gone,
)
assertThat(currentSceneKey).isEqualTo(SceneKey.Gone)
assertThat(isVisible).isTrue()
underTest.start()
sceneInteractor.setCurrentScene(
SceneContainerNames.SYSTEM_UI_DEFAULT,
SceneModel(SceneKey.Gone)
)
assertThat(isVisible).isFalse()
sceneInteractor.setCurrentScene(
@@ -74,14 +92,26 @@ class SystemUiDefaultSceneContainerStartableTest : SysuiTestCase() {
}
@Test
fun start_featureDisabled_doesNotUpdateVisibility() =
fun hydrateVisibility_featureDisabled() =
testScope.runTest {
featureFlags.set(Flags.SCENE_CONTAINER, false)
val currentSceneKey by
collectLastValue(
sceneInteractor.currentScene(SceneContainerNames.SYSTEM_UI_DEFAULT).map {
it.key
}
)
val isVisible by
collectLastValue(sceneInteractor.isVisible(SceneContainerNames.SYSTEM_UI_DEFAULT))
prepareState(
isFeatureEnabled = false,
isDeviceUnlocked = true,
initialSceneKey = SceneKey.Lockscreen,
)
assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
assertThat(isVisible).isTrue()
underTest.start()
assertThat(isVisible).isTrue()
sceneInteractor.setCurrentScene(
SceneContainerNames.SYSTEM_UI_DEFAULT,
@@ -95,4 +125,172 @@ class SystemUiDefaultSceneContainerStartableTest : SysuiTestCase() {
)
assertThat(isVisible).isTrue()
}
@Test
fun switchToLockscreenWhenDeviceLocks_featureEnabled() =
testScope.runTest {
val currentSceneKey by
collectLastValue(
sceneInteractor.currentScene(SceneContainerNames.SYSTEM_UI_DEFAULT).map {
it.key
}
)
prepareState(
isFeatureEnabled = true,
isDeviceUnlocked = true,
initialSceneKey = SceneKey.Gone,
)
assertThat(currentSceneKey).isEqualTo(SceneKey.Gone)
underTest.start()
authenticationRepository.setUnlocked(false)
assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
}
@Test
fun switchToLockscreenWhenDeviceLocks_featureDisabled() =
testScope.runTest {
val currentSceneKey by
collectLastValue(
sceneInteractor.currentScene(SceneContainerNames.SYSTEM_UI_DEFAULT).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.currentScene(SceneContainerNames.SYSTEM_UI_DEFAULT).map {
it.key
}
)
prepareState(
isFeatureEnabled = true,
isDeviceUnlocked = false,
initialSceneKey = SceneKey.Bouncer,
)
assertThat(currentSceneKey).isEqualTo(SceneKey.Bouncer)
underTest.start()
authenticationRepository.setUnlocked(true)
assertThat(currentSceneKey).isEqualTo(SceneKey.Gone)
}
@Test
fun switchFromBouncerToGoneWhenDeviceUnlocked_featureDisabled() =
testScope.runTest {
val currentSceneKey by
collectLastValue(
sceneInteractor.currentScene(SceneContainerNames.SYSTEM_UI_DEFAULT).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.currentScene(SceneContainerNames.SYSTEM_UI_DEFAULT).map {
it.key
}
)
prepareState(
isFeatureEnabled = true,
isBypassEnabled = true,
initialSceneKey = SceneKey.Lockscreen,
)
assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
underTest.start()
authenticationRepository.setUnlocked(true)
assertThat(currentSceneKey).isEqualTo(SceneKey.Gone)
}
@Test
fun switchFromLockscreenToGoneWhenDeviceUnlocksWithBypassOn_featureOn_bypassOff() =
testScope.runTest {
val currentSceneKey by
collectLastValue(
sceneInteractor.currentScene(SceneContainerNames.SYSTEM_UI_DEFAULT).map {
it.key
}
)
prepareState(
isFeatureEnabled = true,
isBypassEnabled = false,
initialSceneKey = SceneKey.Lockscreen,
)
assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
underTest.start()
authenticationRepository.setUnlocked(true)
assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
}
@Test
fun switchFromLockscreenToGoneWhenDeviceUnlocksWithBypassOn_featureOff_bypassOn() =
testScope.runTest {
val currentSceneKey by
collectLastValue(
sceneInteractor.currentScene(SceneContainerNames.SYSTEM_UI_DEFAULT).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)
}
private fun prepareState(
isFeatureEnabled: Boolean = true,
isDeviceUnlocked: Boolean = false,
isBypassEnabled: Boolean = false,
initialSceneKey: SceneKey? = null,
) {
featureFlags.set(Flags.SCENE_CONTAINER, isFeatureEnabled)
authenticationRepository.setUnlocked(isDeviceUnlocked)
authenticationRepository.setBypassEnabled(isBypassEnabled)
initialSceneKey?.let {
sceneInteractor.setCurrentScene(SceneContainerNames.SYSTEM_UI_DEFAULT, SceneModel(it))
}
}
}

View File

@@ -22,7 +22,6 @@ import com.android.systemui.authentication.shared.model.AuthenticationMethodMode
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.keyguard.domain.interactor.LockscreenSceneInteractor
import com.android.systemui.scene.SceneTestUtils
import com.android.systemui.scene.SceneTestUtils.Companion.CONTAINER_1
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
import com.google.common.truth.Truth.assertThat
@@ -54,7 +53,6 @@ class ShadeSceneViewModelTest : SysuiTestCase() {
override fun create(containerName: String): LockscreenSceneInteractor {
return utils.lockScreenSceneInteractor(
authenticationInteractor = authenticationInteractor,
sceneInteractor = sceneInteractor,
bouncerInteractor =
utils.bouncerInteractor(
authenticationInteractor = authenticationInteractor,
@@ -89,7 +87,8 @@ class ShadeSceneViewModelTest : SysuiTestCase() {
@Test
fun onContentClicked_deviceUnlocked_switchesToGone() =
testScope.runTest {
val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
val currentScene by
collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
utils.authenticationRepository.setUnlocked(true)
runCurrent()
@@ -102,7 +101,8 @@ class ShadeSceneViewModelTest : SysuiTestCase() {
@Test
fun onContentClicked_deviceLockedSecurely_switchesToBouncer() =
testScope.runTest {
val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
val currentScene by
collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
utils.authenticationRepository.setUnlocked(false)
runCurrent()

View File

@@ -24,6 +24,8 @@ import com.android.systemui.authentication.domain.interactor.AuthenticationInter
import com.android.systemui.bouncer.data.repository.BouncerRepository
import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.domain.interactor.LockscreenSceneInteractor
import com.android.systemui.scene.data.repository.SceneContainerRepository
import com.android.systemui.scene.domain.interactor.SceneInteractor
@@ -51,6 +53,7 @@ class SceneTestUtils(
) {
val testDispatcher = StandardTestDispatcher()
val testScope = TestScope(testDispatcher)
val featureFlags = FakeFeatureFlags().apply { set(Flags.SCENE_CONTAINER, true) }
private val userRepository: UserRepository by lazy {
FakeUserRepository().apply {
val users = listOf(UserInfo(/* id= */ 0, "name", /* flags= */ 0))
@@ -129,6 +132,7 @@ class SceneTestUtils(
repository = BouncerRepository(),
authenticationInteractor = authenticationInteractor,
sceneInteractor = sceneInteractor,
featureFlags = featureFlags,
containerName = CONTAINER_1,
)
}
@@ -145,13 +149,13 @@ class SceneTestUtils(
return bouncerInteractor
}
},
featureFlags = featureFlags,
containerName = CONTAINER_1,
)
}
fun lockScreenSceneInteractor(
authenticationInteractor: AuthenticationInteractor,
sceneInteractor: SceneInteractor,
bouncerInteractor: BouncerInteractor,
): LockscreenSceneInteractor {
return LockscreenSceneInteractor(
@@ -163,7 +167,6 @@ class SceneTestUtils(
return bouncerInteractor
}
},
sceneInteractor = sceneInteractor,
containerName = CONTAINER_1,
)
}