Loading packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt +9 −0 Original line number Original line Diff line number Diff line Loading @@ -270,6 +270,15 @@ class CredentialSelectorViewModel( ) ) } } fun getFlowOnMoreOptionOnlySelected() { Log.d(Constants.LOG_TAG, "More Option Only selected") uiState = uiState.copy( getCredentialUiState = uiState.getCredentialUiState?.copy( currentScreenState = GetScreenState.ALL_SIGN_IN_OPTIONS_ONLY ) ) } fun getFlowOnMoreOptionOnSnackBarSelected(isNoAccount: Boolean) { fun getFlowOnMoreOptionOnSnackBarSelected(isNoAccount: Boolean) { Log.d(Constants.LOG_TAG, "More Option on snackBar selected") Log.d(Constants.LOG_TAG, "More Option on snackBar selected") uiState = uiState.copy( uiState = uiState.copy( Loading packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt +32 −0 Original line number Original line Diff line number Diff line Loading @@ -349,6 +349,38 @@ fun MoreOptionTopAppBar( } } } } @Composable fun MoreOptionTopAppBarWithCustomNavigation( text: String, onNavigationIconClicked: () -> Unit, navigationIcon: ImageVector, navigationIconContentDescription: String, bottomPadding: Dp, ) { Row( modifier = Modifier.padding(top = 12.dp, bottom = bottomPadding), verticalAlignment = Alignment.CenterVertically, ) { IconButton( modifier = Modifier.padding(top = 8.dp, bottom = 8.dp, start = 4.dp).size(48.dp), onClick = onNavigationIconClicked ) { Box( modifier = Modifier.size(48.dp), contentAlignment = Alignment.Center, ) { Icon( imageVector = navigationIcon, contentDescription = navigationIconContentDescription, modifier = Modifier.size(24.dp).autoMirrored(), tint = LocalAndroidColorScheme.current.onSurfaceVariant, ) } } LargeTitleText(text = text, modifier = Modifier.padding(horizontal = 4.dp)) } } private fun Modifier.autoMirrored() = composed { private fun Modifier.autoMirrored() = composed { when (LocalLayoutDirection.current) { when (LocalLayoutDirection.current) { LayoutDirection.Rtl -> graphicsLayer(scaleX = -1f) LayoutDirection.Rtl -> graphicsLayer(scaleX = -1f) Loading packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt +42 −7 Original line number Original line Diff line number Diff line Loading @@ -32,6 +32,7 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Close import androidx.compose.material.icons.outlined.QrCodeScanner import androidx.compose.material.icons.outlined.QrCodeScanner import androidx.compose.material3.Divider import androidx.compose.material3.Divider import androidx.compose.material3.TextButton import androidx.compose.material3.TextButton Loading Loading @@ -70,6 +71,7 @@ import com.android.credentialmanager.common.ui.HeadlineText import com.android.credentialmanager.common.ui.LargeLabelTextOnSurfaceVariant import com.android.credentialmanager.common.ui.LargeLabelTextOnSurfaceVariant import com.android.credentialmanager.common.ui.ModalBottomSheet import com.android.credentialmanager.common.ui.ModalBottomSheet import com.android.credentialmanager.common.ui.MoreOptionTopAppBar import com.android.credentialmanager.common.ui.MoreOptionTopAppBar import com.android.credentialmanager.common.ui.MoreOptionTopAppBarWithCustomNavigation import com.android.credentialmanager.common.ui.SheetContainerCard import com.android.credentialmanager.common.ui.SheetContainerCard import com.android.credentialmanager.common.ui.Snackbar import com.android.credentialmanager.common.ui.Snackbar import com.android.credentialmanager.common.ui.SnackbarActionText import com.android.credentialmanager.common.ui.SnackbarActionText Loading Loading @@ -148,7 +150,7 @@ fun GetCredentialScreen( .currentScreenState == GetScreenState.BIOMETRIC_SELECTION) { .currentScreenState == GetScreenState.BIOMETRIC_SELECTION) { BiometricSelectionPage( BiometricSelectionPage( biometricEntry = getCredentialUiState.activeEntry, biometricEntry = getCredentialUiState.activeEntry, onMoreOptionSelected = viewModel::getFlowOnMoreOptionSelected, onMoreOptionSelected = viewModel::getFlowOnMoreOptionOnlySelected, onCancelFlowAndFinish = viewModel::onUserCancel, onCancelFlowAndFinish = viewModel::onUserCancel, onIllegalStateAndFinish = viewModel::onIllegalUiState, onIllegalStateAndFinish = viewModel::onIllegalUiState, requestDisplayInfo = getCredentialUiState.requestDisplayInfo, requestDisplayInfo = getCredentialUiState.requestDisplayInfo, Loading @@ -163,6 +165,28 @@ fun GetCredentialScreen( onBiometricPromptStateChange = onBiometricPromptStateChange = viewModel::onBiometricPromptStateChange viewModel::onBiometricPromptStateChange ) ) } else if (credmanBiometricApiEnabled() && getCredentialUiState.currentScreenState == GetScreenState.ALL_SIGN_IN_OPTIONS_ONLY) { AllSignInOptionCard( providerInfoList = getCredentialUiState.providerInfoList, providerDisplayInfo = getCredentialUiState.providerDisplayInfo, onEntrySelected = viewModel::getFlowOnEntrySelected, onBackButtonClicked = viewModel::onUserCancel, onCancel = viewModel::onUserCancel, onLog = { viewModel.logUiEvent(it) }, customTopBar = { MoreOptionTopAppBarWithCustomNavigation( text = stringResource( R.string.get_dialog_title_sign_in_options), onNavigationIconClicked = viewModel::onUserCancel, navigationIcon = Icons.Filled.Close, navigationIconContentDescription = stringResource(R.string.accessibility_close_button), bottomPadding = 0.dp ) } ) viewModel.uiMetrics.log(GetCredentialEvent .CREDMAN_GET_CRED_SCREEN_ALL_SIGN_IN_OPTIONS) } else { } else { AllSignInOptionCard( AllSignInOptionCard( providerInfoList = getCredentialUiState.providerInfoList, providerInfoList = getCredentialUiState.providerInfoList, Loading Loading @@ -642,7 +666,13 @@ private fun findSingleProviderIdForPrimaryPage( return providerId return providerId } } /** Draws the secondary credential selection page, where all sign-in options are listed. */ /** * Draws the secondary credential selection page, where all sign-in options are listed. * * By default, this card has 'back' navigation whereby user can navigate back to invoke * [onBackButtonClicked]. However if a different top bar with possibly a different navigation * is required, then the caller of this Composable can set a [customTopBar]. */ @Composable @Composable fun AllSignInOptionCard( fun AllSignInOptionCard( providerInfoList: List<ProviderInfo>, providerInfoList: List<ProviderInfo>, Loading @@ -651,16 +681,21 @@ fun AllSignInOptionCard( onBackButtonClicked: () -> Unit, onBackButtonClicked: () -> Unit, onCancel: () -> Unit, onCancel: () -> Unit, onLog: @Composable (UiEventEnum) -> Unit, onLog: @Composable (UiEventEnum) -> Unit, customTopBar: (@Composable() () -> Unit)? = null ) { ) { val sortedUserNameToCredentialEntryList = val sortedUserNameToCredentialEntryList = providerDisplayInfo.sortedUserNameToCredentialEntryList providerDisplayInfo.sortedUserNameToCredentialEntryList val authenticationEntryList = providerDisplayInfo.authenticationEntryList val authenticationEntryList = providerDisplayInfo.authenticationEntryList SheetContainerCard(topAppBar = { SheetContainerCard(topAppBar = { if (customTopBar != null) { customTopBar() } else { MoreOptionTopAppBar( MoreOptionTopAppBar( text = stringResource(R.string.get_dialog_title_sign_in_options), text = stringResource(R.string.get_dialog_title_sign_in_options), onNavigationIconClicked = onBackButtonClicked, onNavigationIconClicked = onBackButtonClicked, bottomPadding = 0.dp, bottomPadding = 0.dp, ) ) } }) { }) { var isFirstSection = true var isFirstSection = true // For username // For username Loading packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt +14 −2 Original line number Original line Diff line number Diff line Loading @@ -163,7 +163,11 @@ enum class GetScreenState { /** The single tap biometric selection page. */ /** The single tap biometric selection page. */ BIOMETRIC_SELECTION, BIOMETRIC_SELECTION, /** The secondary credential selection page, where all sign-in options are listed. */ /** * The secondary credential selection page, where all sign-in options are listed. * * This state is expected to go back to PRIMARY_SELECTION on back navigation */ ALL_SIGN_IN_OPTIONS, ALL_SIGN_IN_OPTIONS, /** The snackbar only page when there's no account but only a remoteEntry. */ /** The snackbar only page when there's no account but only a remoteEntry. */ Loading @@ -171,6 +175,14 @@ enum class GetScreenState { /** The snackbar when there are only auth entries and all of them turn out to be empty. */ /** The snackbar when there are only auth entries and all of them turn out to be empty. */ UNLOCKED_AUTH_ENTRIES_ONLY, UNLOCKED_AUTH_ENTRIES_ONLY, /** * The secondary credential selection page, where all sign-in options are listed. * * This state has no option for the user to navigate back to PRIMARY_SELECTION, and * instead can be terminated independently. */ ALL_SIGN_IN_OPTIONS_ONLY, } } Loading Loading @@ -285,7 +297,7 @@ private fun toGetScreenState( providerDisplayInfo.remoteEntry != null) providerDisplayInfo.remoteEntry != null) GetScreenState.REMOTE_ONLY GetScreenState.REMOTE_ONLY else if (isRequestForAllOptions) else if (isRequestForAllOptions) GetScreenState.ALL_SIGN_IN_OPTIONS GetScreenState.ALL_SIGN_IN_OPTIONS_ONLY else if (isBiometricFlow(providerDisplayInfo, isFlowAutoSelectable(providerDisplayInfo))) else if (isBiometricFlow(providerDisplayInfo, isFlowAutoSelectable(providerDisplayInfo))) GetScreenState.BIOMETRIC_SELECTION GetScreenState.BIOMETRIC_SELECTION else GetScreenState.PRIMARY_SELECTION else GetScreenState.PRIMARY_SELECTION Loading services/autofill/java/com/android/server/autofill/Session.java +5 −3 Original line number Original line Diff line number Diff line Loading @@ -5188,11 +5188,13 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState String[] exception = resultData.getStringArray( String[] exception = resultData.getStringArray( CredentialProviderService.EXTRA_GET_CREDENTIAL_EXCEPTION); CredentialProviderService.EXTRA_GET_CREDENTIAL_EXCEPTION); if (exception != null && exception.length >= 2) { if (exception != null && exception.length >= 2) { String errType = exception[0]; String errMsg = exception[1]; Slog.w(TAG, "Credman bottom sheet from pinned " Slog.w(TAG, "Credman bottom sheet from pinned " + "entry failed with: + " + exception[0] + " , " + "entry failed with: + " + errType + " , " + exception[1]); + errMsg); sendCredentialManagerResponseToApp(/*response=*/ null, sendCredentialManagerResponseToApp(/*response=*/ null, new GetCredentialException(exception[0], exception[1]), new GetCredentialException(errType, errMsg), mAutofillId); mAutofillId); } } } else { } else { Loading Loading
packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt +9 −0 Original line number Original line Diff line number Diff line Loading @@ -270,6 +270,15 @@ class CredentialSelectorViewModel( ) ) } } fun getFlowOnMoreOptionOnlySelected() { Log.d(Constants.LOG_TAG, "More Option Only selected") uiState = uiState.copy( getCredentialUiState = uiState.getCredentialUiState?.copy( currentScreenState = GetScreenState.ALL_SIGN_IN_OPTIONS_ONLY ) ) } fun getFlowOnMoreOptionOnSnackBarSelected(isNoAccount: Boolean) { fun getFlowOnMoreOptionOnSnackBarSelected(isNoAccount: Boolean) { Log.d(Constants.LOG_TAG, "More Option on snackBar selected") Log.d(Constants.LOG_TAG, "More Option on snackBar selected") uiState = uiState.copy( uiState = uiState.copy( Loading
packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt +32 −0 Original line number Original line Diff line number Diff line Loading @@ -349,6 +349,38 @@ fun MoreOptionTopAppBar( } } } } @Composable fun MoreOptionTopAppBarWithCustomNavigation( text: String, onNavigationIconClicked: () -> Unit, navigationIcon: ImageVector, navigationIconContentDescription: String, bottomPadding: Dp, ) { Row( modifier = Modifier.padding(top = 12.dp, bottom = bottomPadding), verticalAlignment = Alignment.CenterVertically, ) { IconButton( modifier = Modifier.padding(top = 8.dp, bottom = 8.dp, start = 4.dp).size(48.dp), onClick = onNavigationIconClicked ) { Box( modifier = Modifier.size(48.dp), contentAlignment = Alignment.Center, ) { Icon( imageVector = navigationIcon, contentDescription = navigationIconContentDescription, modifier = Modifier.size(24.dp).autoMirrored(), tint = LocalAndroidColorScheme.current.onSurfaceVariant, ) } } LargeTitleText(text = text, modifier = Modifier.padding(horizontal = 4.dp)) } } private fun Modifier.autoMirrored() = composed { private fun Modifier.autoMirrored() = composed { when (LocalLayoutDirection.current) { when (LocalLayoutDirection.current) { LayoutDirection.Rtl -> graphicsLayer(scaleX = -1f) LayoutDirection.Rtl -> graphicsLayer(scaleX = -1f) Loading
packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt +42 −7 Original line number Original line Diff line number Diff line Loading @@ -32,6 +32,7 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Close import androidx.compose.material.icons.outlined.QrCodeScanner import androidx.compose.material.icons.outlined.QrCodeScanner import androidx.compose.material3.Divider import androidx.compose.material3.Divider import androidx.compose.material3.TextButton import androidx.compose.material3.TextButton Loading Loading @@ -70,6 +71,7 @@ import com.android.credentialmanager.common.ui.HeadlineText import com.android.credentialmanager.common.ui.LargeLabelTextOnSurfaceVariant import com.android.credentialmanager.common.ui.LargeLabelTextOnSurfaceVariant import com.android.credentialmanager.common.ui.ModalBottomSheet import com.android.credentialmanager.common.ui.ModalBottomSheet import com.android.credentialmanager.common.ui.MoreOptionTopAppBar import com.android.credentialmanager.common.ui.MoreOptionTopAppBar import com.android.credentialmanager.common.ui.MoreOptionTopAppBarWithCustomNavigation import com.android.credentialmanager.common.ui.SheetContainerCard import com.android.credentialmanager.common.ui.SheetContainerCard import com.android.credentialmanager.common.ui.Snackbar import com.android.credentialmanager.common.ui.Snackbar import com.android.credentialmanager.common.ui.SnackbarActionText import com.android.credentialmanager.common.ui.SnackbarActionText Loading Loading @@ -148,7 +150,7 @@ fun GetCredentialScreen( .currentScreenState == GetScreenState.BIOMETRIC_SELECTION) { .currentScreenState == GetScreenState.BIOMETRIC_SELECTION) { BiometricSelectionPage( BiometricSelectionPage( biometricEntry = getCredentialUiState.activeEntry, biometricEntry = getCredentialUiState.activeEntry, onMoreOptionSelected = viewModel::getFlowOnMoreOptionSelected, onMoreOptionSelected = viewModel::getFlowOnMoreOptionOnlySelected, onCancelFlowAndFinish = viewModel::onUserCancel, onCancelFlowAndFinish = viewModel::onUserCancel, onIllegalStateAndFinish = viewModel::onIllegalUiState, onIllegalStateAndFinish = viewModel::onIllegalUiState, requestDisplayInfo = getCredentialUiState.requestDisplayInfo, requestDisplayInfo = getCredentialUiState.requestDisplayInfo, Loading @@ -163,6 +165,28 @@ fun GetCredentialScreen( onBiometricPromptStateChange = onBiometricPromptStateChange = viewModel::onBiometricPromptStateChange viewModel::onBiometricPromptStateChange ) ) } else if (credmanBiometricApiEnabled() && getCredentialUiState.currentScreenState == GetScreenState.ALL_SIGN_IN_OPTIONS_ONLY) { AllSignInOptionCard( providerInfoList = getCredentialUiState.providerInfoList, providerDisplayInfo = getCredentialUiState.providerDisplayInfo, onEntrySelected = viewModel::getFlowOnEntrySelected, onBackButtonClicked = viewModel::onUserCancel, onCancel = viewModel::onUserCancel, onLog = { viewModel.logUiEvent(it) }, customTopBar = { MoreOptionTopAppBarWithCustomNavigation( text = stringResource( R.string.get_dialog_title_sign_in_options), onNavigationIconClicked = viewModel::onUserCancel, navigationIcon = Icons.Filled.Close, navigationIconContentDescription = stringResource(R.string.accessibility_close_button), bottomPadding = 0.dp ) } ) viewModel.uiMetrics.log(GetCredentialEvent .CREDMAN_GET_CRED_SCREEN_ALL_SIGN_IN_OPTIONS) } else { } else { AllSignInOptionCard( AllSignInOptionCard( providerInfoList = getCredentialUiState.providerInfoList, providerInfoList = getCredentialUiState.providerInfoList, Loading Loading @@ -642,7 +666,13 @@ private fun findSingleProviderIdForPrimaryPage( return providerId return providerId } } /** Draws the secondary credential selection page, where all sign-in options are listed. */ /** * Draws the secondary credential selection page, where all sign-in options are listed. * * By default, this card has 'back' navigation whereby user can navigate back to invoke * [onBackButtonClicked]. However if a different top bar with possibly a different navigation * is required, then the caller of this Composable can set a [customTopBar]. */ @Composable @Composable fun AllSignInOptionCard( fun AllSignInOptionCard( providerInfoList: List<ProviderInfo>, providerInfoList: List<ProviderInfo>, Loading @@ -651,16 +681,21 @@ fun AllSignInOptionCard( onBackButtonClicked: () -> Unit, onBackButtonClicked: () -> Unit, onCancel: () -> Unit, onCancel: () -> Unit, onLog: @Composable (UiEventEnum) -> Unit, onLog: @Composable (UiEventEnum) -> Unit, customTopBar: (@Composable() () -> Unit)? = null ) { ) { val sortedUserNameToCredentialEntryList = val sortedUserNameToCredentialEntryList = providerDisplayInfo.sortedUserNameToCredentialEntryList providerDisplayInfo.sortedUserNameToCredentialEntryList val authenticationEntryList = providerDisplayInfo.authenticationEntryList val authenticationEntryList = providerDisplayInfo.authenticationEntryList SheetContainerCard(topAppBar = { SheetContainerCard(topAppBar = { if (customTopBar != null) { customTopBar() } else { MoreOptionTopAppBar( MoreOptionTopAppBar( text = stringResource(R.string.get_dialog_title_sign_in_options), text = stringResource(R.string.get_dialog_title_sign_in_options), onNavigationIconClicked = onBackButtonClicked, onNavigationIconClicked = onBackButtonClicked, bottomPadding = 0.dp, bottomPadding = 0.dp, ) ) } }) { }) { var isFirstSection = true var isFirstSection = true // For username // For username Loading
packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt +14 −2 Original line number Original line Diff line number Diff line Loading @@ -163,7 +163,11 @@ enum class GetScreenState { /** The single tap biometric selection page. */ /** The single tap biometric selection page. */ BIOMETRIC_SELECTION, BIOMETRIC_SELECTION, /** The secondary credential selection page, where all sign-in options are listed. */ /** * The secondary credential selection page, where all sign-in options are listed. * * This state is expected to go back to PRIMARY_SELECTION on back navigation */ ALL_SIGN_IN_OPTIONS, ALL_SIGN_IN_OPTIONS, /** The snackbar only page when there's no account but only a remoteEntry. */ /** The snackbar only page when there's no account but only a remoteEntry. */ Loading @@ -171,6 +175,14 @@ enum class GetScreenState { /** The snackbar when there are only auth entries and all of them turn out to be empty. */ /** The snackbar when there are only auth entries and all of them turn out to be empty. */ UNLOCKED_AUTH_ENTRIES_ONLY, UNLOCKED_AUTH_ENTRIES_ONLY, /** * The secondary credential selection page, where all sign-in options are listed. * * This state has no option for the user to navigate back to PRIMARY_SELECTION, and * instead can be terminated independently. */ ALL_SIGN_IN_OPTIONS_ONLY, } } Loading Loading @@ -285,7 +297,7 @@ private fun toGetScreenState( providerDisplayInfo.remoteEntry != null) providerDisplayInfo.remoteEntry != null) GetScreenState.REMOTE_ONLY GetScreenState.REMOTE_ONLY else if (isRequestForAllOptions) else if (isRequestForAllOptions) GetScreenState.ALL_SIGN_IN_OPTIONS GetScreenState.ALL_SIGN_IN_OPTIONS_ONLY else if (isBiometricFlow(providerDisplayInfo, isFlowAutoSelectable(providerDisplayInfo))) else if (isBiometricFlow(providerDisplayInfo, isFlowAutoSelectable(providerDisplayInfo))) GetScreenState.BIOMETRIC_SELECTION GetScreenState.BIOMETRIC_SELECTION else GetScreenState.PRIMARY_SELECTION else GetScreenState.PRIMARY_SELECTION Loading
services/autofill/java/com/android/server/autofill/Session.java +5 −3 Original line number Original line Diff line number Diff line Loading @@ -5188,11 +5188,13 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState String[] exception = resultData.getStringArray( String[] exception = resultData.getStringArray( CredentialProviderService.EXTRA_GET_CREDENTIAL_EXCEPTION); CredentialProviderService.EXTRA_GET_CREDENTIAL_EXCEPTION); if (exception != null && exception.length >= 2) { if (exception != null && exception.length >= 2) { String errType = exception[0]; String errMsg = exception[1]; Slog.w(TAG, "Credman bottom sheet from pinned " Slog.w(TAG, "Credman bottom sheet from pinned " + "entry failed with: + " + exception[0] + " , " + "entry failed with: + " + errType + " , " + exception[1]); + errMsg); sendCredentialManagerResponseToApp(/*response=*/ null, sendCredentialManagerResponseToApp(/*response=*/ null, new GetCredentialException(exception[0], exception[1]), new GetCredentialException(errType, errMsg), mAutofillId); mAutofillId); } } } else { } else { Loading