summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt27
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt80
2 files changed, 90 insertions, 17 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
index e8b8f54220aa..be08932cfa64 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
@@ -78,17 +78,6 @@ constructor(
private val _isOverlayTouched: MutableStateFlow<Boolean> = MutableStateFlow(false)
- /**
- * If the API caller or the user's personal preferences require explicit confirmation after
- * successful authentication.
- */
- val isConfirmationRequired: Flow<Boolean> =
- combine(_isOverlayTouched, promptSelectorInteractor.isConfirmationRequired) {
- isOverlayTouched,
- isConfirmationRequired ->
- !isOverlayTouched && isConfirmationRequired
- }
-
/** The kind of credential the user has. */
val credentialKind: Flow<PromptKind> = promptSelectorInteractor.credentialKind
@@ -137,6 +126,15 @@ constructor(
}
.distinctUntilChanged()
+ /**
+ * If the API caller or the user's personal preferences require explicit confirmation after
+ * successful authentication. Confirmation always required when in explicit flow.
+ */
+ val isConfirmationRequired: Flow<Boolean> =
+ combine(_isOverlayTouched, size) { isOverlayTouched, size ->
+ !isOverlayTouched && size.isNotSmall
+ }
+
/** Title for the prompt. */
val title: Flow<String> =
promptSelectorInteractor.prompt.map { it?.title ?: "" }.distinctUntilChanged()
@@ -170,12 +168,7 @@ constructor(
.distinctUntilChanged()
/** If the icon can be used as a confirmation button. */
- val isIconConfirmButton: Flow<Boolean> =
- combine(size, promptSelectorInteractor.isConfirmationRequired) {
- size,
- isConfirmationRequired ->
- size.isNotSmall && isConfirmationRequired
- }
+ val isIconConfirmButton: Flow<Boolean> = size.map { it.isNotSmall }.distinctUntilChanged()
/** If the negative button should be shown. */
val isNegativeButtonVisible: Flow<Boolean> =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
index 47084c087952..0ed46da27b55 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
@@ -20,6 +20,7 @@ import android.hardware.biometrics.PromptInfo
import android.hardware.face.FaceSensorPropertiesInternal
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
import android.view.HapticFeedbackConstants
+import android.view.MotionEvent
import androidx.test.filters.SmallTest
import com.android.internal.widget.LockPatternUtils
import com.android.systemui.SysuiTestCase
@@ -500,6 +501,81 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
}
@Test
+ fun auto_confirm_authentication_when_finger_down() = runGenericTest {
+ val expectConfirmation = testCase.expectConfirmation(atLeastOneFailure = false)
+
+ // No icon button when face only, can't confirm before auth
+ if (!testCase.isFaceOnly) {
+ viewModel.onOverlayTouch(obtainMotionEvent(MotionEvent.ACTION_DOWN))
+ }
+ viewModel.showAuthenticated(testCase.authenticatedModality, 0)
+
+ val authenticating by collectLastValue(viewModel.isAuthenticating)
+ val authenticated by collectLastValue(viewModel.isAuthenticated)
+ val message by collectLastValue(viewModel.message)
+ val size by collectLastValue(viewModel.size)
+ val legacyState by collectLastValue(viewModel.legacyState)
+ val canTryAgain by collectLastValue(viewModel.canTryAgainNow)
+
+ assertThat(authenticating).isFalse()
+ assertThat(canTryAgain).isFalse()
+ assertThat(authenticated?.isAuthenticated).isTrue()
+
+ if (testCase.isFaceOnly && expectConfirmation) {
+ assertThat(legacyState).isEqualTo(AuthBiometricView.STATE_PENDING_CONFIRMATION)
+
+ assertThat(size).isEqualTo(PromptSize.MEDIUM)
+ assertButtonsVisible(
+ cancel = true,
+ confirm = true,
+ )
+
+ viewModel.confirmAuthenticated()
+ assertThat(message).isEqualTo(PromptMessage.Empty)
+ assertButtonsVisible()
+ } else {
+ assertThat(legacyState).isEqualTo(AuthBiometricView.STATE_AUTHENTICATED)
+ }
+ }
+
+ @Test
+ fun cannot_auto_confirm_authentication_when_finger_up() = runGenericTest {
+ val expectConfirmation = testCase.expectConfirmation(atLeastOneFailure = false)
+
+ // No icon button when face only, can't confirm before auth
+ if (!testCase.isFaceOnly) {
+ viewModel.onOverlayTouch(obtainMotionEvent(MotionEvent.ACTION_DOWN))
+ viewModel.onOverlayTouch(obtainMotionEvent(MotionEvent.ACTION_UP))
+ }
+ viewModel.showAuthenticated(testCase.authenticatedModality, 0)
+
+ val authenticating by collectLastValue(viewModel.isAuthenticating)
+ val authenticated by collectLastValue(viewModel.isAuthenticated)
+ val message by collectLastValue(viewModel.message)
+ val size by collectLastValue(viewModel.size)
+ val legacyState by collectLastValue(viewModel.legacyState)
+ val canTryAgain by collectLastValue(viewModel.canTryAgainNow)
+
+ assertThat(authenticated?.needsUserConfirmation).isEqualTo(expectConfirmation)
+ if (expectConfirmation) {
+ assertThat(size).isEqualTo(PromptSize.MEDIUM)
+ assertButtonsVisible(
+ cancel = true,
+ confirm = true,
+ )
+
+ viewModel.confirmAuthenticated()
+ assertThat(message).isEqualTo(PromptMessage.Empty)
+ assertButtonsVisible()
+ }
+
+ assertThat(authenticating).isFalse()
+ assertThat(authenticated?.isAuthenticated).isTrue()
+ assertThat(legacyState).isEqualTo(AuthBiometricView.STATE_AUTHENTICATED)
+ assertThat(canTryAgain).isFalse()
+ }
+
+ @Test
fun cannot_confirm_unless_authenticated() = runGenericTest {
val authenticating by collectLastValue(viewModel.isAuthenticating)
val authenticated by collectLastValue(viewModel.isAuthenticated)
@@ -679,6 +755,10 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
testScope.runTest { block() }
}
+ /** Obtain a MotionEvent with the specified MotionEvent action constant */
+ private fun obtainMotionEvent(action: Int): MotionEvent =
+ MotionEvent.obtain(0, 0, action, 0f, 0f, 0)
+
companion object {
@JvmStatic
@Parameterized.Parameters(name = "{0}")