Merge "[CredManUi] Support auto select." into udc-dev

This commit is contained in:
Helen Qin
2023-03-06 18:40:18 +00:00
committed by Android (Google) Code Review
6 changed files with 83 additions and 22 deletions

View File

@@ -42,11 +42,13 @@ import com.android.credentialmanager.createflow.DisabledProviderInfo
import com.android.credentialmanager.createflow.EnabledProviderInfo
import com.android.credentialmanager.createflow.RequestDisplayInfo
import com.android.credentialmanager.getflow.GetCredentialUiState
import com.android.credentialmanager.getflow.findAutoSelectEntry
import androidx.credentials.CreateCredentialRequest.DisplayInfo
import androidx.credentials.CreatePublicKeyCredentialRequest
import androidx.credentials.CreatePasswordRequest
import androidx.credentials.GetPasswordOption
import androidx.credentials.GetPublicKeyCredentialOption
import com.android.credentialmanager.common.ProviderActivityState
import java.time.Instant
@@ -128,10 +130,20 @@ class CredentialManagerRepo(
getCredentialUiState = null,
)
}
RequestInfo.TYPE_GET -> UiState(
createCredentialUiState = null,
getCredentialUiState = getCredentialInitialUiState(originName)!!,
)
RequestInfo.TYPE_GET -> {
val getCredentialInitialUiState = getCredentialInitialUiState(originName)!!
val autoSelectEntry =
findAutoSelectEntry(getCredentialInitialUiState.providerDisplayInfo)
UiState(
createCredentialUiState = null,
getCredentialUiState = getCredentialInitialUiState,
selectedEntry = autoSelectEntry,
providerActivityState =
if (autoSelectEntry == null) ProviderActivityState.NOT_APPLICABLE
else ProviderActivityState.READY_TO_LAUNCH,
isAutoSelectFlow = autoSelectEntry != null,
)
}
else -> throw IllegalStateException("Unrecognized request type: ${requestInfo.type}")
}
}

View File

