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

Commit 069fd1bf authored by Daniel's avatar Daniel
Browse files

Fix bug for duplicate entries

The intent had credential entries from duplicate credential options.
This resulted in duplicate entries when clicking on pinned entry. In
Credential Autofill Service, map the credential entries to autofill Id,
so that the entries will be deduped. Then pass the deduped entries to
the auth extras.

Bug: b/319331068
Test: atest CtsAutoFillServiceTestCases:android.autofillservice.cts.inline.InlineLoginMixedCredentialActivityTest
Change-Id: I7537976b50c6a0bbb914c7b883ac469af6d3167a
parent 0744deb6
Loading
Loading
Loading
Loading
+35 −12
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@ import static android.credentials.flags.Flags.FLAG_CONFIGURABLE_SELECTOR_UI_ENAB

import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.annotation.TestApi;
import android.content.ComponentName;
@@ -49,7 +50,7 @@ public class IntentFactory {
    public static Intent createCredentialSelectorIntent(
            @NonNull RequestInfo requestInfo,
            @SuppressLint("ConcreteCollection") // Concrete collection needed for marshalling.
            @NonNull
            @Nullable
            ArrayList<ProviderData> enabledProviderDataList,
            @SuppressLint("ConcreteCollection") // Concrete collection needed for marshalling.
            @NonNull
@@ -57,20 +58,27 @@ public class IntentFactory {
            @NonNull ResultReceiver resultReceiver,
            boolean isRequestForAllOptions) {

        Intent intent = createCredentialSelectorIntent(requestInfo, enabledProviderDataList,
        Intent intent;
        if (enabledProviderDataList != null) {
            intent = createCredentialSelectorIntent(requestInfo, enabledProviderDataList,
                    disabledProviderDataList, resultReceiver);
        } else {
            intent = createCredentialSelectorIntent(requestInfo,
                    disabledProviderDataList, resultReceiver);
        }
        intent.putExtra(Constants.EXTRA_REQ_FOR_ALL_OPTIONS, isRequestForAllOptions);

        return intent;
    }

    /** Generate a new launch intent to the Credential Selector UI. */
    /**
     * Generate a new launch intent to the Credential Selector UI.
     *
     * @hide
     */
    @NonNull
    public static Intent createCredentialSelectorIntent(
            @NonNull RequestInfo requestInfo,
            @SuppressLint("ConcreteCollection") // Concrete collection needed for marshalling.
                    @NonNull
                    ArrayList<ProviderData> enabledProviderDataList,
            @SuppressLint("ConcreteCollection") // Concrete collection needed for marshalling.
            @NonNull
            ArrayList<DisabledProviderData> disabledProviderDataList,
@@ -83,9 +91,6 @@ public class IntentFactory {
                                        com.android.internal.R.string
                                                .config_credentialManagerDialogComponent));
        intent.setComponent(componentName);

        intent.putParcelableArrayListExtra(
                ProviderData.EXTRA_ENABLED_PROVIDER_DATA_LIST, enabledProviderDataList);
        intent.putParcelableArrayListExtra(
                ProviderData.EXTRA_DISABLED_PROVIDER_DATA_LIST, disabledProviderDataList);
        intent.putExtra(RequestInfo.EXTRA_REQUEST_INFO, requestInfo);
@@ -95,6 +100,24 @@ public class IntentFactory {
        return intent;
    }

    /** Generate a new launch intent to the Credential Selector UI. */
    @NonNull
    public static Intent createCredentialSelectorIntent(
            @NonNull RequestInfo requestInfo,
            @SuppressLint("ConcreteCollection") // Concrete collection needed for marshalling.
            @NonNull
            ArrayList<ProviderData> enabledProviderDataList,
            @SuppressLint("ConcreteCollection") // Concrete collection needed for marshalling.
            @NonNull
            ArrayList<DisabledProviderData> disabledProviderDataList,
            @NonNull ResultReceiver resultReceiver) {
        Intent intent = createCredentialSelectorIntent(requestInfo,
                disabledProviderDataList, resultReceiver);
        intent.putParcelableArrayListExtra(
                ProviderData.EXTRA_ENABLED_PROVIDER_DATA_LIST, enabledProviderDataList);
        return intent;
    }

    /**
     * Creates an Intent that cancels any UI matching the given request token id.
     *
+23 −3
Original line number Diff line number Diff line
@@ -32,6 +32,7 @@ import android.os.IBinder
import android.os.Bundle
import android.os.ResultReceiver
import android.util.Log
import android.view.autofill.AutofillManager
import com.android.credentialmanager.createflow.DisabledProviderInfo
import com.android.credentialmanager.createflow.EnabledProviderInfo
import com.android.credentialmanager.createflow.RequestDisplayInfo
@@ -80,9 +81,10 @@ class CredentialManagerRepo(
                    CreateCredentialProviderData::class.java
                ) ?: emptyList()
            RequestInfo.TYPE_GET ->
                intent.extras?.getParcelableArrayList(
                    ProviderData.EXTRA_ENABLED_PROVIDER_DATA_LIST,
                    GetCredentialProviderData::class.java
                getEnabledProviderDataList(
                    intent
                ) ?: getEnabledProviderDataListFromAuthExtras(
                    intent
                ) ?: emptyList()
            else -> {
                Log.d(
@@ -238,6 +240,24 @@ class CredentialManagerRepo(
        )
    }

    private fun getEnabledProviderDataList(intent: Intent): List<GetCredentialProviderData>? {
        return intent.extras?.getParcelableArrayList(
            ProviderData.EXTRA_ENABLED_PROVIDER_DATA_LIST,
            GetCredentialProviderData::class.java
        )
    }

    private fun getEnabledProviderDataListFromAuthExtras(
        intent: Intent
    ): List<GetCredentialProviderData>? {
        return intent.getBundleExtra(
            AutofillManager.EXTRA_AUTH_STATE
        ) ?.getParcelableArrayList(
            ProviderData.EXTRA_ENABLED_PROVIDER_DATA_LIST,
            GetCredentialProviderData::class.java
        )
    }

    // IMPORTANT: new invocation should be mindful that this method can throw.
    private fun getCreateProviderEnableListInitialUiState(): List<EnabledProviderInfo> {
        return CreateFlowUtils.toEnabledProviderList(
+65 −61
Original line number Diff line number Diff line
@@ -16,22 +16,24 @@

package com.android.credentialmanager.autofill

import android.R
import android.app.PendingIntent
import android.app.assist.AssistStructure
import android.content.Context
import android.app.PendingIntent
import android.credentials.GetCredentialResponse
import android.credentials.GetCredentialRequest
import android.credentials.GetCandidateCredentialsResponse
import android.credentials.GetCandidateCredentialsException
import android.credentials.Credential
import android.credentials.CredentialManager
import android.credentials.CredentialOption
import android.credentials.GetCandidateCredentialsException
import android.credentials.GetCandidateCredentialsResponse
import android.credentials.GetCredentialRequest
import android.credentials.GetCredentialResponse
import android.credentials.selection.Entry
import android.credentials.selection.GetCredentialProviderData
import android.credentials.selection.ProviderData
import android.graphics.drawable.Icon
import android.os.Bundle
import android.os.CancellationSignal
import android.os.OutcomeReceiver
import android.provider.Settings
import android.credentials.Credential
import android.service.autofill.AutofillService
import android.service.autofill.Dataset
import android.service.autofill.Field
@@ -44,12 +46,11 @@ import android.service.autofill.SaveCallback
import android.service.autofill.SaveRequest
import android.service.credentials.CredentialProviderService
import android.util.Log
import android.view.autofill.AutofillId
import android.view.autofill.AutofillValue
import android.view.autofill.IAutoFillManagerClient
import android.view.autofill.AutofillId
import android.widget.inline.InlinePresentationSpec
import android.credentials.CredentialManager
import android.widget.RemoteViews
import android.widget.inline.InlinePresentationSpec
import androidx.autofill.inline.v1.InlineSuggestionUi
import androidx.credentials.provider.CustomCredentialEntry
import androidx.credentials.provider.PasswordCredentialEntry
@@ -61,10 +62,9 @@ import com.android.credentialmanager.getflow.toProviderDisplayInfo
import com.android.credentialmanager.ktx.credentialEntry
import com.android.credentialmanager.model.CredentialType
import com.android.credentialmanager.model.get.CredentialEntryInfo
import com.android.credentialmanager.model.get.ProviderInfo
import java.util.concurrent.Executors
import org.json.JSONException
import org.json.JSONObject
import java.util.concurrent.Executors


class CredentialAutofillService : AutofillService() {
@@ -153,8 +153,7 @@ class CredentialAutofillService : AutofillService() {
                    return
                }

                val fillResponse = convertToFillResponse(result, request,
                        this@CredentialAutofillService)
                val fillResponse = convertToFillResponse(result, request)
                if (fillResponse != null) {
                    callback.onSuccess(fillResponse)
                } else {
@@ -231,7 +230,7 @@ class CredentialAutofillService : AutofillService() {
    }

    private fun getEntryToIconMap(
            candidateProviderDataList: MutableList<GetCredentialProviderData>
            candidateProviderDataList: List<GetCredentialProviderData>
    ): Map<String, Icon> {
        val entryIconMap: MutableMap<String, Icon> = mutableMapOf()
        candidateProviderDataList.forEach { provider ->
@@ -261,20 +260,16 @@ class CredentialAutofillService : AutofillService() {

    private fun convertToFillResponse(
            getCredResponse: GetCandidateCredentialsResponse,
            filLRequest: FillRequest,
            context: Context
            filLRequest: FillRequest
    ): FillResponse? {
        val providerList = GetFlowUtils.toProviderList(
                getCredResponse.candidateProviderDataList,
                context)
        if (providerList.isEmpty()) {
        val candidateProviders = getCredResponse.candidateProviderDataList
        if (candidateProviders.isEmpty()) {
            return null
        }

        val entryIconMap: Map<String, Icon> =
                getEntryToIconMap(getCredResponse.candidateProviderDataList)
        val autofillIdToProvidersMap: Map<AutofillId, List<ProviderInfo>> =
                mapAutofillIdToProviders(providerList)
        val entryIconMap: Map<String, Icon> = getEntryToIconMap(candidateProviders)
        val autofillIdToProvidersMap: Map<AutofillId, ArrayList<GetCredentialProviderData>> =
                mapAutofillIdToProviders(candidateProviders)
        val fillResponseBuilder = FillResponse.Builder()
        var validFillResponse = false
        autofillIdToProvidersMap.forEach { (autofillId, providers) ->
@@ -292,11 +287,14 @@ class CredentialAutofillService : AutofillService() {
    private fun processProvidersForAutofillId(
            filLRequest: FillRequest,
            autofillId: AutofillId,
            providerList: List<ProviderInfo>,
            providerDataList: ArrayList<GetCredentialProviderData>,
            entryIconMap: Map<String, Icon>,
            fillResponseBuilder: FillResponse.Builder,
            bottomSheetPendingIntent: PendingIntent?
    ): Boolean {
        val providerList = GetFlowUtils.toProviderList(
            providerDataList,
            this@CredentialAutofillService)
        if (providerList.isEmpty()) {
            return false
        }
@@ -340,7 +338,7 @@ class CredentialAutofillService : AutofillService() {
                return@usernameLoop
            }
            if (i >= maxInlineItemCount && i >= lastDropdownDatasetIndex) {
                return@usernameLoop;
                return@usernameLoop
            }
            val icon: Icon = if (primaryEntry.icon == null) {
                // The empty entry icon has non-null icon reference but null drawable reference.
@@ -398,19 +396,20 @@ class CredentialAutofillService : AutofillService() {
                inlinePresentationSpecsCount)
        if (datasetAdded && bottomSheetPendingIntent != null && pinnedSpec != null) {
            addPinnedInlineSuggestion(bottomSheetPendingIntent, pinnedSpec, autofillId,
                    fillResponseBuilder)
                    fillResponseBuilder, providerDataList)
        }
        return datasetAdded
    }

    private fun createInlinePresentation(primaryEntry: CredentialEntryInfo,
    private fun createInlinePresentation(
        primaryEntry: CredentialEntryInfo,
        pendingIntent: PendingIntent,
        icon: Icon,
        spec: InlinePresentationSpec,
                                         duplicateDisplayNameForPasskeys: MutableMap<String, Boolean>):
            InlinePresentation {
        val displayName: String = if (primaryEntry.credentialType == CredentialType.PASSKEY
                && primaryEntry.displayName != null) {
        duplicateDisplayNameForPasskeys: MutableMap<String, Boolean>
    ): InlinePresentation {
        val displayName: String = if (primaryEntry.credentialType == CredentialType.PASSKEY &&
            primaryEntry.displayName != null) {
            primaryEntry.displayName!!
        } else {
            primaryEntry.userName
@@ -430,7 +429,8 @@ class CredentialAutofillService : AutofillService() {
    private fun addDropdownMoreOptionsPresentation(
            bottomSheetPendingIntent: PendingIntent,
            autofillId: AutofillId,
            fillResponseBuilder: FillResponse.Builder) {
            fillResponseBuilder: FillResponse.Builder
    ) {
        val presentationBuilder = Presentations.Builder()
                .setMenuPresentation(RemoteViewsFactory.createMoreSignInOptionsPresentation(this))

@@ -460,7 +460,8 @@ class CredentialAutofillService : AutofillService() {
            bottomSheetPendingIntent: PendingIntent,
            spec: InlinePresentationSpec,
            autofillId: AutofillId,
            fillResponseBuilder: FillResponse.Builder
            fillResponseBuilder: FillResponse.Builder,
            providerDataList: ArrayList<GetCredentialProviderData>
    ) {
        val dataSetBuilder = Dataset.Builder()
        val sliceBuilder = InlineSuggestionUi
@@ -471,6 +472,10 @@ class CredentialAutofillService : AutofillService() {
                .setInlinePresentation(InlinePresentation(
                        sliceBuilder.build().slice, spec, /* pinned= */ true))

        val extraBundle = Bundle()
        extraBundle.putParcelableArrayList(
                        ProviderData.EXTRA_ENABLED_PROVIDER_DATA_LIST, providerDataList)

        fillResponseBuilder.addDataset(
                dataSetBuilder
                        .setField(
@@ -479,6 +484,7 @@ class CredentialAutofillService : AutofillService() {
                                        presentationBuilder.build())
                                        .build())
                        .setAuthentication(bottomSheetPendingIntent.intentSender)
                        .setAuthenticationExtras(extraBundle)
                        .build()
        )
    }
