From b78f3eed0e3d6d04ce7349b1f132302126cf5fb9 Mon Sep 17 00:00:00 2001 From: Qinmei Du Date: Thu, 10 Nov 2022 07:45:56 +0000 Subject: [PATCH 1/2] Add the Another device row in the more options screen screencast: https://screencast.googleplex.com/cast/NTEwMDE0ODM2MDIxNjU3NnxmODFmOGRjMy0zMw Test: deployed locally Bug: 253157211 Change-Id: Ib0572d4b1b0e1f576b1cc748252782f382f8a4b1 --- .../ui/CreateCredentialProviderData.java | 7 ++ .../res/drawable/ic_other_devices.xml | 15 ++++ .../CredentialManager/res/values/strings.xml | 1 + .../CredentialManagerRepo.kt | 16 ++++ .../credentialmanager/DataConverter.kt | 14 ++++ .../createflow/CreateModel.kt | 6 ++ .../createflow/CreatePasskeyComponents.kt | 82 +++++++++++++++---- .../createflow/CreatePasskeyViewModel.kt | 6 +- 8 files changed, 130 insertions(+), 17 deletions(-) create mode 100644 packages/CredentialManager/res/drawable/ic_other_devices.xml diff --git a/core/java/android/credentials/ui/CreateCredentialProviderData.java b/core/java/android/credentials/ui/CreateCredentialProviderData.java index 98157d73ad427..044427894d016 100644 --- a/core/java/android/credentials/ui/CreateCredentialProviderData.java +++ b/core/java/android/credentials/ui/CreateCredentialProviderData.java @@ -131,6 +131,13 @@ public class CreateCredentialProviderData extends ProviderData implements Parcel return this; } + /** Sets the remote entry of the provider. */ + @NonNull + public Builder setRemoteEntry(@Nullable Entry remoteEntry) { + mRemoteEntry = remoteEntry; + return this; + } + /** Builds a {@link CreateCredentialProviderData}. */ @NonNull public CreateCredentialProviderData build() { diff --git a/packages/CredentialManager/res/drawable/ic_other_devices.xml b/packages/CredentialManager/res/drawable/ic_other_devices.xml new file mode 100644 index 0000000000000..754648cbca1df --- /dev/null +++ b/packages/CredentialManager/res/drawable/ic_other_devices.xml @@ -0,0 +1,15 @@ + + + \ No newline at end of file diff --git a/packages/CredentialManager/res/values/strings.xml b/packages/CredentialManager/res/values/strings.xml index cdd1a8964b2b4..70376e4ca696b 100644 --- a/packages/CredentialManager/res/values/strings.xml +++ b/packages/CredentialManager/res/values/strings.xml @@ -28,6 +28,7 @@ passkey password sign-ins + Another device Other password manager "Close sheet" diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt index b71af2b654e4b..0d5009a3bf800 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt @@ -181,6 +181,9 @@ class CredentialManagerRepo( 20, 7, 27, 11000), ) ) + .setRemoteEntry( + newRemoteEntry("key1", "subkey-1") + ) .setIsDefaultProvider(true) .build(), CreateCredentialProviderData @@ -308,6 +311,19 @@ class CredentialManagerRepo( ) } + private fun newRemoteEntry( + key: String, + subkey: String, + ): Entry { + return Entry( + key, + subkey, + Slice.Builder( + Entry.CREDENTIAL_MANAGER_ENTRY_URI, SliceSpec(Entry.VERSION, 1) + ).build() + ) + } + private fun testCreateRequestInfo(): RequestInfo { val data = Bundle() return RequestInfo.newCreateRequestInfo( diff --git a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt index 53e817e1a03ab..1e43d541ac131 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt @@ -23,6 +23,7 @@ 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.createflow.RemoteInfo import com.android.credentialmanager.getflow.ActionEntryInfo import com.android.credentialmanager.getflow.AuthenticationEntryInfo import com.android.credentialmanager.getflow.CredentialEntryInfo @@ -120,6 +121,7 @@ class CreateFlowUtils { displayName = pkgInfo.applicationInfo.loadLabel(packageManager).toString(), createOptions = toCreationOptionInfoList(it.saveEntries, context), isDefault = it.isDefaultProvider, + remoteEntry = toRemoteInfo(it.remoteEntry), ) } } @@ -164,5 +166,17 @@ class CreateFlowUtils { ) } } + + private fun toRemoteInfo( + remoteEntry: Entry?, + ): RemoteInfo? { + // TODO: should also call fromSlice after getting the official jetpack code. + return if (remoteEntry != null) { + RemoteInfo( + entryKey = remoteEntry.key, + entrySubkey = remoteEntry.subkey, + ) + } else null + } } } diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt index 0c3447f02b147..123c3d454905d 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt @@ -30,6 +30,7 @@ class EnabledProviderInfo( displayName: String, var createOptions: List, val isDefault: Boolean, + var remoteEntry: RemoteInfo?, ) : ProviderInfo(icon, name, displayName) class DisabledProviderInfo( @@ -55,6 +56,11 @@ class CreateOptionInfo( val lastUsedTimeMillis: Long?, ) : EntryInfo(entryKey, entrySubkey) +class RemoteInfo( + entryKey: String, + entrySubkey: String, +) : EntryInfo(entryKey, entrySubkey) + data class RequestDisplayInfo( val userName: String, val displayName: String, diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt index 4769a26b8eb18..e1313e8b565f1 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt @@ -80,7 +80,8 @@ fun CreatePasskeyScreen( disabledProviderList = uiState.disabledProviders, onBackButtonSelected = viewModel::onBackButtonSelected, onOptionSelected = viewModel::onMoreOptionsRowSelected, - onDisabledPasswordManagerSelected = viewModel::onDisabledPasswordManagerSelected + onDisabledPasswordManagerSelected = viewModel::onDisabledPasswordManagerSelected, + onRemoteEntrySelected = viewModel::onRemoteEntrySelected ) CreateScreenState.MORE_OPTIONS_ROW_INTRO -> MoreOptionsRowIntroCard( providerInfo = uiState.activeEntry?.activeProvider!!, @@ -216,10 +217,11 @@ fun ProviderSelectionCard( fun MoreOptionsSelectionCard( requestDisplayInfo: RequestDisplayInfo, enabledProviderList: List, - disabledProviderList: List, + disabledProviderList: List?, onBackButtonSelected: () -> Unit, onOptionSelected: (ActiveEntry) -> Unit, onDisabledPasswordManagerSelected: () -> Unit, + onRemoteEntrySelected: () -> Unit, ) { Card() { Column() { @@ -267,11 +269,26 @@ fun MoreOptionsSelectionCard( } } } - item { - MoreOptionsDisabledProvidersRow( - disabledProviders = disabledProviderList, - onDisabledPasswordManagerSelected = onDisabledPasswordManagerSelected, - ) + if (disabledProviderList != null) { + item { + MoreOptionsDisabledProvidersRow( + disabledProviders = disabledProviderList, + onDisabledPasswordManagerSelected = onDisabledPasswordManagerSelected, + ) + } + } + var hasRemoteInfo = false + enabledProviderList.forEach { + if (it.remoteEntry != null) { + hasRemoteInfo = true + } + } + if (hasRemoteInfo) { + item { + RemoteEntryRow( + onRemoteEntrySelected = onRemoteEntrySelected, + ) + } } } } @@ -498,7 +515,7 @@ fun MoreOptionsInfoRow( modifier = Modifier.fillMaxWidth(), onClick = onOptionSelected, icon = { - Image(modifier = Modifier.size(32.dp, 32.dp).padding(start = 10.dp), + Image(modifier = Modifier.size(32.dp, 32.dp).padding(start = 16.dp), bitmap = providerInfo.icon.toBitmap().asImageBitmap(), contentDescription = null) }, @@ -508,12 +525,14 @@ fun MoreOptionsInfoRow( Text( text = providerInfo.displayName, style = MaterialTheme.typography.titleLarge, - modifier = Modifier.padding(top = 16.dp) + modifier = Modifier.padding(top = 16.dp, start = 16.dp) ) if (createOptionInfo.userProviderDisplayName != null) { Text( text = createOptionInfo.userProviderDisplayName, - style = MaterialTheme.typography.bodyMedium) + style = MaterialTheme.typography.bodyMedium, + modifier = Modifier.padding(start = 16.dp) + ) } if (createOptionInfo.passwordCount != null && createOptionInfo.passkeyCount != null) { Text( @@ -524,7 +543,7 @@ fun MoreOptionsInfoRow( createOptionInfo.passkeyCount ), style = MaterialTheme.typography.bodyMedium, - modifier = Modifier.padding(bottom = 16.dp) + modifier = Modifier.padding(bottom = 16.dp, start = 16.dp) ) } else if (createOptionInfo.passwordCount != null) { Text( @@ -534,7 +553,7 @@ fun MoreOptionsInfoRow( createOptionInfo.passwordCount ), style = MaterialTheme.typography.bodyMedium, - modifier = Modifier.padding(bottom = 16.dp) + modifier = Modifier.padding(bottom = 16.dp, start = 16.dp) ) } else if (createOptionInfo.passkeyCount != null) { Text( @@ -544,7 +563,7 @@ fun MoreOptionsInfoRow( createOptionInfo.passkeyCount ), style = MaterialTheme.typography.bodyMedium, - modifier = Modifier.padding(bottom = 16.dp) + modifier = Modifier.padding(bottom = 16.dp, start = 16.dp) ) } else if (createOptionInfo.totalCredentialCount != null) { // TODO: Handle the case when there is total count @@ -567,7 +586,8 @@ fun MoreOptionsDisabledProvidersRow( icon = { Icon( Icons.Filled.Add, - contentDescription = null + contentDescription = null, + modifier = Modifier.padding(start = 16.dp) ) }, shape = MaterialTheme.shapes.large, @@ -576,12 +596,42 @@ fun MoreOptionsDisabledProvidersRow( Text( text = stringResource(R.string.other_password_manager), style = MaterialTheme.typography.titleLarge, - modifier = Modifier.padding(top = 16.dp) + modifier = Modifier.padding(top = 16.dp, start = 16.dp) ) Text( text = disabledProviders.joinToString(separator = ", "){ it.displayName }, style = MaterialTheme.typography.bodyMedium, - modifier = Modifier.padding(bottom = 16.dp) + modifier = Modifier.padding(bottom = 16.dp, start = 16.dp) + ) + } + } + ) +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun RemoteEntryRow( + onRemoteEntrySelected: () -> Unit, +) { + SuggestionChip( + modifier = Modifier.fillMaxWidth(), + onClick = onRemoteEntrySelected, + icon = { + Icon( + painter = painterResource(R.drawable.ic_other_devices), + contentDescription = null, + tint = Color.Unspecified, + modifier = Modifier.padding(start = 18.dp) + ) + }, + shape = MaterialTheme.shapes.large, + label = { + Column() { + Text( + text = stringResource(R.string.another_device), + style = MaterialTheme.typography.titleLarge, + modifier = Modifier.padding(start = 16.dp, top = 18.dp, bottom = 18.dp) + .align(alignment = Alignment.CenterHorizontally) ) } } diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyViewModel.kt index 8b94201ad787d..af74b8ea4de16 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyViewModel.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyViewModel.kt @@ -29,7 +29,7 @@ import com.android.credentialmanager.common.ResultState data class CreatePasskeyUiState( val enabledProviders: List, - val disabledProviders: List, + val disabledProviders: List? = null, val currentScreenState: CreateScreenState, val requestDisplayInfo: RequestDisplayInfo, val activeEntry: ActiveEntry? = null, @@ -103,6 +103,10 @@ class CreatePasskeyViewModel( // TODO: Complete this function } + fun onRemoteEntrySelected() { + // TODO: Complete this function + } + fun onCancel() { CredentialManagerRepo.getInstance().onCancel() dialogResult.value = DialogResult(ResultState.CANCELED) From df17e143d39d8b6fefe54ae3a1300e76abb97c80 Mon Sep 17 00:00:00 2001 From: Qinmei Du Date: Thu, 10 Nov 2022 09:01:18 +0000 Subject: [PATCH 2/2] Update the intro screen after select on a more options row screenshot: https://screenshot.googleplex.com/jCnd2ZamuTFDnNZ Test: deployed locally Bug: 253157211 Change-Id: Ie96bf43dea6a2380f123b084190c541c5c7fcc08 --- packages/CredentialManager/res/values/strings.xml | 2 ++ .../createflow/CreatePasskeyComponents.kt | 13 +++++++++++++ 2 files changed, 15 insertions(+) diff --git a/packages/CredentialManager/res/values/strings.xml b/packages/CredentialManager/res/values/strings.xml index 70376e4ca696b..d47e3f49242c2 100644 --- a/packages/CredentialManager/res/values/strings.xml +++ b/packages/CredentialManager/res/values/strings.xml @@ -30,6 +30,8 @@ sign-ins Another device Other password manager + + This password manager will store your passwords and passkeys to help you easily sign in. "Close sheet" diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt index e1313e8b565f1..67b704f5d787c 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.outlined.NewReleases import androidx.compose.material.icons.filled.Add import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -309,9 +310,21 @@ fun MoreOptionsRowIntroCard( ) { Card() { Column() { + Icon( + Icons.Outlined.NewReleases, + contentDescription = null, + modifier = Modifier.align(alignment = Alignment.CenterHorizontally).padding(all = 24.dp) + ) Text( text = stringResource(R.string.use_provider_for_all_title, providerInfo.displayName), style = MaterialTheme.typography.titleMedium, + modifier = Modifier.padding(horizontal = 24.dp) + .align(alignment = Alignment.CenterHorizontally), + textAlign = TextAlign.Center, + ) + Text( + text = stringResource(R.string.confirm_default_or_use_once_description), + style = MaterialTheme.typography.bodyLarge, modifier = Modifier.padding(all = 24.dp).align(alignment = Alignment.CenterHorizontally) ) Row(