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

Commit 4387a784 authored by Helen Qin's avatar Helen Qin Committed by Android (Google) Code Review
Browse files

Merge "Log UI configuration metrics" into main

parents 02d1c2d0 e5cee0e9
Loading
Loading
Loading
Loading
+155 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.credentials.selection;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Intent;

/**
 * Result of creating a Credential Manager UI intent.
 *
 * @hide
 */
public final class IntentCreationResult {
    @NonNull
    private final Intent mIntent;
    @Nullable
    private final String mFallbackUiPackageName;
    @Nullable
    private final String mOemUiPackageName;
    @NonNull
    private final OemUiUsageStatus mOemUiUsageStatus;

    private IntentCreationResult(@NonNull Intent intent, @Nullable String fallbackUiPackageName,
            @Nullable String oemUiPackageName, OemUiUsageStatus oemUiUsageStatus) {
        mIntent = intent;
        mFallbackUiPackageName = fallbackUiPackageName;
        mOemUiPackageName = oemUiPackageName;
        mOemUiUsageStatus = oemUiUsageStatus;
    }

    /** Returns the UI intent. */
    @NonNull
    public Intent getIntent() {
        return mIntent;
    }

    /**
     * Returns the result of attempting to use the config_oemCredentialManagerDialogComponent
     * as the Credential Manager UI.
     */
    @NonNull
    public OemUiUsageStatus getOemUiUsageStatus() {
        return mOemUiUsageStatus;
    }

    /**
     * Returns the package name of the ui component specified in
     * config_fallbackCredentialManagerDialogComponent, or null if unspecified / not parsable
     * successfully.
     */
    @Nullable
    public String getFallbackUiPackageName() {
        return mFallbackUiPackageName;
    }

    /**
     * Returns the package name of the oem ui component specified in
     * config_oemCredentialManagerDialogComponent, or null if unspecified / not parsable.
     */
    @Nullable
    public String getOemUiPackageName() {
        return mOemUiPackageName;
    }

    /**
     * Result of attempting to use the config_oemCredentialManagerDialogComponent as the Credential
     * Manager UI.
     */
    public enum OemUiUsageStatus {
        UNKNOWN,
        // Success: the UI specified in config_oemCredentialManagerDialogComponent was used to
        // fulfill the request.
        SUCCESS,
        // The config value was not specified (e.g. left empty).
        OEM_UI_CONFIG_NOT_SPECIFIED,
        // The config value component was specified but not found (e.g. component doesn't exist or
        // component isn't a system app).
        OEM_UI_CONFIG_SPECIFIED_BUT_NOT_FOUND,
        // The config value component was found but not enabled.
        OEM_UI_CONFIG_SPECIFIED_FOUND_BUT_NOT_ENABLED,
    }

    /**
     * Builder for {@link IntentCreationResult}.
     *
     * @hide
     */
    public static final class Builder {
        @NonNull
        private Intent mIntent;
        @Nullable
        private String mFallbackUiPackageName = null;
        @Nullable
        private String mOemUiPackageName = null;
        @NonNull
        private OemUiUsageStatus mOemUiUsageStatus = OemUiUsageStatus.UNKNOWN;

        public Builder(Intent intent) {
            mIntent = intent;
        }

        /**
         * Sets the package name of the ui component specified in
         * config_fallbackCredentialManagerDialogComponent, or null if unspecified / not parsable
         * successfully.
         */
        @NonNull
        public Builder setFallbackUiPackageName(@Nullable String fallbackUiPackageName) {
            mFallbackUiPackageName = fallbackUiPackageName;
            return this;
        }

        /**
         * Sets the package name of the oem ui component specified in
         * config_oemCredentialManagerDialogComponent, or null if unspecified / not parsable.
         */
        @NonNull
        public Builder setOemUiPackageName(@Nullable String oemUiPackageName) {
            mOemUiPackageName = oemUiPackageName;
            return this;
        }

