chromium/chrome/android/java/src/org/chromium/chrome/browser/new_tab_url/DseNewTabUrlManager.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.chrome.browser.new_tab_url;

import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;

import org.chromium.base.Callback;
import org.chromium.base.cached_flags.BooleanCachedFieldTrialParameter;
import org.chromium.base.supplier.ObservableSupplier;
import org.chromium.chrome.browser.flags.ChromeFeatureList;
import org.chromium.chrome.browser.preferences.ChromePreferenceKeys;
import org.chromium.chrome.browser.preferences.ChromeSharedPreferences;
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.chrome.browser.search_engines.TemplateUrlServiceFactory;
import org.chromium.components.embedder_support.util.UrlUtilities;
import org.chromium.components.search_engines.TemplateUrl;
import org.chromium.components.search_engines.TemplateUrlService;
import org.chromium.components.search_engines.TemplateUrlService.TemplateUrlServiceObserver;
import org.chromium.url.GURL;

/**
 * A central class for feature NewTabSearchEngineUrlAndroid which swaps out NTP if the default
 * search engine isn't Google. It holds a reference of {@link TemplateUrlService} and observes the
 * DSE changes to update the cached values in the SharedPreference.
 */
public class DseNewTabUrlManager {
    private ObservableSupplier<Profile> mProfileSupplier;
    private Callback<Profile> mProfileCallback;
    private TemplateUrlService mTemplateUrlService;
    private TemplateUrlServiceObserver mTemplateUrlServiceObserver;

    private static final String SWAP_OUT_NTP_PARAM = "swap_out_ntp";
    public static final BooleanCachedFieldTrialParameter SWAP_OUT_NTP =
            ChromeFeatureList.newBooleanCachedFieldTrialParameter(
                    ChromeFeatureList.NEW_TAB_SEARCH_ENGINE_URL_ANDROID, SWAP_OUT_NTP_PARAM, false);

    public DseNewTabUrlManager(ObservableSupplier<Profile> profileSupplier) {
        mProfileSupplier = profileSupplier;
        mProfileCallback = this::onProfileAvailable;
        mProfileSupplier.addObserver(mProfileCallback);
    }

    /**
     * Returns the new Tab URL of the default search engine if should override any NTP's URL.
     * Returns the given URL if don't need to override.
     * @param gurl The GURL to check.
     */
    public GURL maybeGetOverrideUrl(GURL gurl) {
        if (isIncognito()
                || !shouldSwapOutNtp()
                || isDefaultSearchEngineGoogle()
                || !UrlUtilities.isNtpUrl(gurl)) {
            return gurl;
        }

        String newTabUrl = getDSENewTabUrl(mTemplateUrlService);
        return newTabUrl != null ? new GURL(newTabUrl) : gurl;
    }

    public void destroy() {
        if (mProfileSupplier != null && mProfileCallback != null) {
            mProfileSupplier.removeObserver(mProfileCallback);
            mProfileCallback = null;
            mProfileSupplier = null;
        }
        if (mTemplateUrlService != null) {
            mTemplateUrlService.removeObserver(mTemplateUrlServiceObserver);
            mTemplateUrlServiceObserver = null;
            mTemplateUrlService = null;
        }
    }

    /**
     * Returns the new Tab URL of the default search engine if should override any NTP's URL.
     * Returns the given URL if don't need to override.
     *
     * @param gurl The URL to check.
     * @param profile The instance of the current {@link Profile}.
     */
    public static GURL maybeGetOverrideUrl(GURL gurl, Profile profile) {
        if ((profile != null && profile.isOffTheRecord())
                || !shouldSwapOutNtp()
                || isDefaultSearchEngineGoogle()
                || !UrlUtilities.isNtpUrl(gurl)) {
            return gurl;
        }

        TemplateUrlService templateUrlService =
                profile != null ? TemplateUrlServiceFactory.getForProfile(profile) : null;
        String newTabUrl = getDSENewTabUrl(templateUrlService);
        return newTabUrl != null ? new GURL(newTabUrl) : gurl;
    }

    /** Returns whether the feature NewTabSearchEngineUrlAndroid is enabled. */
    public static boolean isNewTabSearchEngineUrlAndroidEnabled() {
        return ChromeSharedPreferences.getInstance()
                .readBoolean(ChromePreferenceKeys.IS_EEA_CHOICE_COUNTRY, false);
    }

