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 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.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,30 +95,7 @@ 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)
                    }
                }
                .distinctUntilChanged()
                .collect { isUnlocked ->
                    if (isUnlocked) {
                        sceneInteractor.setCurrentScene(
                            containerName = containerName,
                            scene = SceneModel(SceneKey.Gone),
                        )
                    }
                }
        }

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

    /**
     * Returns the currently-configured authentication method. This determines how the
+44 −41
Original line number 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.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,6 +105,38 @@ constructor(
        )

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

    /** The user-facing message to show in the bouncer. */
    val message: StateFlow<MessageViewModel> =
@@ -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
+0 −47
Original line number 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.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)
+50 −3
Original line number Diff line number Diff line
@@ -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
    }
+0 −17
Original line number Diff line number Diff line
@@ -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,
Loading