@@ -514,16 +520,16 @@ class CredentialAutofillService : AutofillService() {
     *     }
     */
    private fun mapAutofillIdToProviders(
            providerList: List<ProviderInfo>
    ): Map<AutofillId, List<ProviderInfo>> {
        val autofillIdToProviders: MutableMap<AutofillId, MutableList<ProviderInfo>> =
        providerList: List<GetCredentialProviderData>
    ): Map<AutofillId, ArrayList<GetCredentialProviderData>> {
        val autofillIdToProviders: MutableMap<AutofillId, ArrayList<GetCredentialProviderData>> =
            mutableMapOf()
        providerList.forEach { provider ->
            val autofillIdToCredentialEntries:
                    MutableMap<AutofillId, MutableList<CredentialEntryInfo>> =
                    mapAutofillIdToCredentialEntries(provider.credentialEntryList)
                    MutableMap<AutofillId, ArrayList<Entry>> =
                mapAutofillIdToCredentialEntries(provider.credentialEntries)
            autofillIdToCredentialEntries.forEach { (autofillId, entries) ->
                autofillIdToProviders.getOrPut(autofillId) { mutableListOf() }
                autofillIdToProviders.getOrPut(autofillId) { ArrayList() }
                        .add(copyProviderInfo(provider, entries))
            }
        }
@@ -531,13 +537,13 @@ class CredentialAutofillService : AutofillService() {
    }

    private fun mapAutofillIdToCredentialEntries(
            credentialEntryList: List<CredentialEntryInfo>
    ): MutableMap<AutofillId, MutableList<CredentialEntryInfo>> {
            credentialEntryList: List<Entry>
    ): MutableMap<AutofillId, ArrayList<Entry>> {
        val autofillIdToCredentialEntries:
                MutableMap<AutofillId, MutableList<CredentialEntryInfo>> = mutableMapOf()
                MutableMap<AutofillId, ArrayList<Entry>> = mutableMapOf()
        credentialEntryList.forEach entryLoop@{ credentialEntry ->
            val autofillId: AutofillId? = credentialEntry
                    .fillInIntent
                    .frameworkExtrasIntent
                    ?.getParcelableExtra(
                            CredentialProviderService.EXTRA_AUTOFILL_ID,
                            AutofillId::class.java)
@@ -546,24 +552,22 @@ class CredentialAutofillService : AutofillService() {
                        " Integration might be disabled.")
                return@entryLoop
            }
            autofillIdToCredentialEntries.getOrPut(autofillId) { mutableListOf() }
            autofillIdToCredentialEntries.getOrPut(autofillId) { ArrayList() }
                    .add(credentialEntry)
        }
        return autofillIdToCredentialEntries
    }

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

+1 −1
Original line number Diff line number Diff line
@@ -178,7 +178,7 @@ public class CredentialManagerUi {
        // intents
        return PendingIntent.getActivityAsUser(
                mContext, /*requestCode=*/0, intent,
                PendingIntent.FLAG_IMMUTABLE, /*options=*/null,
                PendingIntent.FLAG_MUTABLE, /*options=*/null,
                UserHandle.of(mUserId));
    }
}
+1 −1
Original line number Diff line number Diff line
@@ -116,7 +116,7 @@ public class GetCandidateRequestSession extends RequestSession<GetCredentialRequ
                        mRequestId, mClientRequest, mClientAppInfo.getPackageName(),
                        PermissionUtils.hasPermission(mContext, mClientAppInfo.getPackageName(),
                                Manifest.permission.CREDENTIAL_MANAGER_SET_ALLOWED_PROVIDERS)),
                providerDataList,
                /*providerDataList=*/ null,
                /*isRequestForAllOptions=*/ true);

        List<GetCredentialProviderData> candidateProviderDataList = new ArrayList<>();