summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Arpan Kaphle <akaphle@google.com> 2024-04-02 22:15:13 +0000
committer Arpan Kaphle <akaphle@google.com> 2024-04-09 20:20:04 +0000
commite7f4a7ab18ee75dfc370335a357372ff5960b315 (patch)
tree3c6fded768f1437e6f93ed91e43dbc47b1d47f79
parent37ee1886cd3cad64b4fca9ec4045c84db024f78e (diff)
Fixing Rotation Bug for BiometricCredMan
This fixes the rotation bug for credential manager by creating a [BiometricFlowState] object. This object is used throughout the flow to ensure that on re-compose, the state does not violate any constraints. Namely, if a rotation or other similar configuration change happens that causes a recompose, the Ui has consistent state. Bug: 330396089 Test: Visual/Build Test updated to bug Change-Id: Ic8a1a09647999abc9bf935d143c7006e0f3860e7
-rw-r--r--packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/CredentialKtx.kt10
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt60
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt12
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/common/BiometricFlowType.kt (renamed from packages/CredentialManager/src/com/android/credentialmanager/common/FlowType.kt)2
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/common/BiometricHandler.kt164
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/common/BiometricPromptState.kt26
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt20
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt9
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt20
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt13
10 files changed, 250 insertions, 86 deletions
diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/CredentialKtx.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/CredentialKtx.kt
index b408c1553d94..9c8ec3b56813 100644
--- a/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/CredentialKtx.kt
+++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/CredentialKtx.kt
@@ -142,7 +142,7 @@ private fun getCredentialOptionInfoList(
isDefaultIconPreferredAsSingleProvider =
credentialEntry.isDefaultIconPreferredAsSingleProvider,
affiliatedDomain = credentialEntry.affiliatedDomain?.toString(),
- biometricRequest = predetermineAndValidateBiometricFlow(it,
+ biometricRequest = retrieveEntryBiometricRequest(it,
CREDENTIAL_ENTRY_PREFIX),
)
)
@@ -172,7 +172,7 @@ private fun getCredentialOptionInfoList(
isDefaultIconPreferredAsSingleProvider =
credentialEntry.isDefaultIconPreferredAsSingleProvider,
affiliatedDomain = credentialEntry.affiliatedDomain?.toString(),
- biometricRequest = predetermineAndValidateBiometricFlow(it,
+ biometricRequest = retrieveEntryBiometricRequest(it,
CREDENTIAL_ENTRY_PREFIX),
)
)
@@ -201,7 +201,7 @@ private fun getCredentialOptionInfoList(
isDefaultIconPreferredAsSingleProvider =
credentialEntry.isDefaultIconPreferredAsSingleProvider,
affiliatedDomain = credentialEntry.affiliatedDomain?.toString(),
- biometricRequest = predetermineAndValidateBiometricFlow(it,
+ biometricRequest = retrieveEntryBiometricRequest(it,
CREDENTIAL_ENTRY_PREFIX),
)
)
@@ -216,7 +216,7 @@ private fun getCredentialOptionInfoList(
}
/**
- * This validates if this is a biometric flow or not, and if it is, this returns the expected
+ * This validates if the entry calling this method contains biometric info, and if so, returns a
* [BiometricRequestInfo]. Namely, the biometric flow must have at least the
* ALLOWED_AUTHENTICATORS bit passed from Jetpack.
* Note that the required values, such as the provider info's icon or display name, or the entries
@@ -230,7 +230,7 @@ private fun getCredentialOptionInfoList(
* // TODO(b/326243754) : Presently, due to dependencies, the opId bit is parsed but is never
* // expected to be used. When it is added, it should be lightly validated.
*/
-fun predetermineAndValidateBiometricFlow(
+fun retrieveEntryBiometricRequest(
entry: Entry,
hintPrefix: String,
): BiometricRequestInfo? {
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
index a03975375e8a..888777e81d65 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
@@ -30,6 +30,8 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
+import com.android.credentialmanager.common.BiometricFlowType
+import com.android.credentialmanager.common.BiometricPromptState
import com.android.credentialmanager.common.BiometricResult
import com.android.credentialmanager.common.BiometricState
import com.android.credentialmanager.model.EntryInfo
@@ -40,7 +42,7 @@ import com.android.credentialmanager.common.ProviderActivityState
import com.android.credentialmanager.createflow.ActiveEntry
import com.android.credentialmanager.createflow.CreateCredentialUiState
import com.android.credentialmanager.createflow.CreateScreenState
-import com.android.credentialmanager.createflow.findBiometricFlowEntry
+import com.android.credentialmanager.createflow.isBiometricFlow
import com.android.credentialmanager.getflow.GetCredentialUiState
import com.android.credentialmanager.getflow.GetScreenState
import com.android.credentialmanager.logging.LifecycleEvent
@@ -303,13 +305,23 @@ class CredentialSelectorViewModel(
}
fun createFlowOnEntrySelectedFromMoreOptionScreen(activeEntry: ActiveEntry) {
+ val isBiometricFlow = isBiometricFlow(activeEntry = activeEntry, isAutoSelectFlow = false)
+ if (isBiometricFlow) {
+ // This atomically ensures that the only edge case that *restarts* the biometric flow
+ // doesn't risk a configuration change bug on the more options page during create.
+ // Namely, it's atomic in that it happens only on a tap, and it is not possible to
+ // reproduce a tap and a rotation at the same time. However, even if it were, it would
+ // just be an alternate way to jump back into the biometric selection flow after this
+ // reset, and thus, the state machine is maintained.
+ onBiometricPromptStateChange(BiometricPromptState.INACTIVE)
+ }
uiState = uiState.copy(
createCredentialUiState = uiState.createCredentialUiState?.copy(
currentScreenState =
// An autoselect flow never makes it to the more options screen
- if (findBiometricFlowEntry(activeEntry = activeEntry,
- isAutoSelectFlow = false) != null) CreateScreenState.BIOMETRIC_SELECTION
- else if (
+ if (isBiometricFlow) {
+ CreateScreenState.BIOMETRIC_SELECTION
+ } else if (
uiState.createCredentialUiState?.requestDisplayInfo?.userSetDefaultProviderIds
?.contains(activeEntry.activeProvider.id) ?: true ||
!(uiState.createCredentialUiState?.foundCandidateFromUserDefaultProvider
@@ -375,6 +387,46 @@ class CredentialSelectorViewModel(
}
}
+ /**************************************************************************/
+ /***** Biometric Flow Callbacks *****/
+ /**************************************************************************/
+
+ /**
+ * This allows falling back from the biometric prompt screen to the normal get flow by applying
+ * a reset to all necessary states involved in the fallback.
+ */
+ fun fallbackFromBiometricToNormalFlow(biometricFlowType: BiometricFlowType) {
+ onBiometricPromptStateChange(BiometricPromptState.INACTIVE)
+ when (biometricFlowType) {
+ BiometricFlowType.GET -> getFlowOnBackToPrimarySelectionScreen()
+ BiometricFlowType.CREATE -> createFlowOnUseOnceSelected()
+ }
+ }
+
+ /**
+ * This method can be used to change the [BiometricPromptState] according to the necessity.
+ * For example, if resetting, one might use [BiometricPromptState.INACTIVE], but if the flow
+ * has just launched, to avoid configuration errors, one can use
+ * [BiometricPromptState.PENDING].
+ */
+ fun onBiometricPromptStateChange(biometricPromptState: BiometricPromptState) {
+ uiState = uiState.copy(
+ biometricState = uiState.biometricState.copy(
+ biometricStatus = biometricPromptState
+ )
+ )
+ }
+
+ /**
+ * This returns the present biometric state.
+ */
+ fun getBiometricPromptState(): BiometricPromptState =
+ uiState.biometricState.biometricStatus
+
+ /**************************************************************************/
+ /***** Misc. Callbacks/Logs *****/
+ /**************************************************************************/
+
@Composable
fun logUiEvent(uiEventEnum: UiEventEnum) {
this.uiMetrics.log(uiEventEnum, credManRepo.requestInfo?.packageName)
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
index 358ebfa1ec90..c477f30a1d2f 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
@@ -53,8 +53,9 @@ import androidx.credentials.provider.RemoteEntry
import org.json.JSONObject
import android.credentials.flags.Flags
import com.android.credentialmanager.createflow.isBiometricFlow
+import com.android.credentialmanager.createflow.isFlowAutoSelectable
import com.android.credentialmanager.getflow.TopBrandingContent
-import com.android.credentialmanager.ktx.predetermineAndValidateBiometricFlow
+import com.android.credentialmanager.ktx.retrieveEntryBiometricRequest
import java.time.Instant
fun getAppLabel(
@@ -431,7 +432,12 @@ class CreateFlowUtils {
remoteEntryProvider = remoteEntryProvider,
)
val isBiometricFlow = if (activeEntry == null) false else isBiometricFlow(activeEntry,
- sortedCreateOptionsPairs, requestDisplayInfo)
+ isFlowAutoSelectable(
+ requestDisplayInfo = requestDisplayInfo,
+ activeEntry = activeEntry,
+ sortedCreateOptionsPairs = sortedCreateOptionsPairs
+ )
+ )
val initialScreenState = toCreateScreenState(
createOptionSize = createOptionsPairs.size,
remoteEntry = remoteEntry,
@@ -514,7 +520,7 @@ class CreateFlowUtils {
it.hasHint("androidx.credentials.provider.createEntry.SLICE_HINT_AUTO_" +
"SELECT_ALLOWED")
}?.text == "true",
- biometricRequest = predetermineAndValidateBiometricFlow(it,
+ biometricRequest = retrieveEntryBiometricRequest(it,
CREATE_ENTRY_PREFIX),
)
)
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/FlowType.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/BiometricFlowType.kt
index f6140f51b7b5..263cfd574d73 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/FlowType.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/BiometricFlowType.kt
@@ -16,7 +16,7 @@
package com.android.credentialmanager.common
-enum class FlowType {
+enum class BiometricFlowType {
GET,
CREATE
} \ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/BiometricHandler.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/BiometricHandler.kt
index fa177351be30..be3e0437a83e 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/BiometricHandler.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/BiometricHandler.kt
@@ -59,7 +59,7 @@ import java.lang.Exception
* ).
*
* The above are examples; the credential type can change depending on scenario.
- * // TODO(b/326243891) : Finalize once all the strings and create flow is iterated to completion
+ * // TODO(b/333445112) : Finalize once all the strings and create flow is iterated to completion
*/
data class BiometricDisplayInfo(
val providerIcon: Bitmap,
@@ -75,7 +75,8 @@ data class BiometricDisplayInfo(
* additional states that may improve the flow.
*/
data class BiometricState(
- val biometricResult: BiometricResult? = null
+ val biometricResult: BiometricResult? = null,
+ val biometricStatus: BiometricPromptState = BiometricPromptState.INACTIVE
)
/**
@@ -104,58 +105,115 @@ data class BiometricHelp(
)
/**
- * This will handle the logic for integrating credential manager with the biometric prompt for the
- * single account biometric experience. This simultaneously handles both the get and create flows,
- * by retrieving all the data from credential manager, and properly parsing that data into the
- * biometric prompt.
+ * This is the entry point to start the integrated biometric prompt for 'get' flows. It captures
+ * information specific to the get flow, along with required shared callbacks and more general
+ * info across both flows, such as the tapped [EntryInfo] or [sendDataToProvider].
*/
-fun runBiometricFlow(
+fun runBiometricFlowForGet(
biometricEntry: EntryInfo,
context: Context,
openMoreOptionsPage: () -> Unit,
sendDataToProvider: (EntryInfo, BiometricPrompt.AuthenticationResult) -> Unit,
onCancelFlowAndFinish: () -> Unit,
onIllegalStateAndFinish: (String) -> Unit,
+ getBiometricPromptState: () -> BiometricPromptState,
+ onBiometricPromptStateChange: (BiometricPromptState) -> Unit,
+ onBiometricFailureFallback: (BiometricFlowType) -> Unit,
getRequestDisplayInfo: RequestDisplayInfo? = null,
getProviderInfoList: List<ProviderInfo>? = null,
getProviderDisplayInfo: ProviderDisplayInfo? = null,
- onBiometricFailureFallback: () -> Unit,
+) {
+ if (getBiometricPromptState() != BiometricPromptState.INACTIVE) {
+ // Screen is already up, do not re-launch
+ return
+ }
+ onBiometricPromptStateChange(BiometricPromptState.PENDING)
+ val biometricDisplayInfo = validateAndRetrieveBiometricGetDisplayInfo(
+ getRequestDisplayInfo,
+ getProviderInfoList,
+ getProviderDisplayInfo,
+ context, biometricEntry
+ )
+
+ if (biometricDisplayInfo == null) {
+ onBiometricFailureFallback(BiometricFlowType.GET)
+ return
+ }
+
+ val callback: BiometricPrompt.AuthenticationCallback =
+ setupBiometricAuthenticationCallback(sendDataToProvider, biometricEntry,
+ onCancelFlowAndFinish, onIllegalStateAndFinish, onBiometricPromptStateChange)
+
+ Log.d(TAG, "The BiometricPrompt API call begins.")
+ runBiometricFlow(context, biometricDisplayInfo, callback, openMoreOptionsPage,
+ onBiometricFailureFallback, BiometricFlowType.GET)
+}
+
+/**
+ * This is the entry point to start the integrated biometric prompt for 'create' flows. It captures
+ * information specific to the create flow, along with required shared callbacks and more general
+ * info across both flows, such as the tapped [EntryInfo] or [sendDataToProvider].
+ */
+fun runBiometricFlowForCreate(
+ biometricEntry: EntryInfo,
+ context: Context,
+ openMoreOptionsPage: () -> Unit,
+ sendDataToProvider: (EntryInfo, BiometricPrompt.AuthenticationResult) -> Unit,
+ onCancelFlowAndFinish: () -> Unit,
+ onIllegalStateAndFinish: (String) -> Unit,
+ getBiometricPromptState: () -> BiometricPromptState,
+ onBiometricPromptStateChange: (BiometricPromptState) -> Unit,
+ onBiometricFailureFallback: (BiometricFlowType) -> Unit,
createRequestDisplayInfo: com.android.credentialmanager.createflow
.RequestDisplayInfo? = null,
createProviderInfo: EnabledProviderInfo? = null,
) {
- // TODO(b/330396089) : Add rotation configuration fix with state machine
- var biometricDisplayInfo: BiometricDisplayInfo? = null
- var flowType = FlowType.GET
- if (getRequestDisplayInfo != null) {
- biometricDisplayInfo = validateAndRetrieveBiometricGetDisplayInfo(getRequestDisplayInfo,
- getProviderInfoList,
- getProviderDisplayInfo,
- context, biometricEntry)
- } else if (createRequestDisplayInfo != null) {
- flowType = FlowType.CREATE
- biometricDisplayInfo = validateAndRetrieveBiometricCreateDisplayInfo(
- createRequestDisplayInfo,
- createProviderInfo,
- context, biometricEntry)
+ if (getBiometricPromptState() != BiometricPromptState.INACTIVE) {
+ // Screen is already up, do not re-launch
+ return
}
+ onBiometricPromptStateChange(BiometricPromptState.PENDING)
+ val biometricDisplayInfo = validateAndRetrieveBiometricCreateDisplayInfo(
+ createRequestDisplayInfo,
+ createProviderInfo,
+ context, biometricEntry
+ )
if (biometricDisplayInfo == null) {
- onBiometricFailureFallback()
+ onBiometricFailureFallback(BiometricFlowType.CREATE)
return
}
- val biometricPrompt = setupBiometricPrompt(context, biometricDisplayInfo, openMoreOptionsPage,
- biometricDisplayInfo.biometricRequestInfo.allowedAuthenticators, flowType)
-
val callback: BiometricPrompt.AuthenticationCallback =
setupBiometricAuthenticationCallback(sendDataToProvider, biometricEntry,
- onCancelFlowAndFinish, onIllegalStateAndFinish)
+ onCancelFlowAndFinish, onIllegalStateAndFinish, onBiometricPromptStateChange)
+
+ Log.d(TAG, "The BiometricPrompt API call begins.")
+ runBiometricFlow(context, biometricDisplayInfo, callback, openMoreOptionsPage,
+ onBiometricFailureFallback, BiometricFlowType.CREATE)
+}
+
+/**
+ * This will handle the logic for integrating credential manager with the biometric prompt for the
+ * single account biometric experience. This simultaneously handles both the get and create flows,
+ * by retrieving all the data from credential manager, and properly parsing that data into the
+ * biometric prompt.
+ */
+private fun runBiometricFlow(
+ context: Context,
+ biometricDisplayInfo: BiometricDisplayInfo,
+ callback: BiometricPrompt.AuthenticationCallback,
+ openMoreOptionsPage: () -> Unit,
+ onBiometricFailureFallback: (BiometricFlowType) -> Unit,
+ biometricFlowType: BiometricFlowType
+) {
+ val biometricPrompt = setupBiometricPrompt(context, biometricDisplayInfo, openMoreOptionsPage,
+ biometricDisplayInfo.biometricRequestInfo, biometricFlowType)
val cancellationSignal = CancellationSignal()
cancellationSignal.setOnCancelListener {
Log.d(TAG, "Your cancellation signal was called.")
- // TODO(b/326243754) : Migrate towards passing along the developer cancellation signal
+ // TODO(b/333445112) : Migrate towards passing along the developer cancellation signal
// or validate the necessity for this
}
@@ -165,27 +223,28 @@ fun runBiometricFlow(
biometricPrompt.authenticate(cancellationSignal, executor, callback)
} catch (e: IllegalArgumentException) {
Log.w(TAG, "Calling the biometric prompt API failed with: /n${e.localizedMessage}\n")
- onBiometricFailureFallback()
+ onBiometricFailureFallback(biometricFlowType)
}
}
/**
* Sets up the biometric prompt with the UI specific bits.
- * // TODO(b/326243754) : Pass in opId once dependency is confirmed via CryptoObject
+ * // TODO(b/333445112) : Pass in opId once dependency is confirmed via CryptoObject
*/
private fun setupBiometricPrompt(
context: Context,
biometricDisplayInfo: BiometricDisplayInfo,
openMoreOptionsPage: () -> Unit,
- requestAllowedAuthenticators: Int,
- flowType: FlowType,
+ biometricRequestInfo: BiometricRequestInfo,
+ biometricFlowType: BiometricFlowType,
): BiometricPrompt {
- val finalAuthenticators = removeDeviceCredential(requestAllowedAuthenticators)
+ val finalAuthenticators = removeDeviceCredential(biometricRequestInfo.allowedAuthenticators)
val biometricPrompt = BiometricPrompt.Builder(context)
.setTitle(biometricDisplayInfo.displayTitleText)
- // TODO(b/326243754) : Migrate to using new methods recently aligned upon
- .setNegativeButton(context.getString(if (flowType == FlowType.GET) R.string
+ // TODO(b/333445112) : Migrate to using new methods and strings recently aligned upon
+ .setNegativeButton(context.getString(if (biometricFlowType == BiometricFlowType.GET)
+ R.string
.dropdown_presentation_more_sign_in_options_text else R.string.string_more_options),
getMainExecutor(context)) { _, _ ->
openMoreOptionsPage()
@@ -200,7 +259,7 @@ private fun setupBiometricPrompt(
return biometricPrompt
}
-// TODO(b/326243754) : Remove after larger level alignments made on fallback negative button
+// TODO(b/333445112) : Remove after larger level alignments made on fallback negative button
// For the time being, we do not support the pin fallback until UX is decided.
private fun removeDeviceCredential(requestAllowedAuthenticators: Int): Int {
var finalAuthenticators = requestAllowedAuthenticators
@@ -230,16 +289,18 @@ private fun setupBiometricAuthenticationCallback(
selectedEntry: EntryInfo,
onCancelFlowAndFinish: () -> Unit,
onIllegalStateAndFinish: (String) -> Unit,
+ onBiometricPromptStateChange: (BiometricPromptState) -> Unit
): BiometricPrompt.AuthenticationCallback {
val callback: BiometricPrompt.AuthenticationCallback =
object : BiometricPrompt.AuthenticationCallback() {
- // TODO(b/326243754) : Validate remaining callbacks
+ // TODO(b/333445772) : Validate remaining callbacks
override fun onAuthenticationSucceeded(
authResult: BiometricPrompt.AuthenticationResult?
) {
super.onAuthenticationSucceeded(authResult)
try {
if (authResult != null) {
+ onBiometricPromptStateChange(BiometricPromptState.COMPLETE)
sendDataToProvider(selectedEntry, authResult)
} else {
onIllegalStateAndFinish("The biometric flow succeeded but unexpectedly " +
@@ -254,26 +315,24 @@ private fun setupBiometricAuthenticationCallback(
override fun onAuthenticationHelp(helpCode: Int, helpString: CharSequence?) {
super.onAuthenticationHelp(helpCode, helpString)
Log.d(TAG, "Authentication help discovered: $helpCode and $helpString")
- // TODO(b/326243754) : Decide on strategy with provider (a simple log probably
- // suffices here)
}
override fun onAuthenticationError(errorCode: Int, errString: CharSequence?) {
super.onAuthenticationError(errorCode, errString)
Log.d(TAG, "Authentication error-ed out: $errorCode and $errString")
+ onBiometricPromptStateChange(BiometricPromptState.COMPLETE)
if (errorCode == BiometricPrompt.BIOMETRIC_ERROR_USER_CANCELED) {
// Note that because the biometric prompt is imbued directly
// into the selector, parity applies to the selector's cancellation instead
// of the provider's biometric prompt cancellation.
onCancelFlowAndFinish()
}
- // TODO(b/326243754) : Propagate to provider
+ // TODO(b/333445772) : Propagate to provider
}
override fun onAuthenticationFailed() {
super.onAuthenticationFailed()
Log.d(TAG, "Authentication failed.")
- // TODO(b/326243754) : Propagate to provider
}
}
return callback
@@ -299,7 +358,7 @@ private fun validateAndRetrieveBiometricGetDisplayInfo(
if (getRequestDisplayInfo != null && getProviderInfoList != null &&
getProviderDisplayInfo != null) {
if (selectedEntry !is CredentialEntryInfo) { return null }
- return getBiometricDisplayValues(getProviderInfoList,
+ return retrieveBiometricGetDisplayValues(getProviderInfoList,
context, getRequestDisplayInfo, selectedEntry)
}
return null
@@ -308,7 +367,8 @@ private fun validateAndRetrieveBiometricGetDisplayInfo(
/**
* Creates the [BiometricDisplayInfo] for create flows, and early handles conditional
* checking between the two. The reason for this method matches the logic for the
- * [validateBiometricGetFlow] with the only difference being that this is for the create flow.
+ * [validateAndRetrieveBiometricGetDisplayInfo] with the only difference being that this is for
+ * the create flow.
*/
private fun validateAndRetrieveBiometricCreateDisplayInfo(
createRequestDisplayInfo: com.android.credentialmanager.createflow.RequestDisplayInfo?,
@@ -318,8 +378,8 @@ private fun validateAndRetrieveBiometricCreateDisplayInfo(
): BiometricDisplayInfo? {
if (createRequestDisplayInfo != null && createProviderInfo != null) {
if (selectedEntry !is CreateOptionInfo) { return null }
- return createBiometricDisplayValues(createRequestDisplayInfo, createProviderInfo, context,
- selectedEntry)
+ return retrieveBiometricCreateDisplayValues(createRequestDisplayInfo, createProviderInfo,
+ context, selectedEntry)
}
return null
}
@@ -330,16 +390,16 @@ private fun validateAndRetrieveBiometricCreateDisplayInfo(
* to the original selector. Note that these redundant checks are just failsafe; the original
* flow should never reach here with invalid params.
*/
-private fun getBiometricDisplayValues(
+private fun retrieveBiometricGetDisplayValues(
getProviderInfoList: List<ProviderInfo>,
context: Context,
getRequestDisplayInfo: RequestDisplayInfo,
selectedEntry: CredentialEntryInfo,
): BiometricDisplayInfo? {
- var icon: Bitmap? = null
- var providerName: String? = null
- var displayTitleText: String? = null
- var descriptionText: String? = null
+ val icon: Bitmap?
+ val providerName: String?
+ val displayTitleText: String?
+ val descriptionText: String?
val primaryAccountsProviderInfo = retrievePrimaryAccountProviderInfo(selectedEntry.providerId,
getProviderInfoList)
icon = primaryAccountsProviderInfo?.icon?.toBitmap()
@@ -373,7 +433,7 @@ private fun getBiometricDisplayValues(
* if this is called, a result is guaranteed. Specifically, this is guaranteed to return a non-null
* value unlike the get counterpart.
*/
-private fun createBiometricDisplayValues(
+private fun retrieveBiometricCreateDisplayValues(
createRequestDisplayInfo: com.android.credentialmanager.createflow.RequestDisplayInfo,
createProviderInfo: EnabledProviderInfo,
context: Context,
@@ -401,7 +461,7 @@ private fun createBiometricDisplayValues(
},
createRequestDisplayInfo.appName,
)
- // TODO(b/327620327) : Add a subtitle and any other recently aligned ideas
+ // TODO(b/333445112) : Add a subtitle and any other recently aligned ideas
return BiometricDisplayInfo(providerIcon = icon, providerName = providerName,
displayTitleText = displayTitleText, descriptionForCredential = descriptionText,
biometricRequestInfo = selectedEntry.biometricRequest as BiometricRequestInfo)
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/BiometricPromptState.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/BiometricPromptState.kt
new file mode 100644
index 000000000000..e1aa0418e7a0
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/BiometricPromptState.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.credentialmanager.common
+
+enum class BiometricPromptState {
+ /** The biometric prompt hasn't been activated. */
+ INACTIVE,
+ /** The biometric prompt is active but data hasn't been returned yet. */
+ PENDING,
+ /** The biometric prompt has closed and returned data we then send to the provider activity. */
+ COMPLETE
+} \ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
index 25fb477cbf38..122b8964dc96 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
@@ -46,11 +46,13 @@ import androidx.core.graphics.drawable.toBitmap
import com.android.compose.theme.LocalAndroidColorScheme
import com.android.credentialmanager.CredentialSelectorViewModel
import com.android.credentialmanager.R
+import com.android.credentialmanager.common.BiometricFlowType
+import com.android.credentialmanager.common.BiometricPromptState
import com.android.credentialmanager.model.EntryInfo
import com.android.credentialmanager.model.CredentialType
import com.android.credentialmanager.common.ProviderActivityState
import com.android.credentialmanager.common.material.ModalBottomSheetDefaults
-import com.android.credentialmanager.common.runBiometricFlow
+import com.android.credentialmanager.common.runBiometricFlowForCreate
import com.android.credentialmanager.common.ui.ActionButton
import com.android.credentialmanager.common.ui.BodyMediumText
import com.android.credentialmanager.common.ui.BodySmallText
@@ -111,7 +113,11 @@ fun CreateCredentialScreen(
onBiometricEntrySelected =
viewModel::createFlowOnEntrySelected,
fallbackToOriginalFlow =
- viewModel::getFlowOnBackToPrimarySelectionScreen,
+ viewModel::fallbackFromBiometricToNormalFlow,
+ getBiometricPromptState =
+ viewModel::getBiometricPromptState,
+ onBiometricPromptStateChange =
+ viewModel::onBiometricPromptStateChange
)
CreateScreenState.MORE_OPTIONS_SELECTION -> MoreOptionsSelectionCard(
requestDisplayInfo = createCredentialUiState.requestDisplayInfo,
@@ -578,18 +584,22 @@ internal fun BiometricSelectionPage(
onBiometricEntrySelected: (EntryInfo, BiometricPrompt.AuthenticationResult) -> Unit,
onCancelFlowAndFinish: () -> Unit,
onIllegalScreenStateAndFinish: (String) -> Unit,
- fallbackToOriginalFlow: () -> Unit,
+ fallbackToOriginalFlow: (BiometricFlowType) -> Unit,
+ getBiometricPromptState: () -> BiometricPromptState,
+ onBiometricPromptStateChange: (BiometricPromptState) -> Unit,
) {
if (biometricEntry == null) {
- fallbackToOriginalFlow()
+ fallbackToOriginalFlow(BiometricFlowType.CREATE)
return
}
- runBiometricFlow(
+ runBiometricFlowForCreate(
biometricEntry = biometricEntry,
context = LocalContext.current,
openMoreOptionsPage = onMoreOptionSelected,
sendDataToProvider = onBiometricEntrySelected,
onCancelFlowAndFinish = onCancelFlowAndFinish,
+ getBiometricPromptState = getBiometricPromptState,
+ onBiometricPromptStateChange = onBiometricPromptStateChange,
createRequestDisplayInfo = requestDisplayInfo,
createProviderInfo = enabledProviderInfo,
onBiometricFailureFallback = fallbackToOriginalFlow,
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
index 1d262ba5261a..ddd4139b65b6 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
@@ -78,13 +78,8 @@ internal fun getCreateEntry(
*/
internal fun isBiometricFlow(
activeEntry: ActiveEntry,
- sortedCreateOptionsPairs: List<Pair<CreateOptionInfo, EnabledProviderInfo>>,
- requestDisplayInfo: RequestDisplayInfo,
-) = findBiometricFlowEntry(activeEntry, isFlowAutoSelectable(
- requestDisplayInfo = requestDisplayInfo,
- activeEntry = activeEntry,
- sortedCreateOptionsPairs = sortedCreateOptionsPairs
-)) != null
+ isAutoSelectFlow: Boolean,
+) = findBiometricFlowEntry(activeEntry, isAutoSelectFlow) != null
/**
* This utility presents the correct resource string for the create flows title conditionally.
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
index 6d1a3dd98210..72b7814a791a 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
@@ -52,9 +52,11 @@ import androidx.compose.ui.unit.dp
import androidx.core.graphics.drawable.toBitmap
import com.android.credentialmanager.CredentialSelectorViewModel
import com.android.credentialmanager.R
+import com.android.credentialmanager.common.BiometricFlowType
+import com.android.credentialmanager.common.BiometricPromptState
import com.android.credentialmanager.common.ProviderActivityState
import com.android.credentialmanager.common.material.ModalBottomSheetDefaults
-import com.android.credentialmanager.common.runBiometricFlow
+import com.android.credentialmanager.common.runBiometricFlowForGet
import com.android.credentialmanager.common.ui.ActionButton
import com.android.credentialmanager.common.ui.ActionEntry
import com.android.credentialmanager.common.ui.ConfirmButton
@@ -154,7 +156,11 @@ fun GetCredentialScreen(
onBiometricEntrySelected =
viewModel::getFlowOnEntrySelected,
fallbackToOriginalFlow =
- viewModel::getFlowOnBackToPrimarySelectionScreen,
+ viewModel::fallbackFromBiometricToNormalFlow,
+ getBiometricPromptState =
+ viewModel::getBiometricPromptState,
+ onBiometricPromptStateChange =
+ viewModel::onBiometricPromptStateChange
)
} else {
AllSignInOptionCard(
@@ -218,19 +224,23 @@ internal fun BiometricSelectionPage(
providerInfoList: List<ProviderInfo>,
providerDisplayInfo: ProviderDisplayInfo,
onBiometricEntrySelected: (EntryInfo, BiometricPrompt.AuthenticationResult?) -> Unit,
- fallbackToOriginalFlow: () -> Unit,
+ fallbackToOriginalFlow: (BiometricFlowType) -> Unit,
+ getBiometricPromptState: () -> BiometricPromptState,
+ onBiometricPromptStateChange: (BiometricPromptState) -> Unit,
) {
if (biometricEntry == null) {
- fallbackToOriginalFlow()
+ fallbackToOriginalFlow(BiometricFlowType.GET)
return
}
- runBiometricFlow(
+ runBiometricFlowForGet(
biometricEntry = biometricEntry,
context = LocalContext.current,
openMoreOptionsPage = onMoreOptionSelected,
sendDataToProvider = onBiometricEntrySelected,
onCancelFlowAndFinish = onCancelFlowAndFinish,
onIllegalStateAndFinish = onIllegalStateAndFinish,
+ getBiometricPromptState = getBiometricPromptState,
+ onBiometricPromptStateChange = onBiometricPromptStateChange,
getRequestDisplayInfo = requestDisplayInfo,
getProviderInfoList = providerInfoList,
getProviderDisplayInfo = providerDisplayInfo,
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
index ac776af4f627..b03407b9ebea 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
@@ -286,7 +286,7 @@ private fun toGetScreenState(
GetScreenState.REMOTE_ONLY
else if (isRequestForAllOptions)
GetScreenState.ALL_SIGN_IN_OPTIONS
- else if (isBiometricFlow(providerDisplayInfo))
+ else if (isBiometricFlow(providerDisplayInfo, isFlowAutoSelectable(providerDisplayInfo)))
GetScreenState.BIOMETRIC_SELECTION
else GetScreenState.PRIMARY_SELECTION
}
@@ -294,9 +294,14 @@ private fun toGetScreenState(
/**
* Determines if the flow is a biometric flow by taking into account autoselect criteria.
*/
-internal fun isBiometricFlow(providerDisplayInfo: ProviderDisplayInfo) =
- findBiometricFlowEntry(providerDisplayInfo,
- findAutoSelectEntry(providerDisplayInfo) != null) != null
+internal fun isBiometricFlow(providerDisplayInfo: ProviderDisplayInfo, isAutoSelectFlow: Boolean) =
+ findBiometricFlowEntry(providerDisplayInfo, isAutoSelectFlow) != null
+
+/**
+ * Determines if the flow is an autoselect flow.
+ */
+internal fun isFlowAutoSelectable(providerDisplayInfo: ProviderDisplayInfo) =
+ findAutoSelectEntry(providerDisplayInfo) != null
internal class CredentialEntryInfoComparatorByTypeThenTimestamp(
val typePriorityMap: Map<String, Int>,