Merge "Add choose other password manager row in the more options screen"

This commit is contained in:
Qinmei Du
2022-11-10 18:02:51 +00:00
committed by Android (Google) Code Review
6 changed files with 137 additions and 41 deletions

View File

@@ -28,6 +28,7 @@
<string name="passkeys">passkeys</string>
<string name="passwords">passwords</string>
<string name="sign_ins">sign-ins</string>
<string name="other_password_manager">Other password manager</string>
<string name="createOptionInfo_icon_description">CreateOptionInfo credentialType icon</string>
<!-- Spoken content description of an element which will close the sheet when clicked. -->
<string name="close_sheet">"Close sheet"</string>

View File

@@ -28,6 +28,7 @@ import android.credentials.ui.Constants
import android.credentials.ui.Entry
import android.credentials.ui.CreateCredentialProviderData
import android.credentials.ui.GetCredentialProviderData
import android.credentials.ui.DisabledProviderData
import android.credentials.ui.ProviderData
import android.credentials.ui.RequestInfo
import android.credentials.ui.BaseDialogResult
@@ -39,7 +40,7 @@ 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.EnabledProviderInfo
import com.android.credentialmanager.createflow.RequestDisplayInfo
import com.android.credentialmanager.getflow.GetCredentialUiState
import com.android.credentialmanager.getflow.GetScreenState
@@ -51,7 +52,8 @@ class CredentialManagerRepo(
intent: Intent,
) {
private val requestInfo: RequestInfo
private val providerList: List<ProviderData>
private val providerEnabledList: List<ProviderData>
private val providerDisabledList: List<DisabledProviderData>
// TODO: require non-null.
val resultReceiver: ResultReceiver?
@@ -61,16 +63,16 @@ class CredentialManagerRepo(
RequestInfo::class.java
) ?: testCreateRequestInfo()
providerList = when (requestInfo.type) {
providerEnabledList = when (requestInfo.type) {
RequestInfo.TYPE_CREATE ->
intent.extras?.getParcelableArrayList(
ProviderData.EXTRA_ENABLED_PROVIDER_DATA_LIST,
CreateCredentialProviderData::class.java
) ?: testCreateCredentialProviderList()
) ?: testCreateCredentialEnabledProviderList()
RequestInfo.TYPE_GET ->
intent.extras?.getParcelableArrayList(
ProviderData.EXTRA_ENABLED_PROVIDER_DATA_LIST,
GetCredentialProviderData::class.java
DisabledProviderData::class.java
) ?: testGetCredentialProviderList()
else -> {
// TODO: fail gracefully
@@ -78,6 +80,12 @@ class CredentialManagerRepo(
}
}
providerDisabledList =
intent.extras?.getParcelableArrayList(
ProviderData.EXTRA_DISABLED_PROVIDER_DATA_LIST,
DisabledProviderData::class.java
) ?: testDisabledProviderList()
resultReceiver = intent.getParcelableExtra(
Constants.EXTRA_RESULT_RECEIVER,
ResultReceiver::class.java
@@ -103,25 +111,28 @@ class CredentialManagerRepo(
}
fun getCredentialInitialUiState(): GetCredentialUiState {
val providerList = GetFlowUtils.toProviderList(
val providerEnabledList = GetFlowUtils.toProviderList(
// TODO: handle runtime cast error
providerList as List<GetCredentialProviderData>, context)
providerEnabledList as List<GetCredentialProviderData>, context)
// TODO: covert from real requestInfo
val requestDisplayInfo = com.android.credentialmanager.getflow.RequestDisplayInfo("tribank")
return GetCredentialUiState(
providerList,
providerEnabledList,
GetScreenState.PRIMARY_SELECTION,
requestDisplayInfo,
)
}
fun createPasskeyInitialUiState(): CreatePasskeyUiState {
val providerList = CreateFlowUtils.toProviderList(
val providerEnabledList = CreateFlowUtils.toEnabledProviderList(
// Handle runtime cast error
providerList as List<CreateCredentialProviderData>, context)
providerEnabledList as List<CreateCredentialProviderData>, context)
val providerDisabledList = CreateFlowUtils.toDisabledProviderList(
// Handle runtime cast error
providerDisabledList as List<DisabledProviderData>, context)
var hasDefault = false
var defaultProvider: ProviderInfo = providerList.first()
providerList.forEach{providerInfo -> providerInfo.createOptions =
var defaultProvider: EnabledProviderInfo = providerEnabledList.first()
providerEnabledList.forEach{providerInfo -> providerInfo.createOptions =
providerInfo.createOptions.sortedWith(compareBy { it.lastUsedTimeMillis }).reversed()
if (providerInfo.isDefault) {hasDefault = true; defaultProvider = providerInfo} }
// TODO: covert from real requestInfo
@@ -131,7 +142,8 @@ class CredentialManagerRepo(
TYPE_PUBLIC_KEY_CREDENTIAL,
"tribank")
return CreatePasskeyUiState(
providers = providerList,
enabledProviders = providerEnabledList,
disabledProviders = providerDisabledList,
if (hasDefault)
{CreateScreenState.CREATION_OPTION_SELECTION} else {CreateScreenState.PASSKEY_INTRO},
requestDisplayInfo,
@@ -157,7 +169,7 @@ class CredentialManagerRepo(
}
// TODO: below are prototype functionalities. To be removed for productionization.
private fun testCreateCredentialProviderList(): List<CreateCredentialProviderData> {
private fun testCreateCredentialEnabledProviderList(): List<CreateCredentialProviderData> {
return listOf(
CreateCredentialProviderData
.Builder("com.google/com.google.CredentialManagerService")
@@ -185,6 +197,13 @@ class CredentialManagerRepo(
)
}
private fun testDisabledProviderList(): List<DisabledProviderData> {
return listOf(
DisabledProviderData("LastPass"),
DisabledProviderData("Xyzinstalledbutdisabled"),
)
}
private fun testGetCredentialProviderList(): List<GetCredentialProviderData> {
return listOf(
GetCredentialProviderData.Builder("com.google/com.google.CredentialManagerService")

View File

@@ -20,6 +20,7 @@ import android.content.Context
import android.credentials.ui.Entry
import android.credentials.ui.GetCredentialProviderData
import android.credentials.ui.CreateCredentialProviderData
import android.credentials.ui.DisabledProviderData
import com.android.credentialmanager.createflow.CreateOptionInfo
import com.android.credentialmanager.getflow.ActionEntryInfo
import com.android.credentialmanager.getflow.AuthenticationEntryInfo
@@ -103,12 +104,12 @@ class GetFlowUtils {
class CreateFlowUtils {
companion object {
fun toProviderList(
fun toEnabledProviderList(
providerDataList: List<CreateCredentialProviderData>,
context: Context,
): List<com.android.credentialmanager.createflow.ProviderInfo> {
): List<com.android.credentialmanager.createflow.EnabledProviderInfo> {
return providerDataList.map {
com.android.credentialmanager.createflow.ProviderInfo(
com.android.credentialmanager.createflow.EnabledProviderInfo(
// TODO: replace to extract from the service data structure when available
icon = context.getDrawable(R.drawable.ic_passkey)!!,
name = it.providerFlattenedComponentName,
@@ -119,6 +120,20 @@ class CreateFlowUtils {
}
}
fun toDisabledProviderList(
providerDataList: List<DisabledProviderData>,
context: Context,
): List<com.android.credentialmanager.createflow.DisabledProviderInfo> {
return providerDataList.map {
com.android.credentialmanager.createflow.DisabledProviderInfo(
// TODO: replace to extract from the service data structure when available
icon = context.getDrawable(R.drawable.ic_passkey)!!,
name = it.providerFlattenedComponentName,
displayName = it.providerFlattenedComponentName,
)
}
}
private fun toCreationOptionInfoList(
creationEntries: List<Entry>,
context: Context,

View File

@@ -18,13 +18,25 @@ package com.android.credentialmanager.createflow
import android.graphics.drawable.Drawable
data class ProviderInfo(
open class ProviderInfo(
val icon: Drawable,
val name: String,
val displayName: String,
)
class EnabledProviderInfo(
icon: Drawable,
name: String,
displayName: String,
var createOptions: List<CreateOptionInfo>,
val isDefault: Boolean,
)
) : ProviderInfo(icon, name, displayName)
class DisabledProviderInfo(
icon: Drawable,
name: String,
displayName: String,
) : ProviderInfo(icon, name, displayName)
open class EntryInfo (
val entryKey: String,
@@ -55,7 +67,7 @@ data class RequestDisplayInfo(
* user selects a different entry on the more option page.
*/
data class ActiveEntry (
val activeProvider: ProviderInfo,
val activeProvider: EnabledProviderInfo,
val activeEntryInfo: EntryInfo,
)

View File

@@ -21,6 +21,7 @@ import androidx.compose.material3.TextButton
import androidx.compose.material3.TopAppBar
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material.icons.filled.Add
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Alignment
@@ -59,7 +60,7 @@ fun CreatePasskeyScreen(
onCancel = viewModel::onCancel,
)
CreateScreenState.PROVIDER_SELECTION -> ProviderSelectionCard(
providerList = uiState.providers,
enabledProviderList = uiState.enabledProviders,
onCancel = viewModel::onCancel,
onProviderSelected = viewModel::onProviderSelected
)
@@ -70,14 +71,16 @@ fun CreatePasskeyScreen(
onOptionSelected = viewModel::onPrimaryCreateOptionInfoSelected,
onConfirm = viewModel::onPrimaryCreateOptionInfoSelected,
onCancel = viewModel::onCancel,
multiProvider = uiState.providers.size > 1,
multiProvider = uiState.enabledProviders.size > 1,
onMoreOptionsSelected = viewModel::onMoreOptionsSelected
)
CreateScreenState.MORE_OPTIONS_SELECTION -> MoreOptionsSelectionCard(
requestDisplayInfo = uiState.requestDisplayInfo,
providerList = uiState.providers,
enabledProviderList = uiState.enabledProviders,
disabledProviderList = uiState.disabledProviders,
onBackButtonSelected = viewModel::onBackButtonSelected,
onOptionSelected = viewModel::onMoreOptionsRowSelected
onOptionSelected = viewModel::onMoreOptionsRowSelected,
onDisabledPasswordManagerSelected = viewModel::onDisabledPasswordManagerSelected
)
CreateScreenState.MORE_OPTIONS_ROW_INTRO -> MoreOptionsRowIntroCard(
providerInfo = uiState.activeEntry?.activeProvider!!,
@@ -153,7 +156,7 @@ fun ConfirmationCard(
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ProviderSelectionCard(
providerList: List<ProviderInfo>,
enabledProviderList: List<EnabledProviderInfo>,
onProviderSelected: (String) -> Unit,
onCancel: () -> Unit
) {
@@ -182,7 +185,7 @@ fun ProviderSelectionCard(
LazyColumn(
verticalArrangement = Arrangement.spacedBy(2.dp)
) {
providerList.forEach {
enabledProviderList.forEach {
item {
ProviderRow(providerInfo = it, onProviderSelected = onProviderSelected)
}
@@ -212,9 +215,11 @@ fun ProviderSelectionCard(
@Composable
fun MoreOptionsSelectionCard(
requestDisplayInfo: RequestDisplayInfo,
providerList: List<ProviderInfo>,
enabledProviderList: List<EnabledProviderInfo>,
disabledProviderList: List<DisabledProviderInfo>,
onBackButtonSelected: () -> Unit,
onOptionSelected: (ActiveEntry) -> Unit
onOptionSelected: (ActiveEntry) -> Unit,
onDisabledPasswordManagerSelected: () -> Unit,
) {
Card() {
Column() {
@@ -250,18 +255,24 @@ fun MoreOptionsSelectionCard(
LazyColumn(
verticalArrangement = Arrangement.spacedBy(2.dp)
) {
providerList.forEach { providerInfo ->
providerInfo.createOptions.forEach { createOptionInfo ->
enabledProviderList.forEach { enabledProviderInfo ->
enabledProviderInfo.createOptions.forEach { createOptionInfo ->
item {
MoreOptionsInfoRow(
providerInfo = providerInfo,
providerInfo = enabledProviderInfo,
createOptionInfo = createOptionInfo,
onOptionSelected = {
onOptionSelected(ActiveEntry(providerInfo, createOptionInfo))
onOptionSelected(ActiveEntry(enabledProviderInfo, createOptionInfo))
})
}
}
}
item {
MoreOptionsDisabledProvidersRow(
disabledProviders = disabledProviderList,
onDisabledPasswordManagerSelected = onDisabledPasswordManagerSelected,
)
}
}
}
Divider(
@@ -276,7 +287,7 @@ fun MoreOptionsSelectionCard(
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun MoreOptionsRowIntroCard(
providerInfo: ProviderInfo,
providerInfo: EnabledProviderInfo,
onDefaultOrNotSelected: () -> Unit,
) {
Card() {
@@ -483,7 +494,7 @@ fun PrimaryCreateOptionRow(
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun MoreOptionsInfoRow(
providerInfo: ProviderInfo,
providerInfo: EnabledProviderInfo,
createOptionInfo: CreateOptionInfo,
onOptionSelected: () -> Unit
) {
@@ -546,4 +557,37 @@ fun MoreOptionsInfoRow(
}
}
)
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun MoreOptionsDisabledProvidersRow(
disabledProviders: List<ProviderInfo>,
onDisabledPasswordManagerSelected: () -> Unit,
) {
SuggestionChip(
modifier = Modifier.fillMaxWidth(),
onClick = onDisabledPasswordManagerSelected,
icon = {
Icon(
Icons.Filled.Add,
contentDescription = null
)
},
shape = MaterialTheme.shapes.large,
label = {
Column() {
Text(
text = stringResource(R.string.other_password_manager),
style = MaterialTheme.typography.titleLarge,
modifier = Modifier.padding(top = 16.dp)
)
Text(
text = disabledProviders.joinToString(separator = ", "){ it.displayName },
style = MaterialTheme.typography.bodyMedium,
modifier = Modifier.padding(bottom = 16.dp)
)
}
}
)
}

View File

@@ -28,7 +28,8 @@ import com.android.credentialmanager.common.DialogResult
import com.android.credentialmanager.common.ResultState
data class CreatePasskeyUiState(
val providers: List<ProviderInfo>,
val enabledProviders: List<EnabledProviderInfo>,
val disabledProviders: List<DisabledProviderInfo>,
val currentScreenState: CreateScreenState,
val requestDisplayInfo: RequestDisplayInfo,
val activeEntry: ActiveEntry? = null,
@@ -50,15 +51,15 @@ class CreatePasskeyViewModel(
}
fun onConfirmIntro() {
if (uiState.providers.size > 1) {
if (uiState.enabledProviders.size > 1) {
uiState = uiState.copy(
currentScreenState = CreateScreenState.PROVIDER_SELECTION
)
} else if (uiState.providers.size == 1){
} else if (uiState.enabledProviders.size == 1){
uiState = uiState.copy(
currentScreenState = CreateScreenState.CREATION_OPTION_SELECTION,
activeEntry = ActiveEntry(uiState.providers.first(),
uiState.providers.first().createOptions.first())
activeEntry = ActiveEntry(uiState.enabledProviders.first(),
uiState.enabledProviders.first().createOptions.first())
)
} else {
throw java.lang.IllegalStateException("Empty provider list.")
@@ -73,8 +74,8 @@ class CreatePasskeyViewModel(
)
}
fun getProviderInfoByName(providerName: String): ProviderInfo {
return uiState.providers.single {
fun getProviderInfoByName(providerName: String): EnabledProviderInfo {
return uiState.enabledProviders.single {
it.name.equals(providerName)
}
}
@@ -98,6 +99,10 @@ class CreatePasskeyViewModel(
)
}
fun onDisabledPasswordManagerSelected() {
// TODO: Complete this function
}
fun onCancel() {
CredentialManagerRepo.getInstance().onCancel()
dialogResult.value = DialogResult(ResultState.CANCELED)