Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit f56b0db7 authored by Ale Nijamkin's avatar Ale Nijamkin Committed by Android (Google) Code Review
Browse files

Merge "[flexiglass] Centralizes automatic scene changing business logic." into udc-qpr-dev

parents eaf79c20 778b918d
Loading
Loading
Loading
Loading
+9 −31
Original line number Original line Diff line number Diff line
@@ -25,6 +25,8 @@ import com.android.systemui.authentication.shared.model.AuthenticationMethodMode
import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel
import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel
import com.android.systemui.bouncer.data.repository.BouncerRepository
import com.android.systemui.bouncer.data.repository.BouncerRepository
import com.android.systemui.dagger.qualifiers.Application
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.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
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.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
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.flow.stateIn
import kotlinx.coroutines.launch
import kotlinx.coroutines.launch


@@ -53,6 +52,7 @@ constructor(
    private val repository: BouncerRepository,
    private val repository: BouncerRepository,
    private val authenticationInteractor: AuthenticationInteractor,
    private val authenticationInteractor: AuthenticationInteractor,
    private val sceneInteractor: SceneInteractor,
    private val sceneInteractor: SceneInteractor,
    featureFlags: FeatureFlags,
    @Assisted private val containerName: String,
    @Assisted private val containerName: String,
) {
) {


@@ -95,30 +95,7 @@ constructor(
    val isPatternVisible: StateFlow<Boolean> = authenticationInteractor.isPatternVisible
    val isPatternVisible: StateFlow<Boolean> = authenticationInteractor.isPatternVisible


    init {
    init {
        // UNLOCKING SHOWS Gone.
        if (featureFlags.isEnabled(Flags.SCENE_CONTAINER)) {
        //
        // 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)
                    }
                }
                .distinctUntilChanged()
                .collect { isUnlocked ->
                    if (isUnlocked) {
                        sceneInteractor.setCurrentScene(
                            containerName = containerName,
                            scene = SceneModel(SceneKey.Gone),
                        )
                    }
                }
        }

            // Clear the message if moved from throttling to no-longer throttling.
            // Clear the message if moved from throttling to no-longer throttling.
            applicationScope.launch {
            applicationScope.launch {
                isThrottled.pairwise().collect { (wasThrottled, currentlyThrottled) ->
                isThrottled.pairwise().collect { (wasThrottled, currentlyThrottled) ->
@@ -128,6 +105,7 @@ constructor(
                }
                }
            }
            }
        }
        }
    }


    /**
    /**
     * Returns the currently-configured authentication method. This determines how the
     * Returns the currently-configured authentication method. This determines how the
+44 −41
Original line number Original line Diff line number Diff line
@@ -23,6 +23,8 @@ import com.android.systemui.R
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
import com.android.systemui.dagger.qualifiers.Application
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 com.android.systemui.util.kotlin.pairwise
import dagger.assisted.Assisted
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedFactory
@@ -49,6 +51,7 @@ constructor(
    @Application private val applicationContext: Context,
    @Application private val applicationContext: Context,
    @Application private val applicationScope: CoroutineScope,
    @Application private val applicationScope: CoroutineScope,
    interactorFactory: BouncerInteractor.Factory,
    interactorFactory: BouncerInteractor.Factory,
    featureFlags: FeatureFlags,
    @Assisted containerName: String,
    @Assisted containerName: String,
) {
) {
    private val interactor: BouncerInteractor = interactorFactory.create(containerName)
    private val interactor: BouncerInteractor = interactorFactory.create(containerName)
@@ -102,6 +105,38 @@ constructor(
        )
        )


    init {
    init {
        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 {
            applicationScope.launch {
                _authMethod.subscriptionCount
                _authMethod.subscriptionCount
                    .pairwise()
                    .pairwise()
@@ -113,6 +148,7 @@ constructor(
                    }
                    }
            }
            }
        }
        }
    }


    /** The user-facing message to show in the bouncer. */
    /** The user-facing message to show in the bouncer. */
    val message: StateFlow<MessageViewModel> =
    val message: StateFlow<MessageViewModel> =
