diff options
5 files changed, 62 insertions, 6 deletions
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt index af3ddfca14b6..29ee87466f1a 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt @@ -17,6 +17,9 @@ package com.android.systemui.bouncer.ui.viewmodel import android.content.pm.UserInfo +import android.platform.test.annotations.EnableFlags +import android.view.KeyEvent +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 @@ -27,6 +30,7 @@ import com.android.systemui.authentication.shared.model.AuthenticationMethodMode import com.android.systemui.bouncer.domain.interactor.bouncerInteractor import com.android.systemui.coroutines.collectLastValue import com.android.systemui.coroutines.collectValues +import com.android.systemui.flags.EnableSceneContainer import com.android.systemui.inputmethod.data.model.InputMethodModel import com.android.systemui.inputmethod.data.repository.fakeInputMethodRepository import com.android.systemui.inputmethod.domain.interactor.inputMethodInteractor @@ -67,11 +71,12 @@ class PasswordBouncerViewModelTest : SysuiTestCase() { private val inputMethodInteractor by lazy { kosmos.inputMethodInteractor } private val isInputEnabled = MutableStateFlow(true) - private val underTest = + private val underTest by lazy { kosmos.passwordBouncerViewModelFactory.create( isInputEnabled = isInputEnabled, onIntentionalUserInput = {}, ) + } @Before fun setUp() { @@ -345,6 +350,37 @@ class PasswordBouncerViewModelTest : SysuiTestCase() { assertThat(textInputFocusRequested).isFalse() } + @EnableFlags(com.android.systemui.Flags.FLAG_COMPOSE_BOUNCER) + @Test + fun consumeConfirmKeyEvents_toPreventItFromPropagating() = + testScope.runTest { verifyConfirmKeyEventsBehavior(keyUpEventConsumed = true) } + + @EnableFlags(com.android.systemui.Flags.FLAG_COMPOSE_BOUNCER) + @EnableSceneContainer + @Test + fun noops_whenSceneContainerIsAlsoEnabled() = + testScope.runTest { verifyConfirmKeyEventsBehavior(keyUpEventConsumed = false) } + + private fun verifyConfirmKeyEventsBehavior(keyUpEventConsumed: Boolean) { + assertThat(underTest.onKeyEvent(KeyEventType.KeyDown, KeyEvent.KEYCODE_DPAD_CENTER)) + .isFalse() + assertThat(underTest.onKeyEvent(KeyEventType.KeyUp, KeyEvent.KEYCODE_DPAD_CENTER)) + .isEqualTo(keyUpEventConsumed) + + assertThat(underTest.onKeyEvent(KeyEventType.KeyDown, KeyEvent.KEYCODE_ENTER)).isFalse() + assertThat(underTest.onKeyEvent(KeyEventType.KeyUp, KeyEvent.KEYCODE_ENTER)) + .isEqualTo(keyUpEventConsumed) + + assertThat(underTest.onKeyEvent(KeyEventType.KeyDown, KeyEvent.KEYCODE_NUMPAD_ENTER)) + .isFalse() + assertThat(underTest.onKeyEvent(KeyEventType.KeyUp, KeyEvent.KEYCODE_NUMPAD_ENTER)) + .isEqualTo(keyUpEventConsumed) + + // space is ignored. + assertThat(underTest.onKeyEvent(KeyEventType.KeyUp, KeyEvent.KEYCODE_SPACE)).isFalse() + assertThat(underTest.onKeyEvent(KeyEventType.KeyDown, KeyEvent.KEYCODE_SPACE)).isFalse() + } + 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/AuthMethodBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt index 148b9ea61e2c..1bcc1eeb4f12 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt @@ -17,6 +17,7 @@ package com.android.systemui.bouncer.ui.viewmodel import android.annotation.StringRes +import androidx.compose.ui.input.key.KeyEventType import com.android.systemui.authentication.domain.interactor.AuthenticationResult import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.bouncer.domain.interactor.BouncerInteractor @@ -122,6 +123,13 @@ sealed class AuthMethodBouncerViewModel( /** Invoked after a successful authentication. */ protected open fun onSuccessfulAuthentication() = Unit + /** + * Invoked for any key events on the bouncer. + * + * @return whether the event was consumed by this method and should not be propagated further. + */ + open fun onKeyEvent(type: KeyEventType, keyCode: Int): Boolean = false + /** Perform authentication result haptics */ private fun performAuthenticationHapticFeedback(result: AuthenticationResult) { if (result == AuthenticationResult.SKIPPED) return diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneContentViewModel.kt index 5f973917440c..8427b27b78e1 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneContentViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneContentViewModel.kt @@ -337,10 +337,8 @@ constructor( * @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 + return authMethodViewModel.value?.onKeyEvent(keyEvent.type, keyEvent.nativeKeyEvent.keyCode) + ?: false } data class DialogViewModel( diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt index 1427d787ea86..b8c6ab3783d5 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt @@ -16,9 +16,12 @@ package com.android.systemui.bouncer.ui.viewmodel +import android.view.KeyEvent import androidx.annotation.VisibleForTesting +import androidx.compose.ui.input.key.KeyEventType import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.bouncer.domain.interactor.BouncerInteractor +import com.android.systemui.bouncer.shared.flag.ComposeBouncerFlags import com.android.systemui.inputmethod.domain.interactor.InputMethodInteractor import com.android.systemui.res.R import com.android.systemui.user.domain.interactor.SelectedUserInteractor @@ -150,6 +153,17 @@ constructor( return _password.value.toCharArray().toList() } + override fun onKeyEvent(type: KeyEventType, keyCode: Int): Boolean { + // Ignore SPACE as a confirm key to allow the space character within passwords. + val isKeyboardEnterKey = + KeyEvent.isConfirmKey(keyCode) && + keyCode != KeyEvent.KEYCODE_SPACE && + type == KeyEventType.KeyUp + // consume confirm key events while on the bouncer. This prevents it from propagating + // and avoids other parent elements from receiving it. + return isKeyboardEnterKey && ComposeBouncerFlags.isOnlyComposeBouncerEnabled() + } + override fun onSuccessfulAuthentication() { wasSuccessfullyAuthenticated = true } 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 0cb4260e4d7f..5c8a9a692baf 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 @@ -251,7 +251,7 @@ constructor( * * @return `true` when the [KeyEvent] was consumed as user input on bouncer; `false` otherwise. */ - fun onKeyEvent(type: KeyEventType, keyCode: Int): Boolean { + override fun onKeyEvent(type: KeyEventType, keyCode: Int): Boolean { return when (type) { KeyEventType.KeyUp -> { if (isConfirmKey(keyCode)) { |