diff --git a/packages/CredentialManager/res/values/strings.xml b/packages/CredentialManager/res/values/strings.xml
index 252ecf4a6005f..8c9023c55effd 100644
--- a/packages/CredentialManager/res/values/strings.xml
+++ b/packages/CredentialManager/res/values/strings.xml
@@ -49,6 +49,8 @@
Save password to
Save sign-in to
+
+ Create a passkey in another device?
Use %1$s for all your sign-ins?
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
index 530f1c467a760..0cc11946ca85b 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
@@ -44,6 +44,8 @@ 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
@@ -142,25 +144,22 @@ class CredentialManagerRepo(
val providerDisabledList = CreateFlowUtils.toDisabledProviderList(
// Handle runtime cast error
providerDisabledList as List, context)
- var hasDefault = false
- var defaultProvider: EnabledProviderInfo = providerEnabledList.first()
+ var defaultProvider: EnabledProviderInfo? = null
+ var remoteEntry: RemoteInfo? = null
providerEnabledList.forEach{providerInfo -> providerInfo.createOptions =
providerInfo.createOptions.sortedWith(compareBy { it.lastUsedTimeMillis }).reversed()
- if (providerInfo.isDefault) {hasDefault = true; defaultProvider = providerInfo} }
+ if (providerInfo.isDefault) {defaultProvider = providerInfo}
+ if (providerInfo.remoteEntry != null) {
+ remoteEntry = providerInfo.remoteEntry!!
+ }
+ }
return CreateCredentialUiState(
enabledProviders = providerEnabledList,
disabledProviders = providerDisabledList,
- // TODO: Add the screen when defaultProvider has no createOption but
- // there's remoteInfo under other providers
- if (!hasDefault || defaultProvider.createOptions.isEmpty()) {
- if (requestDisplayInfo.type == TYPE_PUBLIC_KEY_CREDENTIAL)
- {CreateScreenState.PASSKEY_INTRO} else {CreateScreenState.PROVIDER_SELECTION}
- } else {CreateScreenState.CREATION_OPTION_SELECTION},
+ toCreateScreenState(requestDisplayInfo, defaultProvider, remoteEntry),
requestDisplayInfo,
false,
- if (hasDefault) {
- ActiveEntry(defaultProvider, defaultProvider.createOptions.first())
- } else null
+ toActiveEntry(defaultProvider, remoteEntry),
)
}
@@ -509,4 +508,38 @@ 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
+ }
}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
index fde32792e8f84..20b3d81c8b3ca 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
@@ -60,7 +60,7 @@ fun CreateCredentialScreen(
viewModel.onEntrySelected(it, providerActivityLauncher)
}
val confirmEntryCallback: () -> Unit = {
- viewModel.onConfirmCreationSelected(providerActivityLauncher)
+ viewModel.onConfirmEntrySelected(providerActivityLauncher)
}
val state = rememberModalBottomSheetState(
initialValue = ModalBottomSheetValue.Expanded,
@@ -108,6 +108,13 @@ fun CreateCredentialScreen(
providerInfo = uiState.activeEntry?.activeProvider!!,
onDefaultOrNotSelected = viewModel::onDefaultOrNotSelected
)
+ CreateScreenState.EXTERNAL_ONLY_SELECTION -> ExternalOnlySelectionCard(
+ requestDisplayInfo = uiState.requestDisplayInfo,
+ activeRemoteEntry = uiState.activeEntry?.activeEntryInfo!!,
+ onOptionSelected = selectEntryCallback,
+ onConfirm = confirmEntryCallback,
+ onCancel = viewModel::onCancel,
+ )
}
},
scrimColor = MaterialTheme.colorScheme.scrim,
@@ -489,7 +496,7 @@ fun CreationSelectionCard(
) {
PrimaryCreateOptionRow(
requestDisplayInfo = requestDisplayInfo,
- createOptionInfo = createOptionInfo,
+ entryInfo = createOptionInfo,
onOptionSelected = onOptionSelected
)
}
@@ -560,18 +567,87 @@ fun CreationSelectionCard(
}
}
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun ExternalOnlySelectionCard(
+ requestDisplayInfo: RequestDisplayInfo,
+ activeRemoteEntry: EntryInfo,
+ onOptionSelected: (EntryInfo) -> Unit,
+ onConfirm: () -> Unit,
+ onCancel: () -> Unit,
+) {
+ Card() {
+ Column() {
+ Icon(
+ painter = painterResource(R.drawable.ic_other_devices),
+ contentDescription = null,
+ tint = Color.Unspecified,
+ modifier = Modifier.align(alignment = Alignment.CenterHorizontally)
+ .padding(all = 24.dp).size(32.dp)
+ )
+ Text(
+ text = stringResource(R.string.create_passkey_in_other_device_title),
+ style = MaterialTheme.typography.titleMedium,
+ modifier = Modifier.padding(horizontal = 24.dp)
+ .align(alignment = Alignment.CenterHorizontally),
+ textAlign = TextAlign.Center,
+ )
+ Divider(
+ thickness = 24.dp,
+ color = Color.Transparent
+ )
+ Card(
+ shape = MaterialTheme.shapes.medium,
+ modifier = Modifier
+ .padding(horizontal = 24.dp)
+ .align(alignment = Alignment.CenterHorizontally),
+ ) {
+ PrimaryCreateOptionRow(
+ requestDisplayInfo = requestDisplayInfo,
+ entryInfo = activeRemoteEntry,
+ onOptionSelected = onOptionSelected
+ )
+ }
+ Divider(
+ thickness = 24.dp,
+ color = Color.Transparent
+ )
+ Row(
+ horizontalArrangement = Arrangement.SpaceBetween,
+ modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
+ ) {
+ CancelButton(
+ stringResource(R.string.string_cancel),
+ onClick = onCancel
+ )
+ ConfirmButton(
+ stringResource(R.string.string_continue),
+ onClick = onConfirm
+ )
+ }
+ Divider(
+ thickness = 18.dp,
+ color = Color.Transparent,
+ modifier = Modifier.padding(bottom = 16.dp)
+ )
+ }
+ }
+}
+
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun PrimaryCreateOptionRow(
requestDisplayInfo: RequestDisplayInfo,
- createOptionInfo: CreateOptionInfo,
+ entryInfo: EntryInfo,
onOptionSelected: (EntryInfo) -> Unit
) {
Entry(
- onClick = {onOptionSelected(createOptionInfo)},
+ onClick = {onOptionSelected(entryInfo)},
icon = {
Icon(
- bitmap = createOptionInfo.profileIcon.toBitmap().asImageBitmap(),
+ bitmap = if (entryInfo is CreateOptionInfo) {
+ entryInfo.profileIcon.toBitmap().asImageBitmap()
+ } else {requestDisplayInfo.typeIcon.toBitmap().asImageBitmap()},
contentDescription = null,
tint = LocalAndroidColorScheme.current.colorAccentPrimaryVariant,
modifier = Modifier.padding(start = 18.dp).size(32.dp)
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialViewModel.kt
index 0f685a1043297..393cf7d9ae01e 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialViewModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialViewModel.kt
@@ -155,7 +155,7 @@ class CreateCredentialViewModel(
}
}
- fun onConfirmCreationSelected(
+ fun onConfirmEntrySelected(
launcher: ManagedActivityResultLauncher
) {
val selectedEntry = uiState.activeEntry?.activeEntryInfo
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
index 31d0365a821f8..9ac524a4e6d98 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
@@ -95,4 +95,5 @@ enum class CreateScreenState {
CREATION_OPTION_SELECTION,
MORE_OPTIONS_SELECTION,
MORE_OPTIONS_ROW_INTRO,
+ EXTERNAL_ONLY_SELECTION,
}