@@ -144,39 +180,6 @@ constructor(
     */
     */
    val throttlingDialogMessage: StateFlow<String?> = _throttlingDialogMessage.asStateFlow()
    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. */
    /** Notifies that the emergency services button was clicked. */
    fun onEmergencyServicesButtonClicked() {
    fun onEmergencyServicesButtonClicked() {
        // TODO(b/280877228): implement this
        // TODO(b/280877228): implement this
+0 −47
Original line number Original line Diff line number Diff line
@@ -20,20 +20,14 @@ import com.android.systemui.authentication.domain.interactor.AuthenticationInter
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
import com.android.systemui.dagger.qualifiers.Application
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.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import dagger.assisted.AssistedInject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch


/** Hosts business and application state accessing logic for the lockscreen scene. */
/** Hosts business and application state accessing logic for the lockscreen scene. */
class LockscreenSceneInteractor
class LockscreenSceneInteractor
@@ -42,7 +36,6 @@ constructor(
    @Application applicationScope: CoroutineScope,
    @Application applicationScope: CoroutineScope,
    private val authenticationInteractor: AuthenticationInteractor,
    private val authenticationInteractor: AuthenticationInteractor,
    bouncerInteractorFactory: BouncerInteractor.Factory,
    bouncerInteractorFactory: BouncerInteractor.Factory,
    private val sceneInteractor: SceneInteractor,
    @Assisted private val containerName: String,
    @Assisted private val containerName: String,
) {
) {
    private val bouncerInteractor: BouncerInteractor =
    private val bouncerInteractor: BouncerInteractor =
@@ -72,46 +65,6 @@ constructor(
                initialValue = false,
                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. */
    /** Attempts to dismiss the lockscreen. This will cause the bouncer to show, if needed. */
    fun dismissLockscreen() {
    fun dismissLockscreen() {
        bouncerInteractor.showOrUnlockDevice(containerName = containerName)
        bouncerInteractor.showOrUnlockDevice(containerName = containerName)
+50 −3
Original line number Original line Diff line number Diff line
@@ -17,6 +17,7 @@
package com.android.systemui.scene.domain.startable
package com.android.systemui.scene.domain.startable


import com.android.systemui.CoreStartable
import com.android.systemui.CoreStartable
import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.flags.FeatureFlags
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.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.model.SceneContainerNames
import com.android.systemui.scene.shared.model.SceneContainerNames
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
import javax.inject.Inject
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import kotlinx.coroutines.launch


@@ -41,17 +44,19 @@ class SystemUiDefaultSceneContainerStartable
constructor(
constructor(
    @Application private val applicationScope: CoroutineScope,
    @Application private val applicationScope: CoroutineScope,
    private val sceneInteractor: SceneInteractor,
    private val sceneInteractor: SceneInteractor,
    private val authenticationInteractor: AuthenticationInteractor,
    private val featureFlags: FeatureFlags,
    private val featureFlags: FeatureFlags,
) : CoreStartable {
) : CoreStartable {


    override fun start() {
    override fun start() {
        if (featureFlags.isEnabled(Flags.SCENE_CONTAINER)) {
        if (featureFlags.isEnabled(Flags.SCENE_CONTAINER)) {
            keepVisibilityUpdated()
            hydrateVisibility()
            automaticallySwitchScenes()
        }
        }
    }
    }


    /** Drives visibility of the scene container. */
    /** Updates the visibility of the scene container based on the current scene. */
    private fun keepVisibilityUpdated() {
    private fun hydrateVisibility() {
        applicationScope.launch {
        applicationScope.launch {
            sceneInteractor
            sceneInteractor
                .currentScene(CONTAINER_NAME)
                .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 {
    companion object {
        private const val CONTAINER_NAME = SceneContainerNames.SYSTEM_UI_DEFAULT
        private const val CONTAINER_NAME = SceneContainerNames.SYSTEM_UI_DEFAULT
    }
    }
+0 −17
Original line number Original line Diff line number Diff line
@@ -345,23 +345,6 @@ class BouncerInteractorTest : SysuiTestCase() {
            assertThat(throttling).isEqualTo(AuthenticationThrottlingModel())
            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(
    private fun assertTryAgainMessage(
        message: String?,
        message: String?,
        time: Int,
        time: Int,
Loading