Merge "Migrate haptic to view model and out of legacy components." into udc-d1-dev
This commit is contained in:
@@ -737,7 +737,7 @@ public abstract class AuthBiometricView extends LinearLayout implements AuthBiom
|
|||||||
});
|
});
|
||||||
|
|
||||||
mUseCredentialButton.setOnClickListener((view) -> {
|
mUseCredentialButton.setOnClickListener((view) -> {
|
||||||
startTransitionToCredentialUI();
|
startTransitionToCredentialUI(false /* isError */);
|
||||||
});
|
});
|
||||||
|
|
||||||
mConfirmButton.setOnClickListener((view) -> {
|
mConfirmButton.setOnClickListener((view) -> {
|
||||||
@@ -768,9 +768,12 @@ public abstract class AuthBiometricView extends LinearLayout implements AuthBiom
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Kicks off the animation process and invokes the callback.
|
* Kicks off the animation process and invokes the callback.
|
||||||
|
*
|
||||||
|
* @param isError if this was triggered due to an error and not a user action (unused,
|
||||||
|
* previously for haptics).
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void startTransitionToCredentialUI() {
|
public void startTransitionToCredentialUI(boolean isError) {
|
||||||
updateSize(AuthDialog.SIZE_LARGE);
|
updateSize(AuthDialog.SIZE_LARGE);
|
||||||
mCallback.onAction(Callback.ACTION_USE_DEVICE_CREDENTIAL);
|
mCallback.onAction(Callback.ACTION_USE_DEVICE_CREDENTIAL);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ interface AuthBiometricViewAdapter {
|
|||||||
|
|
||||||
fun onHelp(@BiometricAuthenticator.Modality modality: Int, help: String)
|
fun onHelp(@BiometricAuthenticator.Modality modality: Int, help: String)
|
||||||
|
|
||||||
fun startTransitionToCredentialUI()
|
fun startTransitionToCredentialUI(isError: Boolean)
|
||||||
|
|
||||||
fun requestLayout()
|
fun requestLayout()
|
||||||
|
|
||||||
|
|||||||
@@ -801,9 +801,9 @@ public class AuthContainerView extends LinearLayout
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void animateToCredentialUI() {
|
public void animateToCredentialUI(boolean isError) {
|
||||||
if (mBiometricView != null) {
|
if (mBiometricView != null) {
|
||||||
mBiometricView.startTransitionToCredentialUI();
|
mBiometricView.startTransitionToCredentialUI(isError);
|
||||||
} else {
|
} else {
|
||||||
Log.e(TAG, "animateToCredentialUI(): mBiometricView is null");
|
Log.e(TAG, "animateToCredentialUI(): mBiometricView is null");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -85,7 +85,6 @@ import com.android.systemui.flags.FeatureFlags;
|
|||||||
import com.android.systemui.keyguard.WakefulnessLifecycle;
|
import com.android.systemui.keyguard.WakefulnessLifecycle;
|
||||||
import com.android.systemui.keyguard.data.repository.BiometricType;
|
import com.android.systemui.keyguard.data.repository.BiometricType;
|
||||||
import com.android.systemui.statusbar.CommandQueue;
|
import com.android.systemui.statusbar.CommandQueue;
|
||||||
import com.android.systemui.statusbar.VibratorHelper;
|
|
||||||
import com.android.systemui.util.concurrency.DelayableExecutor;
|
import com.android.systemui.util.concurrency.DelayableExecutor;
|
||||||
import com.android.systemui.util.concurrency.Execution;
|
import com.android.systemui.util.concurrency.Execution;
|
||||||
|
|
||||||
@@ -185,18 +184,6 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks,
|
|||||||
private final @Background DelayableExecutor mBackgroundExecutor;
|
private final @Background DelayableExecutor mBackgroundExecutor;
|
||||||
private final DisplayInfo mCachedDisplayInfo = new DisplayInfo();
|
private final DisplayInfo mCachedDisplayInfo = new DisplayInfo();
|
||||||
|
|
||||||
private final VibratorHelper mVibratorHelper;
|
|
||||||
|
|
||||||
private void vibrateSuccess(int modality) {
|
|
||||||
mVibratorHelper.vibrateAuthSuccess(
|
|
||||||
getClass().getSimpleName() + ", modality = " + modality + "BP::success");
|
|
||||||
}
|
|
||||||
|
|
||||||
private void vibrateError(int modality) {
|
|
||||||
mVibratorHelper.vibrateAuthError(
|
|
||||||
getClass().getSimpleName() + ", modality = " + modality + "BP::error");
|
|
||||||
}
|
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
final TaskStackListener mTaskStackListener = new TaskStackListener() {
|
final TaskStackListener mTaskStackListener = new TaskStackListener() {
|
||||||
@Override
|
@Override
|
||||||
@@ -776,7 +763,6 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks,
|
|||||||
@NonNull InteractionJankMonitor jankMonitor,
|
@NonNull InteractionJankMonitor jankMonitor,
|
||||||
@Main Handler handler,
|
@Main Handler handler,
|
||||||
@Background DelayableExecutor bgExecutor,
|
@Background DelayableExecutor bgExecutor,
|
||||||
@NonNull VibratorHelper vibrator,
|
|
||||||
@NonNull UdfpsUtils udfpsUtils) {
|
@NonNull UdfpsUtils udfpsUtils) {
|
||||||
mContext = context;
|
mContext = context;
|
||||||
mFeatureFlags = featureFlags;
|
mFeatureFlags = featureFlags;
|
||||||
@@ -798,7 +784,6 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks,
|
|||||||
mUdfpsEnrolledForUser = new SparseBooleanArray();
|
mUdfpsEnrolledForUser = new SparseBooleanArray();
|
||||||
mSfpsEnrolledForUser = new SparseBooleanArray();
|
mSfpsEnrolledForUser = new SparseBooleanArray();
|
||||||
mFaceEnrolledForUser = new SparseBooleanArray();
|
mFaceEnrolledForUser = new SparseBooleanArray();
|
||||||
mVibratorHelper = vibrator;
|
|
||||||
mUdfpsUtils = udfpsUtils;
|
mUdfpsUtils = udfpsUtils;
|
||||||
mApplicationCoroutineScope = applicationCoroutineScope;
|
mApplicationCoroutineScope = applicationCoroutineScope;
|
||||||
|
|
||||||
@@ -987,8 +972,6 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks,
|
|||||||
public void onBiometricAuthenticated(@Modality int modality) {
|
public void onBiometricAuthenticated(@Modality int modality) {
|
||||||
if (DEBUG) Log.d(TAG, "onBiometricAuthenticated: ");
|
if (DEBUG) Log.d(TAG, "onBiometricAuthenticated: ");
|
||||||
|
|
||||||
vibrateSuccess(modality);
|
|
||||||
|
|
||||||
if (mCurrentDialog != null) {
|
if (mCurrentDialog != null) {
|
||||||
mCurrentDialog.onAuthenticationSucceeded(modality);
|
mCurrentDialog.onAuthenticationSucceeded(modality);
|
||||||
} else {
|
} else {
|
||||||
@@ -1085,8 +1068,6 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks,
|
|||||||
Log.d(TAG, String.format("onBiometricError(%d, %d, %d)", modality, error, vendorCode));
|
Log.d(TAG, String.format("onBiometricError(%d, %d, %d)", modality, error, vendorCode));
|
||||||
}
|
}
|
||||||
|
|
||||||
vibrateError(modality);
|
|
||||||
|
|
||||||
final boolean isLockout = (error == BiometricConstants.BIOMETRIC_ERROR_LOCKOUT)
|
final boolean isLockout = (error == BiometricConstants.BIOMETRIC_ERROR_LOCKOUT)
|
||||||
|| (error == BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT);
|
|| (error == BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT);
|
||||||
|
|
||||||
@@ -1103,7 +1084,7 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks,
|
|||||||
if (mCurrentDialog != null) {
|
if (mCurrentDialog != null) {
|
||||||
if (mCurrentDialog.isAllowDeviceCredentials() && isLockout) {
|
if (mCurrentDialog.isAllowDeviceCredentials() && isLockout) {
|
||||||
if (DEBUG) Log.d(TAG, "onBiometricError, lockout");
|
if (DEBUG) Log.d(TAG, "onBiometricError, lockout");
|
||||||
mCurrentDialog.animateToCredentialUI();
|
mCurrentDialog.animateToCredentialUI(true /* isError */);
|
||||||
} else if (isSoftError) {
|
} else if (isSoftError) {
|
||||||
final String errorMessage = (error == BiometricConstants.BIOMETRIC_PAUSED_REJECTED)
|
final String errorMessage = (error == BiometricConstants.BIOMETRIC_PAUSED_REJECTED)
|
||||||
? getNotRecognizedString(modality)
|
? getNotRecognizedString(modality)
|
||||||
|
|||||||
@@ -162,7 +162,7 @@ public interface AuthDialog extends Dumpable {
|
|||||||
/**
|
/**
|
||||||
* Animate to credential UI. Typically called after biometric is locked out.
|
* Animate to credential UI. Typically called after biometric is locked out.
|
||||||
*/
|
*/
|
||||||
void animateToCredentialUI();
|
void animateToCredentialUI(boolean isError);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return true if device credential is allowed.
|
* @return true if device credential is allowed.
|
||||||
|
|||||||
@@ -522,6 +522,7 @@ private class Spaghetti(
|
|||||||
viewModel.showTemporaryError(
|
viewModel.showTemporaryError(
|
||||||
help,
|
help,
|
||||||
messageAfterError = modalities.asDefaultHelpMessage(applicationContext),
|
messageAfterError = modalities.asDefaultHelpMessage(applicationContext),
|
||||||
|
hapticFeedback = false,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -534,7 +535,7 @@ private class Spaghetti(
|
|||||||
else -> false
|
else -> false
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun startTransitionToCredentialUI() {
|
override fun startTransitionToCredentialUI(isError: Boolean) {
|
||||||
applicationScope.launch {
|
applicationScope.launch {
|
||||||
viewModel.onSwitchToCredential()
|
viewModel.onSwitchToCredential()
|
||||||
legacyCallback?.onAction(Callback.ACTION_USE_DEVICE_CREDENTIAL)
|
legacyCallback?.onAction(Callback.ACTION_USE_DEVICE_CREDENTIAL)
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteracto
|
|||||||
import com.android.systemui.biometrics.domain.model.BiometricModalities
|
import com.android.systemui.biometrics.domain.model.BiometricModalities
|
||||||
import com.android.systemui.biometrics.domain.model.BiometricModality
|
import com.android.systemui.biometrics.domain.model.BiometricModality
|
||||||
import com.android.systemui.biometrics.shared.model.PromptKind
|
import com.android.systemui.biometrics.shared.model.PromptKind
|
||||||
|
import com.android.systemui.statusbar.VibratorHelper
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.coroutineScope
|
import kotlinx.coroutines.coroutineScope
|
||||||
@@ -41,6 +42,7 @@ class PromptViewModel
|
|||||||
@Inject
|
@Inject
|
||||||
constructor(
|
constructor(
|
||||||
private val interactor: PromptSelectorInteractor,
|
private val interactor: PromptSelectorInteractor,
|
||||||
|
private val vibrator: VibratorHelper,
|
||||||
) {
|
) {
|
||||||
/** The set of modalities available for this prompt */
|
/** The set of modalities available for this prompt */
|
||||||
val modalities: Flow<BiometricModalities> =
|
val modalities: Flow<BiometricModalities> =
|
||||||
@@ -205,17 +207,19 @@ constructor(
|
|||||||
private var messageJob: Job? = null
|
private var messageJob: Job? = null
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Show a temporary error [message] associated with an optional [failedModality].
|
* Show a temporary error [message] associated with an optional [failedModality] and play
|
||||||
|
* [hapticFeedback].
|
||||||
*
|
*
|
||||||
* An optional [messageAfterError] will be shown via [showAuthenticating] when
|
* An optional [messageAfterError] will be shown via [showAuthenticating] when
|
||||||
* [authenticateAfterError] is set (or via [showHelp] when not set) after the error is
|
* [authenticateAfterError] is set (or via [showHelp] when not set) after the error is
|
||||||
* dismissed.
|
* dismissed.
|
||||||
*
|
*
|
||||||
* The error is ignored if the user has already authenticated and it is treated as
|
* The error is ignored if the user has already authenticated or if [suppressIfErrorShowing] is
|
||||||
* [onSilentError] if [suppressIfErrorShowing] is set and an error message is already showing.
|
* set and an error message is already showing.
|
||||||
*/
|
*/
|
||||||
suspend fun showTemporaryError(
|
suspend fun showTemporaryError(
|
||||||
message: String,
|
message: String,
|
||||||
|
hapticFeedback: Boolean = true,
|
||||||
messageAfterError: String = "",
|
messageAfterError: String = "",
|
||||||
authenticateAfterError: Boolean = false,
|
authenticateAfterError: Boolean = false,
|
||||||
suppressIfErrorShowing: Boolean = false,
|
suppressIfErrorShowing: Boolean = false,
|
||||||
@@ -225,7 +229,9 @@ constructor(
|
|||||||
return@coroutineScope
|
return@coroutineScope
|
||||||
}
|
}
|
||||||
if (_message.value.isErrorOrHelp && suppressIfErrorShowing) {
|
if (_message.value.isErrorOrHelp && suppressIfErrorShowing) {
|
||||||
onSilentError(failedModality)
|
if (_isAuthenticated.value.isNotAuthenticated) {
|
||||||
|
_canTryAgainNow.value = supportsRetry(failedModality)
|
||||||
|
}
|
||||||
return@coroutineScope
|
return@coroutineScope
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -236,6 +242,10 @@ constructor(
|
|||||||
_message.value = PromptMessage.Error(message)
|
_message.value = PromptMessage.Error(message)
|
||||||
_legacyState.value = AuthBiometricView.STATE_ERROR
|
_legacyState.value = AuthBiometricView.STATE_ERROR
|
||||||
|
|
||||||
|
if (hapticFeedback) {
|
||||||
|
vibrator.error(failedModality)
|
||||||
|
}
|
||||||
|
|
||||||
messageJob?.cancel()
|
messageJob?.cancel()
|
||||||
messageJob = launch {
|
messageJob = launch {
|
||||||
delay(BiometricPrompt.HIDE_DIALOG_DELAY.toLong())
|
delay(BiometricPrompt.HIDE_DIALOG_DELAY.toLong())
|
||||||
@@ -247,18 +257,6 @@ constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Call instead of [showTemporaryError] if an error from the HAL should be silently ignored to
|
|
||||||
* enable retry (if the [failedModality] supports retrying).
|
|
||||||
*
|
|
||||||
* Ignored if the user has already authenticated.
|
|
||||||
*/
|
|
||||||
private fun onSilentError(failedModality: BiometricModality = BiometricModality.None) {
|
|
||||||
if (_isAuthenticated.value.isNotAuthenticated) {
|
|
||||||
_canTryAgainNow.value = supportsRetry(failedModality)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Call to ensure the fingerprint sensor has started. Either when the dialog is first shown
|
* Call to ensure the fingerprint sensor has started. Either when the dialog is first shown
|
||||||
* (most cases) or when it should be enabled after a first error (coex implicit flow).
|
* (most cases) or when it should be enabled after a first error (coex implicit flow).
|
||||||
@@ -376,6 +374,8 @@ constructor(
|
|||||||
AuthBiometricView.STATE_AUTHENTICATED
|
AuthBiometricView.STATE_AUTHENTICATED
|
||||||
}
|
}
|
||||||
|
|
||||||
|
vibrator.success(modality)
|
||||||
|
|
||||||
messageJob?.cancel()
|
messageJob?.cancel()
|
||||||
messageJob = null
|
messageJob = null
|
||||||
|
|
||||||
@@ -386,18 +386,18 @@ constructor(
|
|||||||
|
|
||||||
private suspend fun needsExplicitConfirmation(modality: BiometricModality): Boolean {
|
private suspend fun needsExplicitConfirmation(modality: BiometricModality): Boolean {
|
||||||
val availableModalities = modalities.first()
|
val availableModalities = modalities.first()
|
||||||
val confirmationRequested = interactor.isConfirmationRequired.first()
|
val confirmationRequired = isConfirmationRequired.first()
|
||||||
|
|
||||||
if (availableModalities.hasFaceAndFingerprint) {
|
if (availableModalities.hasFaceAndFingerprint) {
|
||||||
// coex only needs confirmation when face is successful, unless it happens on the
|
// coex only needs confirmation when face is successful, unless it happens on the
|
||||||
// first attempt (i.e. without failure) before fingerprint scanning starts
|
// first attempt (i.e. without failure) before fingerprint scanning starts
|
||||||
|
val fingerprintStarted = fingerprintStartMode.first() != FingerprintStartMode.Pending
|
||||||
if (modality == BiometricModality.Face) {
|
if (modality == BiometricModality.Face) {
|
||||||
return (fingerprintStartMode.first() != FingerprintStartMode.Pending) ||
|
return fingerprintStarted || confirmationRequired
|
||||||
confirmationRequested
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (availableModalities.hasFaceOnly) {
|
if (availableModalities.hasFaceOnly) {
|
||||||
return confirmationRequested
|
return confirmationRequired
|
||||||
}
|
}
|
||||||
// fingerprint only never requires confirmation
|
// fingerprint only never requires confirmation
|
||||||
return false
|
return false
|
||||||
@@ -412,7 +412,6 @@ constructor(
|
|||||||
fun confirmAuthenticated() {
|
fun confirmAuthenticated() {
|
||||||
val authState = _isAuthenticated.value
|
val authState = _isAuthenticated.value
|
||||||
if (authState.isNotAuthenticated) {
|
if (authState.isNotAuthenticated) {
|
||||||
"Cannot show authenticated after authenticated"
|
|
||||||
Log.w(TAG, "Cannot confirm authenticated when not authenticated")
|
Log.w(TAG, "Cannot confirm authenticated when not authenticated")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -434,6 +433,12 @@ constructor(
|
|||||||
_forceLargeSize.value = true
|
_forceLargeSize.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun VibratorHelper.success(modality: BiometricModality) =
|
||||||
|
vibrateAuthSuccess("$TAG, modality = $modality BP::success")
|
||||||
|
|
||||||
|
private fun VibratorHelper.error(modality: BiometricModality = BiometricModality.None) =
|
||||||
|
vibrateAuthError("$TAG, modality = $modality BP::error")
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val TAG = "PromptViewModel"
|
private const val TAG = "PromptViewModel"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ import com.android.systemui.biometrics.ui.viewmodel.PromptViewModel
|
|||||||
import com.android.systemui.flags.FakeFeatureFlags
|
import com.android.systemui.flags.FakeFeatureFlags
|
||||||
import com.android.systemui.flags.Flags
|
import com.android.systemui.flags.Flags
|
||||||
import com.android.systemui.keyguard.WakefulnessLifecycle
|
import com.android.systemui.keyguard.WakefulnessLifecycle
|
||||||
|
import com.android.systemui.statusbar.VibratorHelper
|
||||||
import com.android.systemui.util.concurrency.FakeExecutor
|
import com.android.systemui.util.concurrency.FakeExecutor
|
||||||
import com.android.systemui.util.time.FakeSystemClock
|
import com.android.systemui.util.time.FakeSystemClock
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
@@ -99,6 +100,8 @@ open class AuthContainerViewTest : SysuiTestCase() {
|
|||||||
lateinit var windowToken: IBinder
|
lateinit var windowToken: IBinder
|
||||||
@Mock
|
@Mock
|
||||||
lateinit var interactionJankMonitor: InteractionJankMonitor
|
lateinit var interactionJankMonitor: InteractionJankMonitor
|
||||||
|
@Mock
|
||||||
|
lateinit var vibrator: VibratorHelper
|
||||||
|
|
||||||
// TODO(b/278622168): remove with flag
|
// TODO(b/278622168): remove with flag
|
||||||
open val useNewBiometricPrompt = false
|
open val useNewBiometricPrompt = false
|
||||||
@@ -325,7 +328,7 @@ open class AuthContainerViewTest : SysuiTestCase() {
|
|||||||
authenticators = BiometricManager.Authenticators.BIOMETRIC_WEAK or
|
authenticators = BiometricManager.Authenticators.BIOMETRIC_WEAK or
|
||||||
BiometricManager.Authenticators.DEVICE_CREDENTIAL
|
BiometricManager.Authenticators.DEVICE_CREDENTIAL
|
||||||
)
|
)
|
||||||
container.animateToCredentialUI()
|
container.animateToCredentialUI(false)
|
||||||
waitForIdleSync()
|
waitForIdleSync()
|
||||||
|
|
||||||
assertThat(container.hasCredentialView()).isTrue()
|
assertThat(container.hasCredentialView()).isTrue()
|
||||||
@@ -514,7 +517,7 @@ open class AuthContainerViewTest : SysuiTestCase() {
|
|||||||
{ authBiometricFingerprintViewModel },
|
{ authBiometricFingerprintViewModel },
|
||||||
{ promptSelectorInteractor },
|
{ promptSelectorInteractor },
|
||||||
{ bpCredentialInteractor },
|
{ bpCredentialInteractor },
|
||||||
PromptViewModel(promptSelectorInteractor),
|
PromptViewModel(promptSelectorInteractor, vibrator),
|
||||||
{ credentialViewModel },
|
{ credentialViewModel },
|
||||||
Handler(TestableLooper.get(this).looper),
|
Handler(TestableLooper.get(this).looper),
|
||||||
fakeExecutor
|
fakeExecutor
|
||||||
|
|||||||
@@ -202,9 +202,6 @@ public class AuthControllerTest extends SysuiTestCase {
|
|||||||
private TestableAuthController mAuthController;
|
private TestableAuthController mAuthController;
|
||||||
private FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();
|
private FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();
|
||||||
|
|
||||||
@Mock
|
|
||||||
private VibratorHelper mVibratorHelper;
|
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setup() throws RemoteException {
|
public void setup() throws RemoteException {
|
||||||
// TODO(b/278622168): remove with flag
|
// TODO(b/278622168): remove with flag
|
||||||
@@ -267,7 +264,6 @@ public class AuthControllerTest extends SysuiTestCase {
|
|||||||
true /* supportsSelfIllumination */,
|
true /* supportsSelfIllumination */,
|
||||||
true /* resetLockoutRequireHardwareAuthToken */));
|
true /* resetLockoutRequireHardwareAuthToken */));
|
||||||
when(mFaceManager.getSensorPropertiesInternal()).thenReturn(faceProps);
|
when(mFaceManager.getSensorPropertiesInternal()).thenReturn(faceProps);
|
||||||
when(mVibratorHelper.hasVibrator()).thenReturn(true);
|
|
||||||
|
|
||||||
mAuthController = new TestableAuthController(mContextSpy);
|
mAuthController = new TestableAuthController(mContextSpy);
|
||||||
|
|
||||||
@@ -594,7 +590,7 @@ public class AuthControllerTest extends SysuiTestCase {
|
|||||||
|
|
||||||
mAuthController.onBiometricError(BiometricAuthenticator.TYPE_FACE, error, vendorCode);
|
mAuthController.onBiometricError(BiometricAuthenticator.TYPE_FACE, error, vendorCode);
|
||||||
verify(mDialog1, never()).onError(anyInt(), anyString());
|
verify(mDialog1, never()).onError(anyInt(), anyString());
|
||||||
verify(mDialog1).animateToCredentialUI();
|
verify(mDialog1).animateToCredentialUI(eq(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -607,7 +603,7 @@ public class AuthControllerTest extends SysuiTestCase {
|
|||||||
|
|
||||||
mAuthController.onBiometricError(BiometricAuthenticator.TYPE_FACE, error, vendorCode);
|
mAuthController.onBiometricError(BiometricAuthenticator.TYPE_FACE, error, vendorCode);
|
||||||
verify(mDialog1, never()).onError(anyInt(), anyString());
|
verify(mDialog1, never()).onError(anyInt(), anyString());
|
||||||
verify(mDialog1).animateToCredentialUI();
|
verify(mDialog1).animateToCredentialUI(eq(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -622,7 +618,7 @@ public class AuthControllerTest extends SysuiTestCase {
|
|||||||
mAuthController.onBiometricError(modality, error, vendorCode);
|
mAuthController.onBiometricError(modality, error, vendorCode);
|
||||||
verify(mDialog1).onError(
|
verify(mDialog1).onError(
|
||||||
eq(modality), eq(FaceManager.getErrorString(mContext, error, vendorCode)));
|
eq(modality), eq(FaceManager.getErrorString(mContext, error, vendorCode)));
|
||||||
verify(mDialog1, never()).animateToCredentialUI();
|
verify(mDialog1, never()).animateToCredentialUI(eq(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -637,7 +633,7 @@ public class AuthControllerTest extends SysuiTestCase {
|
|||||||
mAuthController.onBiometricError(modality, error, vendorCode);
|
mAuthController.onBiometricError(modality, error, vendorCode);
|
||||||
verify(mDialog1).onError(
|
verify(mDialog1).onError(
|
||||||
eq(modality), eq(FaceManager.getErrorString(mContext, error, vendorCode)));
|
eq(modality), eq(FaceManager.getErrorString(mContext, error, vendorCode)));
|
||||||
verify(mDialog1, never()).animateToCredentialUI();
|
verify(mDialog1, never()).animateToCredentialUI(eq(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -1081,7 +1077,7 @@ public class AuthControllerTest extends SysuiTestCase {
|
|||||||
() -> mBiometricPromptCredentialInteractor, () -> mPromptSelectionInteractor,
|
() -> mBiometricPromptCredentialInteractor, () -> mPromptSelectionInteractor,
|
||||||
() -> mCredentialViewModel, () -> mPromptViewModel,
|
() -> mCredentialViewModel, () -> mPromptViewModel,
|
||||||
mInteractionJankMonitor, mHandler,
|
mInteractionJankMonitor, mHandler,
|
||||||
mBackgroundExecutor, mVibratorHelper, mUdfpsUtils);
|
mBackgroundExecutor, mUdfpsUtils);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -32,6 +32,8 @@ import com.android.systemui.biometrics.extractAuthenticatorTypes
|
|||||||
import com.android.systemui.biometrics.faceSensorPropertiesInternal
|
import com.android.systemui.biometrics.faceSensorPropertiesInternal
|
||||||
import com.android.systemui.biometrics.fingerprintSensorPropertiesInternal
|
import com.android.systemui.biometrics.fingerprintSensorPropertiesInternal
|
||||||
import com.android.systemui.coroutines.collectLastValue
|
import com.android.systemui.coroutines.collectLastValue
|
||||||
|
import com.android.systemui.statusbar.VibratorHelper
|
||||||
|
import com.android.systemui.util.mockito.any
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
@@ -45,6 +47,9 @@ import org.junit.Test
|
|||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
import org.junit.runners.Parameterized
|
import org.junit.runners.Parameterized
|
||||||
import org.mockito.Mock
|
import org.mockito.Mock
|
||||||
|
import org.mockito.Mockito.never
|
||||||
|
import org.mockito.Mockito.times
|
||||||
|
import org.mockito.Mockito.verify
|
||||||
import org.mockito.junit.MockitoJUnit
|
import org.mockito.junit.MockitoJUnit
|
||||||
|
|
||||||
private const val USER_ID = 4
|
private const val USER_ID = 4
|
||||||
@@ -58,6 +63,7 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
|
|||||||
@JvmField @Rule var mockitoRule = MockitoJUnit.rule()
|
@JvmField @Rule var mockitoRule = MockitoJUnit.rule()
|
||||||
|
|
||||||
@Mock private lateinit var lockPatternUtils: LockPatternUtils
|
@Mock private lateinit var lockPatternUtils: LockPatternUtils
|
||||||
|
@Mock private lateinit var vibrator: VibratorHelper
|
||||||
|
|
||||||
private val testScope = TestScope()
|
private val testScope = TestScope()
|
||||||
private val promptRepository = FakePromptRepository()
|
private val promptRepository = FakePromptRepository()
|
||||||
@@ -70,11 +76,11 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
|
|||||||
selector = PromptSelectorInteractorImpl(promptRepository, lockPatternUtils)
|
selector = PromptSelectorInteractorImpl(promptRepository, lockPatternUtils)
|
||||||
selector.resetPrompt()
|
selector.resetPrompt()
|
||||||
|
|
||||||
viewModel = PromptViewModel(selector)
|
viewModel = PromptViewModel(selector, vibrator)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `start idle and show authenticating`() =
|
fun start_idle_and_show_authenticating() =
|
||||||
runGenericTest(doNotStart = true) {
|
runGenericTest(doNotStart = true) {
|
||||||
val expectedSize =
|
val expectedSize =
|
||||||
if (testCase.shouldStartAsImplicitFlow) PromptSize.SMALL else PromptSize.MEDIUM
|
if (testCase.shouldStartAsImplicitFlow) PromptSize.SMALL else PromptSize.MEDIUM
|
||||||
@@ -107,7 +113,7 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `shows authenticated - no errors`() = runGenericTest {
|
fun shows_authenticated_with_no_errors() = runGenericTest {
|
||||||
// this case can't happen until fingerprint is started
|
// this case can't happen until fingerprint is started
|
||||||
// trigger it now since no error has occurred in this test
|
// trigger it now since no error has occurred in this test
|
||||||
val forceError = testCase.isCoex && testCase.authenticatedByFingerprint
|
val forceError = testCase.isCoex && testCase.authenticatedByFingerprint
|
||||||
@@ -124,6 +130,22 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun plays_haptic_on_authenticated() = runGenericTest {
|
||||||
|
viewModel.showAuthenticated(testCase.authenticatedModality, 1000L)
|
||||||
|
|
||||||
|
verify(vibrator).vibrateAuthSuccess(any())
|
||||||
|
verify(vibrator, never()).vibrateAuthError(any())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun plays_no_haptic_on_confirm() = runGenericTest {
|
||||||
|
viewModel.confirmAuthenticated()
|
||||||
|
|
||||||
|
verify(vibrator, never()).vibrateAuthSuccess(any())
|
||||||
|
verify(vibrator, never()).vibrateAuthError(any())
|
||||||
|
}
|
||||||
|
|
||||||
private suspend fun TestScope.showAuthenticated(
|
private suspend fun TestScope.showAuthenticated(
|
||||||
authenticatedModality: BiometricModality,
|
authenticatedModality: BiometricModality,
|
||||||
expectConfirmation: Boolean,
|
expectConfirmation: Boolean,
|
||||||
@@ -172,7 +194,7 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `shows temporary errors`() = runGenericTest {
|
fun shows_temporary_errors() = runGenericTest {
|
||||||
val checkAtEnd = suspend { assertButtonsVisible(negative = true) }
|
val checkAtEnd = suspend { assertButtonsVisible(negative = true) }
|
||||||
|
|
||||||
showTemporaryErrors(restart = false) { checkAtEnd() }
|
showTemporaryErrors(restart = false) { checkAtEnd() }
|
||||||
@@ -180,6 +202,22 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
|
|||||||
showTemporaryErrors(restart = true) { checkAtEnd() }
|
showTemporaryErrors(restart = true) { checkAtEnd() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun plays_haptic_on_errors() = runGenericTest {
|
||||||
|
viewModel.showTemporaryError("so sad", hapticFeedback = true)
|
||||||
|
|
||||||
|
verify(vibrator).vibrateAuthError(any())
|
||||||
|
verify(vibrator, never()).vibrateAuthSuccess(any())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun plays_haptic_on_errors_unless_skipped() = runGenericTest {
|
||||||
|
viewModel.showTemporaryError("still sad", hapticFeedback = false)
|
||||||
|
|
||||||
|
verify(vibrator, never()).vibrateAuthError(any())
|
||||||
|
verify(vibrator, never()).vibrateAuthSuccess(any())
|
||||||
|
}
|
||||||
|
|
||||||
private suspend fun TestScope.showTemporaryErrors(
|
private suspend fun TestScope.showTemporaryErrors(
|
||||||
restart: Boolean,
|
restart: Boolean,
|
||||||
helpAfterError: String = "",
|
helpAfterError: String = "",
|
||||||
@@ -233,7 +271,7 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `no errors or temporary help after authenticated`() = runGenericTest {
|
fun no_errors_or_temporary_help_after_authenticated() = runGenericTest {
|
||||||
val authenticating by collectLastValue(viewModel.isAuthenticating)
|
val authenticating by collectLastValue(viewModel.isAuthenticating)
|
||||||
val authenticated by collectLastValue(viewModel.isAuthenticated)
|
val authenticated by collectLastValue(viewModel.isAuthenticated)
|
||||||
val message by collectLastValue(viewModel.message)
|
val message by collectLastValue(viewModel.message)
|
||||||
@@ -277,7 +315,7 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `authenticated at most once`() = runGenericTest {
|
fun authenticated_at_most_once() = runGenericTest {
|
||||||
val authenticating by collectLastValue(viewModel.isAuthenticating)
|
val authenticating by collectLastValue(viewModel.isAuthenticating)
|
||||||
val authenticated by collectLastValue(viewModel.isAuthenticated)
|
val authenticated by collectLastValue(viewModel.isAuthenticated)
|
||||||
|
|
||||||
@@ -293,7 +331,7 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `authenticating cannot restart after authenticated`() = runGenericTest {
|
fun authenticating_cannot_restart_after_authenticated() = runGenericTest {
|
||||||
val authenticating by collectLastValue(viewModel.isAuthenticating)
|
val authenticating by collectLastValue(viewModel.isAuthenticating)
|
||||||
val authenticated by collectLastValue(viewModel.isAuthenticated)
|
val authenticated by collectLastValue(viewModel.isAuthenticated)
|
||||||
|
|
||||||
@@ -309,7 +347,7 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `confirm authentication`() = runGenericTest {
|
fun confirm_authentication() = runGenericTest {
|
||||||
val expectConfirmation = testCase.expectConfirmation(atLeastOneFailure = false)
|
val expectConfirmation = testCase.expectConfirmation(atLeastOneFailure = false)
|
||||||
|
|
||||||
viewModel.showAuthenticated(testCase.authenticatedModality, 0)
|
viewModel.showAuthenticated(testCase.authenticatedModality, 0)
|
||||||
@@ -341,7 +379,7 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `cannot confirm unless authenticated`() = runGenericTest {
|
fun cannot_confirm_unless_authenticated() = runGenericTest {
|
||||||
val authenticating by collectLastValue(viewModel.isAuthenticating)
|
val authenticating by collectLastValue(viewModel.isAuthenticating)
|
||||||
val authenticated by collectLastValue(viewModel.isAuthenticated)
|
val authenticated by collectLastValue(viewModel.isAuthenticated)
|
||||||
|
|
||||||
@@ -360,7 +398,7 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `shows help - before authenticated`() = runGenericTest {
|
fun shows_help_before_authenticated() = runGenericTest {
|
||||||
val helpMessage = "please help yourself to some cookies"
|
val helpMessage = "please help yourself to some cookies"
|
||||||
val message by collectLastValue(viewModel.message)
|
val message by collectLastValue(viewModel.message)
|
||||||
val messageVisible by collectLastValue(viewModel.isIndicatorMessageVisible)
|
val messageVisible by collectLastValue(viewModel.isIndicatorMessageVisible)
|
||||||
@@ -379,7 +417,7 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `shows help - after authenticated`() = runGenericTest {
|
fun shows_help_after_authenticated() = runGenericTest {
|
||||||
val expectConfirmation = testCase.expectConfirmation(atLeastOneFailure = false)
|
val expectConfirmation = testCase.expectConfirmation(atLeastOneFailure = false)
|
||||||
val helpMessage = "more cookies please"
|
val helpMessage = "more cookies please"
|
||||||
val authenticating by collectLastValue(viewModel.isAuthenticating)
|
val authenticating by collectLastValue(viewModel.isAuthenticating)
|
||||||
@@ -409,7 +447,7 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `retries after failure`() = runGenericTest {
|
fun retries_after_failure() = runGenericTest {
|
||||||
val errorMessage = "bad"
|
val errorMessage = "bad"
|
||||||
val helpMessage = "again?"
|
val helpMessage = "again?"
|
||||||
val expectTryAgainButton = testCase.isFaceOnly
|
val expectTryAgainButton = testCase.isFaceOnly
|
||||||
@@ -455,7 +493,7 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `switch to credential fallback`() = runGenericTest {
|
fun switch_to_credential_fallback() = runGenericTest {
|
||||||
val size by collectLastValue(viewModel.size)
|
val size by collectLastValue(viewModel.size)
|
||||||
|
|
||||||
// TODO(b/251476085): remove Spaghetti, migrate logic, and update this test
|
// TODO(b/251476085): remove Spaghetti, migrate logic, and update this test
|
||||||
|
|||||||
Reference in New Issue
Block a user