Loading packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt +2 −1 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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 -> Loading packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt +46 −0 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading Loading @@ -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 Loading packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt +19 −2 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -326,7 +328,8 @@ class BouncerViewModel( { message }, failedAttempts, remainingAttempts, ) ?: message ) ?: message } else { message } Loading @@ -343,7 +346,8 @@ class BouncerViewModel( .KEYGUARD_DIALOG_FAILED_ATTEMPTS_ERASING_PROFILE, { message }, failedAttempts, ) ?: message ) ?: message } else { message } Loading Loading @@ -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, Loading packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt +46 −0 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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. */ Loading Loading
packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt +2 −1 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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 -> Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt +46 −0 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading Loading @@ -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 Loading
packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt +19 −2 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -326,7 +328,8 @@ class BouncerViewModel( { message }, failedAttempts, remainingAttempts, ) ?: message ) ?: message } else { message } Loading @@ -343,7 +346,8 @@ class BouncerViewModel( .KEYGUARD_DIALOG_FAILED_ATTEMPTS_ERASING_PROFILE, { message }, failedAttempts, ) ?: message ) ?: message } else { message } Loading Loading @@ -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, Loading
packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt +46 −0 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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. */ Loading