diff options
6 files changed, 97 insertions, 10 deletions
diff --git a/packages/CredentialManager/res/values/strings.xml b/packages/CredentialManager/res/values/strings.xml index 2f6d1b4bea02..114de89ce82a 100644 --- a/packages/CredentialManager/res/values/strings.xml +++ b/packages/CredentialManager/res/values/strings.xml @@ -62,4 +62,8 @@ <string name="locked_credential_entry_label_subtext">Tap to unlock</string> <!-- Column heading for displaying action chips for managing sign-ins from each credential provider. [CHAR LIMIT=80] --> <string name="get_dialog_heading_manage_sign_ins">Manage sign-ins</string> + <!-- Column heading for displaying option to use sign-ins saved on a different device. [CHAR LIMIT=80] --> + <string name="get_dialog_heading_from_another_device">From another device</string> + <!-- Headline text for an option to use sign-ins saved on a different device. [CHAR LIMIT=120] --> + <string name="get_dialog_option_headline_use_a_different_device">Use a different device</string> </resources>
\ No newline at end of file diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt index b848a47f37de..7e69987f1bf8 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt @@ -194,7 +194,7 @@ class CredentialManagerRepo( ) ) .setRemoteEntry( - newRemoteEntry("key1", "subkey-1") + newRemoteEntry("key2", "subkey-1") ) .setIsDefaultProvider(true) .build(), @@ -252,6 +252,8 @@ class CredentialManagerRepo( "Open Google Password Manager", "beckett-family@gmail.com" ), ) + ).setRemoteEntry( + newRemoteEntry("key4", "subkey-1") ).build(), GetCredentialProviderData.Builder("com.dashlane") .setCredentialEntries( diff --git a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt index df797436baf6..fad9364e3c23 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt @@ -30,6 +30,7 @@ import com.android.credentialmanager.getflow.ActionEntryInfo import com.android.credentialmanager.getflow.AuthenticationEntryInfo import com.android.credentialmanager.getflow.CredentialEntryInfo import com.android.credentialmanager.getflow.ProviderInfo +import com.android.credentialmanager.getflow.RemoteEntryInfo import com.android.credentialmanager.jetpack.provider.ActionUi import com.android.credentialmanager.jetpack.provider.CredentialEntryUi import com.android.credentialmanager.jetpack.provider.SaveEntryUi @@ -59,10 +60,11 @@ class GetFlowUtils { credentialEntryList = getCredentialOptionInfoList( it.providerFlattenedComponentName, it.credentialEntries, context), authenticationEntry = getAuthenticationEntry( - it.providerFlattenedComponentName, - providerDisplayName, - providerIcon, - it.authenticationEntry), + it.providerFlattenedComponentName, + providerDisplayName, + providerIcon, + it.authenticationEntry), + remoteEntry = getRemoteEntry(it.providerFlattenedComponentName, it.remoteEntry), actionEntryList = getActionEntryList( it.providerFlattenedComponentName, it.actionChips, context), ) @@ -116,6 +118,18 @@ class GetFlowUtils { ) } + private fun getRemoteEntry(providerId: String, remoteEntry: Entry?): RemoteEntryInfo? { + // TODO: should also call fromSlice after getting the official jetpack code. + if (remoteEntry == null) { + return null + } + return RemoteEntryInfo( + providerId = providerId, + entryKey = remoteEntry.key, + entrySubkey = remoteEntry.subkey, + ) + } + private fun getActionEntryList( providerId: String, actionEntries: List<Entry>, diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt index 6fd51dd3b69a..19a032fcf4b8 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt @@ -45,6 +45,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.asImageBitmap +import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp @@ -220,16 +221,25 @@ fun AllSignInOptionCard( ) } // Locked password manager - item { - if (!authenticationEntryList.isEmpty()) { + if (!authenticationEntryList.isEmpty()) { + item { LockedCredentials( authenticationEntryList = authenticationEntryList, onEntrySelected = onEntrySelected, ) } } - // TODO: Remote action - // Manage sign-ins + // From another device + val remoteEntry = providerDisplayInfo.remoteEntry + if (remoteEntry != null) { + item { + RemoteEntryCard( + remoteEntry = remoteEntry, + onEntrySelected = onEntrySelected, + ) + } + } + // Manage sign-ins (action chips) item { ActionChips(providerInfoList = providerInfoList, onEntrySelected = onEntrySelected) } @@ -271,6 +281,47 @@ fun ActionChips( } @Composable +fun RemoteEntryCard( + remoteEntry: RemoteEntryInfo, + onEntrySelected: (EntryInfo) -> Unit, +) { + Text( + text = stringResource(R.string.get_dialog_heading_from_another_device), + style = MaterialTheme.typography.labelLarge, + modifier = Modifier.padding(vertical = 8.dp) + ) + Card( + modifier = Modifier.fillMaxWidth().wrapContentHeight(), + shape = MaterialTheme.shapes.medium, + ) { + Column( + modifier = Modifier.fillMaxWidth().wrapContentHeight(), + verticalArrangement = Arrangement.spacedBy(2.dp), + ) { + Entry( + onClick = {onEntrySelected(remoteEntry)}, + icon = { + Icon( + painter = painterResource(R.drawable.ic_other_devices), + contentDescription = null, + tint = Color.Unspecified, + modifier = Modifier.padding(start = 18.dp) + ) + }, + label = { + Text( + text = stringResource(R.string.get_dialog_option_headline_use_a_different_device), + style = MaterialTheme.typography.titleLarge, + modifier = Modifier.padding(start = 16.dp, top = 18.dp, bottom = 18.dp) + .align(alignment = Alignment.CenterHorizontally) + ) + } + ) + } + } +} + +@Composable fun LockedCredentials( authenticationEntryList: List<AuthenticationEntryInfo>, onEntrySelected: (EntryInfo) -> Unit, diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialViewModel.kt index f78456aab332..22370a91cde4 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialViewModel.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialViewModel.kt @@ -27,6 +27,7 @@ import com.android.credentialmanager.CredentialManagerRepo import com.android.credentialmanager.common.DialogResult import com.android.credentialmanager.common.ResultState import com.android.credentialmanager.jetpack.developer.PublicKeyCredential +import com.android.internal.util.Preconditions data class GetCredentialUiState( val providerInfoList: List<ProviderInfo>, @@ -86,10 +87,14 @@ private fun toProviderDisplayInfo( val userNameToCredentialEntryMap = mutableMapOf<String, MutableList<CredentialEntryInfo>>() val authenticationEntryList = mutableListOf<AuthenticationEntryInfo>() + val remoteEntryList = mutableListOf<RemoteEntryInfo>() providerInfoList.forEach { providerInfo -> if (providerInfo.authenticationEntry != null) { authenticationEntryList.add(providerInfo.authenticationEntry) } + if (providerInfo.remoteEntry != null) { + remoteEntryList.add(providerInfo.remoteEntry) + } providerInfo.credentialEntryList.forEach { userNameToCredentialEntryMap.compute( @@ -105,6 +110,9 @@ private fun toProviderDisplayInfo( } } } + // There can only be at most one remote entry + // TODO: fail elegantly + Preconditions.checkState(remoteEntryList.size <= 1) // Compose sortedUserNameToCredentialEntryList val comparator = CredentialEntryInfoComparator() @@ -122,6 +130,7 @@ private fun toProviderDisplayInfo( return ProviderDisplayInfo( sortedUserNameToCredentialEntryList = sortedUserNameToCredentialEntryList, authenticationEntryList = authenticationEntryList, + remoteEntry = remoteEntryList.getOrNull(0), ) } diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt index c541e08ffbb4..76d9847f5356 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt @@ -28,8 +28,8 @@ data class ProviderInfo( val displayName: String, val credentialEntryList: List<CredentialEntryInfo>, val authenticationEntry: AuthenticationEntryInfo?, + val remoteEntry: RemoteEntryInfo?, val actionEntryList: List<ActionEntryInfo>, - // TODO: add remote entry ) /** Display-centric data structure derived from the [ProviderInfo]. This abstraction is not grouping @@ -41,6 +41,7 @@ data class ProviderDisplayInfo( */ val sortedUserNameToCredentialEntryList: List<PerUserNameCredentialEntryList>, val authenticationEntryList: List<AuthenticationEntryInfo>, + val remoteEntry: RemoteEntryInfo? ) abstract class EntryInfo ( @@ -72,6 +73,12 @@ class AuthenticationEntryInfo( val icon: Drawable, ) : EntryInfo(providerId, entryKey, entrySubkey) +class RemoteEntryInfo( + providerId: String, + entryKey: String, + entrySubkey: String, +) : EntryInfo(providerId, entryKey, entrySubkey) + class ActionEntryInfo( providerId: String, entryKey: String, |