Add the only one createOption but has remoteEntry screen for create passkey
screenshot: https://screenshot.googleplex.com/TULWynJWxx4cAeP Test: deployed locally Bug: 253157211 Change-Id: I1865b89f71129ec94cac3ef0ea09a6b631eac65b
This commit is contained in:
@@ -6,6 +6,7 @@
|
||||
<string name="string_more_options">More options</string>
|
||||
<string name="string_create_in_another_place">Create in another place</string>
|
||||
<string name="string_save_to_another_place">Save to another place</string>
|
||||
<string name="string_use_another_device">Use another device</string>
|
||||
<string name="string_no_thanks">No thanks</string>
|
||||
<string name="passkey_creation_intro_title">A simple way to sign in safely</string>
|
||||
<string name="passkey_creation_intro_body">Use your fingerprint, face or screen lock to sign in with a unique passkey that can’t be forgotten or stolen. Learn more</string>
|
||||
|
||||
@@ -149,8 +149,8 @@ class CredentialManagerRepo(
|
||||
if (providerInfo.isDefault) {hasDefault = true; defaultProvider = providerInfo} }
|
||||
// TODO: covert from real requestInfo for create passkey
|
||||
var requestDisplayInfo = RequestDisplayInfo(
|
||||
"Elisa Beckett",
|
||||
"beckett-bakert@gmail.com",
|
||||
"Elisa Beckett",
|
||||
TYPE_PUBLIC_KEY_CREDENTIAL,
|
||||
"tribank")
|
||||
val createCredentialRequest = requestInfo.createCredentialRequest
|
||||
@@ -165,8 +165,12 @@ class CredentialManagerRepo(
|
||||
return CreateCredentialUiState(
|
||||
enabledProviders = providerEnabledList,
|
||||
disabledProviders = providerDisabledList,
|
||||
if (hasDefault)
|
||||
{CreateScreenState.CREATION_OPTION_SELECTION} else {CreateScreenState.PASSKEY_INTRO},
|
||||
// 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},
|
||||
requestDisplayInfo,
|
||||
if (hasDefault) {
|
||||
ActiveEntry(defaultProvider, defaultProvider.createOptions.first())
|
||||
@@ -439,7 +443,7 @@ class CredentialManagerRepo(
|
||||
return RequestInfo.newCreateRequestInfo(
|
||||
Binder(),
|
||||
CreateCredentialRequest(
|
||||
TYPE_PASSWORD_CREDENTIAL,
|
||||
TYPE_PUBLIC_KEY_CREDENTIAL,
|
||||
data
|
||||
),
|
||||
/*isFirstUsage=*/false,
|
||||
|
||||
@@ -193,9 +193,10 @@ class CreateFlowUtils {
|
||||
icon = pkgInfo.applicationInfo.loadIcon(packageManager)!!,
|
||||
name = it.providerFlattenedComponentName,
|
||||
displayName = pkgInfo.applicationInfo.loadLabel(packageManager).toString(),
|
||||
createOptions = toCreationOptionInfoList(it.saveEntries, context),
|
||||
createOptions = toCreationOptionInfoList(
|
||||
it.providerFlattenedComponentName, it.saveEntries, context),
|
||||
isDefault = it.isDefaultProvider,
|
||||
remoteEntry = toRemoteInfo(it.remoteEntry),
|
||||
remoteEntry = toRemoteInfo(it.providerFlattenedComponentName, it.remoteEntry),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -219,6 +220,7 @@ class CreateFlowUtils {
|
||||
}
|
||||
|
||||
private fun toCreationOptionInfoList(
|
||||
providerId: String,
|
||||
creationEntries: List<Entry>,
|
||||
context: Context,
|
||||
): List<CreateOptionInfo> {
|
||||
@@ -227,6 +229,7 @@ class CreateFlowUtils {
|
||||
|
||||
return@map CreateOptionInfo(
|
||||
// TODO: remove fallbacks
|
||||
providerId = providerId,
|
||||
entryKey = it.key,
|
||||
entrySubkey = it.subkey,
|
||||
pendingIntent = it.pendingIntent,
|
||||
@@ -245,11 +248,13 @@ class CreateFlowUtils {
|
||||
}
|
||||
|
||||
private fun toRemoteInfo(
|
||||
providerId: String,
|
||||
remoteEntry: Entry?,
|
||||
): RemoteInfo? {
|
||||
// TODO: should also call fromSlice after getting the official jetpack code.
|
||||
return if (remoteEntry != null) {
|
||||
RemoteInfo(
|
||||
providerId = providerId,
|
||||
entryKey = remoteEntry.key,
|
||||
entrySubkey = remoteEntry.subkey,
|
||||
pendingIntent = remoteEntry.pendingIntent,
|
||||
|
||||
@@ -55,8 +55,11 @@ fun CreateCredentialScreen(
|
||||
viewModel: CreateCredentialViewModel,
|
||||
providerActivityLauncher: ManagedActivityResultLauncher<IntentSenderRequest, ActivityResult>
|
||||
) {
|
||||
val primaryEntryCallback: () -> Unit = {
|
||||
viewModel.onPrimaryCreateOptionInfoSelected(providerActivityLauncher)
|
||||
val selectEntryCallback: (EntryInfo) -> Unit = {
|
||||
viewModel.onEntrySelected(it, providerActivityLauncher)
|
||||
}
|
||||
val confirmEntryCallback: () -> Unit = {
|
||||
viewModel.onConfirmCreationSelected(providerActivityLauncher)
|
||||
}
|
||||
val state = rememberModalBottomSheetState(
|
||||
initialValue = ModalBottomSheetValue.Expanded,
|
||||
@@ -78,23 +81,23 @@ fun CreateCredentialScreen(
|
||||
)
|
||||
CreateScreenState.CREATION_OPTION_SELECTION -> CreationSelectionCard(
|
||||
requestDisplayInfo = uiState.requestDisplayInfo,
|
||||
enabledProviderList = uiState.enabledProviders,
|
||||
providerInfo = uiState.activeEntry?.activeProvider!!,
|
||||
createOptionInfo = uiState.activeEntry.activeEntryInfo as CreateOptionInfo,
|
||||
onOptionSelected = primaryEntryCallback,
|
||||
onConfirm = primaryEntryCallback,
|
||||
onOptionSelected = selectEntryCallback,
|
||||
onConfirm = confirmEntryCallback,
|
||||
onCancel = viewModel::onCancel,
|
||||
multiProvider = uiState.enabledProviders.size > 1,
|
||||
onMoreOptionsSelected = viewModel::onMoreOptionsSelected
|
||||
onMoreOptionsSelected = viewModel::onMoreOptionsSelected,
|
||||
)
|
||||
CreateScreenState.MORE_OPTIONS_SELECTION -> MoreOptionsSelectionCard(
|
||||
requestDisplayInfo = uiState.requestDisplayInfo,
|
||||
enabledProviderList = uiState.enabledProviders,
|
||||
disabledProviderList = uiState.disabledProviders,
|
||||
onBackButtonSelected = viewModel::onBackButtonSelected,
|
||||
onOptionSelected = viewModel::onMoreOptionsRowSelected,
|
||||
onDisabledPasswordManagerSelected = viewModel::onDisabledPasswordManagerSelected,
|
||||
onRemoteEntrySelected = viewModel::onRemoteEntrySelected
|
||||
)
|
||||
requestDisplayInfo = uiState.requestDisplayInfo,
|
||||
enabledProviderList = uiState.enabledProviders,
|
||||
disabledProviderList = uiState.disabledProviders,
|
||||
onBackButtonSelected = viewModel::onBackButtonSelected,
|
||||
onOptionSelected = viewModel::onMoreOptionsRowSelected,
|
||||
onDisabledPasswordManagerSelected = viewModel::onDisabledPasswordManagerSelected,
|
||||
onRemoteEntrySelected = selectEntryCallback,
|
||||
)
|
||||
CreateScreenState.MORE_OPTIONS_ROW_INTRO -> MoreOptionsRowIntroCard(
|
||||
providerInfo = uiState.activeEntry?.activeProvider!!,
|
||||
onDefaultOrNotSelected = viewModel::onDefaultOrNotSelected
|
||||
@@ -234,7 +237,7 @@ fun MoreOptionsSelectionCard(
|
||||
onBackButtonSelected: () -> Unit,
|
||||
onOptionSelected: (ActiveEntry) -> Unit,
|
||||
onDisabledPasswordManagerSelected: () -> Unit,
|
||||
onRemoteEntrySelected: () -> Unit,
|
||||
onRemoteEntrySelected: (EntryInfo) -> Unit,
|
||||
) {
|
||||
Card() {
|
||||
Column() {
|
||||
@@ -292,17 +295,15 @@ fun MoreOptionsSelectionCard(
|
||||
)
|
||||
}
|
||||
}
|
||||
var hasRemoteInfo = false
|
||||
// TODO: handle the error situation that if multiple remoteInfos exists
|
||||
enabledProviderList.forEach {
|
||||
if (it.remoteEntry != null) {
|
||||
hasRemoteInfo = true
|
||||
}
|
||||
}
|
||||
if (hasRemoteInfo) {
|
||||
item {
|
||||
RemoteEntryRow(
|
||||
onRemoteEntrySelected = onRemoteEntrySelected,
|
||||
)
|
||||
item {
|
||||
RemoteEntryRow(
|
||||
remoteInfo = it.remoteEntry!!,
|
||||
onRemoteEntrySelected = onRemoteEntrySelected,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -388,12 +389,12 @@ fun ProviderRow(providerInfo: ProviderInfo, onProviderSelected: (String) -> Unit
|
||||
@Composable
|
||||
fun CreationSelectionCard(
|
||||
requestDisplayInfo: RequestDisplayInfo,
|
||||
providerInfo: ProviderInfo,
|
||||
enabledProviderList: List<EnabledProviderInfo>,
|
||||
providerInfo: EnabledProviderInfo,
|
||||
createOptionInfo: CreateOptionInfo,
|
||||
onOptionSelected: () -> Unit,
|
||||
onOptionSelected: (EntryInfo) -> Unit,
|
||||
onConfirm: () -> Unit,
|
||||
onCancel: () -> Unit,
|
||||
multiProvider: Boolean,
|
||||
onMoreOptionsSelected: () -> Unit,
|
||||
) {
|
||||
Card() {
|
||||
@@ -442,19 +443,16 @@ fun CreationSelectionCard(
|
||||
.padding(horizontal = 24.dp)
|
||||
.align(alignment = Alignment.CenterHorizontally),
|
||||
) {
|
||||
LazyColumn(
|
||||
verticalArrangement = Arrangement.spacedBy(2.dp)
|
||||
) {
|
||||
item {
|
||||
PrimaryCreateOptionRow(
|
||||
requestDisplayInfo = requestDisplayInfo,
|
||||
createOptionInfo = createOptionInfo,
|
||||
onOptionSelected = onOptionSelected
|
||||
)
|
||||
}
|
||||
}
|
||||
PrimaryCreateOptionRow(
|
||||
requestDisplayInfo = requestDisplayInfo,
|
||||
createOptionInfo = createOptionInfo,
|
||||
onOptionSelected = onOptionSelected
|
||||
)
|
||||
}
|
||||
if (multiProvider) {
|
||||
var createOptionsSize = 0
|
||||
enabledProviderList.forEach{
|
||||
enabledProvider -> createOptionsSize += enabledProvider.createOptions.size}
|
||||
if (createOptionsSize > 1) {
|
||||
TextButton(
|
||||
onClick = onMoreOptionsSelected,
|
||||
modifier = Modifier
|
||||
@@ -469,6 +467,26 @@ fun CreationSelectionCard(
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
}
|
||||
} else if (
|
||||
requestDisplayInfo.type == TYPE_PUBLIC_KEY_CREDENTIAL
|
||||
) {
|
||||
// TODO: handle the error situation that if multiple remoteInfos exists
|
||||
enabledProviderList.forEach { enabledProvider ->
|
||||
if (enabledProvider.remoteEntry != null) {
|
||||
TextButton(
|
||||
onClick = {
|
||||
onOptionSelected(enabledProvider.remoteEntry!!) },
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 24.dp)
|
||||
.align(alignment = Alignment.CenterHorizontally)
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(R.string.string_use_another_device),
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Divider(
|
||||
thickness = 24.dp,
|
||||
@@ -501,10 +519,10 @@ fun CreationSelectionCard(
|
||||
fun PrimaryCreateOptionRow(
|
||||
requestDisplayInfo: RequestDisplayInfo,
|
||||
createOptionInfo: CreateOptionInfo,
|
||||
onOptionSelected: () -> Unit
|
||||
onOptionSelected: (EntryInfo) -> Unit
|
||||
) {
|
||||
Entry(
|
||||
onClick = onOptionSelected,
|
||||
onClick = {onOptionSelected(createOptionInfo)},
|
||||
icon = {
|
||||
Image(modifier = Modifier.size(32.dp).padding(start = 10.dp),
|
||||
bitmap = createOptionInfo.credentialTypeIcon.toBitmap().asImageBitmap(),
|
||||
@@ -527,7 +545,7 @@ fun PrimaryCreateOptionRow(
|
||||
)
|
||||
} else {
|
||||
Text(
|
||||
text = requestDisplayInfo.subtitle,
|
||||
text = requestDisplayInfo.title,
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
modifier = Modifier.padding(top = 16.dp, bottom = 16.dp)
|
||||
)
|
||||
@@ -640,10 +658,11 @@ fun MoreOptionsDisabledProvidersRow(
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun RemoteEntryRow(
|
||||
onRemoteEntrySelected: () -> Unit,
|
||||
remoteInfo: RemoteInfo,
|
||||
onRemoteEntrySelected: (RemoteInfo) -> Unit,
|
||||
) {
|
||||
Entry(
|
||||
onClick = onRemoteEntrySelected,
|
||||
onClick = {onRemoteEntrySelected(remoteInfo)},
|
||||
icon = {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.ic_other_devices),
|
||||
|
||||
@@ -37,6 +37,7 @@ data class CreateCredentialUiState(
|
||||
val currentScreenState: CreateScreenState,
|
||||
val requestDisplayInfo: RequestDisplayInfo,
|
||||
val activeEntry: ActiveEntry? = null,
|
||||
val selectedEntry: EntryInfo? = null,
|
||||
)
|
||||
|
||||
class CreateCredentialViewModel(
|
||||
@@ -109,10 +110,6 @@ class CreateCredentialViewModel(
|
||||
// TODO: Complete this function
|
||||
}
|
||||
|
||||
fun onRemoteEntrySelected() {
|
||||
// TODO: Complete this function
|
||||
}
|
||||
|
||||
fun onCancel() {
|
||||
CredentialManagerRepo.getInstance().onCancel()
|
||||
dialogResult.value = DialogResult(ResultState.CANCELED)
|
||||
@@ -125,46 +122,54 @@ class CreateCredentialViewModel(
|
||||
// TODO: implement the if choose as default or not logic later
|
||||
}
|
||||
|
||||
fun onPrimaryCreateOptionInfoSelected(
|
||||
fun onEntrySelected(
|
||||
selectedEntry: EntryInfo,
|
||||
launcher: ManagedActivityResultLauncher<IntentSenderRequest, ActivityResult>
|
||||
) {
|
||||
val providerId = selectedEntry.providerId
|
||||
val entryKey = selectedEntry.entryKey
|
||||
val entrySubkey = selectedEntry.entrySubkey
|
||||
Log.d(
|
||||
"Account Selector", "Option selected for entry: " +
|
||||
" {provider=$providerId, key=$entryKey, subkey=$entrySubkey")
|
||||
if (selectedEntry.pendingIntent != null) {
|
||||
uiState = uiState.copy(selectedEntry = selectedEntry)
|
||||
val intentSenderRequest = IntentSenderRequest.Builder(selectedEntry.pendingIntent)
|
||||
.setFillInIntent(selectedEntry.fillInIntent).build()
|
||||
launcher.launch(intentSenderRequest)
|
||||
} else {
|
||||
CredentialManagerRepo.getInstance().onOptionSelected(
|
||||
providerId,
|
||||
entryKey,
|
||||
entrySubkey
|
||||
)
|
||||
dialogResult.value = DialogResult(
|
||||
ResultState.COMPLETE,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun onConfirmCreationSelected(
|
||||
launcher: ManagedActivityResultLauncher<IntentSenderRequest, ActivityResult>
|
||||
) {
|
||||
val selectedEntry = uiState.activeEntry?.activeEntryInfo
|
||||
if (selectedEntry != null) {
|
||||
val entryKey = selectedEntry.entryKey
|
||||
val entrySubkey = selectedEntry.entrySubkey
|
||||
Log.d(
|
||||
"Account Selector",
|
||||
"Option selected for creation: " +
|
||||
"{key = $entryKey, subkey = $entrySubkey}"
|
||||
)
|
||||
if (selectedEntry.pendingIntent != null) {
|
||||
val intentSenderRequest = IntentSenderRequest.Builder(selectedEntry.pendingIntent)
|
||||
.setFillInIntent(selectedEntry.fillInIntent).build()
|
||||
launcher.launch(intentSenderRequest)
|
||||
} else {
|
||||
CredentialManagerRepo.getInstance().onOptionSelected(
|
||||
uiState.activeEntry?.activeProvider!!.name,
|
||||
entryKey,
|
||||
entrySubkey
|
||||
)
|
||||
dialogResult.value = DialogResult(
|
||||
ResultState.COMPLETE,
|
||||
)
|
||||
}
|
||||
onEntrySelected(selectedEntry, launcher)
|
||||
} else {
|
||||
Log.w("Account Selector",
|
||||
"Illegal state: confirm is pressed but activeEntry isn't set.")
|
||||
dialogResult.value = DialogResult(
|
||||
ResultState.COMPLETE,
|
||||
)
|
||||
TODO("Gracefully handle illegal state.")
|
||||
}
|
||||
}
|
||||
|
||||
fun onProviderActivityResult(providerActivityResult: ProviderActivityResult) {
|
||||
val entry = uiState.activeEntry?.activeEntryInfo
|
||||
val entry = uiState.selectedEntry
|
||||
val resultCode = providerActivityResult.resultCode
|
||||
val resultData = providerActivityResult.data
|
||||
val providerId = uiState.activeEntry?.activeProvider!!.name
|
||||
if (entry != null) {
|
||||
val providerId = entry.providerId
|
||||
Log.d("Account Selector", "Got provider activity result: {provider=" +
|
||||
"$providerId, key=${entry.entryKey}, subkey=${entry.entrySubkey}, " +
|
||||
"resultCode=$resultCode, resultData=$resultData}"
|
||||
|
||||
@@ -42,6 +42,7 @@ class DisabledProviderInfo(
|
||||
) : ProviderInfo(icon, name, displayName)
|
||||
|
||||
open class EntryInfo (
|
||||
val providerId: String,
|
||||
val entryKey: String,
|
||||
val entrySubkey: String,
|
||||
val pendingIntent: PendingIntent?,
|
||||
@@ -49,6 +50,7 @@ open class EntryInfo (
|
||||
)
|
||||
|
||||
class CreateOptionInfo(
|
||||
providerId: String,
|
||||
entryKey: String,
|
||||
entrySubkey: String,
|
||||
pendingIntent: PendingIntent?,
|
||||
@@ -60,14 +62,15 @@ class CreateOptionInfo(
|
||||
val passkeyCount: Int?,
|
||||
val totalCredentialCount: Int?,
|
||||
val lastUsedTimeMillis: Long?,
|
||||
) : EntryInfo(entryKey, entrySubkey, pendingIntent, fillInIntent)
|
||||
) : EntryInfo(providerId, entryKey, entrySubkey, pendingIntent, fillInIntent)
|
||||
|
||||
class RemoteInfo(
|
||||
providerId: String,
|
||||
entryKey: String,
|
||||
entrySubkey: String,
|
||||
pendingIntent: PendingIntent?,
|
||||
fillInIntent: Intent?,
|
||||
) : EntryInfo(entryKey, entrySubkey, pendingIntent, fillInIntent)
|
||||
) : EntryInfo(providerId, entryKey, entrySubkey, pendingIntent, fillInIntent)
|
||||
|
||||
data class RequestDisplayInfo(
|
||||
val title: String,
|
||||
|
||||
Reference in New Issue
Block a user