Merge "Support autoselect for create flows." into udc-dev am: fcdac40e64

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/23089737

Change-Id: Ic5d6b155a2888ff3ab93920c3a763fc1d0da5beb
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
Treehugger Robot
2023-05-09 10:22:21 +00:00
committed by Automerger Merge Worker
7 changed files with 81 additions and 28 deletions

View File

@@ -38,6 +38,7 @@ import com.android.credentialmanager.createflow.RequestDisplayInfo
import com.android.credentialmanager.getflow.GetCredentialUiState import com.android.credentialmanager.getflow.GetCredentialUiState
import com.android.credentialmanager.getflow.findAutoSelectEntry import com.android.credentialmanager.getflow.findAutoSelectEntry
import com.android.credentialmanager.common.ProviderActivityState import com.android.credentialmanager.common.ProviderActivityState
import com.android.credentialmanager.createflow.isFlowAutoSelectable
/** /**
* Client for interacting with Credential Manager. Also holds data inputs from it. * Client for interacting with Credential Manager. Also holds data inputs from it.
@@ -113,8 +114,7 @@ class CredentialManagerRepo(
val providerDisableListUiState = getCreateProviderDisableListInitialUiState() val providerDisableListUiState = getCreateProviderDisableListInitialUiState()
val requestDisplayInfoUiState = val requestDisplayInfoUiState =
getCreateRequestDisplayInfoInitialUiState(originName)!! getCreateRequestDisplayInfoInitialUiState(originName)!!
UiState( val createCredentialUiState = CreateFlowUtils.toCreateCredentialUiState(
createCredentialUiState = CreateFlowUtils.toCreateCredentialUiState(
enabledProviders = providerEnableListUiState, enabledProviders = providerEnableListUiState,
disabledProviders = providerDisableListUiState, disabledProviders = providerDisableListUiState,
defaultProviderIdPreferredByApp = defaultProviderIdPreferredByApp =
@@ -124,10 +124,20 @@ class CredentialManagerRepo(
requestDisplayInfo = requestDisplayInfoUiState, requestDisplayInfo = requestDisplayInfoUiState,
isOnPasskeyIntroStateAlready = false, isOnPasskeyIntroStateAlready = false,
isPasskeyFirstUse = isPasskeyFirstUse, isPasskeyFirstUse = isPasskeyFirstUse,
)!!, )!!
val isFlowAutoSelectable = isFlowAutoSelectable(createCredentialUiState)
UiState(
createCredentialUiState = createCredentialUiState,
getCredentialUiState = null, getCredentialUiState = null,
cancelRequestState = cancelUiRequestState, cancelRequestState = cancelUiRequestState,
isInitialRender = isNewActivity, isInitialRender = isNewActivity,
isAutoSelectFlow = isFlowAutoSelectable,
providerActivityState =
if (isFlowAutoSelectable) ProviderActivityState.READY_TO_LAUNCH
else ProviderActivityState.NOT_APPLICABLE,
selectedEntry =
if (isFlowAutoSelectable) createCredentialUiState.activeEntry?.activeEntryInfo
else null,
) )
} }
RequestInfo.TYPE_GET -> { RequestInfo.TYPE_GET -> {

View File

@@ -34,6 +34,7 @@ import com.android.credentialmanager.common.DialogState
import com.android.credentialmanager.common.ProviderActivityResult import com.android.credentialmanager.common.ProviderActivityResult
import com.android.credentialmanager.common.ProviderActivityState import com.android.credentialmanager.common.ProviderActivityState
import com.android.credentialmanager.createflow.ActiveEntry import com.android.credentialmanager.createflow.ActiveEntry
import com.android.credentialmanager.createflow.isFlowAutoSelectable
import com.android.credentialmanager.createflow.CreateCredentialUiState import com.android.credentialmanager.createflow.CreateCredentialUiState
import com.android.credentialmanager.createflow.CreateScreenState import com.android.credentialmanager.createflow.CreateScreenState
import com.android.credentialmanager.getflow.GetCredentialUiState import com.android.credentialmanager.getflow.GetCredentialUiState
@@ -262,30 +263,39 @@ class CredentialSelectorViewModel(
/***** Create Flow Callbacks *****/ /***** Create Flow Callbacks *****/
/**************************************************************************/ /**************************************************************************/
fun createFlowOnConfirmIntro() { fun createFlowOnConfirmIntro() {
userConfigRepo.setIsPasskeyFirstUse(false)
val prevUiState = uiState.createCredentialUiState val prevUiState = uiState.createCredentialUiState
if (prevUiState == null) { if (prevUiState == null) {
Log.d(Constants.LOG_TAG, "Encountered unexpected null create ui state") Log.d(Constants.LOG_TAG, "Encountered unexpected null create ui state")
onInternalError() onInternalError()
return return
} }
val newUiState = CreateFlowUtils.toCreateCredentialUiState( val newScreenState = CreateFlowUtils.toCreateScreenState(
enabledProviders = prevUiState.enabledProviders, createOptionSize = prevUiState.sortedCreateOptionsPairs.size,
disabledProviders = prevUiState.disabledProviders,
defaultProviderIdPreferredByApp =
prevUiState.requestDisplayInfo.appPreferredDefaultProviderId,
defaultProviderIdsSetByUser =
prevUiState.requestDisplayInfo.userSetDefaultProviderIds,
requestDisplayInfo = prevUiState.requestDisplayInfo,
isOnPasskeyIntroStateAlready = true, isOnPasskeyIntroStateAlready = true,
isPasskeyFirstUse = userConfigRepo.getIsPasskeyFirstUse() requestDisplayInfo = prevUiState.requestDisplayInfo,
remoteEntry = prevUiState.remoteEntry,
isPasskeyFirstUse = true,
) )
if (newUiState == null) { if (newScreenState == null) {
Log.d(Constants.LOG_TAG, "Unable to update create ui state") Log.d(Constants.LOG_TAG, "Unexpected: couldn't resolve new screen state")
onInternalError() onInternalError()
return return
} }
uiState = uiState.copy(createCredentialUiState = newUiState) val newCreateCredentialUiState = prevUiState.copy(
userConfigRepo.setIsPasskeyFirstUse(false) currentScreenState = newScreenState,
)
val isFlowAutoSelectable = isFlowAutoSelectable(newCreateCredentialUiState)
uiState = uiState.copy(
createCredentialUiState = newCreateCredentialUiState,
isAutoSelectFlow = isFlowAutoSelectable,
providerActivityState =
if (isFlowAutoSelectable) ProviderActivityState.READY_TO_LAUNCH
else ProviderActivityState.NOT_APPLICABLE,
selectedEntry =
if (isFlowAutoSelectable) newCreateCredentialUiState.activeEntry?.activeEntryInfo
else null,
)
} }
fun createFlowOnMoreOptionsSelectedOnCreationSelection() { fun createFlowOnMoreOptionsSelectedOnCreationSelection() {

View File

@@ -487,6 +487,7 @@ class CreateFlowUtils {
createCredentialRequestJetpack.preferImmediatelyAvailableCredentials, createCredentialRequestJetpack.preferImmediatelyAvailableCredentials,
appPreferredDefaultProviderId = appPreferredDefaultProviderId, appPreferredDefaultProviderId = appPreferredDefaultProviderId,
userSetDefaultProviderIds = requestInfo.defaultProviderIds.toSet(), userSetDefaultProviderIds = requestInfo.defaultProviderIds.toSet(),
isAutoSelectRequest = createCredentialRequestJetpack.isAutoSelectAllowed,
) )
is CreatePublicKeyCredentialRequest -> { is CreatePublicKeyCredentialRequest -> {
newRequestDisplayInfoFromPasskeyJson( newRequestDisplayInfoFromPasskeyJson(
@@ -497,6 +498,7 @@ class CreateFlowUtils {
createCredentialRequestJetpack.preferImmediatelyAvailableCredentials, createCredentialRequestJetpack.preferImmediatelyAvailableCredentials,
appPreferredDefaultProviderId = appPreferredDefaultProviderId, appPreferredDefaultProviderId = appPreferredDefaultProviderId,
userSetDefaultProviderIds = requestInfo.defaultProviderIds.toSet(), userSetDefaultProviderIds = requestInfo.defaultProviderIds.toSet(),
isAutoSelectRequest = createCredentialRequestJetpack.isAutoSelectAllowed,
) )
} }
is CreateCustomCredentialRequest -> { is CreateCustomCredentialRequest -> {
@@ -515,6 +517,7 @@ class CreateFlowUtils {
createCredentialRequestJetpack.preferImmediatelyAvailableCredentials, createCredentialRequestJetpack.preferImmediatelyAvailableCredentials,
appPreferredDefaultProviderId = appPreferredDefaultProviderId, appPreferredDefaultProviderId = appPreferredDefaultProviderId,
userSetDefaultProviderIds = requestInfo.defaultProviderIds.toSet(), userSetDefaultProviderIds = requestInfo.defaultProviderIds.toSet(),
isAutoSelectRequest = createCredentialRequestJetpack.isAutoSelectAllowed,
) )
} }
else -> null else -> null
@@ -602,7 +605,7 @@ class CreateFlowUtils {
) )
} }
private fun toCreateScreenState( fun toCreateScreenState(
createOptionSize: Int, createOptionSize: Int,
isOnPasskeyIntroStateAlready: Boolean, isOnPasskeyIntroStateAlready: Boolean,
requestDisplayInfo: RequestDisplayInfo, requestDisplayInfo: RequestDisplayInfo,
@@ -662,7 +665,13 @@ class CreateFlowUtils {
passkeyCount = createEntry.getPublicKeyCredentialCount(), passkeyCount = createEntry.getPublicKeyCredentialCount(),
totalCredentialCount = createEntry.getTotalCredentialCount(), totalCredentialCount = createEntry.getTotalCredentialCount(),
lastUsedTime = createEntry.lastUsedTime ?: Instant.MIN, lastUsedTime = createEntry.lastUsedTime ?: Instant.MIN,
footerDescription = createEntry.description?.toString() footerDescription = createEntry.description?.toString(),
// TODO(b/281065680): replace with official library constant once available
allowAutoSelect =
it.slice.items.firstOrNull {
it.hasHint("androidx.credentials.provider.createEntry.SLICE_HINT_AUTO_" +
"SELECT_ALLOWED")
}?.text == "true",
)) ))
} }
return result.sortedWith( return result.sortedWith(
@@ -694,6 +703,7 @@ class CreateFlowUtils {
preferImmediatelyAvailableCredentials: Boolean, preferImmediatelyAvailableCredentials: Boolean,
appPreferredDefaultProviderId: String?, appPreferredDefaultProviderId: String?,
userSetDefaultProviderIds: Set<String>, userSetDefaultProviderIds: Set<String>,
isAutoSelectRequest: Boolean
): RequestDisplayInfo? { ): RequestDisplayInfo? {
val json = JSONObject(requestJson) val json = JSONObject(requestJson)
var passkeyUsername = "" var passkeyUsername = ""
@@ -716,6 +726,7 @@ class CreateFlowUtils {
preferImmediatelyAvailableCredentials, preferImmediatelyAvailableCredentials,
appPreferredDefaultProviderId, appPreferredDefaultProviderId,
userSetDefaultProviderIds, userSetDefaultProviderIds,
isAutoSelectRequest,
) )
} }
} }

