diff options
| author | 2022-11-09 11:28:16 +0000 | |
|---|---|---|
| committer | 2022-11-10 07:53:28 +0000 | |
| commit | 96d5bac21ab946f1effdc9cb2c781697caff4ea8 (patch) | |
| tree | 595b2c5b0e6fb5bb0f490225926b6a34064d63a1 | |
| parent | cef8c91bae32c83173c82ffb5bbe28fd79b16f95 (diff) | |
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
6 files changed, 137 insertions, 41 deletions
diff --git a/packages/CredentialManager/res/values/strings.xml b/packages/CredentialManager/res/values/strings.xml index 25fa34b418ab..1a852c51744a 100644 --- a/packages/CredentialManager/res/values/strings.xml +++ b/packages/CredentialManager/res/values/strings.xml @@ -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> diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt index 2099a235a3e8..6e4bfd8a6f1e 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt @@ -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 + providerEnabledList as List<CreateCredentialProviderData>, context) + val providerDisabledList = CreateFlowUtils.toDisabledProviderList( // Handle runtime cast error - providerList as List<CreateCredentialProviderData>, context) + 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") diff --git a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt index 5c7956423469..e4fab079651e 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt @@ -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, diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt index 21190e7dc8c4..0c3447f02b14 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt @@ -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, ) diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt index 437e7b213620..06e437c874c3 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt @@ -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) + ) + } + } + ) }
\ No newline at end of file diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyViewModel.kt index 2e9758aece33..8b94201ad787 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyViewModel.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyViewModel.kt @@ -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) |