summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Helen Qin <helenqin@google.com> 2024-03-04 01:07:22 +0000
committer Helen Qin <helenqin@google.com> 2024-03-04 16:34:57 +0000
commitba3f8eddbc42e8582e3fa6a66cea7e83701bf52e (patch)
treed667ce3b8245fd63ced0239062b255aa06de5df5
parentd6211262dee90347a57b9f41118440b41ac9d4df (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
-rw-r--r--packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/CredentialKtx.kt4
-rw-r--r--packages/CredentialManager/shared/src/com/android/credentialmanager/model/get/CredentialEntryInfo.kt5
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt27
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt14
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt28
-rw-r--r--packages/CredentialManager/tests/robotests/screenshot/src/com/android/credentialmanager/GetCredScreenshotTest.kt6
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(),