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

This commit is contained in:
Treehugger Robot
2023-05-09 01:10:40 +00:00
committed by Android (Google) Code Review
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.findAutoSelectEntry
import com.android.credentialmanager.common.ProviderActivityState
import com.android.credentialmanager.createflow.isFlowAutoSelectable
/**
* Client for interacting with Credential Manager. Also holds data inputs from it.
@@ -113,21 +114,30 @@ class CredentialManagerRepo(
val providerDisableListUiState = getCreateProviderDisableListInitialUiState()
val requestDisplayInfoUiState =
getCreateRequestDisplayInfoInitialUiState(originName)!!
val createCredentialUiState = CreateFlowUtils.toCreateCredentialUiState(
enabledProviders = providerEnableListUiState,
disabledProviders = providerDisableListUiState,
defaultProviderIdPreferredByApp =
requestDisplayInfoUiState.appPreferredDefaultProviderId,
defaultProviderIdsSetByUser =
requestDisplayInfoUiState.userSetDefaultProviderIds,
requestDisplayInfo = requestDisplayInfoUiState,
isOnPasskeyIntroStateAlready = false,
isPasskeyFirstUse = isPasskeyFirstUse,
)!!
val isFlowAutoSelectable = isFlowAutoSelectable(createCredentialUiState)
UiState(
createCredentialUiState = CreateFlowUtils.toCreateCredentialUiState(
enabledProviders = providerEnableListUiState,
disabledProviders = providerDisableListUiState,
defaultProviderIdPreferredByApp =
requestDisplayInfoUiState.appPreferredDefaultProviderId,
defaultProviderIdsSetByUser =
requestDisplayInfoUiState.userSetDefaultProviderIds,
requestDisplayInfo = requestDisplayInfoUiState,
isOnPasskeyIntroStateAlready = false,
isPasskeyFirstUse = isPasskeyFirstUse,
)!!,
createCredentialUiState = createCredentialUiState,
getCredentialUiState = null,
cancelRequestState = cancelUiRequestState,
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 -> {

View File

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

View File

@@ -487,6 +487,7 @@ class CreateFlowUtils {
createCredentialRequestJetpack.preferImmediatelyAvailableCredentials,
appPreferredDefaultProviderId = appPreferredDefaultProviderId,
userSetDefaultProviderIds = requestInfo.defaultProviderIds.toSet(),
isAutoSelectRequest = createCredentialRequestJetpack.isAutoSelectAllowed,
)
is CreatePublicKeyCredentialRequest -> {
newRequestDisplayInfoFromPasskeyJson(
@@ -497,6 +498,7 @@ class CreateFlowUtils {
createCredentialRequestJetpack.preferImmediatelyAvailableCredentials,
appPreferredDefaultProviderId = appPreferredDefaultProviderId,
userSetDefaultProviderIds = requestInfo.defaultProviderIds.toSet(),
isAutoSelectRequest = createCredentialRequestJetpack.isAutoSelectAllowed,
)
}
is CreateCustomCredentialRequest -> {
@@ -515,6 +517,7 @@ class CreateFlowUtils {
createCredentialRequestJetpack.preferImmediatelyAvailableCredentials,
appPreferredDefaultProviderId = appPreferredDefaultProviderId,
userSetDefaultProviderIds = requestInfo.defaultProviderIds.toSet(),
isAutoSelectRequest = createCredentialRequestJetpack.isAutoSelectAllowed,
)
}
else -> null
@@ -602,7 +605,7 @@ class CreateFlowUtils {
)
}
private fun toCreateScreenState(
fun toCreateScreenState(
createOptionSize: Int,
isOnPasskeyIntroStateAlready: Boolean,
requestDisplayInfo: RequestDisplayInfo,
@@ -662,7 +665,13 @@ class CreateFlowUtils {
passkeyCount = createEntry.getPublicKeyCredentialCount(),
totalCredentialCount = createEntry.getTotalCredentialCount(),
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(
@@ -694,6 +703,7 @@ class CreateFlowUtils {
preferImmediatelyAvailableCredentials: Boolean,
appPreferredDefaultProviderId: String?,
userSetDefaultProviderIds: Set<String>,
isAutoSelectRequest: Boolean
): RequestDisplayInfo? {
val json = JSONObject(requestJson)
var passkeyUsername = ""
@@ -716,6 +726,7 @@ class CreateFlowUtils {
preferImmediatelyAvailableCredentials,
appPreferredDefaultProviderId,
userSetDefaultProviderIds,
isAutoSelectRequest,
)
}
}

View File

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

View File

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

View File

@@ -34,6 +34,21 @@ data class CreateCredentialUiState(
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 {
return state.sortedCreateOptionsPairs.isNotEmpty() ||
(!state.requestDisplayInfo.preferImmediatelyAvailableCredentials &&
@@ -74,6 +89,7 @@ class CreateOptionInfo(
val totalCredentialCount: Int?,
val lastUsedTime: Instant,
val footerDescription: String?,
val allowAutoSelect: Boolean,
) : BaseEntry(
providerId,
entryKey,
@@ -107,6 +123,8 @@ data class RequestDisplayInfo(
val preferImmediatelyAvailableCredentials: Boolean,
val appPreferredDefaultProviderId: 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,
isInitialRender = viewModel.uiState.isInitialRender,
isAutoSelectFlow = viewModel.uiState.isAutoSelectFlow,
onInitialRenderComplete = viewModel::onInitialRenderComplete,
)
}