[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:
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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() =
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user