Merge "Adding MSDL feedback to the Biometric prompt." into main
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthControllerTest.java
index b7d99d2..65825b2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthControllerTest.java
@@ -97,6 +97,8 @@
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.time.FakeSystemClock;
+import com.google.android.msdl.domain.MSDLPlayer;
+
import dagger.Lazy;
import org.junit.Before;
@@ -185,6 +187,8 @@
private Resources mResources;
@Mock
private VibratorHelper mVibratorHelper;
+ @Mock
+ private MSDLPlayer mMSDLPlayer;
private TestableContext mContextSpy;
private Execution mExecution;
@@ -1066,7 +1070,7 @@
() -> mLogContextInteractor, () -> mPromptSelectionInteractor,
() -> mCredentialViewModel, () -> mPromptViewModel, mInteractionJankMonitor,
mHandler, mBackgroundExecutor, mUdfpsUtils, mVibratorHelper,
- mLazyViewCapture);
+ mLazyViewCapture, mMSDLPlayer);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index 970fdea..69ab976 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -78,6 +78,8 @@
import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.util.concurrency.DelayableExecutor;
+import com.google.android.msdl.domain.MSDLPlayer;
+
import kotlin.Lazy;
import kotlinx.coroutines.CoroutineScope;
@@ -157,6 +159,8 @@
private final @Background DelayableExecutor mBackgroundExecutor;
+ private final MSDLPlayer mMSDLPlayer;
+
// Non-null only if the dialog is in the act of dismissing and has not sent the reason yet.
@Nullable @AuthDialogCallback.DismissedReason private Integer mPendingCallbackReason;
// HAT received from LockSettingsService when credential is verified.
@@ -292,7 +296,8 @@
@NonNull Provider<CredentialViewModel> credentialViewModelProvider,
@NonNull @Background DelayableExecutor bgExecutor,
@NonNull VibratorHelper vibratorHelper,
- Lazy<ViewCapture> lazyViewCapture) {
+ Lazy<ViewCapture> lazyViewCapture,
+ @NonNull MSDLPlayer msdlPlayer) {
super(config.mContext);
mConfig = config;
@@ -309,6 +314,7 @@
.getDimension(R.dimen.biometric_dialog_animation_translation_offset);
mLinearOutSlowIn = Interpolators.LINEAR_OUT_SLOW_IN;
mBiometricCallback = new BiometricCallback();
+ mMSDLPlayer = msdlPlayer;
final BiometricModalities biometricModalities = new BiometricModalities(
Utils.findFirstSensorProperties(fpProps, mConfig.mSensorIds),
@@ -379,7 +385,7 @@
getJankListener(mLayout, TRANSIT,
BiometricViewSizeBinder.ANIMATE_MEDIUM_TO_LARGE_DURATION_MS),
mBackgroundView, mBiometricCallback, mApplicationCoroutineScope,
- vibratorHelper);
+ vibratorHelper, mMSDLPlayer);
}
@VisibleForTesting
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index 097ab72..b39aae9 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -89,6 +89,8 @@
import com.android.systemui.util.concurrency.DelayableExecutor;
import com.android.systemui.util.concurrency.Execution;
+import com.google.android.msdl.domain.MSDLPlayer;
+
import dagger.Lazy;
import kotlin.Unit;
@@ -183,6 +185,7 @@
private final @Background DelayableExecutor mBackgroundExecutor;
private final DisplayInfo mCachedDisplayInfo = new DisplayInfo();
@NonNull private final VibratorHelper mVibratorHelper;
+ @NonNull private final MSDLPlayer mMSDLPlayer;
private final kotlin.Lazy<ViewCapture> mLazyViewCapture;
@@ -742,7 +745,8 @@
@Background DelayableExecutor bgExecutor,
@NonNull UdfpsUtils udfpsUtils,
@NonNull VibratorHelper vibratorHelper,
- Lazy<ViewCapture> daggerLazyViewCapture) {
+ Lazy<ViewCapture> daggerLazyViewCapture,
+ @NonNull MSDLPlayer msdlPlayer) {
mContext = context;
mExecution = execution;
mUserManager = userManager;
@@ -764,6 +768,7 @@
mUdfpsUtils = udfpsUtils;
mApplicationCoroutineScope = applicationCoroutineScope;
mVibratorHelper = vibratorHelper;
+ mMSDLPlayer = msdlPlayer;
mLogContextInteractor = logContextInteractor;
mPromptSelectorInteractor = promptSelectorInteractorProvider;
@@ -1327,7 +1332,7 @@
wakefulnessLifecycle, userManager, lockPatternUtils,
mInteractionJankMonitor, mPromptSelectorInteractor, viewModel,
mCredentialViewModelProvider, bgExecutor, mVibratorHelper,
- mLazyViewCapture);
+ mLazyViewCapture, mMSDLPlayer);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
index 0b440ad..e7e8d8f 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
@@ -25,7 +25,6 @@
import android.hardware.biometrics.Flags
import android.hardware.face.FaceManager
import android.util.Log
-import android.view.HapticFeedbackConstants
import android.view.MotionEvent
import android.view.View
import android.view.View.IMPORTANT_FOR_ACCESSIBILITY_NO
@@ -59,6 +58,7 @@
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.res.R
import com.android.systemui.statusbar.VibratorHelper
+import com.google.android.msdl.domain.MSDLPlayer
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.combine
@@ -83,6 +83,7 @@
legacyCallback: Spaghetti.Callback,
applicationScope: CoroutineScope,
vibratorHelper: VibratorHelper,
+ msdlPlayer: MSDLPlayer,
): Spaghetti {
val accessibilityManager = view.context.getSystemService(AccessibilityManager::class.java)!!
@@ -434,21 +435,27 @@
// Play haptics
launch {
viewModel.hapticsToPlay.collect { haptics ->
- if (haptics.hapticFeedbackConstant != HapticFeedbackConstants.NO_HAPTICS) {
- if (haptics.flag != null) {
- vibratorHelper.performHapticFeedback(
- view,
- haptics.hapticFeedbackConstant,
- haptics.flag,
- )
- } else {
- vibratorHelper.performHapticFeedback(
- view,
- haptics.hapticFeedbackConstant,
- )
+ when (haptics) {
+ is PromptViewModel.HapticsToPlay.HapticConstant -> {
+ if (haptics.flag != null) {
+ vibratorHelper.performHapticFeedback(
+ view,
+ haptics.constant,
+ haptics.flag,
+ )
+ } else {
+ vibratorHelper.performHapticFeedback(
+ view,
+ haptics.constant,
+ )
+ }
}
- viewModel.clearHaptics()
+ is PromptViewModel.HapticsToPlay.MSDL -> {
+ msdlPlayer.playToken(haptics.token, haptics.properties)
+ }
+ is PromptViewModel.HapticsToPlay.None -> {}
}
+ viewModel.clearHaptics()
}
}
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 4c2fe07..168ba11 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
@@ -35,7 +35,9 @@
import android.util.RotationUtils
import android.view.HapticFeedbackConstants
import android.view.MotionEvent
+import com.android.keyguard.AuthInteractionProperties
import com.android.launcher3.icons.IconProvider
+import com.android.systemui.Flags.msdlFeedback
import com.android.systemui.biometrics.UdfpsUtils
import com.android.systemui.biometrics.Utils
import com.android.systemui.biometrics.Utils.isSystem
@@ -53,6 +55,8 @@
import com.android.systemui.keyguard.shared.model.AcquiredFingerprintAuthenticationStatus
import com.android.systemui.res.R
import com.android.systemui.util.kotlin.combine
+import com.google.android.msdl.data.model.MSDLToken
+import com.google.android.msdl.domain.InteractionProperties
import javax.inject.Inject
import kotlinx.coroutines.Job
import kotlinx.coroutines.coroutineScope
@@ -245,8 +249,9 @@
private val _forceLargeSize = MutableStateFlow(false)
private val _forceMediumSize = MutableStateFlow(false)
- private val _hapticsToPlay =
- MutableStateFlow(HapticsToPlay(HapticFeedbackConstants.NO_HAPTICS, /* flag= */ null))
+ private val authInteractionProperties = AuthInteractionProperties()
+ private val _hapticsToPlay: MutableStateFlow<HapticsToPlay> =
+ MutableStateFlow(HapticsToPlay.None)
/** Event fired to the view indicating a [HapticsToPlay] */
val hapticsToPlay = _hapticsToPlay.asStateFlow()
@@ -939,26 +944,52 @@
}
private fun vibrateOnSuccess() {
- _hapticsToPlay.value =
- HapticsToPlay(
- HapticFeedbackConstants.BIOMETRIC_CONFIRM,
- null,
- )
+ val haptics =
+ if (msdlFeedback()) {
+ HapticsToPlay.MSDL(MSDLToken.UNLOCK, authInteractionProperties)
+ } else {
+ HapticsToPlay.HapticConstant(
+ HapticFeedbackConstants.BIOMETRIC_CONFIRM,
+ flag = null,
+ )
+ }
+ _hapticsToPlay.value = haptics
}
private fun vibrateOnError() {
- _hapticsToPlay.value =
- HapticsToPlay(
- HapticFeedbackConstants.BIOMETRIC_REJECT,
- null,
- )
+ val haptics =
+ if (msdlFeedback()) {
+ HapticsToPlay.MSDL(MSDLToken.FAILURE, authInteractionProperties)
+ } else {
+ HapticsToPlay.HapticConstant(
+ HapticFeedbackConstants.BIOMETRIC_REJECT,
+ flag = null,
+ )
+ }
+ _hapticsToPlay.value = haptics
}
/** Clears the [hapticsToPlay] variable by setting its constant to the NO_HAPTICS default. */
fun clearHaptics() {
- _hapticsToPlay.update { previous ->
- HapticsToPlay(HapticFeedbackConstants.NO_HAPTICS, previous.flag)
- }
+ _hapticsToPlay.update { HapticsToPlay.None }
+ }
+
+ /** The state of haptic feedback to play. */
+ sealed interface HapticsToPlay {
+ /**
+ * Haptics using [HapticFeedbackConstants]. It is composed by a [HapticFeedbackConstants]
+ * and a [HapticFeedbackConstants] flag.
+ */
+ data class HapticConstant(val constant: Int, val flag: Int?) : HapticsToPlay
+
+ /**
+ * Haptics using MSDL feedback. It is composed by a [MSDLToken] and optional
+ * [InteractionProperties]
+ */
+ data class MSDL(val token: MSDLToken, val properties: InteractionProperties?) :
+ HapticsToPlay
+
+ data object None : HapticsToPlay
}
companion object {
@@ -1095,9 +1126,3 @@
val isStarted: Boolean
get() = this == Normal || this == Delayed
}
-
-/**
- * The state of haptic feedback to play. It is composed by a [HapticFeedbackConstants] and a
- * [HapticFeedbackConstants] flag.
- */
-data class HapticsToPlay(val hapticFeedbackConstant: Int, val flag: Int?)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
index 1e23690..7889b3c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
@@ -61,10 +61,12 @@
import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel
import com.android.systemui.biometrics.ui.viewmodel.PromptViewModel
import com.android.systemui.display.data.repository.FakeDisplayRepository
+import com.android.systemui.haptics.msdl.msdlPlayer
import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.res.R
import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.statusbar.events.ANIMATING_OUT
+import com.android.systemui.testKosmos
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.time.FakeSystemClock
@@ -145,6 +147,9 @@
private val credentialViewModel = CredentialViewModel(mContext, bpCredentialInteractor)
private val defaultLogoIcon = context.getDrawable(R.drawable.ic_android)
+ private val kosmos = testKosmos()
+ private val msdlPlayer = kosmos.msdlPlayer
+
private var authContainer: TestAuthContainerView? = null
@Before
@@ -668,7 +673,8 @@
{ credentialViewModel },
fakeExecutor,
vibrator,
- lazyViewCapture
+ lazyViewCapture,
+ msdlPlayer,
) {
override fun postOnAnimation(runnable: Runnable) {
runnable.run()
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 4fc4166..55fd344 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
@@ -37,12 +37,15 @@
import android.hardware.face.FaceSensorPropertiesInternal
import android.hardware.fingerprint.FingerprintSensorProperties
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
+import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.view.HapticFeedbackConstants
import android.view.MotionEvent
import android.view.Surface
import androidx.test.filters.SmallTest
import com.android.app.activityTaskManager
+import com.android.keyguard.AuthInteractionProperties
+import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.AuthController
import com.android.systemui.biometrics.Utils.toBitmap
@@ -72,6 +75,7 @@
import com.android.systemui.kosmos.testScope
import com.android.systemui.res.R
import com.android.systemui.util.mockito.withArgCaptor
+import com.google.android.msdl.data.model.MSDLToken
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.first
@@ -124,6 +128,7 @@
private val defaultLogoDescriptionFromActivityInfo = "Test Coke App"
private val logoDescriptionFromApp = "Test Cake App"
private val packageNameForLogoWithOverrides = "should.use.overridden.logo"
+ private val authInteractionProperties = AuthInteractionProperties()
/** Prompt panel size padding */
private val smallHorizontalGuidelinePadding =
@@ -707,31 +712,66 @@
}
@Test
+ @DisableFlags(Flags.FLAG_MSDL_FEEDBACK)
fun set_haptic_on_confirm_when_confirmation_required_otherwise_on_authenticated() =
runGenericTest {
val expectConfirmation = testCase.expectConfirmation(atLeastOneFailure = false)
kosmos.promptViewModel.showAuthenticated(testCase.authenticatedModality, 1_000L)
- val confirmHaptics by collectLastValue(kosmos.promptViewModel.hapticsToPlay)
- assertThat(confirmHaptics?.hapticFeedbackConstant)
- .isEqualTo(
- if (expectConfirmation) HapticFeedbackConstants.NO_HAPTICS
- else HapticFeedbackConstants.BIOMETRIC_CONFIRM
- )
- assertThat(confirmHaptics?.flag).isNull()
+ val hapticsPreConfirm by collectLastValue(kosmos.promptViewModel.hapticsToPlay)
+ if (expectConfirmation) {
+ assertThat(hapticsPreConfirm).isEqualTo(PromptViewModel.HapticsToPlay.None)
+ } else {
+ val confirmHaptics =
+ hapticsPreConfirm as PromptViewModel.HapticsToPlay.HapticConstant
+ assertThat(confirmHaptics.constant)
+ .isEqualTo(HapticFeedbackConstants.BIOMETRIC_CONFIRM)
+ assertThat(confirmHaptics.flag).isNull()
+ }
if (expectConfirmation) {
kosmos.promptViewModel.confirmAuthenticated()
}
- val confirmedHaptics by collectLastValue(kosmos.promptViewModel.hapticsToPlay)
- assertThat(confirmedHaptics?.hapticFeedbackConstant)
+ val hapticsPostConfirm by collectLastValue(kosmos.promptViewModel.hapticsToPlay)
+ val confirmedHaptics =
+ hapticsPostConfirm as PromptViewModel.HapticsToPlay.HapticConstant
+ assertThat(confirmedHaptics.constant)
.isEqualTo(HapticFeedbackConstants.BIOMETRIC_CONFIRM)
- assertThat(confirmedHaptics?.flag).isNull()
+ assertThat(confirmedHaptics.flag).isNull()
}
@Test
+ @EnableFlags(Flags.FLAG_MSDL_FEEDBACK)
+ fun set_msdl_haptic_on_confirm_when_confirmation_required_otherwise_on_authenticated() =
+ runGenericTest {
+ val expectConfirmation = testCase.expectConfirmation(atLeastOneFailure = false)
+
+ kosmos.promptViewModel.showAuthenticated(testCase.authenticatedModality, 1_000L)
+
+ val hapticsPreConfirm by collectLastValue(kosmos.promptViewModel.hapticsToPlay)
+
+ if (expectConfirmation) {
+ assertThat(hapticsPreConfirm).isEqualTo(PromptViewModel.HapticsToPlay.None)
+ } else {
+ val confirmHaptics = hapticsPreConfirm as PromptViewModel.HapticsToPlay.MSDL
+ assertThat(confirmHaptics.token).isEqualTo(MSDLToken.UNLOCK)
+ assertThat(confirmHaptics.properties).isEqualTo(authInteractionProperties)
+ }
+
+ if (expectConfirmation) {
+ kosmos.promptViewModel.confirmAuthenticated()
+ }
+
+ val hapticsPostConfirm by collectLastValue(kosmos.promptViewModel.hapticsToPlay)
+ val confirmedHaptics = hapticsPostConfirm as PromptViewModel.HapticsToPlay.MSDL
+ assertThat(confirmedHaptics.token).isEqualTo(MSDLToken.UNLOCK)
+ assertThat(confirmedHaptics.properties).isEqualTo(authInteractionProperties)
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_MSDL_FEEDBACK)
fun playSuccessHaptic_SetsConfirmConstant() = runGenericTest {
val expectConfirmation = testCase.expectConfirmation(atLeastOneFailure = false)
kosmos.promptViewModel.showAuthenticated(testCase.authenticatedModality, 1_000L)
@@ -740,20 +780,48 @@
kosmos.promptViewModel.confirmAuthenticated()
}
- val currentHaptics by collectLastValue(kosmos.promptViewModel.hapticsToPlay)
- assertThat(currentHaptics?.hapticFeedbackConstant)
- .isEqualTo(HapticFeedbackConstants.BIOMETRIC_CONFIRM)
- assertThat(currentHaptics?.flag).isNull()
+ val haptics by collectLastValue(kosmos.promptViewModel.hapticsToPlay)
+ val currentHaptics = haptics as PromptViewModel.HapticsToPlay.HapticConstant
+ assertThat(currentHaptics.constant).isEqualTo(HapticFeedbackConstants.BIOMETRIC_CONFIRM)
+ assertThat(currentHaptics.flag).isNull()
}
@Test
+ @EnableFlags(Flags.FLAG_MSDL_FEEDBACK)
+ fun playSuccessHaptic_SetsUnlockMSDLFeedback() = runGenericTest {
+ val expectConfirmation = testCase.expectConfirmation(atLeastOneFailure = false)
+ kosmos.promptViewModel.showAuthenticated(testCase.authenticatedModality, 1_000L)
+
+ if (expectConfirmation) {
+ kosmos.promptViewModel.confirmAuthenticated()
+ }
+
+ val haptics by collectLastValue(kosmos.promptViewModel.hapticsToPlay)
+ val currentHaptics = haptics as PromptViewModel.HapticsToPlay.MSDL
+ assertThat(currentHaptics.token).isEqualTo(MSDLToken.UNLOCK)
+ assertThat(currentHaptics.properties).isEqualTo(authInteractionProperties)
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_MSDL_FEEDBACK)
fun playErrorHaptic_SetsRejectConstant() = runGenericTest {
kosmos.promptViewModel.showTemporaryError("test", "messageAfterError", false)
- val currentHaptics by collectLastValue(kosmos.promptViewModel.hapticsToPlay)
- assertThat(currentHaptics?.hapticFeedbackConstant)
- .isEqualTo(HapticFeedbackConstants.BIOMETRIC_REJECT)
- assertThat(currentHaptics?.flag).isNull()
+ val haptics by collectLastValue(kosmos.promptViewModel.hapticsToPlay)
+ val currentHaptics = haptics as PromptViewModel.HapticsToPlay.HapticConstant
+ assertThat(currentHaptics.constant).isEqualTo(HapticFeedbackConstants.BIOMETRIC_REJECT)
+ assertThat(currentHaptics.flag).isNull()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_MSDL_FEEDBACK)
+ fun playErrorHaptic_SetsFailureMSDLFeedback() = runGenericTest {
+ kosmos.promptViewModel.showTemporaryError("test", "messageAfterError", false)
+
+ val haptics by collectLastValue(kosmos.promptViewModel.hapticsToPlay)
+ val currentHaptics = haptics as PromptViewModel.HapticsToPlay.MSDL
+ assertThat(currentHaptics.token).isEqualTo(MSDLToken.FAILURE)
+ assertThat(currentHaptics.properties).isEqualTo(authInteractionProperties)
}
// biometricprompt_sfps_fingerprint_authenticating reused across rotations
@@ -855,6 +923,7 @@
}
@Test
+ @DisableFlags(Flags.FLAG_MSDL_FEEDBACK)
fun set_haptic_on_errors() = runGenericTest {
kosmos.promptViewModel.showTemporaryError(
"so sad",
@@ -863,13 +932,30 @@
hapticFeedback = true,
)
- val haptics by collectLastValue(kosmos.promptViewModel.hapticsToPlay)
- assertThat(haptics?.hapticFeedbackConstant)
- .isEqualTo(HapticFeedbackConstants.BIOMETRIC_REJECT)
- assertThat(haptics?.flag).isNull()
+ val hapticsToPlay by collectLastValue(kosmos.promptViewModel.hapticsToPlay)
+ val haptics = hapticsToPlay as PromptViewModel.HapticsToPlay.HapticConstant
+ assertThat(haptics.constant).isEqualTo(HapticFeedbackConstants.BIOMETRIC_REJECT)
+ assertThat(haptics.flag).isNull()
}
@Test
+ @EnableFlags(Flags.FLAG_MSDL_FEEDBACK)
+ fun set_msdl_haptic_on_errors() = runGenericTest {
+ kosmos.promptViewModel.showTemporaryError(
+ "so sad",
+ messageAfterError = "",
+ authenticateAfterError = false,
+ hapticFeedback = true,
+ )
+
+ val hapticsToPlay by collectLastValue(kosmos.promptViewModel.hapticsToPlay)
+ val haptics = hapticsToPlay as PromptViewModel.HapticsToPlay.MSDL
+ assertThat(haptics.token).isEqualTo(MSDLToken.FAILURE)
+ assertThat(haptics.properties).isEqualTo(authInteractionProperties)
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_MSDL_FEEDBACK)
fun plays_haptic_on_errors_unless_skipped() = runGenericTest {
kosmos.promptViewModel.showTemporaryError(
"still sad",
@@ -878,11 +964,26 @@
hapticFeedback = false,
)
- val haptics by collectLastValue(kosmos.promptViewModel.hapticsToPlay)
- assertThat(haptics?.hapticFeedbackConstant).isEqualTo(HapticFeedbackConstants.NO_HAPTICS)
+ val hapticsToPlay by collectLastValue(kosmos.promptViewModel.hapticsToPlay)
+ assertThat(hapticsToPlay).isEqualTo(PromptViewModel.HapticsToPlay.None)
}
@Test
+ @EnableFlags(Flags.FLAG_MSDL_FEEDBACK)
+ fun plays_msdl_haptic_on_errors_unless_skipped() = runGenericTest {
+ kosmos.promptViewModel.showTemporaryError(
+ "still sad",
+ messageAfterError = "",
+ authenticateAfterError = false,
+ hapticFeedback = false,
+ )
+
+ val hapticsToPlay by collectLastValue(kosmos.promptViewModel.hapticsToPlay)
+ assertThat(hapticsToPlay).isEqualTo(PromptViewModel.HapticsToPlay.None)
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_MSDL_FEEDBACK)
fun plays_haptic_on_error_after_auth_when_confirmation_needed() = runGenericTest {
val expectConfirmation = testCase.expectConfirmation(atLeastOneFailure = false)
kosmos.promptViewModel.showAuthenticated(testCase.authenticatedModality, 0)
@@ -894,17 +995,39 @@
hapticFeedback = true,
)
- val haptics by collectLastValue(kosmos.promptViewModel.hapticsToPlay)
+ val hapticsToPlay by collectLastValue(kosmos.promptViewModel.hapticsToPlay)
+ val haptics = hapticsToPlay as PromptViewModel.HapticsToPlay.HapticConstant
if (expectConfirmation) {
- assertThat(haptics?.hapticFeedbackConstant)
- .isEqualTo(HapticFeedbackConstants.BIOMETRIC_REJECT)
- assertThat(haptics?.flag).isNull()
+ assertThat(haptics.constant).isEqualTo(HapticFeedbackConstants.BIOMETRIC_REJECT)
+ assertThat(haptics.flag).isNull()
} else {
- assertThat(haptics?.hapticFeedbackConstant)
- .isEqualTo(HapticFeedbackConstants.BIOMETRIC_CONFIRM)
+ assertThat(haptics.constant).isEqualTo(HapticFeedbackConstants.BIOMETRIC_CONFIRM)
}
}
+ @Test
+ @EnableFlags(Flags.FLAG_MSDL_FEEDBACK)
+ fun plays_msdl_haptic_on_error_after_auth_when_confirmation_needed() = runGenericTest {
+ val expectConfirmation = testCase.expectConfirmation(atLeastOneFailure = false)
+ kosmos.promptViewModel.showAuthenticated(testCase.authenticatedModality, 0)
+
+ kosmos.promptViewModel.showTemporaryError(
+ "still sad",
+ messageAfterError = "",
+ authenticateAfterError = false,
+ hapticFeedback = true,
+ )
+
+ val hapticsToPlay by collectLastValue(kosmos.promptViewModel.hapticsToPlay)
+ val haptics = hapticsToPlay as PromptViewModel.HapticsToPlay.MSDL
+ if (expectConfirmation) {
+ assertThat(haptics.token).isEqualTo(MSDLToken.FAILURE)
+ } else {
+ assertThat(haptics.token).isEqualTo(MSDLToken.UNLOCK)
+ }
+ assertThat(haptics.properties).isEqualTo(authInteractionProperties)
+ }
+
private suspend fun TestScope.showTemporaryErrors(
restart: Boolean,
helpAfterError: String = "",