chromium/android_webview/java/src/org/chromium/android_webview/safe_browsing/AwSafeBrowsingConfigHelper.java

// Copyright 2017 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.android_webview.safe_browsing;

import androidx.annotation.IntDef;
import androidx.annotation.VisibleForTesting;

import org.jni_zero.CalledByNative;
import org.jni_zero.JNINamespace;

import org.chromium.android_webview.ManifestMetadataUtil;
import org.chromium.android_webview.common.AwSwitches;
import org.chromium.android_webview.common.PlatformServiceBridge;
import org.chromium.base.Callback;
import org.chromium.base.CommandLine;
import org.chromium.base.metrics.RecordHistogram;
import org.chromium.base.metrics.ScopedSysTraceEvent;

/** Helper class for getting the configuration settings related to safebrowsing in WebView. */
@JNINamespace("android_webview")
public class AwSafeBrowsingConfigHelper {
    // This does not track the user opt-in. This value tracks whether or not the user opt-in
    // callback has returned. Until the callback has returned, the user opt-in state is unknown.
    private static volatile boolean sUserOptInCallbackReturned;
    // Indicates whether or not we have already invoked getSafeBrowsingUserOptIn().
    private static volatile boolean sHasCalledGetSafeBrowsingUserOptIn;
    // Tracks user opt-in state.
    private static volatile boolean sSafeBrowsingUserOptIn;
    // Tracks developer opt-in state, as expressed via the manifest tag. Note that developers can
    // also invoke WebSettings.setSafeBrowsingEnabled() which overrides the manifest tag, however
    // that state is tracked in AwSettings.
    private static volatile boolean sEnabledByManifest;

    // Used to record the UMA histogram SafeBrowsing.WebView.AppOptIn. Since these values are
    // persisted to logs, they should never be renumbered or reused.
    @IntDef({AppOptIn.NO_PREFERENCE, AppOptIn.OPT_IN, AppOptIn.OPT_OUT})
    @interface AppOptIn {
        int NO_PREFERENCE = 0;
        int OPT_IN = 1;
        int OPT_OUT = 2;

        int COUNT = 3;
    }

    private static void recordAppOptIn(@AppOptIn int value) {
        RecordHistogram.recordEnumeratedHistogram(
                "SafeBrowsing.WebView.AppOptIn", value, AppOptIn.COUNT);
    }

    private static void setSafeBrowsingEnabledByManifest(boolean enabled) {
        sEnabledByManifest = enabled;
    }

    @CalledByNative
    public static boolean getSafeBrowsingEnabledByManifest() {
        return sEnabledByManifest;
    }

    // Should only be called once during startup. Calling this multiple times will skew UMA metrics.
    public static void maybeEnableSafeBrowsingFromManifest() {
        try (ScopedSysTraceEvent e =
                ScopedSysTraceEvent.scoped(
                        "AwSafeBrowsingConfigHelper.maybeEnableSafeBrowsingFromManifest")) {
            Boolean appOptIn = getOptInPreferenceTraced();
            if (appOptIn == null) {
                recordAppOptIn(AppOptIn.NO_PREFERENCE);
            } else if (appOptIn) {
                recordAppOptIn(AppOptIn.OPT_IN);
            } else {
                recordAppOptIn(AppOptIn.OPT_OUT);
            }

            // If the app specifies something, fallback to the app's preference, otherwise check for
            // the existence of the CLI switch.
            setSafeBrowsingEnabledByManifest(
                    appOptIn == null ? !isDisabledByCommandLine() : appOptIn);

            Callback<Boolean> cb =
                    verifyAppsValue ->
                            setSafeBrowsingUserOptIn(Boolean.TRUE.equals(verifyAppsValue));
            PlatformServiceBridge.getInstance().querySafeBrowsingUserConsent(cb);
        }
    }

    private static Boolean getOptInPreferenceTraced() {
        try (ScopedSysTraceEvent e =
                ScopedSysTraceEvent.scoped("AwSafeBrowsingConfigHelper.getAppOptInPreference")) {
            return ManifestMetadataUtil.getSafeBrowsingAppOptInPreference();
        }
    }

    private static boolean isDisabledByCommandLine() {
        try (ScopedSysTraceEvent e =
                ScopedSysTraceEvent.scoped("AwSafeBrowsingConfigHelper.isDisabledByCommandLine")) {
            CommandLine cli = CommandLine.getInstance();
            // Disable flag has higher precedence than the default
            return cli.hasSwitch(AwSwitches.WEBVIEW_DISABLE_SAFEBROWSING_SUPPORT);
        }
    }

    // Can be called from any thread. This returns true or false, depending on user opt-in
    // preference. This returns false if we don't know yet what the user's preference is.
    @CalledByNative
    private static boolean getSafeBrowsingUserOptIn() {
        if (!sHasCalledGetSafeBrowsingUserOptIn) {
            sHasCalledGetSafeBrowsingUserOptIn = true;
            RecordHistogram.recordBooleanHistogram(
                    "SafeBrowsing.WebView.UserOptInKnown.FirstLoad", sUserOptInCallbackReturned);
        }
        RecordHistogram.recordBooleanHistogram(
                "SafeBrowsing.WebView.UserOptInKnown.EveryLoad", sUserOptInCallbackReturned);
        return sSafeBrowsingUserOptIn;
    }

    // This feature checks if GMS is present, enabled, accessible to WebView and has minimum
    // version to support safe browsing
    @CalledByNative
    private static boolean canUseGms() {
        return PlatformServiceBridge.getInstance().canUseGms();
    }

    @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
    public static void setSafeBrowsingUserOptIn(boolean optin) {
        sUserOptInCallbackReturned = true;
        sSafeBrowsingUserOptIn = optin;
    }

    // Not meant to be instantiated.
    private AwSafeBrowsingConfigHelper() {}
}