        /**
         * Sets the result of attempting to use the config_oemCredentialManagerDialogComponent
         * as the Credential Manager UI.
         */
        @NonNull
        public Builder setOemUiUsageStatus(OemUiUsageStatus oemUiUsageStatus) {
            mOemUiUsageStatus = oemUiUsageStatus;
            return this;
        }

        /** Builds a {@link IntentCreationResult}. */
        @NonNull
        public IntentCreationResult build() {
            return new IntentCreationResult(mIntent, mFallbackUiPackageName, mOemUiPackageName,
                    mOemUiUsageStatus);
        }
    }
}
+127 −62
Original line number Diff line number Diff line
@@ -36,6 +36,8 @@ import android.os.ResultReceiver;
import android.text.TextUtils;
import android.util.Slog;

import com.android.internal.annotations.VisibleForTesting;

import java.util.ArrayList;

/**
@@ -57,48 +59,142 @@ public class IntentFactory {
     * @hide
     */
    @NonNull
    public static Intent createCredentialSelectorIntentForAutofill(
    public static IntentCreationResult createCredentialSelectorIntentForAutofill(
            @NonNull Context context,
            @NonNull RequestInfo requestInfo,
            @SuppressLint("ConcreteCollection") // Concrete collection needed for marshalling.
            @NonNull
            ArrayList<DisabledProviderData> disabledProviderDataList,
            @NonNull ResultReceiver resultReceiver) {
        return createCredentialSelectorIntent(context, requestInfo,
        return createCredentialSelectorIntentInternal(context, requestInfo,
                disabledProviderDataList, resultReceiver);
    }

    /**
     * Generate a new launch intent to the Credential Selector UI.
     *
     * @param context                  the CredentialManager system service (only expected caller)
     *                                 context that may be used to query existence of the key UI
     *                                 application
     * @param disabledProviderDataList the list of disabled provider data that when non-empty the
     *                                 UI should accordingly generate an entry suggesting the user
     *                                 to navigate to settings and enable them
     * @param enabledProviderDataList  the list of enabled provider that contain options for this
     *                                 request; the UI should render each option to the user for
     *                                 selection
     * @param requestInfo              the display information about the given app request
     * @param resultReceiver           used by the UI to send the UI selection result back
     * @hide
     */
    @NonNull
    public static IntentCreationResult createCredentialSelectorIntentForCredMan(
            @NonNull Context context,
            @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) {
        IntentCreationResult result = createCredentialSelectorIntentInternal(context, requestInfo,
                disabledProviderDataList, resultReceiver);
        result.getIntent().putParcelableArrayListExtra(
                ProviderData.EXTRA_ENABLED_PROVIDER_DATA_LIST, enabledProviderDataList);
        return result;
    }

    /**
     * Generate a new launch intent to the Credential Selector UI.
     *
     * @param context                  the CredentialManager system service (only expected caller)
     *                                 context that may be used to query existence of the key UI
     *                                 application
     * @param disabledProviderDataList the list of disabled provider data that when non-empty the
     *                                 UI should accordingly generate an entry suggesting the user
     *                                 to navigate to settings and enable them
     * @param enabledProviderDataList  the list of enabled provider that contain options for this
     *                                 request; the UI should render each option to the user for
     *                                 selection
     * @param requestInfo              the display information about the given app request
     * @param resultReceiver           used by the UI to send the UI selection result back
     */
    @VisibleForTesting
    @NonNull
    private static Intent createCredentialSelectorIntent(
    public static Intent createCredentialSelectorIntent(
            @NonNull Context context,
            @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) {
        return createCredentialSelectorIntentForCredMan(context, requestInfo,
                enabledProviderDataList, disabledProviderDataList, resultReceiver).getIntent();
    }

    /**
     * Creates an Intent that cancels any UI matching the given request token id.
     */
    @VisibleForTesting
    @NonNull
    public static Intent createCancelUiIntent(@NonNull Context context,
            @NonNull IBinder requestToken, boolean shouldShowCancellationUi,
            @NonNull String appPackageName) {
        Intent intent = new Intent();
        setCredentialSelectorUiComponentName(context, intent);
        IntentCreationResult.Builder intentResultBuilder = new IntentCreationResult.Builder(intent);
        setCredentialSelectorUiComponentName(context, intent, intentResultBuilder);
        intent.putExtra(CancelSelectionRequest.EXTRA_CANCEL_UI_REQUEST,
                new CancelSelectionRequest(new RequestToken(requestToken), shouldShowCancellationUi,
                        appPackageName));
        return intent;
    }

    /**
     * Generate a new launch intent to the Credential Selector UI.
     */
    @NonNull
    private static IntentCreationResult createCredentialSelectorIntentInternal(
            @NonNull Context context,
            @NonNull RequestInfo requestInfo,
            @SuppressLint("ConcreteCollection") // Concrete collection needed for marshalling.
            @NonNull
            ArrayList<DisabledProviderData> disabledProviderDataList,
            @NonNull ResultReceiver resultReceiver) {
        Intent intent = new Intent();
        IntentCreationResult.Builder intentResultBuilder = new IntentCreationResult.Builder(intent);
        setCredentialSelectorUiComponentName(context, intent, intentResultBuilder);
        intent.putParcelableArrayListExtra(
                ProviderData.EXTRA_DISABLED_PROVIDER_DATA_LIST, disabledProviderDataList);
        intent.putExtra(RequestInfo.EXTRA_REQUEST_INFO, requestInfo);
        intent.putExtra(
                Constants.EXTRA_RESULT_RECEIVER, toIpcFriendlyResultReceiver(resultReceiver));

        return intent;
        return intentResultBuilder.build();
    }

    private static void setCredentialSelectorUiComponentName(@NonNull Context context,
            @NonNull Intent intent) {
            @NonNull Intent intent, @NonNull IntentCreationResult.Builder intentResultBuilder) {
        if (configurableSelectorUiEnabled()) {
            ComponentName componentName = getOemOverrideComponentName(context);
            if (componentName == null) {
                componentName = ComponentName.unflattenFromString(Resources.getSystem().getString(
            ComponentName componentName = getOemOverrideComponentName(context, intentResultBuilder);

            ComponentName fallbackUiComponentName = null;
            try {
                fallbackUiComponentName = ComponentName.unflattenFromString(
                        Resources.getSystem().getString(
                                com.android.internal.R.string
                                        .config_fallbackCredentialManagerDialogComponent));
                intentResultBuilder.setFallbackUiPackageName(
                        fallbackUiComponentName.getPackageName());
            } catch (Exception e) {
                Slog.w(TAG, "Fallback CredMan IU not found: " + e);
            }

            if (componentName == null) {
                componentName = fallbackUiComponentName;
            }

            intent.setComponent(componentName);
        } else {
            ComponentName componentName = ComponentName.unflattenFromString(Resources.getSystem()
@@ -113,7 +209,8 @@ public class IntentFactory {
     * default platform UI component name should be used instead.
     */
    @Nullable
    private static ComponentName getOemOverrideComponentName(@NonNull Context context) {
    private static ComponentName getOemOverrideComponentName(@NonNull Context context,
            @NonNull IntentCreationResult.Builder intentResultBuilder) {
        ComponentName result = null;
        String oemComponentString =
                Resources.getSystem()
@@ -121,85 +218,53 @@ public class IntentFactory {
                                com.android.internal.R.string
                                        .config_oemCredentialManagerDialogComponent);
        if (!TextUtils.isEmpty(oemComponentString)) {
            ComponentName oemComponentName = ComponentName.unflattenFromString(
            ComponentName oemComponentName = null;
            try {
                oemComponentName = ComponentName.unflattenFromString(
                        oemComponentString);
            } catch (Exception e) {
                Slog.i(TAG, "Failed to parse OEM component name " + oemComponentString + ": " + e);
            }
            if (oemComponentName != null) {
                try {
                    intentResultBuilder.setOemUiPackageName(oemComponentName.getPackageName());
                    ActivityInfo info = context.getPackageManager().getActivityInfo(
                            oemComponentName,
                            PackageManager.ComponentInfoFlags.of(
                                    PackageManager.MATCH_SYSTEM_ONLY));
                    if (info.enabled && info.exported) {
                        intentResultBuilder.setOemUiUsageStatus(IntentCreationResult
                                .OemUiUsageStatus.SUCCESS);
                        Slog.i(TAG,
                                "Found enabled oem CredMan UI component."
                                        + oemComponentString);
                        result = oemComponentName;
                    } else {
                        intentResultBuilder.setOemUiUsageStatus(IntentCreationResult
                                .OemUiUsageStatus.OEM_UI_CONFIG_SPECIFIED_FOUND_BUT_NOT_ENABLED);
                        Slog.i(TAG,
                                "Found enabled oem CredMan UI component but it was not "
                                        + "enabled.");
                    }
                } catch (PackageManager.NameNotFoundException e) {
                    intentResultBuilder.setOemUiUsageStatus(IntentCreationResult.OemUiUsageStatus
                            .OEM_UI_CONFIG_SPECIFIED_BUT_NOT_FOUND);
                    Slog.i(TAG, "Unable to find oem CredMan UI component: "
                            + oemComponentString + ".");
                }
            } else {
                intentResultBuilder.setOemUiUsageStatus(IntentCreationResult.OemUiUsageStatus
                        .OEM_UI_CONFIG_SPECIFIED_BUT_NOT_FOUND);
                Slog.i(TAG, "Invalid OEM ComponentName format.");
            }
        } else {
            intentResultBuilder.setOemUiUsageStatus(
                    IntentCreationResult.OemUiUsageStatus.OEM_UI_CONFIG_NOT_SPECIFIED);
            Slog.i(TAG, "Invalid empty OEM component name.");
        }
        return result;
    }

    /**
     * Generate a new launch intent to the Credential Selector UI.
     *
     * @param context                  the CredentialManager system service (only expected caller)
     *                                 context that may be used to query existence of the key UI
     *                                 application
     * @param disabledProviderDataList the list of disabled provider data that when non-empty the
     *                                 UI should accordingly generate an entry suggesting the user
     *                                 to navigate to settings and enable them
     * @param enabledProviderDataList  the list of enabled provider that contain options for this
     *                                 request; the UI should render each option to the user for
     *                                 selection
     * @param requestInfo              the display information about the given app request
     * @param resultReceiver           used by the UI to send the UI selection result back
     */
    @NonNull
    public static Intent createCredentialSelectorIntent(
            @NonNull Context context,
            @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(context, 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.
     */
    @NonNull
    public static Intent createCancelUiIntent(@NonNull Context context,
            @NonNull IBinder requestToken, boolean shouldShowCancellationUi,
            @NonNull String appPackageName) {
        Intent intent = new Intent();
        setCredentialSelectorUiComponentName(context, intent);
        intent.putExtra(CancelSelectionRequest.EXTRA_CANCEL_UI_REQUEST,
                new CancelSelectionRequest(new RequestToken(requestToken), shouldShowCancellationUi,
                        appPackageName));
        return intent;
    }

    /**
     * Convert an instance of a "locally-defined" ResultReceiver to an instance of {@link
     * android.os.ResultReceiver} itself, which the receiving process will be able to unmarshall.
+2 −1
Original line number Diff line number Diff line
@@ -112,7 +112,8 @@ public final class CreateRequestSession extends RequestSession<CreateCredentialR
                                    Manifest.permission.CREDENTIAL_MANAGER_SET_ALLOWED_PROVIDERS),
                            /*defaultProviderId=*/flattenedPrimaryProviders,
                            /*isShowAllOptionsRequested=*/ false),
                    providerDataList);
                    providerDataList,
                    mRequestSessionMetric);
            mClientCallback.onPendingIntent(mPendingIntent);
        } catch (RemoteException e) {
            mRequestSessionMetric.collectUiReturnedFinalPhase(/*uiReturned=*/ false);
+20 −9
Original line number Diff line number Diff line
@@ -25,6 +25,7 @@ import android.content.Intent;
import android.credentials.CredentialManager;
import android.credentials.CredentialProviderInfo;
import android.credentials.selection.DisabledProviderData;
import android.credentials.selection.IntentCreationResult;
import android.credentials.selection.IntentFactory;
import android.credentials.selection.ProviderData;
import android.credentials.selection.RequestInfo;
@@ -37,6 +38,8 @@ import android.os.ResultReceiver;
import android.os.UserHandle;
import android.service.credentials.CredentialProviderInfoFactory;

import com.android.server.credentials.metrics.RequestSessionMetric;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
@@ -159,7 +162,8 @@ public class CredentialManagerUi {
     * @param providerDataList       the list of provider data from remote providers
     */
    public PendingIntent createPendingIntent(
            RequestInfo requestInfo, ArrayList<ProviderData> providerDataList) {
            RequestInfo requestInfo, ArrayList<ProviderData> providerDataList,
            RequestSessionMetric requestSessionMetric) {
        List<CredentialProviderInfo> allProviders =
                CredentialProviderInfoFactory.getCredentialProviderServices(
                        mContext,
@@ -174,10 +178,12 @@ public class CredentialManagerUi {
                .map(disabledProvider -> new DisabledProviderData(
                        disabledProvider.getComponentName().flattenToString())).toList();

        Intent intent;
        intent = IntentFactory.createCredentialSelectorIntent(
                mContext, requestInfo, providerDataList,
        IntentCreationResult intentCreationResult = IntentFactory
                .createCredentialSelectorIntentForCredMan(mContext, requestInfo, providerDataList,
                        new ArrayList<>(disabledProviderDataList), mResultReceiver);
        requestSessionMetric.collectUiConfigurationResults(
                mContext, intentCreationResult, mUserId);
        Intent intent = intentCreationResult.getIntent();
        intent.setAction(UUID.randomUUID().toString());
        //TODO: Create unique pending intent using request code and cancel any pre-existing pending
        // intents
@@ -197,10 +203,15 @@ public class CredentialManagerUi {
     * of the pinned entry.
     *
     * @param requestInfo            the information about the request
     * @param requestSessionMetric   the metric object for logging
     */
    public Intent createIntentForAutofill(RequestInfo requestInfo) {
        return IntentFactory.createCredentialSelectorIntentForAutofill(
                mContext, requestInfo, new ArrayList<>(),
    public Intent createIntentForAutofill(RequestInfo requestInfo,
            RequestSessionMetric requestSessionMetric) {
        IntentCreationResult intentCreationResult = IntentFactory
                .createCredentialSelectorIntentForAutofill(mContext, requestInfo, new ArrayList<>(),
                        mResultReceiver);
        requestSessionMetric.collectUiConfigurationResults(
                mContext, intentCreationResult, mUserId);
        return intentCreationResult.getIntent();
    }
}
+2 −1
Original line number Diff line number Diff line
@@ -122,7 +122,8 @@ public class GetCandidateRequestSession extends RequestSession<GetCredentialRequ
                        mRequestId, mClientRequest, mClientAppInfo.getPackageName(),
                        PermissionUtils.hasPermission(mContext, mClientAppInfo.getPackageName(),
                                Manifest.permission.CREDENTIAL_MANAGER_SET_ALLOWED_PROVIDERS),
                        /*isShowAllOptionsRequested=*/ true));
                        /*isShowAllOptionsRequested=*/ true),
                mRequestSessionMetric);

        List<GetCredentialProviderData> candidateProviderDataList = new ArrayList<>();
        for (ProviderData providerData : providerDataList) {
Loading