chromium/components/webauthn/android/java/src/org/chromium/components/webauthn/cred_man/CredManSupportProvider.java

// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.components.webauthn.cred_man;

import android.content.Context;
import android.os.Build;

import org.jni_zero.CalledByNative;

import org.chromium.base.ContextUtils;
import org.chromium.base.metrics.RecordHistogram;
import org.chromium.base.version_info.VersionInfo;
import org.chromium.components.webauthn.CredManSupport;
import org.chromium.components.webauthn.GmsCoreUtils;
import org.chromium.components.webauthn.WebauthnMode;
import org.chromium.components.webauthn.WebauthnModeProvider;
import org.chromium.device.DeviceFeatureList;
import org.chromium.device.DeviceFeatureMap;

public class CredManSupportProvider {
    private static final int GMSCORE_MIN_VERSION_CANARY_DEV = 241900000;
    private static final int GMSCORE_MIN_VERSION_BETA_STABLE = 242300000;

    private static @CredManSupport int sCredManSupport;

    private static boolean sOverrideVersionCheckForTesting;

    public static void setupForTesting(boolean override) {
        sOverrideVersionCheckForTesting = override;
        sCredManSupport = CredManSupport.NOT_EVALUATED;
    }

    @CalledByNative
    public static @CredManSupport int getCredManSupport() {
        if (sCredManSupport != CredManSupport.NOT_EVALUATED) {
            return sCredManSupport;
        }
        if (!sOverrideVersionCheckForTesting) {
            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
                sCredManSupport = CredManSupport.DISABLED;
                return sCredManSupport;
            }
            if (hasOldGmsVersion()) {
                sCredManSupport = CredManSupport.DISABLED;
                return sCredManSupport;
            }
        }

        if (!sOverrideVersionCheckForTesting
                && ContextUtils.getApplicationContext().getSystemService(Context.CREDENTIAL_SERVICE)
                        == null) {
            sCredManSupport = CredManSupport.DISABLED;
            recordCredManAvailability(/*available*/ false);
            return sCredManSupport;
        }
        recordCredManAvailability(/*available*/ true);

        if (DeviceFeatureMap.isEnabled(DeviceFeatureList.WEBAUTHN_ANDROID_CRED_MAN)) {
            CredManUiRecommender recommender =
                    CredManUiRecommenderProvider.getOrCreate().getCredManUiRecommender();
            boolean customUiRecommended =
                    recommender == null ? false : recommender.recommendsCustomUi();
            boolean gpmInCredMan =
                    DeviceFeatureMap.getInstance()
                            .getFieldTrialParamByFeatureAsBoolean(
                                    DeviceFeatureList.WEBAUTHN_ANDROID_CRED_MAN,
                                    "gpm_in_cred_man",
                                    customUiRecommended);
            boolean isChrome3pPwmMode =
                    WebauthnModeProvider.getInstance().getGlobalWebauthnMode()
                            == WebauthnMode.CHROME_3PP_ENABLED;
            // In CHROME_3PP_ENABLED mode Chrome does not use FIDO2 APIs parallel with CredMan. This
            // is because Chrome's Password Manager capabilities are disabled.
            sCredManSupport =
                    gpmInCredMan || isChrome3pPwmMode
                            ? CredManSupport.FULL_UNLESS_INAPPLICABLE
                            : CredManSupport.PARALLEL_WITH_FIDO_2;
            return sCredManSupport;
        }
        sCredManSupport = CredManSupport.IF_REQUIRED;

        return sCredManSupport;
    }

    public static @CredManSupport int getCredManSupportForWebView() {
        if (sCredManSupport != CredManSupport.NOT_EVALUATED) {
            return sCredManSupport;
        }
        if (!sOverrideVersionCheckForTesting) {
            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
                sCredManSupport = CredManSupport.DISABLED;
                return sCredManSupport;
            }
            if (ContextUtils.getApplicationContext().getSystemService(Context.CREDENTIAL_SERVICE)
                    == null) {
                sCredManSupport = CredManSupport.DISABLED;
                return sCredManSupport;
            }
        }
        sCredManSupport = CredManSupport.FULL_UNLESS_INAPPLICABLE;
        return sCredManSupport;
    }

    private static void recordCredManAvailability(boolean available) {
        RecordHistogram.recordBooleanHistogram(
                "WebAuthentication.Android.CredManAvailability", available);
    }

    private static boolean hasOldGmsVersion() {
        assert !sOverrideVersionCheckForTesting : "Don't use in testing!";
        int gmsVersion = GmsCoreUtils.getGmsCoreVersion();
        if (gmsVersion == -1) {
            return true; // Couldn't get a GMS version. Assume insufficient GMS availability.
        }

        final int requiredMinGmsVersion =
                DeviceFeatureMap.getInstance()
                        .getFieldTrialParamByFeatureAsInt(
                                DeviceFeatureList.WEBAUTHN_ANDROID_CRED_MAN,
                                "min_gms_core_version_no_dots",
                                getMinGmsVersionForCurrentChannel());

        return gmsVersion < requiredMinGmsVersion;
    }

    private static int getMinGmsVersionForCurrentChannel() {
        return (VersionInfo.isBetaBuild() || VersionInfo.isStableBuild())
                ? GMSCORE_MIN_VERSION_BETA_STABLE
                : GMSCORE_MIN_VERSION_CANARY_DEV;
    }
}