Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 7a2b5994 authored by Daniel Kim's avatar Daniel Kim Committed by Android (Google) Code Review
Browse files

Merge "Refactor processing cred response" into main

parents 57660da2 dc9ba485
Loading
Loading
Loading
Loading
+152 −75
Original line number Diff line number Diff line
@@ -50,6 +50,7 @@ import androidx.credentials.provider.PublicKeyCredentialEntry
import com.android.credentialmanager.GetFlowUtils
import com.android.credentialmanager.getflow.CredentialEntryInfo
import com.android.credentialmanager.getflow.ProviderDisplayInfo
import com.android.credentialmanager.getflow.ProviderInfo
import com.android.credentialmanager.getflow.toProviderDisplayInfo
import org.json.JSONObject
import java.util.concurrent.Executors
@@ -155,6 +156,31 @@ class CredentialAutofillService : AutofillService() {
        }
        val entryIconMap: Map<String, Icon> =
                getEntryToIconMap(getCredResponse.candidateProviderDataList)
        val autofillIdToProvidersMap: Map<AutofillId, List<ProviderInfo>> =
                mapAutofillIdToProviders(providerList)
        val fillResponseBuilder = FillResponse.Builder()
        var validFillResponse = false
        autofillIdToProvidersMap.forEach { (autofillId, providers) ->
            validFillResponse = processProvidersForAutofillId(
                    filLRequest, autofillId, providers, entryIconMap, fillResponseBuilder)
                    .or(validFillResponse)
        }
        if (!validFillResponse) {
            return null
        }
        return fillResponseBuilder.build()
    }

    private fun processProvidersForAutofillId(
            filLRequest: FillRequest,
            autofillId: AutofillId,
            providerList: List<ProviderInfo>,
            entryIconMap: Map<String, Icon>,
            fillResponseBuilder: FillResponse.Builder
    ): Boolean {
        if (providerList.isEmpty()) {
            return false
        }
        var totalEntryCount = 0
        providerList.forEach { provider ->
            totalEntryCount += provider.credentialEntryList.size
@@ -169,37 +195,21 @@ class CredentialAutofillService : AutofillService() {
            maxItemCount = maxItemCount.coerceAtMost(inlineMaxSuggestedCount)
        }
        var i = 0
        val fillResponseBuilder = FillResponse.Builder()
        var emptyFillResponse = true
        var datasetAdded = false

        providerDisplayInfo.sortedUserNameToCredentialEntryList.forEach usernameLoop@ {
            val primaryEntry = it.sortedCredentialEntryList.first()
            // In regular CredMan bottomsheet, only one primary entry per username is displayed.
            // But since the credential requests from different fields are allocated into a single
            // request for autofill, there will be duplicate primary entries, especially for
            // username/pw autofill fields. These primary entries will be the same entries except
            // their autofillIds will point to different autofill fields. Process all primary
            // fields.
            // TODO(b/307435163): Merge credential options
            it.sortedCredentialEntryList.forEach entryLoop@ { credentialEntry ->
                if (!isSameCredentialEntry(primaryEntry, credentialEntry)) {
                    // Encountering different credential entry means all the duplicate primary
                    // entries have been processed.
            val pendingIntent = primaryEntry.pendingIntent
            if (pendingIntent == null || primaryEntry.fillInIntent == null) {
                // FillInIntent will not be null because autofillId was retrieved from it.
                Log.e(TAG, "PendingIntent was missing from the entry.")
                return@usernameLoop
            }
                val autofillId: AutofillId? = credentialEntry
                        .fillInIntent
                        ?.getParcelableExtra(
                                CredentialProviderService.EXTRA_AUTOFILL_ID,
                                AutofillId::class.java)
                val pendingIntent = credentialEntry.pendingIntent
                if (autofillId == null || pendingIntent == null) {
                    Log.e(TAG, "AutofillId or pendingIntent was missing from the entry.")
                    return@entryLoop
            if (inlinePresentationSpecs == null || i >= maxItemCount) {
                Log.e(TAG, "Skipping because reached the max item count.")
                return@usernameLoop
            }
                var inlinePresentation: InlinePresentation? = null
            // Create inline presentation
                if (inlinePresentationSpecs != null && i < maxItemCount) {
            val spec: InlinePresentationSpec
            if (i < inlinePresentationSpecsCount) {
                spec = inlinePresentationSpecs[i]
@@ -208,21 +218,19 @@ class CredentialAutofillService : AutofillService() {
            }
            val sliceBuilder = InlineSuggestionUi
                    .newContentBuilder(pendingIntent)
                            .setTitle(credentialEntry.userName)
                    .setTitle(primaryEntry.userName)
            val icon: Icon =
                            entryIconMap[credentialEntry.entryKey + credentialEntry.entrySubkey]
                    entryIconMap[primaryEntry.entryKey + primaryEntry.entrySubkey]
                            ?: getDefaultIcon()
            sliceBuilder.setStartIcon(icon)
                    inlinePresentation = InlinePresentation(
            val inlinePresentation = InlinePresentation(
                    sliceBuilder.build().slice, spec, /* pinned= */ false)
                }
            i++

            val dataSetBuilder = Dataset.Builder()
            val presentationBuilder = Presentations.Builder()
                if (inlinePresentation != null) {
                    presentationBuilder.setInlinePresentation(inlinePresentation)
                }
                    .setInlinePresentation(inlinePresentation)

            fillResponseBuilder.addDataset(
                    dataSetBuilder
                            .setField(
@@ -231,15 +239,95 @@ class CredentialAutofillService : AutofillService() {
                                            presentationBuilder.build())
                                            .build())
                            .setAuthentication(pendingIntent.intentSender)
                                .setAuthenticationExtras(credentialEntry.fillInIntent.extras)
                            .setAuthenticationExtras(primaryEntry.fillInIntent.extras)
                            .build())
                emptyFillResponse = false
            datasetAdded = true
        }
        return datasetAdded
    }
        if (emptyFillResponse) {
            return null

    /**
     *  Maps Autofill Id to provider list. For example, passing in a provider info
     *
     *     ProviderInfo {
     *       id1,
     *       displayName1
     *       [entry1(autofillId1), entry2(autofillId2), entry3(autofillId3)],
     *       ...
     *     }
     *
     *     will result in
     *
     *     { autofillId1: ProviderInfo {
     *         id1,
     *         displayName1,
     *         [entry1(autofillId1)],
     *         ...
     *       }, autofillId2: ProviderInfo {
     *         id1,
     *         displayName1,
     *         [entry2(autofillId2)],
     *         ...
     *       }, autofillId3: ProviderInfo {
     *         id1,
     *         displayName1,
     *         [entry3(autofillId3)],
     *         ...
     *       }
     *     }
     */
    private fun mapAutofillIdToProviders(
            providerList: List<ProviderInfo>
    ): Map<AutofillId, List<ProviderInfo>> {
        val autofillIdToProviders: MutableMap<AutofillId, MutableList<ProviderInfo>> =
                mutableMapOf()
        providerList.forEach { provider ->
            val autofillIdToCredentialEntries:
                    MutableMap<AutofillId, MutableList<CredentialEntryInfo>> =
                    mapAutofillIdToCredentialEntries(provider.credentialEntryList)
            autofillIdToCredentialEntries.forEach { (autofillId, entries) ->
                autofillIdToProviders.getOrPut(autofillId) { mutableListOf() }
                        .add(copyProviderInfo(provider, entries))
            }
        return fillResponseBuilder.build()
        }
        return autofillIdToProviders
    }

    private fun mapAutofillIdToCredentialEntries(
            credentialEntryList: List<CredentialEntryInfo>
    ): MutableMap<AutofillId, MutableList<CredentialEntryInfo>> {
        val autofillIdToCredentialEntries:
                MutableMap<AutofillId, MutableList<CredentialEntryInfo>> = mutableMapOf()
        credentialEntryList.forEach entryLoop@ { credentialEntry ->
            val autofillId: AutofillId? = credentialEntry
                    .fillInIntent
                    ?.getParcelableExtra(
                            CredentialProviderService.EXTRA_AUTOFILL_ID,
                            AutofillId::class.java)
            if (autofillId == null) {
                Log.e(TAG, "AutofillId is missing from credential entry. Credential" +
                        " Integration might be disabled.")
                return@entryLoop
            }
            autofillIdToCredentialEntries.getOrPut(autofillId) { mutableListOf() }
                    .add(credentialEntry)
        }
        return autofillIdToCredentialEntries
    }

    private fun copyProviderInfo(
            providerInfo: ProviderInfo,
            credentialList: List<CredentialEntryInfo>
    ): ProviderInfo {
        return ProviderInfo(
                providerInfo.id,
                providerInfo.icon,
                providerInfo.displayName,
                credentialList,
                providerInfo.authenticationEntryList,
                providerInfo.remoteEntry,
                providerInfo.actionEntryList
        )
    }

    override fun onSaveRequest(request: SaveRequest, callback: SaveCallback) {
@@ -353,15 +441,4 @@ class CredentialAutofillService : AutofillService() {
        }
        return result
    }

    private fun isSameCredentialEntry(
            info1: CredentialEntryInfo,
            info2: CredentialEntryInfo
    ): Boolean {
        return info1.providerId == info2.providerId &&
                info1.lastUsedTimeMillis == info2.lastUsedTimeMillis &&
                info1.credentialType == info2.credentialType &&
                info1.displayName == info2.displayName &&
                info1.userName == info2.userName
    }
}
 No newline at end of file