diff options
4 files changed, 113 insertions, 3 deletions
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt index c19c08e09349..b8f9ca82f072 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt @@ -66,6 +66,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.input.key.onKeyEvent import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalDensity @@ -137,7 +138,7 @@ fun BouncerContent( // Despite the keyboard only being part of the password bouncer, adding it at this level is // both necessary to properly handle the keyboard in all layouts and harmless in cases when // the keyboard isn't used (like the PIN or pattern auth methods). - modifier = modifier.imePadding(), + modifier = modifier.imePadding().onKeyEvent(viewModel::onKeyEvent), ) { when (layout) { BouncerSceneLayout.STANDARD_BOUNCER -> diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt index 256687b56f4e..89bafb952211 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt @@ -16,6 +16,12 @@ package com.android.systemui.bouncer.ui.viewmodel +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_NUMPAD_0 +import androidx.compose.ui.input.key.KeyEventType import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.compose.animation.scene.SceneKey @@ -34,6 +40,8 @@ import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat +import kotlin.random.Random +import kotlin.random.nextInt import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow @@ -444,6 +452,44 @@ class PinBouncerViewModelTest : SysuiTestCase() { assertThat(pin).hasSize(FakeAuthenticationRepository.HINTING_PIN_LENGTH + 1) } + @Test + fun onKeyboardInput_pinInput_isUpdated() = + testScope.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)) } + + // 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]) + + underTest.onKeyEvent(KeyEventType.KeyDown, KEYCODE_NUMPAD_0 + expectedPin[1]) + underTest.onKeyEvent(KeyEventType.KeyUp, KEYCODE_NUMPAD_0 + expectedPin[1]) + + underTest.onKeyEvent(KeyEventType.KeyDown, KEYCODE_0 + expectedPin[2]) + underTest.onKeyEvent(KeyEventType.KeyUp, KEYCODE_0 + expectedPin[2]) + + // Enter an additional digit in between and delete it + underTest.onKeyEvent(KeyEventType.KeyDown, KEYCODE_4) + underTest.onKeyEvent(KeyEventType.KeyUp, KEYCODE_4) + + // Delete that additional digit + underTest.onKeyEvent(KeyEventType.KeyDown, KEYCODE_DEL) + underTest.onKeyEvent(KeyEventType.KeyUp, KEYCODE_DEL) + + // Try entering a non digit character, this should be ignored. + underTest.onKeyEvent(KeyEventType.KeyDown, KEYCODE_A) + underTest.onKeyEvent(KeyEventType.KeyUp, KEYCODE_A) + + underTest.onKeyEvent(KeyEventType.KeyDown, KEYCODE_NUMPAD_0 + expectedPin[3]) + underTest.onKeyEvent(KeyEventType.KeyUp, KEYCODE_NUMPAD_0 + expectedPin[3]) + + assertThat(pin).containsExactly(*expectedPin) + } + private fun TestScope.switchToScene(toScene: SceneKey) { val currentScene by collectLastValue(sceneInteractor.currentScene) val bouncerHidden = currentScene == Scenes.Bouncer && toScene != Scenes.Bouncer diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt index 7c41b75d7105..40a141dcec77 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt @@ -20,6 +20,8 @@ import android.app.admin.DevicePolicyManager import android.app.admin.DevicePolicyResources import android.content.Context import android.graphics.Bitmap +import androidx.compose.ui.input.key.KeyEvent +import androidx.compose.ui.input.key.type import androidx.core.graphics.drawable.toBitmap import com.android.compose.animation.scene.Back import com.android.compose.animation.scene.SceneKey @@ -326,7 +328,8 @@ class BouncerViewModel( { message }, failedAttempts, remainingAttempts, - ) ?: message + ) + ?: message } else { message } @@ -343,7 +346,8 @@ class BouncerViewModel( .KEYGUARD_DIALOG_FAILED_ATTEMPTS_ERASING_PROFILE, { message }, failedAttempts, - ) ?: message + ) + ?: message } else { message } @@ -377,6 +381,19 @@ class BouncerViewModel( Swipe(SwipeDirection.Down) to UserActionResult(prevScene), ) + /** + * Notifies that a key event has occurred. + * + * @return `true` when the [KeyEvent] was consumed as user input on bouncer; `false` otherwise. + */ + fun onKeyEvent(keyEvent: KeyEvent): Boolean { + return (authMethodViewModel.value as? PinBouncerViewModel)?.onKeyEvent( + keyEvent.type, + keyEvent.nativeKeyEvent.keyCode + ) + ?: false + } + data class DialogViewModel( val text: String, diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt index 4c2380c5e4db..aa447ffac154 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt @@ -19,6 +19,14 @@ package com.android.systemui.bouncer.ui.viewmodel import android.content.Context +import android.view.KeyEvent.KEYCODE_0 +import android.view.KeyEvent.KEYCODE_9 +import android.view.KeyEvent.KEYCODE_DEL +import android.view.KeyEvent.KEYCODE_NUMPAD_0 +import android.view.KeyEvent.KEYCODE_NUMPAD_9 +import android.view.KeyEvent.isConfirmKey +import androidx.compose.ui.input.key.KeyEvent +import androidx.compose.ui.input.key.KeyEventType import com.android.keyguard.PinShapeAdapter import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.bouncer.domain.interactor.BouncerInteractor @@ -196,6 +204,44 @@ class PinBouncerViewModel( else -> ActionButtonAppearance.Shown } } + + /** + * Notifies that a key event has occurred. + * + * @return `true` when the [KeyEvent] was consumed as user input on bouncer; `false` otherwise. + */ + fun onKeyEvent(type: KeyEventType, keyCode: Int): Boolean { + return when (type) { + KeyEventType.KeyUp -> { + if (isConfirmKey(keyCode)) { + onAuthenticateButtonClicked() + true + } else { + false + } + } + KeyEventType.KeyDown -> { + when (keyCode) { + KEYCODE_DEL -> { + onBackspaceButtonClicked() + true + } + in KEYCODE_0..KEYCODE_9 -> { + onPinButtonClicked(keyCode - KEYCODE_0) + true + } + in KEYCODE_NUMPAD_0..KEYCODE_NUMPAD_9 -> { + onPinButtonClicked(keyCode - KEYCODE_NUMPAD_0) + true + } + else -> { + false + } + } + } + else -> false + } + } } /** Appearance of pin-pad action buttons. */ |