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

Commit e5cee0e9 authored by Helen Qin's avatar Helen Qin
Browse files

Log UI configuration metrics

Test: statsd_localdrive
Bug: 327514622
Change-Id: I67d996168863823d994d2588044024cd8ef5d515
parent 3cb2f4c3
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