diff options
6 files changed, 68 insertions, 16 deletions
diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/CredentialKtx.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/CredentialKtx.kt index 83490b899eb0..b435bb8ad3a9 100644 --- a/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/CredentialKtx.kt +++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/CredentialKtx.kt @@ -30,6 +30,7 @@ import android.graphics.drawable.Drawable import android.text.TextUtils import android.util.Log import androidx.activity.result.IntentSenderRequest +import androidx.credentials.PasswordCredential import androidx.credentials.PublicKeyCredential import androidx.credentials.provider.Action import androidx.credentials.provider.AuthenticationAction @@ -125,6 +126,7 @@ private fun getCredentialOptionInfoList( pendingIntent = credentialEntry.pendingIntent, fillInIntent = it.frameworkExtrasIntent, credentialType = CredentialType.PASSWORD, + rawCredentialType = PasswordCredential.TYPE_PASSWORD_CREDENTIAL, credentialTypeDisplayName = credentialEntry.typeDisplayName.toString(), userName = credentialEntry.username.toString(), displayName = credentialEntry.displayName?.toString(), @@ -149,6 +151,7 @@ private fun getCredentialOptionInfoList( pendingIntent = credentialEntry.pendingIntent, fillInIntent = it.frameworkExtrasIntent, credentialType = CredentialType.PASSKEY, + rawCredentialType = PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL, credentialTypeDisplayName = credentialEntry.typeDisplayName.toString(), userName = credentialEntry.username.toString(), displayName = credentialEntry.displayName?.toString(), @@ -175,6 +178,7 @@ private fun getCredentialOptionInfoList( pendingIntent = credentialEntry.pendingIntent, fillInIntent = it.frameworkExtrasIntent, credentialType = CredentialType.UNKNOWN, + rawCredentialType = credentialEntry.type, credentialTypeDisplayName = credentialEntry.typeDisplayName?.toString().orEmpty(), userName = credentialEntry.title.toString(), diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/model/get/CredentialEntryInfo.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/model/get/CredentialEntryInfo.kt index 9f360965fcf9..ac42b60d51c1 100644 --- a/packages/CredentialManager/shared/src/com/android/credentialmanager/model/get/CredentialEntryInfo.kt +++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/model/get/CredentialEntryInfo.kt @@ -31,6 +31,11 @@ class CredentialEntryInfo( fillInIntent: Intent?, /** Type of this credential used for sorting. Not localized so must not be directly displayed. */ val credentialType: CredentialType, + /** + * String type value of this credential used for sorting. Not localized so must not be directly + * displayed. + */ + val rawCredentialType: String, /** Localized type value of this credential used for display purpose. */ val credentialTypeDisplayName: String, val providerDisplayName: String, diff --git a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt index ccf401da5a4c..6a1998a5e24e 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt @@ -20,6 +20,7 @@ import android.content.ComponentName import android.content.Context import android.content.pm.PackageInfo import android.content.pm.PackageManager +import android.credentials.GetCredentialRequest import android.credentials.selection.CreateCredentialProviderData import android.credentials.selection.DisabledProviderData import android.credentials.selection.Entry @@ -44,6 +45,9 @@ import androidx.credentials.CreateCredentialRequest import androidx.credentials.CreateCustomCredentialRequest import androidx.credentials.CreatePasswordRequest import androidx.credentials.CreatePublicKeyCredentialRequest +import androidx.credentials.PasswordCredential +import androidx.credentials.PriorityHints +import androidx.credentials.PublicKeyCredential import androidx.credentials.provider.CreateEntry import androidx.credentials.provider.RemoteEntry import org.json.JSONObject @@ -162,6 +166,25 @@ private fun getPackageInfo( /** Utility functions for converting CredentialManager data structures to or from UI formats. */ class GetFlowUtils { companion object { + fun extractTypePriorityMap(request: GetCredentialRequest): Map<String, Int> { + val typePriorityMap = mutableMapOf<String, Int>() + request.credentialOptions.forEach {option -> + // TODO(b/280085288) - use jetpack conversion method when exposed, rather than + // parsing from the raw Bundle + val priority = option.candidateQueryData.getInt( + "androidx.credentials.BUNDLE_KEY_TYPE_PRIORITY_VALUE", + when (option.type) { + PasswordCredential.TYPE_PASSWORD_CREDENTIAL -> + PriorityHints.PRIORITY_PASSWORD_OR_SIMILAR + PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL -> 100 + else -> PriorityHints.PRIORITY_DEFAULT + } + ) + typePriorityMap[option.type] = priority + } + return typePriorityMap + } + // Returns the list (potentially empty) of enabled provider. fun toProviderList( providerDataList: List<GetCredentialProviderData>, @@ -193,6 +216,9 @@ class GetFlowUtils { null } } + + val typePriorityMap = extractTypePriorityMap(getCredentialRequest) + return com.android.credentialmanager.getflow.RequestDisplayInfo( appName = originName?.ifEmpty { null } ?: getAppLabel(context.packageManager, requestInfo.packageName) @@ -203,6 +229,7 @@ class GetFlowUtils { // exposed. "androidx.credentials.BUNDLE_KEY_PREFER_IDENTITY_DOC_UI"), preferTopBrandingContent = preferTopBrandingContent, + typePriorityMap = typePriorityMap, ) } } diff --git a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt index 293e1112636e..4e1f4ee2e565 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt @@ -33,7 +33,6 @@ import android.graphics.drawable.Icon import android.os.Bundle import android.os.CancellationSignal import android.os.OutcomeReceiver -import android.provider.Settings import android.service.autofill.AutofillService import android.service.autofill.Dataset import android.service.autofill.Field @@ -140,7 +139,7 @@ class CredentialAutofillService : AutofillService() { override fun onResult(result: GetCandidateCredentialsResponse) { Log.i(TAG, "getCandidateCredentials onResult") val fillResponse = convertToFillResponse(result, request, - responseClientState) + responseClientState, GetFlowUtils.extractTypePriorityMap(getCredRequest)) if (fillResponse != null) { callback.onSuccess(fillResponse) } else { @@ -197,7 +196,8 @@ class CredentialAutofillService : AutofillService() { private fun convertToFillResponse( getCredResponse: GetCandidateCredentialsResponse, filLRequest: FillRequest, - responseClientState: Bundle + responseClientState: Bundle, + typePriorityMap: Map<String, Int>, ): FillResponse? { val candidateProviders = getCredResponse.candidateProviderDataList if (candidateProviders.isEmpty()) { @@ -213,7 +213,7 @@ class CredentialAutofillService : AutofillService() { autofillIdToProvidersMap.forEach { (autofillId, providers) -> validFillResponse = processProvidersForAutofillId( filLRequest, autofillId, providers, entryIconMap, fillResponseBuilder, - getCredResponse.intent) + getCredResponse.intent, typePriorityMap) .or(validFillResponse) } if (!validFillResponse) { @@ -229,7 +229,8 @@ class CredentialAutofillService : AutofillService() { providerDataList: ArrayList<GetCredentialProviderData>, entryIconMap: Map<String, Icon>, fillResponseBuilder: FillResponse.Builder, - bottomSheetIntent: Intent + bottomSheetIntent: Intent, + typePriorityMap: Map<String, Int>, ): Boolean { val providerList = GetFlowUtils.toProviderList( providerDataList, @@ -237,7 +238,8 @@ class CredentialAutofillService : AutofillService() { if (providerList.isEmpty()) { return false } - val providerDisplayInfo: ProviderDisplayInfo = toProviderDisplayInfo(providerList) + val providerDisplayInfo: ProviderDisplayInfo = + toProviderDisplayInfo(providerList, typePriorityMap) var totalEntryCount = providerDisplayInfo.sortedUserNameToCredentialEntryList.size val inlineSuggestionsRequest = filLRequest.inlineSuggestionsRequest val inlinePresentationSpecs = inlineSuggestionsRequest?.inlinePresentationSpecs diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt index e7f11a15a06c..ef4018833721 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt @@ -18,9 +18,9 @@ package com.android.credentialmanager.getflow import android.credentials.flags.Flags.selectorUiImprovementsEnabled import android.graphics.drawable.Drawable +import androidx.credentials.PriorityHints import com.android.credentialmanager.model.get.ProviderInfo import com.android.credentialmanager.model.EntryInfo -import com.android.credentialmanager.model.CredentialType import com.android.credentialmanager.model.get.AuthenticationEntryInfo import com.android.credentialmanager.model.get.CredentialEntryInfo import com.android.credentialmanager.model.get.RemoteEntryInfo @@ -30,7 +30,8 @@ data class GetCredentialUiState( val isRequestForAllOptions: Boolean, val providerInfoList: List<ProviderInfo>, val requestDisplayInfo: RequestDisplayInfo, - val providerDisplayInfo: ProviderDisplayInfo = toProviderDisplayInfo(providerInfoList), + val providerDisplayInfo: ProviderDisplayInfo = + toProviderDisplayInfo(providerInfoList, requestDisplayInfo.typePriorityMap), val currentScreenState: GetScreenState = toGetScreenState( providerDisplayInfo, isRequestForAllOptions), val activeEntry: EntryInfo? = toActiveEntry(providerDisplayInfo), @@ -79,6 +80,8 @@ data class RequestDisplayInfo( val preferIdentityDocUi: Boolean, // A top level branding icon + display name preferred by the app. val preferTopBrandingContent: TopBrandingContent?, + // Map of credential type -> priority. + val typePriorityMap: Map<String, Int>, ) data class TopBrandingContent( @@ -119,7 +122,8 @@ enum class GetScreenState { * @hide */ fun toProviderDisplayInfo( - providerInfoList: List<ProviderInfo> + providerInfoList: List<ProviderInfo>, + typePriorityMap: Map<String, Int>, ): ProviderDisplayInfo { val userNameToCredentialEntryMap = mutableMapOf<String, MutableList<CredentialEntryInfo>>() val authenticationEntryList = mutableListOf<AuthenticationEntryInfo>() @@ -147,7 +151,7 @@ fun toProviderDisplayInfo( } // Compose sortedUserNameToCredentialEntryList - val comparator = CredentialEntryInfoComparatorByTypeThenTimestamp() + val comparator = CredentialEntryInfoComparatorByTypeThenTimestamp(typePriorityMap) // Sort per username userNameToCredentialEntryMap.values.forEach { it.sortWith(comparator) @@ -203,13 +207,21 @@ private fun toGetScreenState( else GetScreenState.PRIMARY_SELECTION } -internal class CredentialEntryInfoComparatorByTypeThenTimestamp : Comparator<CredentialEntryInfo> { +internal class CredentialEntryInfoComparatorByTypeThenTimestamp( + val typePriorityMap: Map<String, Int>, +) : Comparator<CredentialEntryInfo> { override fun compare(p0: CredentialEntryInfo, p1: CredentialEntryInfo): Int { // First prefer passkey type for its security benefits - if (p0.credentialType != p1.credentialType) { - if (CredentialType.PASSKEY == p0.credentialType) { + if (p0.rawCredentialType != p1.rawCredentialType) { + val p0Priority = typePriorityMap.getOrDefault( + p0.rawCredentialType, PriorityHints.PRIORITY_DEFAULT + ) + val p1Priority = typePriorityMap.getOrDefault( + p1.rawCredentialType, PriorityHints.PRIORITY_DEFAULT + ) + if (p0Priority < p1Priority) { return -1 - } else if (CredentialType.PASSKEY == p1.credentialType) { + } else if (p1Priority < p0Priority) { return 1 } } diff --git a/packages/CredentialManager/tests/robotests/screenshot/src/com/android/credentialmanager/GetCredScreenshotTest.kt b/packages/CredentialManager/tests/robotests/screenshot/src/com/android/credentialmanager/GetCredScreenshotTest.kt index 94217d0c00c3..0820d266a9f5 100644 --- a/packages/CredentialManager/tests/robotests/screenshot/src/com/android/credentialmanager/GetCredScreenshotTest.kt +++ b/packages/CredentialManager/tests/robotests/screenshot/src/com/android/credentialmanager/GetCredScreenshotTest.kt @@ -53,6 +53,7 @@ class GetCredScreenshotTest(emulationSpec: DeviceEmulationSpec) { preferImmediatelyAvailableCredentials = false, preferIdentityDocUi = false, preferTopBrandingContent = null, + typePriorityMap = emptyMap(), ) } @@ -68,7 +69,7 @@ class GetCredScreenshotTest(emulationSpec: DeviceEmulationSpec) { fun singleCredentialScreen_M3BottomSheetDisabled() { setFlagsRule.disableFlags(Flags.FLAG_SELECTOR_UI_IMPROVEMENTS_ENABLED) val providerInfoList = buildProviderInfoList() - val providerDisplayInfo = toProviderDisplayInfo(providerInfoList) + val providerDisplayInfo = toProviderDisplayInfo(providerInfoList, emptyMap()) val activeEntry = toActiveEntry(providerDisplayInfo) screenshotRule.screenshotTest("singleCredentialScreen") { ModalBottomSheet( @@ -96,7 +97,7 @@ class GetCredScreenshotTest(emulationSpec: DeviceEmulationSpec) { fun singleCredentialScreen_M3BottomSheetEnabled() { setFlagsRule.enableFlags(Flags.FLAG_SELECTOR_UI_IMPROVEMENTS_ENABLED) val providerInfoList = buildProviderInfoList() - val providerDisplayInfo = toProviderDisplayInfo(providerInfoList) + val providerDisplayInfo = toProviderDisplayInfo(providerInfoList, emptyMap()) val activeEntry = toActiveEntry(providerDisplayInfo) screenshotRule.screenshotTest( "singleCredentialScreen_newM3BottomSheet", @@ -149,6 +150,7 @@ class GetCredScreenshotTest(emulationSpec: DeviceEmulationSpec) { isAutoSelectable = false, entryGroupId = "username", isDefaultIconPreferredAsSingleProvider = false, + rawCredentialType = "unknown-type", ) ), authenticationEntryList = emptyList(), |