Fix some bugs for the create flow

1.When create for the first time and credential type is not passkey(before even when the createOption size is only 1, we still wrongly ask users to select a createOption)
2.When createOption is only one, the activeProvider should not just be the first one but should be the one has this only createOption
3.Only when requestInfo is firstUse and type is passkey, we show passkey intro page

Test: deployed locally

Bug: 253157231
Change-Id: Ie6ff0582bdf9224a9f714602a356040e16342ee0
This commit is contained in:
Qinmei Du
2022-12-02 01:22:41 +00:00
parent 2e7fcce16e
commit a8fd2db3b5
4 changed files with 89 additions and 58 deletions

View File

@@ -40,12 +40,9 @@ import android.os.Binder
import android.os.Bundle
import android.os.ResultReceiver
import android.service.credentials.CredentialProviderService
import com.android.credentialmanager.createflow.ActiveEntry
import com.android.credentialmanager.createflow.CreateCredentialUiState
import com.android.credentialmanager.createflow.CreateScreenState
import com.android.credentialmanager.createflow.EnabledProviderInfo
import com.android.credentialmanager.createflow.RemoteInfo
import com.android.credentialmanager.createflow.RequestDisplayInfo
import com.android.credentialmanager.getflow.GetCredentialUiState
import com.android.credentialmanager.getflow.GetScreenState
import com.android.credentialmanager.jetpack.developer.CreatePasswordRequest.Companion.toBundle
@@ -146,20 +143,30 @@ class CredentialManagerRepo(
providerDisabledList, context)
var defaultProvider: EnabledProviderInfo? = null
var remoteEntry: RemoteInfo? = null
var createOptionSize = 0
var lastSeenProviderWithNonEmptyCreateOptions: EnabledProviderInfo? = null
providerEnabledList.forEach{providerInfo -> providerInfo.createOptions =
providerInfo.createOptions.sortedWith(compareBy { it.lastUsedTimeMillis }).reversed()
if (providerInfo.isDefault) {defaultProvider = providerInfo}
if (providerInfo.remoteEntry != null) {
remoteEntry = providerInfo.remoteEntry!!
}
if (providerInfo.createOptions.isNotEmpty()) {
createOptionSize += providerInfo.createOptions.size
lastSeenProviderWithNonEmptyCreateOptions = providerInfo
}
}
return CreateCredentialUiState(
enabledProviders = providerEnabledList,
disabledProviders = providerDisabledList,
toCreateScreenState(requestDisplayInfo, defaultProvider, remoteEntry),
CreateFlowUtils.toCreateScreenState(
createOptionSize, false,
requestDisplayInfo, defaultProvider, remoteEntry),
requestDisplayInfo,
false,
toActiveEntry(defaultProvider, remoteEntry),
CreateFlowUtils.toActiveEntry(
/*defaultProvider=*/defaultProvider, createOptionSize,
lastSeenProviderWithNonEmptyCreateOptions, remoteEntry),
)
}
@@ -194,6 +201,7 @@ class CredentialManagerRepo(
.setRemoteEntry(
newRemoteEntry("key2", "subkey-1")
)
.setIsDefaultProvider(true)
.build(),
CreateCredentialProviderData
.Builder("com.dashlane")
@@ -517,38 +525,4 @@ class CredentialManagerRepo(
"tribank.us"
)
}
private fun toCreateScreenState(
requestDisplayInfo: RequestDisplayInfo,
defaultProvider: EnabledProviderInfo?,
remoteEntry: RemoteInfo?,
): CreateScreenState {
return if (
defaultProvider != null && defaultProvider.createOptions.isEmpty() && remoteEntry != null
){
CreateScreenState.EXTERNAL_ONLY_SELECTION
} else if (defaultProvider == null || defaultProvider.createOptions.isEmpty()) {
if (requestDisplayInfo.type == TYPE_PUBLIC_KEY_CREDENTIAL) {
CreateScreenState.PASSKEY_INTRO
} else {
CreateScreenState.PROVIDER_SELECTION
}
} else {
CreateScreenState.CREATION_OPTION_SELECTION
}
}
private fun toActiveEntry(
defaultProvider: EnabledProviderInfo?,
remoteEntry: RemoteInfo?,
): ActiveEntry? {
return if (
defaultProvider != null && defaultProvider.createOptions.isNotEmpty()
) {
ActiveEntry(defaultProvider, defaultProvider.createOptions.first())
} else if (
defaultProvider != null && defaultProvider.createOptions.isEmpty() && remoteEntry != null) {
ActiveEntry(defaultProvider, remoteEntry)
} else null
}
}

View File