View File

@@ -39,14 +39,16 @@ fun ModalBottomSheet(
onDismiss: () -> Unit, onDismiss: () -> Unit,
isInitialRender: Boolean, isInitialRender: Boolean,
onInitialRenderComplete: () -> Unit, onInitialRenderComplete: () -> Unit,
isAutoSelectFlow: Boolean,
) { ) {
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
val state = rememberModalBottomSheetState( val state = rememberModalBottomSheetState(
initialValue = ModalBottomSheetValue.Hidden, initialValue = if (isAutoSelectFlow) ModalBottomSheetValue.Expanded
else ModalBottomSheetValue.Hidden,
skipHalfExpanded = true skipHalfExpanded = true
) )
val sysUiController = rememberSystemUiController() val sysUiController = rememberSystemUiController()
if (state.targetValue == ModalBottomSheetValue.Hidden) { if (state.targetValue == ModalBottomSheetValue.Hidden || isAutoSelectFlow) {
setTransparentSystemBarsColor(sysUiController) setTransparentSystemBarsColor(sysUiController)
} else { } else {
setBottomSheetSystemBarsColor(sysUiController) setBottomSheetSystemBarsColor(sysUiController)

View File

@@ -166,6 +166,7 @@ fun CreateCredentialScreen(
}, },
onDismiss = viewModel::onUserCancel, onDismiss = viewModel::onUserCancel,
isInitialRender = viewModel.uiState.isInitialRender, isInitialRender = viewModel.uiState.isInitialRender,
isAutoSelectFlow = viewModel.uiState.isAutoSelectFlow,
onInitialRenderComplete = viewModel::onInitialRenderComplete, onInitialRenderComplete = viewModel::onInitialRenderComplete,
) )
} }

