diff options
author | 2024-03-04 01:07:22 +0000 | |
---|---|---|
committer | 2024-03-04 16:34:57 +0000 | |
commit | ba3f8eddbc42e8582e3fa6a66cea7e83701bf52e (patch) | |
tree | d667ce3b8245fd63ced0239062b255aa06de5df5 | |
parent | d6211262dee90347a57b9f41118440b41ac9d4df (diff) |
Prioritize Credential Types based on priority set by jetpack library
Before, we hardcode the logic such that passkey > password > others.
Now, we rely instead on the library typePriorityHint bit for each
credential type for ranking.
1. Front page
a. dedup'ed under the same username, entries ranked by type priority and then by lastUsedTime
b. cross-ranked by lastUsedTime
2. More-option page
a. cross sections ranked by lastUsedTime of the front entry within each section
b. each section (under the same username), entries ranked by type priority and then by lastUsedTime
Bug: 280085288
Test: see recording attached in bug
Change-Id: I32d98ef15188c529cc5aa3cd08d898d48f229118
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(), |