    /**
     * Returns whether the parameter SWAP_OUT_NTP is enabled. Note: this method only checks parts of
     * isNewTabSearchEngineUrlAndroidEnabled(), i.e., it doesn't check country code.
     */
    public static boolean isSwapOutNtpFlagEnabled() {
        return SWAP_OUT_NTP.getValue();
    }

    /**
     * Returns cached value of {@link ChromePreferenceKeys.IS_DSE_GOOGLE} in the SharedPreference.
     */
    public static boolean isDefaultSearchEngineGoogle() {
        return ChromeSharedPreferences.getInstance()
                .readBoolean(ChromePreferenceKeys.IS_DSE_GOOGLE, true);
    }

    /**
     * Returns the new Tab URL of the default search engine:
     * 1. Returns the cached value ChromePreferenceKeys.DSE_NEW_TAB_URL in the SharedPreference if
     *    the templateUrlService is null.
     * 2. Returns null if the DSE is Google.
     * 3. Returns the default search engine's URL if the DSE doesn't provide a new Tab Url.
     * @param templateUrlService The instance of {@link TemplateUrlService}.
     */
    @Nullable
    public static String getDSENewTabUrl(TemplateUrlService templateUrlService) {
        if (templateUrlService == null) {
            return ChromeSharedPreferences.getInstance()
                    .readString(ChromePreferenceKeys.DSE_NEW_TAB_URL, null);
        }

        if (templateUrlService.isDefaultSearchEngineGoogle()) return null;

        TemplateUrl templateUrl = templateUrlService.getDefaultSearchEngineTemplateUrl();
        if (templateUrl == null) return null;

        String newTabUrl = templateUrl.getNewTabURL();
        return newTabUrl != null ? newTabUrl : templateUrl.getURL();
    }

    @VisibleForTesting
    public boolean isIncognito() {
        return mProfileSupplier.hasValue() ? mProfileSupplier.get().isOffTheRecord() : false;
    }

    @VisibleForTesting
    void onProfileAvailable(Profile profile) {
        mTemplateUrlService = TemplateUrlServiceFactory.getForProfile(profile);
        if (mTemplateUrlServiceObserver == null) {
            mTemplateUrlServiceObserver = this::onTemplateURLServiceChanged;
            mTemplateUrlService.addObserver(mTemplateUrlServiceObserver);
        }
        onTemplateURLServiceChanged();
        mProfileSupplier.removeObserver(mProfileCallback);
        mProfileCallback = null;
    }

    private void onTemplateURLServiceChanged() {
        boolean isDSEGoogle = mTemplateUrlService.isDefaultSearchEngineGoogle();
        ChromeSharedPreferences.getInstance()
                .writeBoolean(ChromePreferenceKeys.IS_DSE_GOOGLE, isDSEGoogle);
        ChromeSharedPreferences.getInstance()
                .writeBoolean(
                        ChromePreferenceKeys.IS_EEA_CHOICE_COUNTRY,
                        mTemplateUrlService.isEeaChoiceCountry());
        if (isDSEGoogle) {
            ChromeSharedPreferences.getInstance().removeKey(ChromePreferenceKeys.DSE_NEW_TAB_URL);
        } else {
            ChromeSharedPreferences.getInstance()
                    .writeString(
                            ChromePreferenceKeys.DSE_NEW_TAB_URL,
                            getDSENewTabUrl(mTemplateUrlService));
        }
    }

    private static boolean shouldSwapOutNtp() {
        return isNewTabSearchEngineUrlAndroidEnabled() && SWAP_OUT_NTP.getValue();
    }

    public TemplateUrlService getTemplateUrlServiceForTesting() {
        return mTemplateUrlService;
    }

    public static void setIsEeaChoiceCountryForTesting(boolean isEeaChoiceCountry) {
        ChromeSharedPreferences.getInstance()
                .writeBoolean(ChromePreferenceKeys.IS_EEA_CHOICE_COUNTRY, isEeaChoiceCountry);
    }

    public static void resetIsEeaChoiceCountryForTesting() {
        ChromeSharedPreferences.getInstance().removeKey(ChromePreferenceKeys.IS_EEA_CHOICE_COUNTRY);
    }
}