View File

@@ -34,6 +34,21 @@ data class CreateCredentialUiState(
val foundCandidateFromUserDefaultProvider: Boolean, val foundCandidateFromUserDefaultProvider: Boolean,
) )
internal fun isFlowAutoSelectable(
uiState: CreateCredentialUiState
): Boolean {
return uiState.requestDisplayInfo.isAutoSelectRequest &&
// Even if the flow is auto selectable, still allow passkey intro screen to show once if
// applicable.
uiState.currentScreenState != CreateScreenState.PASSKEY_INTRO &&
uiState.currentScreenState != CreateScreenState.MORE_ABOUT_PASSKEYS_INTRO &&
uiState.remoteEntry == null &&
uiState.sortedCreateOptionsPairs.size == 1 &&
uiState.activeEntry?.activeEntryInfo?.let {
it is CreateOptionInfo && it.allowAutoSelect
} ?: false
}
internal fun hasContentToDisplay(state: CreateCredentialUiState): Boolean { internal fun hasContentToDisplay(state: CreateCredentialUiState): Boolean {
return state.sortedCreateOptionsPairs.isNotEmpty() || return state.sortedCreateOptionsPairs.isNotEmpty() ||
(!state.requestDisplayInfo.preferImmediatelyAvailableCredentials && (!state.requestDisplayInfo.preferImmediatelyAvailableCredentials &&
@@ -74,6 +89,7 @@ class CreateOptionInfo(
val totalCredentialCount: Int?, val totalCredentialCount: Int?,
val lastUsedTime: Instant, val lastUsedTime: Instant,
val footerDescription: String?, val footerDescription: String?,
val allowAutoSelect: Boolean,
) : BaseEntry( ) : BaseEntry(
providerId, providerId,
entryKey, entryKey,
@@ -107,6 +123,8 @@ data class RequestDisplayInfo(
val preferImmediatelyAvailableCredentials: Boolean, val preferImmediatelyAvailableCredentials: Boolean,
val appPreferredDefaultProviderId: String?, val appPreferredDefaultProviderId: String?,
val userSetDefaultProviderIds: Set<String>, val userSetDefaultProviderIds: Set<String>,
// Whether the given CreateCredentialRequest allows auto select.
val isAutoSelectRequest: Boolean,
) )
/** /**

View File

@@ -149,6 +149,7 @@ fun GetCredentialScreen(
}, },
onDismiss = viewModel::onUserCancel, onDismiss = viewModel::onUserCancel,
isInitialRender = viewModel.uiState.isInitialRender, isInitialRender = viewModel.uiState.isInitialRender,
isAutoSelectFlow = viewModel.uiState.isAutoSelectFlow,
onInitialRenderComplete = viewModel::onInitialRenderComplete, onInitialRenderComplete = viewModel::onInitialRenderComplete,
) )
} }