chromium/android_webview/java/src/org/chromium/android_webview/supervised_user/AwSupervisedUserUrlClassifier.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.android_webview.supervised_user;

import androidx.annotation.Nullable;

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

import org.chromium.android_webview.AwFeatureMap;
import org.chromium.android_webview.common.AwFeatures;
import org.chromium.android_webview.common.AwSupervisedUserUrlClassifierDelegate;
import org.chromium.android_webview.common.PlatformServiceBridge;
import org.chromium.base.ContextUtils;
import org.chromium.base.ThreadUtils;
import org.chromium.url.GURL;

/**
 * This class is used for determining if the current android user can access a given url. It
 * provides the link between native code and GMS Core where the actual url check takes place. Note
 * that the the user may change whether they are supervised or not between calls, but this is
 * handled on the GMS side.
 *
 * <p>Additionally supervised status is per Android Profile, so will be shared by all WebView
 * Profiles. There is currently no WebView/WebView Profile specific customisation allowed.
 *
 * <p>All of these methods can be called on any thread.
 *
 * <p>Lifetime: Singleton
 */
@JNINamespace("android_webview")
public class AwSupervisedUserUrlClassifier {
    private static @Nullable AwSupervisedUserUrlClassifier sInstance;
    private static final Object sInstanceLock = new Object();
    private static boolean sInitialized;

    private final AwSupervisedUserUrlClassifierDelegate mDelegate;

    private AwSupervisedUserUrlClassifier(AwSupervisedUserUrlClassifierDelegate delegate) {
        mDelegate = delegate;
    }

    public static AwSupervisedUserUrlClassifier getInstance() {
        // Supervised user filters currently do not function in the SDK sandbox.
        // See https://crbug.com/1523530.
        if (ContextUtils.isSdkSandboxProcess()) return null;

        synchronized (sInstanceLock) {
            if (!sInitialized) {
                if (AwFeatureMap.isEnabled(AwFeatures.WEBVIEW_SUPERVISED_USER_SITE_DETECTION)
                        || AwFeatureMap.isEnabled(AwFeatures.WEBVIEW_SUPERVISED_USER_SITE_BLOCK)) {
                    AwSupervisedUserUrlClassifierDelegate delegate =
                            PlatformServiceBridge.getInstance().getUrlClassifierDelegate();
                    if (delegate != null) {
                        sInstance = new AwSupervisedUserUrlClassifier(delegate);
                    }
                }
                sInitialized = true;
            }

            return sInstance;
        }
    }

    public static void resetInstanceForTesting() {
        synchronized (sInstanceLock) {
            sInstance = null;
            sInitialized = false;
        }
    }

    public void checkIfNeedRestrictedContentBlocking() {
        mDelegate.needsRestrictedContentBlocking(
                result -> {
                    ThreadUtils.postOnUiThread(
                            () -> {
                                AwSupervisedUserUrlClassifierJni.get()
                                        .setUserRequiresUrlChecks(result);
                            });
                });
    }

    @CalledByNative
    public static boolean shouldCreateThrottle() {
        return (getInstance() != null);
    }

    @CalledByNative
    public static void shouldBlockUrl(GURL requestUrl, long nativeCallbackPtr) {
        getInstance()
                .mDelegate
                .shouldBlockUrl(
                        requestUrl,
                        shouldBlockUrl -> {
                            ThreadUtils.postOnUiThread(
                                    () -> {
                                        AwSupervisedUserUrlClassifierJni.get()
                                                .onShouldBlockUrlResult(
                                                        nativeCallbackPtr, shouldBlockUrl);
                                    });
                        });
    }

    @NativeMethods
    interface Natives {
        void onShouldBlockUrlResult(long callbackPtr, boolean shouldBlock);

        void setUserRequiresUrlChecks(boolean userRequiresUrlChecks);
    }
}