@@ -44,6 +44,9 @@ data class UiState(
val selectedEntry: BaseEntry? = null,
val providerActivityState: ProviderActivityState = ProviderActivityState.NOT_APPLICABLE,
val dialogState: DialogState = DialogState.ACTIVE,
// True if the UI has one and onely one auto selectable entry. Its provider activiey will be
// launched immediately, and canceling it will cancel the whole UI flow.
val isAutoSelectFlow: Boolean = false,
)
class CredentialSelectorViewModel(
@@ -96,13 +99,20 @@ class CredentialSelectorViewModel(
val resultCode = providerActivityResult.resultCode
val resultData = providerActivityResult.data
if (resultCode == Activity.RESULT_CANCELED) {
// Re-display the CredMan UI if the user canceled from the provider UI.
Log.d(Constants.LOG_TAG, "The provider activity was cancelled," +
" re-displaying our UI.")
uiState = uiState.copy(
selectedEntry = null,
providerActivityState = ProviderActivityState.NOT_APPLICABLE,
)
// Re-display the CredMan UI if the user canceled from the provider UI, or cancel
// the UI if this is the auto select flow.
if (uiState.isAutoSelectFlow) {
Log.d(Constants.LOG_TAG, "The auto selected provider activity was cancelled," +
" ending the credential manager activity.")
onUserCancel()
} else {
Log.d(Constants.LOG_TAG, "The provider activity was cancelled," +
" re-displaying our UI.")
uiState = uiState.copy(
selectedEntry = null,
providerActivityState = ProviderActivityState.NOT_APPLICABLE,
)
}
} else {
if (entry != null) {
Log.d(

View File

@@ -228,6 +228,8 @@ class GetFlowUtils {
icon = credentialEntry.icon.loadDrawable(context),
shouldTintIcon = credentialEntry.isDefaultIcon ?: false,
lastUsedTimeMillis = credentialEntry.lastUsedTime,
isAutoSelectable = credentialEntry.isAutoSelectAllowed &&
credentialEntry.autoSelectAllowedFromOption,
))
}
is PublicKeyCredentialEntry -> {
@@ -245,6 +247,8 @@ class GetFlowUtils {
icon = credentialEntry.icon.loadDrawable(context),
shouldTintIcon = credentialEntry.isDefaultIcon,
lastUsedTimeMillis = credentialEntry.lastUsedTime,
isAutoSelectable = credentialEntry.isAutoSelectAllowed &&
credentialEntry.autoSelectAllowedFromOption,
))
}
is CustomCredentialEntry -> {
@@ -262,6 +266,8 @@ class GetFlowUtils {
icon = credentialEntry.icon.loadDrawable(context),
shouldTintIcon = credentialEntry.isDefaultIcon,
lastUsedTimeMillis = credentialEntry.lastUsedTime,
isAutoSelectable = credentialEntry.isAutoSelectAllowed &&
credentialEntry.autoSelectAllowedFromOption,
))
}
else -> Log.d(

View File

@@ -155,21 +155,23 @@ class GetTestUtils {
userName: String,
userDisplayName: String?,
lastUsedTime: Instant?,
isAutoSelectAllowed: Boolean = false,
): Entry {
val intent = Intent("com.androidauth.androidvault.CONFIRM_PASSWORD")
.setPackage("com.androidauth.androidvault")
intent.putExtra("provider_extra_sample", "testprovider")
val pendingIntent = PendingIntent.getActivity(
context, 1,
intent, (PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
or PendingIntent.FLAG_ONE_SHOT)
val intent = Intent(Settings.ACTION_SYNC_SETTINGS)
val pendingIntent =
PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE)
val candidateQueryData = Bundle()
candidateQueryData.putBoolean(
"androidx.credentials.BUNDLE_KEY_IS_AUTO_SELECT_ALLOWED",
isAutoSelectAllowed
)
val passkeyEntry = PublicKeyCredentialEntry.Builder(
context,
userName,
pendingIntent,
BeginGetPublicKeyCredentialOption(Bundle(), "id", "requestjson")
).setDisplayName(userDisplayName).setLastUsedTime(lastUsedTime).build()
BeginGetPublicKeyCredentialOption(candidateQueryData, "id", "requestjson")
).setDisplayName(userDisplayName).setLastUsedTime(lastUsedTime)
.setAutoSelectAllowed(isAutoSelectAllowed).build()
return Entry(key, subkey, passkeyEntry.slice, Intent())
}
}

View File

@@ -1,4 +1,18 @@
@file:OptIn(ExperimentalMaterial3Api::class)
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.credentialmanager.createflow
@@ -15,7 +29,6 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.material3.Divider
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.NewReleases

View File

@@ -41,6 +41,23 @@ internal fun hasContentToDisplay(state: GetCredentialUiState): Boolean {
!state.requestDisplayInfo.preferImmediatelyAvailableCredentials)
}
internal fun findAutoSelectEntry(providerDisplayInfo: ProviderDisplayInfo): CredentialEntryInfo? {
if (providerDisplayInfo.authenticationEntryList.isNotEmpty()) {
return null
}
if (providerDisplayInfo.sortedUserNameToCredentialEntryList.size == 1) {
val entryList = providerDisplayInfo.sortedUserNameToCredentialEntryList.firstOrNull()
?: return null
if (entryList.sortedCredentialEntryList.size == 1) {
val entry = entryList.sortedCredentialEntryList.firstOrNull() ?: return null
if (entry.isAutoSelectable) {
return entry
}
}
}
return null
}
data class ProviderInfo(
/**
* Unique id (component name) of this provider.
@@ -83,6 +100,7 @@ class CredentialEntryInfo(
val icon: Drawable?,
val shouldTintIcon: Boolean,
val lastUsedTimeMillis: Instant?,
val isAutoSelectable: Boolean,
) : BaseEntry(
providerId,
entryKey,