Revise the save-flow
Now the user can set the default provider from the settings. It is no longer a property internal to the UI module. Also, the provider centric screen is removed to honor the default provider. Bug: 279857017 Test: manual (see bug for recordings) Change-Id: I9c4931fa345ee72b947536c9343b42d55aea5153
This commit is contained in:
@@ -42,15 +42,6 @@
|
||||
android:excludeFromRecents="true"
|
||||
android:theme="@style/Theme.CredentialSelector">
|
||||
</activity>
|
||||
|
||||
<receiver
|
||||
android:name=".CredentialProviderReceiver"
|
||||
android:exported="true"
|
||||
android:permission="android.permission.LAUNCH_CREDENTIAL_SELECTOR">
|
||||
<intent-filter>
|
||||
<action android:name="android.credentials.ui.action.CREDMAN_ENABLED_PROVIDERS_UPDATED"/>
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
|
||||
@@ -107,7 +107,6 @@ class CredentialManagerRepo(
|
||||
|
||||
initialUiState = when (requestInfo?.type) {
|
||||
RequestInfo.TYPE_CREATE -> {
|
||||
val defaultProviderIdSetByUser = userConfigRepo.getDefaultProviderId()
|
||||
val isPasskeyFirstUse = userConfigRepo.getIsPasskeyFirstUse()
|
||||
val providerEnableListUiState = getCreateProviderEnableListInitialUiState()
|
||||
val providerDisableListUiState = getCreateProviderDisableListInitialUiState()
|
||||
@@ -119,7 +118,8 @@ class CredentialManagerRepo(
|
||||
disabledProviders = providerDisableListUiState,
|
||||
defaultProviderIdPreferredByApp =
|
||||
requestDisplayInfoUiState.appPreferredDefaultProviderId,
|
||||
defaultProviderIdSetByUser = defaultProviderIdSetByUser,
|
||||
defaultProviderIdsSetByUser =
|
||||
requestDisplayInfoUiState.userSetDefaultProviderIds,
|
||||
requestDisplayInfo = requestDisplayInfoUiState,
|
||||
isOnPasskeyIntroStateAlready = false,
|
||||
isPasskeyFirstUse = isPasskeyFirstUse,
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.util.Log
|
||||
import com.android.credentialmanager.common.Constants
|
||||
|
||||
|
||||
class CredentialProviderReceiver : BroadcastReceiver() {
|
||||
|
||||
override fun onReceive(context: Context?, intent: Intent?) {
|
||||
Log.d(Constants.LOG_TAG, "Received intent in CredentialProviderReceiver")
|
||||
|
||||
val sharedPreferences = context?.getSharedPreferences(context?.packageName,
|
||||
Context.MODE_PRIVATE)
|
||||
sharedPreferences?.edit()?.remove(UserConfigRepo.DEFAULT_PROVIDER)?.commit()
|
||||
}
|
||||
}
|
||||
@@ -255,7 +255,8 @@ class CredentialSelectorViewModel(
|
||||
disabledProviders = prevUiState.disabledProviders,
|
||||
defaultProviderIdPreferredByApp =
|
||||
prevUiState.requestDisplayInfo.appPreferredDefaultProviderId,
|
||||
defaultProviderIdSetByUser = userConfigRepo.getDefaultProviderId(),
|
||||
defaultProviderIdsSetByUser =
|
||||
prevUiState.requestDisplayInfo.userSetDefaultProviderIds,
|
||||
requestDisplayInfo = prevUiState.requestDisplayInfo,
|
||||
isOnPasskeyIntroStateAlready = true,
|
||||
isPasskeyFirstUse = userConfigRepo.getIsPasskeyFirstUse()
|
||||
@@ -269,28 +270,10 @@ class CredentialSelectorViewModel(
|
||||
userConfigRepo.setIsPasskeyFirstUse(false)
|
||||
}
|
||||
|
||||
fun createFlowOnMoreOptionsSelectedOnProviderSelection() {
|
||||
uiState = uiState.copy(
|
||||
createCredentialUiState = uiState.createCredentialUiState?.copy(
|
||||
currentScreenState = CreateScreenState.MORE_OPTIONS_SELECTION,
|
||||
isFromProviderSelection = true
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
fun createFlowOnMoreOptionsSelectedOnCreationSelection() {
|
||||
uiState = uiState.copy(
|
||||
createCredentialUiState = uiState.createCredentialUiState?.copy(
|
||||
currentScreenState = CreateScreenState.MORE_OPTIONS_SELECTION,
|
||||
isFromProviderSelection = false
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
fun createFlowOnBackProviderSelectionButtonSelected() {
|
||||
uiState = uiState.copy(
|
||||
createCredentialUiState = uiState.createCredentialUiState?.copy(
|
||||
currentScreenState = CreateScreenState.PROVIDER_SELECTION,
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -315,7 +298,10 @@ class CredentialSelectorViewModel(
|
||||
uiState = uiState.copy(
|
||||
createCredentialUiState = uiState.createCredentialUiState?.copy(
|
||||
currentScreenState =
|
||||
if (activeEntry.activeProvider.id == userConfigRepo.getDefaultProviderId() ||
|
||||
if (uiState.createCredentialUiState?.requestDisplayInfo?.userSetDefaultProviderIds
|
||||
?.contains(activeEntry.activeProvider.id) ?: true ||
|
||||
!(uiState.createCredentialUiState?.foundCandidateFromUserDefaultProvider
|
||||
?: false) ||
|
||||
!TextUtils.isEmpty(uiState.createCredentialUiState?.requestDisplayInfo
|
||||
?.appPreferredDefaultProviderId))
|
||||
CreateScreenState.CREATION_OPTION_SELECTION
|
||||
@@ -325,18 +311,7 @@ class CredentialSelectorViewModel(
|
||||
)
|
||||
}
|
||||
|
||||
fun createFlowOnEntrySelectedFromFirstUseScreen(activeEntry: ActiveEntry) {
|
||||
val providerId = activeEntry.activeProvider.id
|
||||
createFlowOnDefaultChanged(providerId)
|
||||
uiState = uiState.copy(
|
||||
createCredentialUiState = uiState.createCredentialUiState?.copy(
|
||||
currentScreenState = CreateScreenState.CREATION_OPTION_SELECTION,
|
||||
activeEntry = activeEntry
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
fun createFlowOnDisabledProvidersSelected() {
|
||||
fun createFlowOnLaunchSettings() {
|
||||
credManRepo.onSettingLaunchCancel()
|
||||
uiState = uiState.copy(dialogState = DialogState.CANCELED_FOR_SETTINGS)
|
||||
}
|
||||
@@ -349,16 +324,6 @@ class CredentialSelectorViewModel(
|
||||
)
|
||||
}
|
||||
|
||||
fun createFlowOnChangeDefaultSelected() {
|
||||
uiState = uiState.copy(
|
||||
createCredentialUiState = uiState.createCredentialUiState?.copy(
|
||||
currentScreenState = CreateScreenState.CREATION_OPTION_SELECTION,
|
||||
)
|
||||
)
|
||||
val providerId = uiState.createCredentialUiState?.activeEntry?.activeProvider?.id
|
||||
createFlowOnDefaultChanged(providerId)
|
||||
}
|
||||
|
||||
fun createFlowOnUseOnceSelected() {
|
||||
uiState = uiState.copy(
|
||||
createCredentialUiState = uiState.createCredentialUiState?.copy(
|
||||
@@ -367,17 +332,6 @@ class CredentialSelectorViewModel(
|
||||
)
|
||||
}
|
||||
|
||||
fun createFlowOnDefaultChanged(providerId: String?) {
|
||||
if (providerId != null) {
|
||||
Log.d(
|
||||
Constants.LOG_TAG, "Default provider changed to: " +
|
||||
" {provider=$providerId")
|
||||
userConfigRepo.setDefaultProvider(providerId)
|
||||
} else {
|
||||
Log.w(Constants.LOG_TAG, "Null provider is being changed")
|
||||
}
|
||||
}
|
||||
|
||||
fun createFlowOnEntrySelected(selectedEntry: BaseEntry) {
|
||||
val providerId = selectedEntry.providerId
|
||||
val entryKey = selectedEntry.entryKey
|
||||
|
||||
@@ -61,6 +61,7 @@ import androidx.credentials.provider.PasswordCredentialEntry
|
||||
import androidx.credentials.provider.PublicKeyCredentialEntry
|
||||
import androidx.credentials.provider.RemoteEntry
|
||||
import org.json.JSONObject
|
||||
import java.time.Instant
|
||||
|
||||
// TODO: remove all !! checks
|
||||
fun getAppLabel(
|
||||
@@ -420,7 +421,7 @@ class CreateFlowUtils {
|
||||
id = it.providerFlattenedComponentName,
|
||||
displayName = providerLabel,
|
||||
icon = providerIcon,
|
||||
createOptions = toCreationOptionInfoList(
|
||||
sortedCreateOptions = toSortedCreationOptionInfoList(
|
||||
it.providerFlattenedComponentName, it.saveEntries, context
|
||||
),
|
||||
remoteEntry = toRemoteInfo(it.providerFlattenedComponentName, it.remoteEntry),
|
||||
@@ -483,6 +484,7 @@ class CreateFlowUtils {
|
||||
context.getDrawable(R.drawable.ic_password_24) ?: return null,
|
||||
preferImmediatelyAvailableCredentials = false,
|
||||
appPreferredDefaultProviderId = appPreferredDefaultProviderId,
|
||||
userSetDefaultProviderIds = requestInfo.defaultProviderIds.toSet(),
|
||||
)
|
||||
is CreatePublicKeyCredentialRequest -> {
|
||||
newRequestDisplayInfoFromPasskeyJson(
|
||||
@@ -492,6 +494,7 @@ class CreateFlowUtils {
|
||||
preferImmediatelyAvailableCredentials =
|
||||
createCredentialRequestJetpack.preferImmediatelyAvailableCredentials,
|
||||
appPreferredDefaultProviderId = appPreferredDefaultProviderId,
|
||||
userSetDefaultProviderIds = requestInfo.defaultProviderIds.toSet(),
|
||||
)
|
||||
}
|
||||
is CreateCustomCredentialRequest -> {
|
||||
@@ -508,6 +511,7 @@ class CreateFlowUtils {
|
||||
?: context.getDrawable(R.drawable.ic_other_sign_in_24) ?: return null,
|
||||
preferImmediatelyAvailableCredentials = false,
|
||||
appPreferredDefaultProviderId = appPreferredDefaultProviderId,
|
||||
userSetDefaultProviderIds = requestInfo.defaultProviderIds.toSet(),
|
||||
)
|
||||
}
|
||||
else -> null
|
||||
@@ -518,13 +522,13 @@ class CreateFlowUtils {
|
||||
enabledProviders: List<EnabledProviderInfo>,
|
||||
disabledProviders: List<DisabledProviderInfo>?,
|
||||
defaultProviderIdPreferredByApp: String?,
|
||||
defaultProviderIdSetByUser: String?,
|
||||
defaultProviderIdsSetByUser: Set<String>,
|
||||
requestDisplayInfo: RequestDisplayInfo,
|
||||
isOnPasskeyIntroStateAlready: Boolean,
|
||||
isPasskeyFirstUse: Boolean,
|
||||
): CreateCredentialUiState? {
|
||||
var lastSeenProviderWithNonEmptyCreateOptions: EnabledProviderInfo? = null
|
||||
var remoteEntry: RemoteInfo? = null
|
||||
var remoteEntryProvider: EnabledProviderInfo? = null
|
||||
var defaultProviderPreferredByApp: EnabledProviderInfo? = null
|
||||
var defaultProviderSetByUser: EnabledProviderInfo? = null
|
||||
var createOptionsPairs:
|
||||
@@ -535,14 +539,24 @@ class CreateFlowUtils {
|
||||
defaultProviderPreferredByApp = enabledProvider
|
||||
}
|
||||
}
|
||||
if (defaultProviderIdSetByUser != null) {
|
||||
if (enabledProvider.id == defaultProviderIdSetByUser) {
|
||||
if (enabledProvider.sortedCreateOptions.isNotEmpty() &&
|
||||
defaultProviderIdsSetByUser.contains(enabledProvider.id)) {
|
||||
if (defaultProviderSetByUser == null) {
|
||||
defaultProviderSetByUser = enabledProvider
|
||||
} else {
|
||||
val newLastUsedTime = enabledProvider.sortedCreateOptions.firstOrNull()
|
||||
?.lastUsedTime
|
||||
val curLastUsedTime = defaultProviderSetByUser?.sortedCreateOptions
|
||||
?.firstOrNull()?.lastUsedTime ?: Instant.MIN
|
||||
if (newLastUsedTime != null) {
|
||||
if (curLastUsedTime == null || newLastUsedTime > curLastUsedTime) {
|
||||
defaultProviderSetByUser = enabledProvider
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (enabledProvider.createOptions.isNotEmpty()) {
|
||||
lastSeenProviderWithNonEmptyCreateOptions = enabledProvider
|
||||
enabledProvider.createOptions.forEach {
|
||||
if (enabledProvider.sortedCreateOptions.isNotEmpty()) {
|
||||
enabledProvider.sortedCreateOptions.forEach {
|
||||
createOptionsPairs.add(Pair(it, enabledProvider))
|
||||
}
|
||||
}
|
||||
@@ -554,6 +568,7 @@ class CreateFlowUtils {
|
||||
return null
|
||||
}
|
||||
remoteEntry = currRemoteEntry
|
||||
remoteEntryProvider = enabledProvider
|
||||
}
|
||||
}
|
||||
val defaultProvider = defaultProviderPreferredByApp ?: defaultProviderSetByUser
|
||||
@@ -561,27 +576,26 @@ class CreateFlowUtils {
|
||||
createOptionSize = createOptionsPairs.size,
|
||||
isOnPasskeyIntroStateAlready = isOnPasskeyIntroStateAlready,
|
||||
requestDisplayInfo = requestDisplayInfo,
|
||||
defaultProvider = defaultProvider,
|
||||
remoteEntry = remoteEntry,
|
||||
isPasskeyFirstUse = isPasskeyFirstUse
|
||||
) ?: return null
|
||||
val sortedCreateOptionsPairs = createOptionsPairs.sortedWith(
|
||||
compareByDescending { it.first.lastUsedTime }
|
||||
)
|
||||
return CreateCredentialUiState(
|
||||
enabledProviders = enabledProviders,
|
||||
disabledProviders = disabledProviders,
|
||||
currentScreenState = initialScreenState,
|
||||
requestDisplayInfo = requestDisplayInfo,
|
||||
sortedCreateOptionsPairs = createOptionsPairs.sortedWith(
|
||||
compareByDescending { it.first.lastUsedTime }
|
||||
),
|
||||
hasDefaultProvider = defaultProvider != null,
|
||||
sortedCreateOptionsPairs = sortedCreateOptionsPairs,
|
||||
activeEntry = toActiveEntry(
|
||||
/*defaultProvider=*/defaultProvider,
|
||||
/*createOptionSize=*/createOptionsPairs.size,
|
||||
/*lastSeenProviderWithNonEmptyCreateOptions=*/
|
||||
lastSeenProviderWithNonEmptyCreateOptions,
|
||||
/*remoteEntry=*/remoteEntry
|
||||
defaultProvider = defaultProvider,
|
||||
sortedCreateOptionsPairs = sortedCreateOptionsPairs,
|
||||
remoteEntry = remoteEntry,
|
||||
remoteEntryProvider = remoteEntryProvider,
|
||||
),
|
||||
remoteEntry = remoteEntry,
|
||||
foundCandidateFromUserDefaultProvider = defaultProviderSetByUser != null,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -589,59 +603,43 @@ class CreateFlowUtils {
|
||||
createOptionSize: Int,
|
||||
isOnPasskeyIntroStateAlready: Boolean,
|
||||
requestDisplayInfo: RequestDisplayInfo,
|
||||
defaultProvider: EnabledProviderInfo?,
|
||||
remoteEntry: RemoteInfo?,
|
||||
isPasskeyFirstUse: Boolean,
|
||||
): CreateScreenState? {
|
||||
return if (isPasskeyFirstUse && requestDisplayInfo.type ==
|
||||
CredentialType.PASSKEY && !isOnPasskeyIntroStateAlready) {
|
||||
return if (isPasskeyFirstUse && requestDisplayInfo.type == CredentialType.PASSKEY &&
|
||||
!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 {
|
||||
Log.d(
|
||||
Constants.LOG_TAG,
|
||||
"Unexpected failure: the screen state failed to instantiate" +
|
||||
" because the provider list is empty."
|
||||
)
|
||||
null
|
||||
CreateScreenState.CREATION_OPTION_SELECTION
|
||||
}
|
||||
}
|
||||
|
||||
private fun toActiveEntry(
|
||||
defaultProvider: EnabledProviderInfo?,
|
||||
createOptionSize: Int,
|
||||
lastSeenProviderWithNonEmptyCreateOptions: EnabledProviderInfo?,
|
||||
sortedCreateOptionsPairs: List<Pair<CreateOptionInfo, EnabledProviderInfo>>,
|
||||
remoteEntry: RemoteInfo?,
|
||||
remoteEntryProvider: EnabledProviderInfo?,
|
||||
): ActiveEntry? {
|
||||
return if (
|
||||
defaultProvider != null && defaultProvider.createOptions.isEmpty() &&
|
||||
remoteEntry != null
|
||||
sortedCreateOptionsPairs.isEmpty() && remoteEntry != null &&
|
||||
remoteEntryProvider != 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()
|
||||
)
|
||||
ActiveEntry(remoteEntryProvider, remoteEntry)
|
||||
} else if (defaultProvider != null &&
|
||||
defaultProvider.sortedCreateOptions.isNotEmpty()) {
|
||||
ActiveEntry(defaultProvider, defaultProvider.sortedCreateOptions.first())
|
||||
} else if (sortedCreateOptionsPairs.isNotEmpty()) {
|
||||
val (topEntry, topEntryProvider) = sortedCreateOptionsPairs.first()
|
||||
ActiveEntry(topEntryProvider, topEntry)
|
||||
} else null
|
||||
}
|
||||
|
||||
/**
|
||||
* Note: caller required handle empty list due to parsing error.
|
||||
*/
|
||||
private fun toCreationOptionInfoList(
|
||||
private fun toSortedCreationOptionInfoList(
|
||||
providerId: String,
|
||||
creationEntries: List<Entry>,
|
||||
context: Context,
|
||||
@@ -664,7 +662,9 @@ class CreateFlowUtils {
|
||||
footerDescription = createEntry.description?.toString()
|
||||
))
|
||||
}
|
||||
return result
|
||||
return result.sortedWith(
|
||||
compareByDescending { it.lastUsedTime }
|
||||
)
|
||||
}
|
||||
|
||||
private fun toRemoteInfo(
|
||||
@@ -690,6 +690,7 @@ class CreateFlowUtils {
|
||||
context: Context,
|
||||
preferImmediatelyAvailableCredentials: Boolean,
|
||||
appPreferredDefaultProviderId: String?,
|
||||
userSetDefaultProviderIds: Set<String>,
|
||||
): RequestDisplayInfo? {
|
||||
val json = JSONObject(requestJson)
|
||||
var passkeyUsername = ""
|
||||
@@ -711,6 +712,7 @@ class CreateFlowUtils {
|
||||
context.getDrawable(R.drawable.ic_passkey_24) ?: return null,
|
||||
preferImmediatelyAvailableCredentials,
|
||||
appPreferredDefaultProviderId,
|
||||
userSetDefaultProviderIds,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,15 +23,6 @@ class UserConfigRepo(context: Context) {
|
||||
val sharedPreferences: SharedPreferences = context.getSharedPreferences(
|
||||
context.packageName, Context.MODE_PRIVATE)
|
||||
|
||||
fun setDefaultProvider(
|
||||
providerId: String
|
||||
) {
|
||||
sharedPreferences.edit().apply {
|
||||
putString(DEFAULT_PROVIDER, providerId)
|
||||
apply()
|
||||
}
|
||||
}
|
||||
|
||||
fun setIsPasskeyFirstUse(
|
||||
isFirstUse: Boolean
|
||||
) {
|
||||
@@ -41,16 +32,11 @@ class UserConfigRepo(context: Context) {
|
||||
}
|
||||
}
|
||||
|
||||
fun getDefaultProviderId(): String? {
|
||||
return sharedPreferences.getString(DEFAULT_PROVIDER, null)
|
||||
}
|
||||
|
||||
fun getIsPasskeyFirstUse(): Boolean {
|
||||
return sharedPreferences.getBoolean(IS_PASSKEY_FIRST_USE, true)
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val DEFAULT_PROVIDER = "default_provider"
|
||||
// This first use value only applies to passkeys, not related with if generally
|
||||
// credential manager is first use or not
|
||||
const val IS_PASSKEY_FIRST_USE = "is_passkey_first_use"
|
||||
|
||||
@@ -88,27 +88,11 @@ fun CreateCredentialScreen(
|
||||
onLearnMore = viewModel::createFlowOnLearnMore,
|
||||
onLog = { viewModel.logUiEvent(it) },
|
||||
)
|
||||
CreateScreenState.PROVIDER_SELECTION -> ProviderSelectionCard(
|
||||
requestDisplayInfo = createCredentialUiState.requestDisplayInfo,
|
||||
disabledProviderList = createCredentialUiState
|
||||
.disabledProviders,
|
||||
sortedCreateOptionsPairs =
|
||||
createCredentialUiState.sortedCreateOptionsPairs,
|
||||
hasRemoteEntry = createCredentialUiState.remoteEntry != null,
|
||||
onOptionSelected =
|
||||
viewModel::createFlowOnEntrySelectedFromFirstUseScreen,
|
||||
onDisabledProvidersSelected =
|
||||
viewModel::createFlowOnDisabledProvidersSelected,
|
||||
onMoreOptionsSelected =
|
||||
viewModel::createFlowOnMoreOptionsSelectedOnProviderSelection,
|
||||
onLog = { viewModel.logUiEvent(it) },
|
||||
)
|
||||
CreateScreenState.CREATION_OPTION_SELECTION -> CreationSelectionCard(
|
||||
requestDisplayInfo = createCredentialUiState.requestDisplayInfo,
|
||||
enabledProviderList = createCredentialUiState.enabledProviders,
|
||||
providerInfo = createCredentialUiState
|
||||
.activeEntry?.activeProvider!!,
|
||||
hasDefaultProvider = createCredentialUiState.hasDefaultProvider,
|
||||
createOptionInfo =
|
||||
createCredentialUiState.activeEntry.activeEntryInfo
|
||||
as CreateOptionInfo,
|
||||
@@ -121,21 +105,15 @@ fun CreateCredentialScreen(
|
||||
CreateScreenState.MORE_OPTIONS_SELECTION -> MoreOptionsSelectionCard(
|
||||
requestDisplayInfo = createCredentialUiState.requestDisplayInfo,
|
||||
enabledProviderList = createCredentialUiState.enabledProviders,
|
||||
disabledProviderList = createCredentialUiState
|
||||
.disabledProviders,
|
||||
disabledProviderList = createCredentialUiState.disabledProviders,
|
||||
sortedCreateOptionsPairs =
|
||||
createCredentialUiState.sortedCreateOptionsPairs,
|
||||
hasDefaultProvider = createCredentialUiState.hasDefaultProvider,
|
||||
isFromProviderSelection =
|
||||
createCredentialUiState.isFromProviderSelection!!,
|
||||
onBackProviderSelectionButtonSelected =
|
||||
viewModel::createFlowOnBackProviderSelectionButtonSelected,
|
||||
onBackCreationSelectionButtonSelected =
|
||||
viewModel::createFlowOnBackCreationSelectionButtonSelected,
|
||||
onOptionSelected =
|
||||
viewModel::createFlowOnEntrySelectedFromMoreOptionScreen,
|
||||
onDisabledProvidersSelected =
|
||||
viewModel::createFlowOnDisabledProvidersSelected,
|
||||
viewModel::createFlowOnLaunchSettings,
|
||||
onRemoteEntrySelected = viewModel::createFlowOnEntrySelected,
|
||||
onLog = { viewModel.logUiEvent(it) },
|
||||
)
|
||||
@@ -144,11 +122,11 @@ fun CreateCredentialScreen(
|
||||
viewModel.onIllegalUiState("Expect active entry to be non-null" +
|
||||
" upon default provider dialog.")
|
||||
} else {
|
||||
DefaultProviderConfirmationCard(
|
||||
NonDefaultUsageConfirmationCard(
|
||||
selectedEntry = createCredentialUiState.activeEntry,
|
||||
onIllegalScreenState = viewModel::onIllegalUiState,
|
||||
onChangeDefaultSelected =
|
||||
viewModel::createFlowOnChangeDefaultSelected,
|
||||
onLaunchSettings =
|
||||
viewModel::createFlowOnLaunchSettings,
|
||||
onUseOnceSelected = viewModel::createFlowOnUseOnceSelected,
|
||||
onLog = { viewModel.logUiEvent(it) },
|
||||
)
|
||||
@@ -262,95 +240,12 @@ fun PasskeyIntroCard(
|
||||
onLog(CreateCredentialEvent.CREDMAN_CREATE_CRED_PASSKEY_INTRO)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ProviderSelectionCard(
|
||||
requestDisplayInfo: RequestDisplayInfo,
|
||||
disabledProviderList: List<DisabledProviderInfo>?,
|
||||
sortedCreateOptionsPairs: List<Pair<CreateOptionInfo, EnabledProviderInfo>>,
|
||||
hasRemoteEntry: Boolean,
|
||||
onOptionSelected: (ActiveEntry) -> Unit,
|
||||
onDisabledProvidersSelected: () -> Unit,
|
||||
onMoreOptionsSelected: () -> Unit,
|
||||
onLog: @Composable (UiEventEnum) -> Unit,
|
||||
) {
|
||||
SheetContainerCard {
|
||||
item { HeadlineIcon(bitmap = requestDisplayInfo.typeIcon.toBitmap().asImageBitmap()) }
|
||||
item { Divider(thickness = 16.dp, color = Color.Transparent) }
|
||||
item {
|
||||
HeadlineText(
|
||||
text = stringResource(
|
||||
R.string.choose_provider_title,
|
||||
when (requestDisplayInfo.type) {
|
||||
CredentialType.PASSKEY ->
|
||||
stringResource(R.string.passkeys)
|
||||
CredentialType.PASSWORD ->
|
||||
stringResource(R.string.passwords)
|
||||
CredentialType.UNKNOWN -> stringResource(R.string.sign_in_info)
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
item { Divider(thickness = 24.dp, color = Color.Transparent) }
|
||||
|
||||
item {
|
||||
Row(modifier = Modifier.fillMaxWidth().wrapContentHeight()) {
|
||||
BodyMediumText(text = stringResource(R.string.choose_provider_body))
|
||||
}
|
||||
}
|
||||
item { Divider(thickness = 16.dp, color = Color.Transparent) }
|
||||
item {
|
||||
CredentialContainerCard {
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(2.dp)
|
||||
) {
|
||||
sortedCreateOptionsPairs.forEach { entry ->
|
||||
MoreOptionsInfoRow(
|
||||
requestDisplayInfo = requestDisplayInfo,
|
||||
providerInfo = entry.second,
|
||||
createOptionInfo = entry.first,
|
||||
onOptionSelected = {
|
||||
onOptionSelected(
|
||||
ActiveEntry(
|
||||
entry.second,
|
||||
entry.first
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
MoreOptionsDisabledProvidersRow(
|
||||
disabledProviders = disabledProviderList,
|
||||
onDisabledProvidersSelected = onDisabledProvidersSelected,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (hasRemoteEntry) {
|
||||
item { Divider(thickness = 24.dp, color = Color.Transparent) }
|
||||
item {
|
||||
CtaButtonRow(
|
||||
leftButton = {
|
||||
ActionButton(
|
||||
stringResource(R.string.string_more_options),
|
||||
onMoreOptionsSelected
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
onLog(CreateCredentialEvent.CREDMAN_CREATE_CRED_PROVIDER_SELECTION)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun MoreOptionsSelectionCard(
|
||||
requestDisplayInfo: RequestDisplayInfo,
|
||||
enabledProviderList: List<EnabledProviderInfo>,
|
||||
disabledProviderList: List<DisabledProviderInfo>?,
|
||||
sortedCreateOptionsPairs: List<Pair<CreateOptionInfo, EnabledProviderInfo>>,
|
||||
hasDefaultProvider: Boolean,
|
||||
isFromProviderSelection: Boolean,
|
||||
onBackProviderSelectionButtonSelected: () -> Unit,
|
||||
onBackCreationSelectionButtonSelected: () -> Unit,
|
||||
onOptionSelected: (ActiveEntry) -> Unit,
|
||||
onDisabledProvidersSelected: () -> Unit,
|
||||
@@ -369,9 +264,7 @@ fun MoreOptionsSelectionCard(
|
||||
CredentialType.UNKNOWN -> stringResource(R.string.sign_in_info)
|
||||
}
|
||||
),
|
||||
onNavigationIconClicked =
|
||||
if (isFromProviderSelection) onBackProviderSelectionButtonSelected
|
||||
else onBackCreationSelectionButtonSelected,
|
||||
onNavigationIconClicked = onBackCreationSelectionButtonSelected,
|
||||
bottomPadding = 16.dp,
|
||||
)
|
||||
}) {
|
||||
@@ -379,30 +272,26 @@ fun MoreOptionsSelectionCard(
|
||||
item {
|
||||
CredentialContainerCard {
|
||||
Column(verticalArrangement = Arrangement.spacedBy(2.dp)) {
|
||||
// Only in the flows with default provider(not first time use) we can show the
|
||||
// createOptions here, or they will be shown on ProviderSelectionCard
|
||||
if (hasDefaultProvider) {
|
||||
sortedCreateOptionsPairs.forEach { entry ->
|
||||
MoreOptionsInfoRow(
|
||||
requestDisplayInfo = requestDisplayInfo,
|
||||
providerInfo = entry.second,
|
||||
createOptionInfo = entry.first,
|
||||
onOptionSelected = {
|
||||
onOptionSelected(
|
||||
ActiveEntry(
|
||||
entry.second,
|
||||
entry.first
|
||||
)
|
||||
sortedCreateOptionsPairs.forEach { entry ->
|
||||
MoreOptionsInfoRow(
|
||||
requestDisplayInfo = requestDisplayInfo,
|
||||
providerInfo = entry.second,
|
||||
createOptionInfo = entry.first,
|
||||
onOptionSelected = {
|
||||
onOptionSelected(
|
||||
ActiveEntry(
|
||||
entry.second,
|
||||
entry.first
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
MoreOptionsDisabledProvidersRow(
|
||||
disabledProviders = disabledProviderList,
|
||||
onDisabledProvidersSelected =
|
||||
onDisabledProvidersSelected,
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
MoreOptionsDisabledProvidersRow(
|
||||
disabledProviders = disabledProviderList,
|
||||
onDisabledProvidersSelected =
|
||||
onDisabledProvidersSelected,
|
||||
)
|
||||
enabledProviderList.forEach {
|
||||
if (it.remoteEntry != null) {
|
||||
RemoteEntryRow(
|
||||
@@ -420,10 +309,10 @@ fun MoreOptionsSelectionCard(
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun DefaultProviderConfirmationCard(
|
||||
fun NonDefaultUsageConfirmationCard(
|
||||
selectedEntry: ActiveEntry,
|
||||
onIllegalScreenState: (String) -> Unit,
|
||||
onChangeDefaultSelected: () -> Unit,
|
||||
onLaunchSettings: () -> Unit,
|
||||
onUseOnceSelected: () -> Unit,
|
||||
onLog: @Composable (UiEventEnum) -> Unit,
|
||||
) {
|
||||
@@ -454,14 +343,14 @@ fun DefaultProviderConfirmationCard(
|
||||
CtaButtonRow(
|
||||
leftButton = {
|
||||
ActionButton(
|
||||
stringResource(R.string.use_once),
|
||||
onClick = onUseOnceSelected
|
||||
stringResource(R.string.settings),
|
||||
onClick = onLaunchSettings,
|
||||
)
|
||||
},
|
||||
rightButton = {
|
||||
ConfirmButton(
|
||||
stringResource(R.string.set_as_default),
|
||||
onClick = onChangeDefaultSelected
|
||||
stringResource(R.string.use_once),
|
||||
onClick = onUseOnceSelected,
|
||||
)
|
||||
},
|
||||
)
|
||||
@@ -479,7 +368,6 @@ fun CreationSelectionCard(
|
||||
onOptionSelected: (BaseEntry) -> Unit,
|
||||
onConfirm: () -> Unit,
|
||||
onMoreOptionsSelected: () -> Unit,
|
||||
hasDefaultProvider: Boolean,
|
||||
onLog: @Composable (UiEventEnum) -> Unit,
|
||||
) {
|
||||
SheetContainerCard {
|
||||
@@ -527,16 +415,9 @@ fun CreationSelectionCard(
|
||||
if (enabledProvider.remoteEntry != null) {
|
||||
remoteEntry = enabledProvider.remoteEntry
|
||||
}
|
||||
createOptionsSize += enabledProvider.createOptions.size
|
||||
}
|
||||
val shouldShowMoreOptionsButton = if (!hasDefaultProvider) {
|
||||
// User has already been presented with all options on the default provider
|
||||
// selection screen. Don't show them again. Therefore, only show the more option
|
||||
// button if remote option is present.
|
||||
remoteEntry != null
|
||||
} else {
|
||||
createOptionsSize > 1 || remoteEntry != null
|
||||
createOptionsSize += enabledProvider.sortedCreateOptions.size
|
||||
}
|
||||
val shouldShowMoreOptionsButton = createOptionsSize > 1 || remoteEntry != null
|
||||
item {
|
||||
CtaButtonRow(
|
||||
leftButton = if (shouldShowMoreOptionsButton) {
|
||||
|
||||
@@ -29,12 +29,9 @@ data class CreateCredentialUiState(
|
||||
val currentScreenState: CreateScreenState,
|
||||
val requestDisplayInfo: RequestDisplayInfo,
|
||||
val sortedCreateOptionsPairs: List<Pair<CreateOptionInfo, EnabledProviderInfo>>,
|
||||
// Should not change with the real time update of default provider, only determine whether
|
||||
// we're showing provider selection page at the beginning
|
||||
val hasDefaultProvider: Boolean,
|
||||
val activeEntry: ActiveEntry? = null,
|
||||
val remoteEntry: RemoteInfo? = null,
|
||||
val isFromProviderSelection: Boolean? = null,
|
||||
val foundCandidateFromUserDefaultProvider: Boolean,
|
||||
)
|
||||
|
||||
internal fun hasContentToDisplay(state: CreateCredentialUiState): Boolean {
|
||||
@@ -50,11 +47,12 @@ open class ProviderInfo(
|
||||
)
|
||||
|
||||
class EnabledProviderInfo(
|
||||
icon: Drawable,
|
||||
id: String,
|
||||
displayName: String,
|
||||
var createOptions: List<CreateOptionInfo>,
|
||||
var remoteEntry: RemoteInfo?,
|
||||
icon: Drawable,
|
||||
id: String,
|
||||
displayName: String,
|
||||
// Sorted by last used time
|
||||
var sortedCreateOptions: List<CreateOptionInfo>,
|
||||
var remoteEntry: RemoteInfo?,
|
||||
) : ProviderInfo(icon, id, displayName)
|
||||
|
||||
class DisabledProviderInfo(
|
||||
@@ -108,6 +106,7 @@ data class RequestDisplayInfo(
|
||||
val typeIcon: Drawable,
|
||||
val preferImmediatelyAvailableCredentials: Boolean,
|
||||
val appPreferredDefaultProviderId: String?,
|
||||
val userSetDefaultProviderIds: Set<String>,
|
||||
)
|
||||
|
||||
/**
|
||||
@@ -123,7 +122,6 @@ data class ActiveEntry (
|
||||
enum class CreateScreenState {
|
||||
PASSKEY_INTRO,
|
||||
MORE_ABOUT_PASSKEYS_INTRO,
|
||||
PROVIDER_SELECTION,
|
||||
CREATION_OPTION_SELECTION,
|
||||
MORE_OPTIONS_SELECTION,
|
||||
DEFAULT_PROVIDER_CONFIRMATION,
|
||||
|
||||
Reference in New Issue
Block a user