diff --git a/packages/CredentialManager/res/values/strings.xml b/packages/CredentialManager/res/values/strings.xml
index 870d9832ca0b2..a3ebf1efbb511 100644
--- a/packages/CredentialManager/res/values/strings.xml
+++ b/packages/CredentialManager/res/values/strings.xml
@@ -80,6 +80,8 @@
"Close sheet"
"Go back to the previous page"
+
+ "Close the Credential Manager action suggestion appearing at the bottom of the screen"
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
index f801ba61e0736..94aa8e81ff609 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
@@ -44,7 +44,6 @@ import com.android.credentialmanager.createflow.CreateCredentialUiState
import com.android.credentialmanager.createflow.EnabledProviderInfo
import com.android.credentialmanager.createflow.RemoteInfo
import com.android.credentialmanager.getflow.GetCredentialUiState
-import com.android.credentialmanager.getflow.GetScreenState
import com.android.credentialmanager.jetpack.developer.CreatePasswordRequest.Companion.toBundle
import com.android.credentialmanager.jetpack.developer.CreatePublicKeyCredentialRequest
import com.android.credentialmanager.jetpack.developer.PublicKeyCredential.Companion.TYPE_PUBLIC_KEY_CREDENTIAL
@@ -124,11 +123,9 @@ class CredentialManagerRepo(
val providerEnabledList = GetFlowUtils.toProviderList(
// TODO: handle runtime cast error
providerEnabledList as List, context)
- // TODO: covert from real requestInfo
- val requestDisplayInfo = com.android.credentialmanager.getflow.RequestDisplayInfo("the app")
+ val requestDisplayInfo = GetFlowUtils.toRequestDisplayInfo(requestInfo)
return GetCredentialUiState(
providerEnabledList,
- GetScreenState.PRIMARY_SELECTION,
requestDisplayInfo,
)
}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
index 0d7e81984eadb..f514b02f8abe2 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
@@ -88,6 +88,14 @@ class GetFlowUtils {
}
}
+ fun toRequestDisplayInfo(
+ requestInfo: RequestInfo,
+ ): com.android.credentialmanager.getflow.RequestDisplayInfo {
+ return com.android.credentialmanager.getflow.RequestDisplayInfo(
+ appDomainName = requestInfo.appPackageName
+ )
+ }
+
/* From service data structure to UI credential entry list representation. */
private fun getCredentialOptionInfoList(
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
index d6d71220e943f..7a015a841fae7 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
@@ -32,15 +32,19 @@ import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.ArrowBack
+import androidx.compose.material.icons.filled.Close
import androidx.compose.material3.Divider
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Snackbar
+import androidx.compose.material3.Text
+import androidx.compose.material3.TextButton
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Alignment
@@ -65,6 +69,7 @@ import com.android.credentialmanager.common.ui.ContainerCard
import com.android.credentialmanager.common.ui.TransparentBackgroundEntry
import com.android.credentialmanager.jetpack.developer.PublicKeyCredential
import com.android.credentialmanager.ui.theme.EntryShape
+import com.android.credentialmanager.ui.theme.LocalAndroidColorScheme
@Composable
fun GetCredentialScreen(
@@ -75,39 +80,50 @@ fun GetCredentialScreen(
initialValue = ModalBottomSheetValue.Expanded,
skipHalfExpanded = true
)
- ModalBottomSheetLayout(
- sheetBackgroundColor = MaterialTheme.colorScheme.surface,
- modifier = Modifier.background(Color.Transparent),
- sheetState = state,
- sheetContent = {
- val uiState = viewModel.uiState
- if (!uiState.hidden) {
- when (uiState.currentScreenState) {
- GetScreenState.PRIMARY_SELECTION -> PrimarySelectionCard(
- requestDisplayInfo = uiState.requestDisplayInfo,
- providerDisplayInfo = uiState.providerDisplayInfo,
- onEntrySelected = viewModel::onEntrySelected,
- onCancel = viewModel::onCancel,
- onMoreOptionSelected = viewModel::onMoreOptionSelected,
- )
- GetScreenState.ALL_SIGN_IN_OPTIONS -> AllSignInOptionCard(
- providerInfoList = uiState.providerInfoList,
- providerDisplayInfo = uiState.providerDisplayInfo,
- onEntrySelected = viewModel::onEntrySelected,
- onBackButtonClicked = viewModel::onBackToPrimarySelectionScreen,
- )
+ val uiState = viewModel.uiState
+ if (uiState.currentScreenState != GetScreenState.REMOTE_ONLY) {
+ ModalBottomSheetLayout(
+ sheetBackgroundColor = MaterialTheme.colorScheme.surface,
+ modifier = Modifier.background(Color.Transparent),
+ sheetState = state,
+ sheetContent = {
+ // TODO: hide UI at top level
+ if (!uiState.hidden) {
+ if (uiState.currentScreenState == GetScreenState.PRIMARY_SELECTION) {
+ PrimarySelectionCard(
+ requestDisplayInfo = uiState.requestDisplayInfo,
+ providerDisplayInfo = uiState.providerDisplayInfo,
+ onEntrySelected = viewModel::onEntrySelected,
+ onCancel = viewModel::onCancel,
+ onMoreOptionSelected = viewModel::onMoreOptionSelected,
+ )
+ } else {
+ AllSignInOptionCard(
+ providerInfoList = uiState.providerInfoList,
+ providerDisplayInfo = uiState.providerDisplayInfo,
+ onEntrySelected = viewModel::onEntrySelected,
+ onBackButtonClicked = viewModel::onBackToPrimarySelectionScreen,
+ onCancel = viewModel::onCancel,
+ isNoAccount = uiState.isNoAccount,
+ )
+ }
+ } else if (uiState.selectedEntry != null && !uiState.providerActivityPending) {
+ viewModel.launchProviderUi(providerActivityLauncher)
}
- } else if (uiState.selectedEntry != null && !uiState.providerActivityPending) {
- viewModel.launchProviderUi(providerActivityLauncher)
+ },
+ scrimColor = MaterialTheme.colorScheme.scrim.copy(alpha = 0.8f),
+ sheetShape = EntryShape.TopRoundedCorner,
+ ) {}
+ LaunchedEffect(state.currentValue) {
+ if (state.currentValue == ModalBottomSheetValue.Hidden) {
+ viewModel.onCancel()
}
- },
- scrimColor = MaterialTheme.colorScheme.scrim.copy(alpha = 0.8f),
- sheetShape = EntryShape.TopRoundedCorner,
- ) {}
- LaunchedEffect(state.currentValue) {
- if (state.currentValue == ModalBottomSheetValue.Hidden) {
- viewModel.onCancel()
}
+ } else {
+ SnackBarScreen(
+ onClick = viewModel::onMoreOptionOnSnackBarSelected,
+ onCancel = viewModel::onCancel,
+ )
}
}
@@ -195,6 +211,8 @@ fun AllSignInOptionCard(
providerDisplayInfo: ProviderDisplayInfo,
onEntrySelected: (EntryInfo) -> Unit,
onBackButtonClicked: () -> Unit,
+ onCancel: () -> Unit,
+ isNoAccount: Boolean,
) {
val sortedUserNameToCredentialEntryList =
providerDisplayInfo.sortedUserNameToCredentialEntryList
@@ -212,7 +230,7 @@ fun AllSignInOptionCard(
)
},
navigationIcon = {
- IconButton(onClick = onBackButtonClicked) {
+ IconButton(onClick = if (isNoAccount) onCancel else onBackButtonClicked) {
Icon(
Icons.Filled.ArrowBack,
contentDescription = stringResource(
@@ -518,3 +536,37 @@ fun SignInAnotherWayRow(onSelect: () -> Unit) {
}
)
}
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun SnackBarScreen(
+ onClick: (Boolean) -> Unit,
+ onCancel: () -> Unit,
+) {
+ // TODO: Change the height, width and position according to the design
+ Snackbar (
+ modifier = Modifier.padding(horizontal = 80.dp).padding(top = 700.dp),
+ shape = EntryShape.FullMediumRoundedCorner,
+ containerColor = LocalAndroidColorScheme.current.colorBackground,
+ contentColor = LocalAndroidColorScheme.current.colorAccentPrimaryVariant,
+ ) {
+ Row(
+ horizontalArrangement = Arrangement.SpaceBetween,
+ verticalAlignment = Alignment.CenterVertically,
+ ) {
+ TextButton(
+ onClick = {onClick(true)},
+ ) {
+ Text(text = stringResource(R.string.get_dialog_use_saved_passkey_for))
+ }
+ IconButton(onClick = onCancel) {
+ Icon(
+ Icons.Filled.Close,
+ contentDescription = stringResource(
+ R.string.accessibility_close_button
+ )
+ )
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialViewModel.kt
index 7b80124d618c8..fcf49db6935ad 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialViewModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialViewModel.kt
@@ -36,12 +36,13 @@ import com.android.internal.util.Preconditions
data class GetCredentialUiState(
val providerInfoList: List,
- val currentScreenState: GetScreenState,
val requestDisplayInfo: RequestDisplayInfo,
+ val currentScreenState: GetScreenState = toGetScreenState(providerInfoList),
val providerDisplayInfo: ProviderDisplayInfo = toProviderDisplayInfo(providerInfoList),
val selectedEntry: EntryInfo? = null,
val hidden: Boolean = false,
val providerActivityPending: Boolean = false,
+ val isNoAccount: Boolean = false,
)
class GetCredentialViewModel(
@@ -127,6 +128,14 @@ class GetCredentialViewModel(
)
}
+ fun onMoreOptionOnSnackBarSelected(isNoAccount: Boolean) {
+ Log.d("Account Selector", "More Option on snackBar selected")
+ uiState = uiState.copy(
+ currentScreenState = GetScreenState.ALL_SIGN_IN_OPTIONS,
+ isNoAccount = isNoAccount,
+ )
+ }
+
fun onBackToPrimarySelectionScreen() {
uiState = uiState.copy(
currentScreenState = GetScreenState.PRIMARY_SELECTION
@@ -192,6 +201,24 @@ private fun toProviderDisplayInfo(
)
}
+private fun toGetScreenState(
+ providerInfoList: List
+): GetScreenState {
+ var noLocalAccount = true
+ var remoteInfo: RemoteEntryInfo? = null
+ providerInfoList.forEach{providerInfo -> if (
+ providerInfo.credentialEntryList.isNotEmpty() || providerInfo.authenticationEntry != null
+ ) { noLocalAccount = false }
+ // TODO: handle the error situation that if multiple remoteInfos exists
+ if (providerInfo.remoteEntry != null) {
+ remoteInfo = providerInfo.remoteEntry
+ }
+ }
+
+ return if (noLocalAccount && remoteInfo != null)
+ GetScreenState.REMOTE_ONLY else GetScreenState.PRIMARY_SELECTION
+}
+
internal class CredentialEntryInfoComparator : Comparator {
override fun compare(p0: CredentialEntryInfo, p1: CredentialEntryInfo): Int {
// First order by last used timestamp
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
index 0c3baffd8864f..3a2a73801d8ee 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
@@ -120,4 +120,6 @@ enum class GetScreenState {
PRIMARY_SELECTION,
/** The secondary credential selection page, where all sign-in options are listed. */
ALL_SIGN_IN_OPTIONS,
+ /** The snackbar only page when there's no account but only a remoteEntry. */
+ REMOTE_ONLY,
}