Update the structure of createOptionInfo as EntryInfo, change the

structure of ProviderInfo

Test: deployed locally

Bug: 253157211
Change-Id: I68de6868cd263d4b1da858e2bce9622e970abe0b
This commit is contained in:
Qinmei Du
2022-10-26 03:39:03 +00:00
parent 89b3a6b46b
commit 9a0a0995e1
10 changed files with 131 additions and 146 deletions

View File

@@ -0,0 +1,11 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"
android:viewportWidth="46"
android:viewportHeight="46"
android:width="46dp"
android:height="46dp">
<path
android:pathData="M45.4247 22.9953C45.4247 35.0229 35.4133 44.7953 23.0359 44.7953C10.6585 44.7953 0.646973 35.0229 0.646973 22.9953C0.646973 10.9677 10.6585 1.19531 23.0359 1.19531C35.4133 1.19531 45.4247 10.9677 45.4247 22.9953Z"
android:strokeColor="#202124"
android:strokeAlpha="0.13"
android:strokeWidth="1" />
</vector>

View File

@@ -22,4 +22,9 @@
<string name="choose_create_option_description">You can use saved <xliff:g id="type">%1$s</xliff:g> on any device. It will be saved to <xliff:g id="providerInfoDisplayName">%2$s</xliff:g> for <xliff:g id="createInfoDisplayName">%3$s</xliff:g></string>
<string name="more_options_title_multiple_options"><xliff:g id="providerInfoDisplayName">%1$s</xliff:g> for <xliff:g id="createInfoTitle">%2$s</xliff:g></string>
<string name="more_options_title_one_option"><xliff:g id="providerInfoDisplayName">%1$s</xliff:g></string>
<string name="more_options_usage_data"><xliff:g id="passwordsNumber">%1$s</xliff:g> passwords and <xliff:g id="passkeyssNumber">%2$s</xliff:g> passkeys saved</string>
<string name="passkeys">passkeys</string>
<string name="passwords">passwords</string>
<string name="sign_ins">sign-ins</string>
<string name="createOptionInfo_icon_description">CreateOptionInfo credentialType icon</string>
</resources>

View File

