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

Commit da0859af authored by Alexander Hendrich's avatar Alexander Hendrich
Browse files

[flexiglass] Fix lost focus on PIN input

When entering a PIN via keyboard, the keyDown event for the enter key resulted in the PinBouncer's VerticalGrid to lose focus and instead focus the next element, which is the first digit in the pin pad. Entering additional numbers still worked, but the enter key on that digit was consumed (resulting in the digit being entered). This prevented submitting the PIN by pressing enter again.

With this change, we also consume the keyDown event for the enter key and therefore don't pass on focus. Consecutive key presses for the enter key are still handled via PinBouncerViewModel's onKeyEvent() like expected then.

Fixes: 413013914
Flag: com.android.systemui.scene_container
Test: atest PinBouncerViewModelTest
Change-Id: Ia56187f23c436df661a43f540f9d42753aca3d24
parent 86238deb
Loading
Loading
Loading
Loading
+55 −24
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@ import android.view.KeyEvent.KEYCODE_0
import android.view.KeyEvent.KEYCODE_4
import android.view.KeyEvent.KEYCODE_A
import android.view.KeyEvent.KEYCODE_DEL
import android.view.KeyEvent.KEYCODE_ENTER
import android.view.KeyEvent.KEYCODE_NUMPAD_0
import androidx.compose.ui.input.key.KeyEventType
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -46,8 +47,6 @@ import com.android.systemui.scene.shared.model.Overlays
import com.android.systemui.testKosmos
import com.google.android.msdl.data.model.MSDLToken
import com.google.common.truth.Truth.assertThat
import kotlin.random.Random
import kotlin.random.nextInt
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.map
import org.junit.Before
@@ -444,37 +443,64 @@ class PinBouncerViewModelTest : SysuiTestCase() {
        kosmos.runTest {
            val pin by collectLastValue(underTest.pinInput.map { it.getPin() })
            lockDeviceAndOpenPinBouncer()
            val random = Random(System.currentTimeMillis())
            // Generate a random 4 digit PIN
            val expectedPin =
                with(random) { arrayOf(nextInt(0..9), nextInt(0..9), nextInt(0..9), nextInt(0..9)) }
            val expectedPin = FakeAuthenticationRepository.WRONG_PIN.take(4).toTypedArray()

            // Enter the PIN using NUM pad and normal number keyboard events
            underTest.onKeyEvent(KeyEventType.KeyDown, KEYCODE_0 + expectedPin[0])
            underTest.onKeyEvent(KeyEventType.KeyUp, KEYCODE_0 + expectedPin[0])
            pressKey(KEYCODE_0 + expectedPin[0])
            pressKey(KEYCODE_NUMPAD_0 + expectedPin[1])
            pressKey(KEYCODE_0 + expectedPin[2])

            underTest.onKeyEvent(KeyEventType.KeyDown, KEYCODE_NUMPAD_0 + expectedPin[1])
            underTest.onKeyEvent(KeyEventType.KeyUp, KEYCODE_NUMPAD_0 + expectedPin[1])
            // Enter an additional digit in between and delete it
            pressKey(KEYCODE_4)
            pressKey(KEYCODE_DEL)

            underTest.onKeyEvent(KeyEventType.KeyDown, KEYCODE_0 + expectedPin[2])
            underTest.onKeyEvent(KeyEventType.KeyUp, KEYCODE_0 + expectedPin[2])
            // Try entering a non digit character, this should be ignored.
            pressKey(KEYCODE_A)

            // Enter an additional digit in between and delete it
            underTest.onKeyEvent(KeyEventType.KeyDown, KEYCODE_4)
            underTest.onKeyEvent(KeyEventType.KeyUp, KEYCODE_4)
            pressKey(KEYCODE_NUMPAD_0 + expectedPin[3])

            // Delete that additional digit
            underTest.onKeyEvent(KeyEventType.KeyDown, KEYCODE_DEL)
            underTest.onKeyEvent(KeyEventType.KeyUp, KEYCODE_DEL)
            assertThat(pin).containsExactly(*expectedPin).inOrder()
        }

            // Try entering a non digit character, this should be ignored.
            underTest.onKeyEvent(KeyEventType.KeyDown, KEYCODE_A)
            underTest.onKeyEvent(KeyEventType.KeyUp, KEYCODE_A)
    @Test
    fun onKeyboardInput_submitOnEnter_wrongPin() =
        kosmos.runTest {
            val pin by collectLastValue(underTest.pinInput.map { it.getPin() })
            val authResult by collectLastValue(authenticationInteractor.onAuthenticationResult)
            fakeAuthenticationRepository.setAutoConfirmFeatureEnabled(false)
            lockDeviceAndOpenPinBouncer()

            val wrongPin = FakeAuthenticationRepository.WRONG_PIN.toTypedArray()

            wrongPin.forEach { pressKey(KEYCODE_0 + it) }

            assertThat(pin).containsExactly(*wrongPin).inOrder()

            assertThat(underTest.onKeyEvent(KeyEventType.KeyDown, KEYCODE_ENTER)).isTrue()
            assertThat(underTest.onKeyEvent(KeyEventType.KeyUp, KEYCODE_ENTER)).isTrue()

            assertThat(authResult).isFalse()
            assertThat(pin).isEmpty()
        }

    @Test
    fun onKeyboardInput_submitOnEnter_correctPin() =
        kosmos.runTest {
            val pin by collectLastValue(underTest.pinInput.map { it.getPin() })
            val authResult by collectLastValue(authenticationInteractor.onAuthenticationResult)
            fakeAuthenticationRepository.setAutoConfirmFeatureEnabled(false)
            lockDeviceAndOpenPinBouncer()

            val correctPin = FakeAuthenticationRepository.DEFAULT_PIN.toTypedArray()

            correctPin.forEach { pressKey(KEYCODE_0 + it) }

            underTest.onKeyEvent(KeyEventType.KeyDown, KEYCODE_NUMPAD_0 + expectedPin[3])
            underTest.onKeyEvent(KeyEventType.KeyUp, KEYCODE_NUMPAD_0 + expectedPin[3])
            assertThat(pin).containsExactly(*correctPin).inOrder()

            assertThat(pin).containsExactly(*expectedPin)
            assertThat(underTest.onKeyEvent(KeyEventType.KeyDown, KEYCODE_ENTER)).isTrue()
            assertThat(underTest.onKeyEvent(KeyEventType.KeyUp, KEYCODE_ENTER)).isTrue()

            assertThat(authResult).isTrue()
        }

    @Test
@@ -527,6 +553,11 @@ class PinBouncerViewModelTest : SysuiTestCase() {
        showBouncer()
    }

    private fun pressKey(keyCode: Int) {
        underTest.onKeyEvent(KeyEventType.KeyDown, keyCode)
        underTest.onKeyEvent(KeyEventType.KeyUp, keyCode)
    }

    companion object {
        private const val ENTER_YOUR_PIN = "Enter your pin"
        private const val WRONG_PIN = "Wrong pin"
+20 −26
Original line number Diff line number Diff line
@@ -248,17 +248,16 @@ constructor(
     * @return `true` when the [KeyEvent] was consumed as user input on bouncer; `false` otherwise.
     */
    override fun onKeyEvent(type: KeyEventType, keyCode: Int): Boolean {
        return when (type) {
            KeyEventType.KeyUp -> {
        if (isConfirmKey(keyCode)) {
            if (type == KeyEventType.KeyUp) {
                onAuthenticateButtonClicked()
                    true
                } else {
                    false
            }
            return true
        }
            KeyEventType.KeyDown -> {
                when (keyCode) {

        if (type != KeyEventType.KeyDown) return false

        return when (keyCode) {
            KEYCODE_DEL -> {
                onBackspaceButtonClicked()
                true
@@ -271,11 +270,6 @@ constructor(
                onPinButtonClicked(keyCode - KEYCODE_NUMPAD_0)
                true
            }
                    else -> {
                        false
                    }
                }
            }
            else -> false
        }
    }
+1 −0
Original line number Diff line number Diff line
@@ -213,6 +213,7 @@ class FakeAuthenticationRepository(private val currentTime: () -> Long) : Authen
        const val LOCKOUT_DURATION_MS = LOCKOUT_DURATION_SECONDS * 1000
        const val HINTING_PIN_LENGTH = 6
        val DEFAULT_PIN = buildList { repeat(HINTING_PIN_LENGTH) { add(it + 1) } }
        val WRONG_PIN = buildList { repeat(HINTING_PIN_LENGTH) { add(9 - it) } }

        private fun AuthenticationMethodModel.toSecurityMode(): SecurityMode {
            return when (this) {