@@ -28,6 +28,9 @@ import android.graphics.drawable.Drawable
import com.android.credentialmanager.createflow.CreateOptionInfo
import com.android.credentialmanager.createflow.RemoteInfo
import com.android.credentialmanager.createflow.RequestDisplayInfo
import com.android.credentialmanager.createflow.EnabledProviderInfo
import com.android.credentialmanager.createflow.CreateScreenState
import com.android.credentialmanager.createflow.ActiveEntry
import com.android.credentialmanager.getflow.ActionEntryInfo
import com.android.credentialmanager.getflow.AuthenticationEntryInfo
import com.android.credentialmanager.getflow.CredentialEntryInfo
@@ -36,6 +39,7 @@ import com.android.credentialmanager.getflow.RemoteEntryInfo
import com.android.credentialmanager.jetpack.developer.CreateCredentialRequest
import com.android.credentialmanager.jetpack.developer.CreatePasswordRequest
import com.android.credentialmanager.jetpack.developer.CreatePublicKeyCredentialRequest
import com.android.credentialmanager.jetpack.developer.PublicKeyCredential.Companion.TYPE_PUBLIC_KEY_CREDENTIAL
import com.android.credentialmanager.jetpack.provider.ActionUi
import com.android.credentialmanager.jetpack.provider.CredentialEntryUi
import com.android.credentialmanager.jetpack.provider.SaveEntryUi
@@ -243,7 +247,8 @@ class CreateFlowUtils {
createCredentialRequestJetpack.password,
createCredentialRequestJetpack.type,
requestInfo.appPackageName,
context.getDrawable(R.drawable.ic_password)!!
context.getDrawable(R.drawable.ic_password)!!,
requestInfo.isFirstUsage
)
}
is CreatePublicKeyCredentialRequest -> {
@@ -261,7 +266,8 @@ class CreateFlowUtils {
displayName,
createCredentialRequestJetpack.type,
requestInfo.appPackageName,
context.getDrawable(R.drawable.ic_passkey)!!)
context.getDrawable(R.drawable.ic_passkey)!!,
requestInfo.isFirstUsage)
}
// TODO: correctly parsing for other sign-ins
else -> {
@@ -270,11 +276,58 @@ class CreateFlowUtils {
"Elisa Beckett",
"other-sign-ins",
requestInfo.appPackageName,
context.getDrawable(R.drawable.ic_other_sign_in)!!)
context.getDrawable(R.drawable.ic_other_sign_in)!!,
requestInfo.isFirstUsage)
}
}
}
fun toCreateScreenState(
createOptionSize: Int,
isOnPasskeyIntroStateAlready: Boolean,
requestDisplayInfo: RequestDisplayInfo,
defaultProvider: EnabledProviderInfo?,
remoteEntry: RemoteInfo?,
): CreateScreenState {
return if (requestDisplayInfo.isFirstUsage && requestDisplayInfo
.type == TYPE_PUBLIC_KEY_CREDENTIAL && !isOnPasskeyIntroStateAlready) {
CreateScreenState.PASSKEY_INTRO
} else if (
(defaultProvider == null || defaultProvider.createOptions.isEmpty()
) && createOptionSize > 1) {
CreateScreenState.PROVIDER_SELECTION
} else if (
((defaultProvider == null || defaultProvider.createOptions.isEmpty()
) && createOptionSize == 1) || (
defaultProvider != null && defaultProvider.createOptions.isNotEmpty())) {
CreateScreenState.CREATION_OPTION_SELECTION
} else if (createOptionSize == 0 && remoteEntry != null) {
CreateScreenState.EXTERNAL_ONLY_SELECTION
} else {
// TODO: properly handle error and gracefully finish itself
throw java.lang.IllegalStateException("Empty provider list.")
}
}
fun toActiveEntry(
defaultProvider: EnabledProviderInfo?,
createOptionSize: Int,
lastSeenProviderWithNonEmptyCreateOptions: EnabledProviderInfo?,
remoteEntry: RemoteInfo?,
): ActiveEntry? {
return if (
defaultProvider != null && defaultProvider.createOptions.isEmpty() && remoteEntry != null) {
ActiveEntry(defaultProvider, remoteEntry)
} else if (
defaultProvider != null && defaultProvider.createOptions.isNotEmpty()
) {
ActiveEntry(defaultProvider, defaultProvider.createOptions.first())
} else if (createOptionSize == 1) {
ActiveEntry(lastSeenProviderWithNonEmptyCreateOptions!!,
lastSeenProviderWithNonEmptyCreateOptions.createOptions.first())
} else null
}
private fun toCreationOptionInfoList(
providerId: String,
creationEntries: List<Entry>,

View File

@@ -27,6 +27,7 @@ import androidx.compose.runtime.setValue
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.android.credentialmanager.CreateFlowUtils
import com.android.credentialmanager.CredentialManagerRepo
import com.android.credentialmanager.common.DialogResult
import com.android.credentialmanager.common.ProviderActivityResult
@@ -60,24 +61,26 @@ class CreateCredentialViewModel(
fun onConfirmIntro() {
var createOptionSize = 0
var lastSeenProviderWithNonEmptyCreateOptions: EnabledProviderInfo? = null
var remoteEntry: RemoteInfo? = null
uiState.enabledProviders.forEach {
enabledProvider -> createOptionSize += enabledProvider.createOptions.size}
uiState = if (createOptionSize > 1) {
uiState.copy(
currentScreenState = CreateScreenState.PROVIDER_SELECTION,
showActiveEntryOnly = true
)
} else if (createOptionSize == 1){
uiState.copy(
currentScreenState = CreateScreenState.CREATION_OPTION_SELECTION,
showActiveEntryOnly = false,
activeEntry = ActiveEntry(uiState.enabledProviders.first(),
uiState.enabledProviders.first().createOptions.first()
)
)
} else {
throw java.lang.IllegalStateException("Empty provider list.")
enabledProvider ->
if (enabledProvider.createOptions.isNotEmpty()) {
createOptionSize += enabledProvider.createOptions.size
lastSeenProviderWithNonEmptyCreateOptions = enabledProvider
}
if (enabledProvider.remoteEntry != null) {
remoteEntry = enabledProvider.remoteEntry!!
}
}
uiState = uiState.copy(
currentScreenState = CreateFlowUtils.toCreateScreenState(
createOptionSize, true,
uiState.requestDisplayInfo, null, remoteEntry),
showActiveEntryOnly = createOptionSize > 1,
activeEntry = CreateFlowUtils.toActiveEntry(
null, createOptionSize, lastSeenProviderWithNonEmptyCreateOptions, remoteEntry),
)
}
fun getProviderInfoByName(providerName: String): EnabledProviderInfo {

View File

@@ -77,6 +77,7 @@ data class RequestDisplayInfo(
val type: String,
val appDomainName: String,
val typeIcon: Drawable,
val isFirstUsage: Boolean,
)
/**