Add choose other password manager row in the more options screen
screenshot: https://screenshot.googleplex.com/4LX6xuETvKHRgCv Test: deployed locally Bug: 253157211 Change-Id: Ia2f3ba2d9eb86dc2216b5f2fcafa5e8aace9e5f0
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
|
||||
@@ -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)
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user