@@ -33,8 +33,10 @@ import android.graphics.drawable.Icon
import android.os.Binder
import android.os.Bundle
import android.os.ResultReceiver
import com.android.credentialmanager.createflow.ActiveEntry
import com.android.credentialmanager.createflow.CreatePasskeyUiState
import com.android.credentialmanager.createflow.CreateScreenState
import com.android.credentialmanager.createflow.ProviderInfo
import com.android.credentialmanager.createflow.RequestDisplayInfo
import com.android.credentialmanager.getflow.GetCredentialUiState
import com.android.credentialmanager.getflow.GetScreenState
@@ -119,6 +121,10 @@ class CredentialManagerRepo(
val providerList = CreateFlowUtils.toProviderList(
// Handle runtime cast error
providerList as List<CreateCredentialProviderData>, context)
var hasDefault = false
var defaultProvider: ProviderInfo = providerList.first()
providerList.forEach{providerInfo ->
if (providerInfo.isDefault) {hasDefault = true; defaultProvider = providerInfo} }
// TODO: covert from real requestInfo
val requestDisplayInfo = RequestDisplayInfo(
"Elisa Beckett",
@@ -127,8 +133,12 @@ class CredentialManagerRepo(
"tribank")
return CreatePasskeyUiState(
providers = providerList,
currentScreenState = CreateScreenState.PASSKEY_INTRO,
if (hasDefault)
{CreateScreenState.CREATION_OPTION_SELECTION} else {CreateScreenState.PASSKEY_INTRO},
requestDisplayInfo,
if (hasDefault) {
ActiveEntry(defaultProvider, defaultProvider.createOptions.first())
} else null
)
}
@@ -150,38 +160,39 @@ class CredentialManagerRepo(
// TODO: below are prototype functionalities. To be removed for productionization.
private fun testCreateCredentialProviderList(): List<CreateCredentialProviderData> {
return listOf(
CreateCredentialProviderData.Builder("com.google/com.google.CredentialManagerService")
CreateCredentialProviderData
.Builder("com.google/com.google.CredentialManagerService")
.setSaveEntries(
listOf<Entry>(
newEntry("key1", "subkey-1", "elisa.beckett@gmail.com",
"Elisa Backett", "20 passwords and 7 passkeys saved"),
20, 7, 27, 10000),
newEntry("key1", "subkey-2", "elisa.work@google.com",
"Elisa Backett Work", "20 passwords and 7 passkeys saved"),
20, 7, 27, 11000),
)
)
.setActionChips(
listOf<Entry>(
newEntry("key2", "subkey-1", "Go to Settings", "",
"20 passwords and 7 passkeys saved"),
newEntry("key2", "subkey-2", "Switch Account", "",
"20 passwords and 7 passkeys saved"),
newEntry("key2", "subkey-1", "Go to Settings",
20, 7, 27, 20000),
newEntry("key2", "subkey-2", "Switch Account",
20, 7, 27, 21000),
),
)
.setIsDefaultProvider(true)
.setIsDefaultProvider(false)
.build(),
CreateCredentialProviderData.Builder("com.dashlane/com.dashlane.CredentialManagerService")
CreateCredentialProviderData
.Builder("com.dashlane/com.dashlane.CredentialManagerService")
.setSaveEntries(
listOf<Entry>(
newEntry("key1", "subkey-3", "elisa.beckett@dashlane.com",
"Elisa Backett", "20 passwords and 7 passkeys saved"),
20, 7, 27, 30000),
newEntry("key1", "subkey-4", "elisa.work@dashlane.com",
"Elisa Backett Work", "20 passwords and 7 passkeys saved"),
20, 7, 27, 31000),
)
).setActionChips(
listOf<Entry>(
newEntry("key2", "subkey-3", "Manage Accounts",
"Manage your accounts in the dashlane app",
"20 passwords and 7 passkeys saved"),
20, 7, 27, 32000),
),
).build(),
)
@@ -193,31 +204,30 @@ class CredentialManagerRepo(
.setCredentialEntries(
listOf<Entry>(
newEntry("key1", "subkey-1", "elisa.beckett@gmail.com",
"Elisa Backett", "20 passwords and 7 passkeys saved"),
20, 7, 27, 10000),
newEntry("key1", "subkey-2", "elisa.work@google.com",
"Elisa Backett Work", "20 passwords and 7 passkeys saved"),
20, 7, 27, 11000),
)
).setActionChips(
listOf<Entry>(
newEntry("key2", "subkey-1", "Go to Settings", "",
"20 passwords and 7 passkeys saved"),
newEntry("key2", "subkey-2", "Switch Account", "",
"20 passwords and 7 passkeys saved"),
newEntry("key2", "subkey-1", "Go to Settings",
20, 7, 27, 20000),
newEntry("key2", "subkey-2", "Switch Account",
20, 7, 27, 21000),
),
).build(),
GetCredentialProviderData.Builder("com.dashlane/com.dashlane.CredentialManagerService")
.setCredentialEntries(
listOf<Entry>(
newEntry("key1", "subkey-3", "elisa.beckett@dashlane.com",
"Elisa Backett", "20 passwords and 7 passkeys saved"),
20, 7, 27, 30000),
newEntry("key1", "subkey-4", "elisa.work@dashlane.com",
"Elisa Backett Work", "20 passwords and 7 passkeys saved"),
20, 7, 27, 31000),
)
).setActionChips(
listOf<Entry>(
newEntry("key2", "subkey-3", "Manage Accounts",
"Manage your accounts in the dashlane app",
"20 passwords and 7 passkeys saved"),
20, 7, 27, 40000),
),
).build(),
)
@@ -226,20 +236,32 @@ class CredentialManagerRepo(
private fun newEntry(
key: String,
subkey: String,
title: String,
subtitle: String,
usageData: String
providerDisplayName: String,
passwordCount: Int,
passkeyCount: Int,
totalCredentialCount: Int,
lastUsedTimeMillis: Long,
): Entry {
val slice = Slice.Builder(
Entry.CREDENTIAL_MANAGER_ENTRY_URI, SliceSpec(Entry.VERSION, 1)
)
.addText(title, null, listOf(Entry.HINT_TITLE))
.addText(subtitle, null, listOf(Entry.HINT_SUBTITLE))
.addText(
providerDisplayName, null, listOf(Entry.HINT_USER_PROVIDER_ACCOUNT_NAME))
.addIcon(
Icon.createWithResource(context, R.drawable.ic_passkey),
null,
listOf(Entry.HINT_ICON))
.addText(usageData, Slice.SUBTYPE_MESSAGE, listOf(Entry.HINT_SUBTITLE))
listOf(Entry.HINT_CREDENTIAL_TYPE_ICON))
.addIcon(
Icon.createWithResource(context, R.drawable.ic_profile),
null,
listOf(Entry.HINT_PROFILE_ICON))
.addInt(
passwordCount, null, listOf(Entry.HINT_PASSWORD_COUNT))
.addInt(
passkeyCount, null, listOf(Entry.HINT_PASSKEY_COUNT))
.addInt(
totalCredentialCount, null, listOf(Entry.HINT_TOTAL_CREDENTIAL_COUNT))
.addLong(lastUsedTimeMillis, null, listOf(Entry.HINT_LAST_USED_TIME_MILLIS))
.build()
return Entry(
key,

View File

@@ -23,6 +23,7 @@ import android.credentials.ui.CreateCredentialProviderData
import com.android.credentialmanager.createflow.CreateOptionInfo
import com.android.credentialmanager.getflow.CredentialOptionInfo
import com.android.credentialmanager.getflow.ProviderInfo
import com.android.credentialmanager.jetpack.provider.SaveEntryUi
/** Utility functions for converting CredentialManager data structures to or from UI formats. */
class GetFlowUtils {
@@ -59,8 +60,6 @@ class GetFlowUtils {
// TODO: remove fallbacks
icon = credentialEntryUi.icon?.loadDrawable(context)
?: context.getDrawable(R.drawable.ic_passkey)!!,
title = credentialEntryUi.userName.toString(),
subtitle = credentialEntryUi.displayName?.toString() ?: "Unknown display name",
entryKey = it.key,
entrySubkey = it.subkey,
usageData = credentialEntryUi.usageData?.toString() ?: "Unknown usageData",
@@ -82,9 +81,7 @@ class CreateFlowUtils {
// TODO: replace to extract from the service data structure when available
icon = context.getDrawable(R.drawable.ic_passkey)!!,
name = it.providerFlattenedComponentName,
// TODO: get the service display name and icon from the component name.
displayName = it.providerFlattenedComponentName,
credentialTypeIcon = context.getDrawable(R.drawable.ic_passkey)!!,
createOptions = toCreationOptionInfoList(it.saveEntries, context),
isDefault = it.isDefaultProvider,
)
@@ -100,13 +97,17 @@ class CreateFlowUtils {
return@map CreateOptionInfo(
// TODO: remove fallbacks
icon = saveEntryUi.icon?.loadDrawable(context)
?: context.getDrawable(R.drawable.ic_passkey)!!,
title = saveEntryUi.title.toString(),
subtitle = saveEntryUi.subTitle?.toString() ?: "Unknown subtitle",
entryKey = it.key,
entrySubkey = it.subkey,
usageData = saveEntryUi.usageData?.toString() ?: "Unknown usageData",
userProviderDisplayName = saveEntryUi.userProviderAccountName as String,
credentialTypeIcon = saveEntryUi.credentialTypeIcon?.loadDrawable(context)
?: context.getDrawable(R.drawable.ic_passkey)!!,
profileIcon = saveEntryUi.profileIcon?.loadDrawable(context)
?: context.getDrawable(R.drawable.ic_profile)!!,
passwordCount = saveEntryUi.passwordCount ?: 0,
passkeyCount = saveEntryUi.passkeyCount ?: 0,
totalCredentialCount = saveEntryUi.totalCredentialCount ?: 0,
lastUsedTimeMillis = saveEntryUi.lastUsedTimeMillis ?: 0,
)
}
}

View File

@@ -1,59 +0,0 @@
/*
* Copyright (C) 2022 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.app.slice.Slice
import android.credentials.ui.Entry
import android.graphics.drawable.Icon
/**
* UI representation for a save entry used during the create credential flow.
*
* TODO: move to jetpack.
*/
class SaveEntryUi(
val title: CharSequence,
val subTitle: CharSequence?,
val icon: Icon?,
val usageData: CharSequence?,
// TODO: add
) {
companion object {
fun fromSlice(slice: Slice): SaveEntryUi {
val items = slice.items
var title: String? = null
var subTitle: String? = null
var icon: Icon? = null
var usageData: String? = null
items.forEach {
if (it.hasHint(Entry.HINT_ICON)) {
icon = it.icon
} else if (it.hasHint(Entry.HINT_SUBTITLE) && it.subType == null) {
subTitle = it.text.toString()
} else if (it.hasHint(Entry.HINT_TITLE)) {
title = it.text.toString()
} else if (it.hasHint(Entry.HINT_SUBTITLE) && it.subType == Slice.SUBTYPE_MESSAGE) {
usageData = it.text.toString()
}
}
// TODO: fail NPE more elegantly.
return SaveEntryUi(title!!, subTitle, icon, usageData)
}
}
}

View File

@@ -22,20 +22,27 @@ data class ProviderInfo(
val icon: Drawable,
val name: String,
val displayName: String,
val credentialTypeIcon: Drawable,
val createOptions: List<CreateOptionInfo>,
val isDefault: Boolean,
)
data class CreateOptionInfo(
val icon: Drawable,
val title: String,
val subtitle: String,
open class EntryInfo (
val entryKey: String,
val entrySubkey: String,
val usageData: String
)
class CreateOptionInfo(
entryKey: String,
entrySubkey: String,
val userProviderDisplayName: String,
val credentialTypeIcon: Drawable,
val profileIcon: Drawable,
val passwordCount: Int,
val passkeyCount: Int,
val totalCredentialCount: Int,
val lastUsedTimeMillis: Long?,
) : EntryInfo(entryKey, entrySubkey)
data class RequestDisplayInfo(
val userName: String,
val displayName: String,
@@ -49,7 +56,7 @@ data class RequestDisplayInfo(
*/
data class ActiveEntry (
val activeProvider: ProviderInfo,
val activeCreateOptionInfo: CreateOptionInfo,
val activeEntryInfo: EntryInfo,
)
/** The name of the current screen. */

View File

@@ -74,7 +74,7 @@ fun CreatePasskeyScreen(
CreateScreenState.CREATION_OPTION_SELECTION -> CreationSelectionCard(
requestDisplayInfo = uiState.requestDisplayInfo,
providerInfo = uiState.activeEntry?.activeProvider!!,
createOptionInfo = uiState.activeEntry.activeCreateOptionInfo,
createOptionInfo = uiState.activeEntry.activeEntryInfo as CreateOptionInfo,
onOptionSelected = viewModel::onPrimaryCreateOptionInfoSelected,
onConfirm = viewModel::onPrimaryCreateOptionInfoSelected,
onCancel = viewModel::onCancel,
@@ -408,7 +408,7 @@ fun CreationSelectionCard(
) {
Column() {
Icon(
bitmap = providerInfo.credentialTypeIcon.toBitmap().asImageBitmap(),
bitmap = createOptionInfo.credentialTypeIcon.toBitmap().asImageBitmap(),
contentDescription = null,
tint = Color.Unspecified,
modifier = Modifier.align(alignment = Alignment.CenterHorizontally).padding(all = 24.dp)
@@ -436,12 +436,12 @@ fun CreationSelectionCard(
text = stringResource(
R.string.choose_create_option_description,
when (requestDisplayInfo.type) {
TYPE_PUBLIC_KEY_CREDENTIAL -> "passkeys"
TYPE_PASSWORD_CREDENTIAL -> "passwords"
else -> "sign-ins"
TYPE_PUBLIC_KEY_CREDENTIAL -> stringResource(R.string.passkeys)
TYPE_PASSWORD_CREDENTIAL -> stringResource(R.string.passwords)
else -> stringResource(R.string.sign_ins)
},
providerInfo.displayName,
createOptionInfo.title),
createOptionInfo.userProviderDisplayName),
style = Typography.body1,
modifier = Modifier.padding(all = 24.dp).align(alignment = Alignment.CenterHorizontally)
)
@@ -455,8 +455,11 @@ fun CreationSelectionCard(
verticalArrangement = Arrangement.spacedBy(2.dp)
) {
item {
PrimaryCreateOptionRow(requestDisplayInfo = requestDisplayInfo,
onOptionSelected = onOptionSelected)
PrimaryCreateOptionRow(
requestDisplayInfo = requestDisplayInfo,
createOptionInfo = createOptionInfo,
onOptionSelected = onOptionSelected
)
}
}
}
@@ -506,12 +509,17 @@ fun CreationSelectionCard(
@Composable
fun PrimaryCreateOptionRow(
requestDisplayInfo: RequestDisplayInfo,
createOptionInfo: CreateOptionInfo,
onOptionSelected: () -> Unit
) {
Chip(
modifier = Modifier.fillMaxWidth(),
onClick = onOptionSelected,
// TODO: Add an icon generated by provider according to requestDisplayInfo type
leadingIcon = {
Image(modifier = Modifier.size(24.dp, 24.dp).padding(start = 10.dp),
bitmap = createOptionInfo.credentialTypeIcon.toBitmap().asImageBitmap(),
contentDescription = stringResource(R.string.createOptionInfo_icon_description))
},
colors = ChipDefaults.chipColors(
backgroundColor = Grey100,
leadingIconContentColor = Grey100
@@ -545,7 +553,7 @@ fun MoreOptionsInfoRow(
onClick = onOptionSelected,
leadingIcon = {
Image(modifier = Modifier.size(24.dp, 24.dp).padding(start = 10.dp),
bitmap = createOptionInfo.icon.toBitmap().asImageBitmap(),
bitmap = createOptionInfo.profileIcon.toBitmap().asImageBitmap(),
// painter = painterResource(R.drawable.ic_passkey),
// TODO: add description.
contentDescription = "")
@@ -561,14 +569,15 @@ fun MoreOptionsInfoRow(
text =
if (providerInfo.createOptions.size > 1)
{stringResource(R.string.more_options_title_multiple_options,
providerInfo.displayName, createOptionInfo.title)} else {
providerInfo.displayName, createOptionInfo.userProviderDisplayName)} else {
stringResource(R.string.more_options_title_one_option,
providerInfo.displayName)},
style = Typography.h6,
modifier = Modifier.padding(top = 16.dp)
)
Text(
text = createOptionInfo.usageData,
text = stringResource(R.string.more_options_usage_data,
createOptionInfo.passwordCount, createOptionInfo.passkeyCount),
style = Typography.body2,
modifier = Modifier.padding(bottom = 16.dp)
)

View File

@@ -73,21 +73,6 @@ class CreatePasskeyViewModel(
)
}
fun onCreateOptionSelected(entryKey: String, entrySubkey: String) {
Log.d(
"Account Selector",
"Option selected for creation: {key = $entryKey, subkey = $entrySubkey}"
)
CredentialManagerRepo.getInstance().onOptionSelected(
uiState.activeEntry?.activeProvider!!.name,
entryKey,
entrySubkey
)
dialogResult.value = DialogResult(
ResultState.COMPLETE,
)
}
fun getProviderInfoByName(providerName: String): ProviderInfo {
return uiState.providers.single {
it.name.equals(providerName)
@@ -126,18 +111,18 @@ class CreatePasskeyViewModel(
}
fun onPrimaryCreateOptionInfoSelected() {
var createOptionEntryKey = uiState.activeEntry?.activeCreateOptionInfo?.entryKey
var createOptionEntrySubkey = uiState.activeEntry?.activeCreateOptionInfo?.entrySubkey
val entryKey = uiState.activeEntry?.activeEntryInfo?.entryKey
val entrySubkey = uiState.activeEntry?.activeEntryInfo?.entrySubkey
Log.d(
"Account Selector",
"Option selected for creation: " +
"{key = $createOptionEntryKey, subkey = $createOptionEntrySubkey}"
"{key = $entryKey, subkey = $entrySubkey}"
)
if (createOptionEntryKey != null && createOptionEntrySubkey != null) {
if (entryKey != null && entrySubkey != null) {
CredentialManagerRepo.getInstance().onOptionSelected(
uiState.activeEntry?.activeProvider!!.name,
createOptionEntryKey,
createOptionEntrySubkey
entryKey,
entrySubkey
)
} else {
TODO("Gracefully handle illegal state.")

View File

@@ -184,12 +184,12 @@ fun CredentialOptionRow(
) {
Column() {
Text(
text = credentialOptionInfo.title,
text = credentialOptionInfo.entryKey,
style = Typography.h6,
modifier = Modifier.padding(top = 16.dp)
)
Text(
text = credentialOptionInfo.subtitle,
text = credentialOptionInfo.entrySubkey,
style = Typography.body2,
modifier = Modifier.padding(bottom = 16.dp)
)

View File

@@ -24,17 +24,21 @@ data class ProviderInfo(
val displayName: String,
val credentialTypeIcon: Drawable,
val credentialOptions: List<CredentialOptionInfo>,
// TODO: Add the authenticationOption
)
data class CredentialOptionInfo(
val icon: Drawable,
val title: String,
val subtitle: String,
open class EntryInfo (
val entryKey: String,
val entrySubkey: String,
val usageData: String
)
class CredentialOptionInfo(
entryKey: String,
entrySubkey: String,
val icon: Drawable,
val usageData: String,
) : EntryInfo(entryKey, entrySubkey)
data class RequestDisplayInfo(
val userName: String,
val displayName: String,