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

Commit f6387084 authored by burakov's avatar burakov
Browse files

[flexiglass] Clear bouncer input when it's dismissed.

Fix: 310960073
Test: Manually verified by typing a password (and PIN), dismissing the
 bouncer, reopening the bouncer, and observing that the input is empty.
Test: Added new unit tests.
Test: Existing unit tests still pass.
Flag: ACONFIG com.android.systemui.scene_container DEVELOPMENT
Change-Id: I1a38e2714130533efc67d3c1b10ec1edbb4330d2
parent 91400a46
Loading
Loading
Loading
Loading
+6 −3
Original line number Diff line number Diff line
@@ -28,6 +28,7 @@ import androidx.compose.material3.LocalTextStyle
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.TextField
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
@@ -63,11 +64,13 @@ internal fun PasswordBouncer(
    val isImeVisible by rememberUpdatedState(WindowInsets.imeAnimationTarget.getBottom(density) > 0)
    LaunchedEffect(isImeVisible) { viewModel.onImeVisibilityChanged(isImeVisible) }

    LaunchedEffect(Unit) {
    DisposableEffect(Unit) {
        viewModel.onShown()

        // When the UI comes up, request focus on the TextField to bring up the software keyboard.
        focusRequester.requestFocus()
        // Also, report that the UI is shown to let the view-model run some logic.
        viewModel.onShown()

        onDispose { viewModel.onHidden() }
    }

    LaunchedEffect(animateFailure) {
+5 −2
Original line number Diff line number Diff line
@@ -26,6 +26,7 @@ import androidx.compose.foundation.gestures.awaitFirstDown
import androidx.compose.foundation.gestures.detectDragGestures
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
@@ -65,8 +66,10 @@ internal fun PatternBouncer(
    viewModel: PatternBouncerViewModel,
    modifier: Modifier = Modifier,
) {
    // Report that the UI is shown to let the view-model run some logic.
    LaunchedEffect(Unit) { viewModel.onShown() }
    DisposableEffect(Unit) {
        viewModel.onShown()
        onDispose { viewModel.onHidden() }
    }

    val colCount = viewModel.columnCount
    val rowCount = viewModel.rowCount
+5 −2
Original line number Diff line number Diff line
@@ -31,6 +31,7 @@ import androidx.compose.foundation.layout.sizeIn
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
@@ -69,8 +70,10 @@ fun PinPad(
    viewModel: PinBouncerViewModel,
    modifier: Modifier = Modifier,
) {
    // Report that the UI is shown to let the view-model run some logic.
    LaunchedEffect(Unit) { viewModel.onShown() }
    DisposableEffect(Unit) {
        viewModel.onShown()
        onDispose { viewModel.onHidden() }
    }

    val isInputEnabled: Boolean by viewModel.isInputEnabled.collectAsState()
    val backspaceButtonAppearance by viewModel.backspaceButtonAppearance.collectAsState()
+7 −2
Original line number Diff line number Diff line
@@ -62,6 +62,13 @@ sealed class AuthMethodBouncerViewModel(

    /** Notifies that the UI has been shown to the user. */
    fun onShown() {
        interactor.resetMessage()
    }

    /**
     * Notifies that the UI has been hidden from the user (after any transitions have completed).
     */
    fun onHidden() {
        clearInput()
        interactor.resetMessage()
    }
@@ -113,8 +120,6 @@ sealed class AuthMethodBouncerViewModel(
            }
            _animateFailure.value = authenticationResult != AuthenticationResult.SUCCEEDED

            // TODO(b/291528545): On success, this should only be cleared after the view is animated
            //  away).
            clearInput()
        }
    }
+38 −22
Original line number Diff line number Diff line
@@ -78,11 +78,27 @@ class PasswordBouncerViewModelTest : SysuiTestCase() {
            lockDeviceAndOpenPasswordBouncer()

            assertThat(message?.text).isEqualTo(ENTER_YOUR_PASSWORD)
            assertThat(password).isEqualTo("")
            assertThat(password).isEmpty()
            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
            assertThat(underTest.authenticationMethod).isEqualTo(AuthenticationMethodModel.Password)
        }

    @Test
    fun onHidden_resetsPasswordInputAndMessage() =
        testScope.runTest {
            val message by collectLastValue(bouncerViewModel.message)
            val password by collectLastValue(underTest.password)
            lockDeviceAndOpenPasswordBouncer()

            underTest.onPasswordInputChanged("password")
            assertThat(message?.text).isNotEqualTo(ENTER_YOUR_PASSWORD)
            assertThat(password).isNotEmpty()

            underTest.onHidden()
            assertThat(message?.text).isEqualTo(ENTER_YOUR_PASSWORD)
            assertThat(password).isEmpty()
        }

    @Test
    fun onPasswordInputChanged() =
        testScope.runTest {
@@ -121,7 +137,7 @@ class PasswordBouncerViewModelTest : SysuiTestCase() {
            underTest.onPasswordInputChanged("wrong")
            underTest.onAuthenticateKeyPressed()

            assertThat(password).isEqualTo("")
            assertThat(password).isEmpty()
            assertThat(message?.text).isEqualTo(WRONG_PASSWORD)
        }

@@ -134,14 +150,13 @@ class PasswordBouncerViewModelTest : SysuiTestCase() {
                AuthenticationMethodModel.Password
            )
            utils.deviceEntryRepository.setUnlocked(false)
            sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
            underTest.onShown()
            // Enter nothing.
            switchToScene(SceneKey.Bouncer)

            // No input entered.

            underTest.onAuthenticateKeyPressed()

            assertThat(password).isEqualTo("")
            assertThat(password).isEmpty()
            assertThat(message?.text).isEqualTo(ENTER_YOUR_PASSWORD)
        }

@@ -182,32 +197,33 @@ class PasswordBouncerViewModelTest : SysuiTestCase() {
            assertThat(password).isEqualTo("password")

            // The user doesn't confirm the password, but navigates back to the lockscreen instead.
            sceneInteractor.changeScene(SceneModel(SceneKey.Lockscreen), "reason")
            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Lockscreen), "reason")
            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
            switchToScene(SceneKey.Lockscreen)

            // The user navigates to the bouncer again.
            sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))

            underTest.onShown()
            switchToScene(SceneKey.Bouncer)

            // Ensure the previously-entered password is not shown.
            assertThat(password).isEmpty()
            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
        }

    private fun TestScope.switchToScene(toScene: SceneKey) {
        val currentScene by collectLastValue(sceneInteractor.desiredScene)
        val bouncerShown = currentScene?.key != SceneKey.Bouncer && toScene == SceneKey.Bouncer
        val bouncerHidden = currentScene?.key == SceneKey.Bouncer && toScene != SceneKey.Bouncer
        sceneInteractor.changeScene(SceneModel(toScene), "reason")
        sceneInteractor.onSceneChanged(SceneModel(toScene), "reason")
        if (bouncerShown) underTest.onShown()
        if (bouncerHidden) underTest.onHidden()
        runCurrent()

        assertThat(currentScene).isEqualTo(SceneModel(toScene))
    }

    private fun TestScope.lockDeviceAndOpenPasswordBouncer() {
        utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Password)
        utils.deviceEntryRepository.setUnlocked(false)
        sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
        sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")

        assertThat(collectLastValue(sceneInteractor.desiredScene).invoke())
            .isEqualTo(SceneModel(SceneKey.Bouncer))
        underTest.onShown()
        runCurrent()
        switchToScene(SceneKey.Bouncer)
    }

    companion object {
Loading