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..d47e3f49242c2 100644
--- a/packages/CredentialManager/res/values/strings.xml
+++ b/packages/CredentialManager/res/values/strings.xml
@@ -28,7 +28,10 @@
passkey
password
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/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..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
@@ -80,7 +81,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 +218,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 +270,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,
+ )
+ }
}
}
}
@@ -292,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(
@@ -498,7 +528,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 +538,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 +556,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 +566,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 +576,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 +599,8 @@ fun MoreOptionsDisabledProvidersRow(
icon = {
Icon(
Icons.Filled.Add,
- contentDescription = null
+ contentDescription = null,
+ modifier = Modifier.padding(start = 16.dp)
)
},
shape = MaterialTheme.shapes.large,
@@ -576,12 +609,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)