chromium/chrome/android/java/src/org/chromium/chrome/browser/privacy/settings/PrivacySettings.java

// Copyright 2015 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.privacy.settings;

import static org.chromium.components.content_settings.PrefNames.COOKIE_CONTROLS_MODE;

import android.os.Build;
import android.os.Bundle;
import android.text.SpannableString;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;

import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;

import org.chromium.base.metrics.RecordHistogram;
import org.chromium.base.metrics.RecordUserAction;
import org.chromium.base.supplier.ObservableSupplier;
import org.chromium.base.supplier.ObservableSupplierImpl;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.enterprise.util.ManagedBrowserUtils;
import org.chromium.chrome.browser.flags.ChromeFeatureList;
import org.chromium.chrome.browser.incognito.reauth.IncognitoReauthSettingSwitchPreference;
import org.chromium.chrome.browser.preferences.Pref;
import org.chromium.chrome.browser.prefetch.settings.PreloadPagesSettingsFragment;
import org.chromium.chrome.browser.privacy.secure_dns.SecureDnsSettings;
import org.chromium.chrome.browser.privacy_guide.PrivacyGuideInteractions;
import org.chromium.chrome.browser.privacy_sandbox.PrivacySandboxBridge;
import org.chromium.chrome.browser.privacy_sandbox.PrivacySandboxReferrer;
import org.chromium.chrome.browser.privacy_sandbox.PrivacySandboxSettingsBaseFragment;
import org.chromium.chrome.browser.quick_delete.QuickDeleteController;
import org.chromium.chrome.browser.safe_browsing.SafeBrowsingBridge;
import org.chromium.chrome.browser.safe_browsing.metrics.SettingsAccessPoint;
import org.chromium.chrome.browser.safe_browsing.settings.SafeBrowsingSettingsFragment;
import org.chromium.chrome.browser.settings.ChromeBaseSettingsFragment;
import org.chromium.chrome.browser.settings.ChromeManagedPreferenceDelegate;
import org.chromium.chrome.browser.settings.SettingsLauncherFactory;
import org.chromium.chrome.browser.signin.services.IdentityServicesProvider;
import org.chromium.chrome.browser.sync.settings.GoogleServicesSettings;
import org.chromium.chrome.browser.sync.settings.ManageSyncSettings;
import org.chromium.chrome.browser.usage_stats.UsageStatsConsentDialog;
import org.chromium.components.browser_ui.settings.ChromeSwitchPreference;
import org.chromium.components.browser_ui.settings.SettingsUtils;
import org.chromium.components.browser_ui.site_settings.ContentSettingsResources;
import org.chromium.components.browser_ui.site_settings.SingleCategorySettings;
import org.chromium.components.browser_ui.util.TraceEventVectorDrawableCompat;
import org.chromium.components.signin.identitymanager.ConsentLevel;
import org.chromium.components.user_prefs.UserPrefs;
import org.chromium.ui.text.NoUnderlineClickableSpan;
import org.chromium.ui.text.SpanApplier;

/** Fragment to keep track of the all the privacy related preferences. */
public class PrivacySettings extends ChromeBaseSettingsFragment
        implements Preference.OnPreferenceChangeListener {
    private static final String PREF_CAN_MAKE_PAYMENT = "can_make_payment";
    private static final String PREF_PRELOAD_PAGES = "preload_pages";
    private static final String PREF_HTTPS_FIRST_MODE = "https_first_mode";
    private static final String PREF_SECURE_DNS = "secure_dns";
    private static final String PREF_USAGE_STATS = "usage_stats_reporting";
    private static final String PREF_DO_NOT_TRACK = "do_not_track";
    private static final String PREF_SAFE_BROWSING = "safe_browsing";
    private static final String PREF_SYNC_AND_SERVICES_LINK = "sync_and_services_link";
    private static final String PREF_PRIVACY_SANDBOX = "privacy_sandbox";
    private static final String PREF_PRIVACY_GUIDE = "privacy_guide";
    private static final String PREF_INCOGNITO_LOCK = "incognito_lock";
    private static final String PREF_THIRD_PARTY_COOKIES = "third_party_cookies";
    private static final String PREF_TRACKING_PROTECTION = "tracking_protection";
    @VisibleForTesting static final String PREF_FP_PROTECTION = "fp_protection";
    @VisibleForTesting static final String PREF_IP_PROTECTION = "ip_protection";
    @VisibleForTesting static final String PREF_CLEAR_BROWSING_DATA = "clear_browsing_data";

    @VisibleForTesting
    static final String PREF_CLEAR_BROWSING_DATA_ADVANCED = "clear_browsing_data_advanced";

    private IncognitoLockSettings mIncognitoLockSettings;
    private final ObservableSupplierImpl<String> mPageTitle = new ObservableSupplierImpl<>();

    @Override
    public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
        mPageTitle.set(getString(R.string.prefs_privacy_security));

        SettingsUtils.addPreferencesFromResource(this, R.xml.privacy_preferences);

        Preference fpProtectionPreference = findPreference(PREF_FP_PROTECTION);
        fpProtectionPreference.setVisible(shouldShowFpProtectionUI());

        Preference ipProtectionPreference = findPreference(PREF_IP_PROTECTION);
        ipProtectionPreference.setVisible(shouldShowIpProtectionUI());
        ipProtectionPreference.setOnPreferenceClickListener(
                preference -> {
                    RecordUserAction.record("Settings.IpProtection.OpenedFromPrivacyPage");
                    return false;
                });

        Preference sandboxPreference = findPreference(PREF_PRIVACY_SANDBOX);
        // Overwrite the click listener to pass a correct referrer to the fragment.
        sandboxPreference.setOnPreferenceClickListener(
                preference -> {
                    PrivacySandboxSettingsBaseFragment.launchPrivacySandboxSettings(
                            getContext(), PrivacySandboxReferrer.PRIVACY_SETTINGS);
                    return true;
                });

        PrivacySandboxBridge privacySandboxBridge = new PrivacySandboxBridge(getProfile());
        if (privacySandboxBridge.isPrivacySandboxRestricted()) {
            if (privacySandboxBridge.isRestrictedNoticeEnabled()) {
                // Update the summary to one that describes only ad measurement if ad-measurement
                // is available to restricted users.
                sandboxPreference.setSummary(
                        getContext()
                                .getString(
                                        R.string
                                                .settings_ad_privacy_restricted_link_row_sub_label));
            } else {
                // Hide the Privacy Sandbox if it is restricted and ad-measurement is not
                // available to restricted users.
                getPreferenceScreen().removePreference(sandboxPreference);
            }
        }

        Preference privacyGuidePreference = findPreference(PREF_PRIVACY_GUIDE);
        // Record the launch of PG from the S&P link-row entry point
        privacyGuidePreference.setOnPreferenceClickListener(
                preference -> {
                    RecordUserAction.record("Settings.PrivacyGuide.StartPrivacySettings");
                    RecordHistogram.recordEnumeratedHistogram(
                            "Settings.PrivacyGuide.EntryExit",
                            PrivacyGuideInteractions.SETTINGS_LINK_ROW_ENTRY,
                            PrivacyGuideInteractions.MAX_VALUE);
                    UserPrefs.get(getProfile()).setBoolean(Pref.PRIVACY_GUIDE_VIEWED, true);
                    return false;
                });
        if (getProfile().isChild() || ManagedBrowserUtils.isBrowserManaged(getProfile())) {
            getPreferenceScreen().removePreference(privacyGuidePreference);
        }

        IncognitoReauthSettingSwitchPreference incognitoReauthPreference =
                (IncognitoReauthSettingSwitchPreference) findPreference(PREF_INCOGNITO_LOCK);
        mIncognitoLockSettings = new IncognitoLockSettings(incognitoReauthPreference, getProfile());
        mIncognitoLockSettings.setUpIncognitoReauthPreference(getActivity());

        Preference safeBrowsingPreference = findPreference(PREF_SAFE_BROWSING);
        safeBrowsingPreference.setSummary(
                SafeBrowsingSettingsFragment.getSafeBrowsingSummaryString(
                        getContext(), getProfile()));
        safeBrowsingPreference.setOnPreferenceClickListener(
                (preference) -> {
                    preference
                            .getExtras()
                            .putInt(
                                    SafeBrowsingSettingsFragment.ACCESS_POINT,
                                    SettingsAccessPoint.PARENT_SETTINGS);
                    return false;
                });

        setHasOptionsMenu(true);

        ChromeSwitchPreference canMakePaymentPref =
                (ChromeSwitchPreference) findPreference(PREF_CAN_MAKE_PAYMENT);
        canMakePaymentPref.setOnPreferenceChangeListener(this);

        ChromeSwitchPreference httpsFirstModePref =
                (ChromeSwitchPreference) findPreference(PREF_HTTPS_FIRST_MODE);
        httpsFirstModePref.setOnPreferenceChangeListener(this);
        httpsFirstModePref.setManagedPreferenceDelegate(
                new ChromeManagedPreferenceDelegate(getProfile()) {
                    @Override
                    public boolean isPreferenceControlledByPolicy(Preference preference) {
                        String key = preference.getKey();
                        assert PREF_HTTPS_FIRST_MODE.equals(key)
                                : "Unexpected preference key: " + key;
                        return UserPrefs.get(getProfile())
                                .isManagedPreference(Pref.HTTPS_ONLY_MODE_ENABLED);
                    }

                    @Override
                    public boolean isPreferenceClickDisabled(Preference preference) {
                        // Advanced Protection automatically enables HTTPS-Only Mode so
                        // lock the setting.
                        return isPreferenceControlledByPolicy(preference)
                                || new SafeBrowsingBridge(getProfile()).isUnderAdvancedProtection();
                    }
                });
        httpsFirstModePref.setChecked(
                UserPrefs.get(getProfile()).getBoolean(Pref.HTTPS_ONLY_MODE_ENABLED));
        if (new SafeBrowsingBridge(getProfile()).isUnderAdvancedProtection()) {
            httpsFirstModePref.setSummary(
                    getContext()
                            .getResources()
                            .getString(
                                    R.string
                                            .settings_https_first_mode_with_advanced_protection_summary));
        }

        Preference syncAndServicesLink = findPreference(PREF_SYNC_AND_SERVICES_LINK);
        syncAndServicesLink.setSummary(buildFooterString());

        Preference thirdPartyCookies = findPreference(PREF_THIRD_PARTY_COOKIES);
        Preference doNotTrackPref = findPreference(PREF_DO_NOT_TRACK);

        if (showTrackingProtectionUI()) {
            if (thirdPartyCookies != null) thirdPartyCookies.setVisible(false);
            if (doNotTrackPref != null) doNotTrackPref.setVisible(false);
            Preference trackingProtection = findPreference(PREF_TRACKING_PROTECTION);
            trackingProtection.setVisible(true);
        } else if (thirdPartyCookies != null) {
            thirdPartyCookies
                    .getExtras()
                    .putString(SingleCategorySettings.EXTRA_CATEGORY, thirdPartyCookies.getKey());
            thirdPartyCookies
                    .getExtras()
                    .putString(
                            SingleCategorySettings.EXTRA_TITLE,
                            thirdPartyCookies.getTitle().toString());
        }

        if (QuickDeleteController.isQuickDeleteFollowupEnabled()) {
            Preference clearBrowsingDataPreference = findPreference(PREF_CLEAR_BROWSING_DATA);
            Preference clearBrowsingDataAdvancedPreference =
                    findPreference(PREF_CLEAR_BROWSING_DATA_ADVANCED);
            clearBrowsingDataPreference.setVisible(false);
            clearBrowsingDataAdvancedPreference.setVisible(true);
        }

        updatePreferences();
    }

    @Override
    public ObservableSupplier<String> getPageTitle() {
        return mPageTitle;
    }

    private SpannableString buildFooterString() {
        NoUnderlineClickableSpan servicesLink =
                new NoUnderlineClickableSpan(
                        getContext(),
                        v -> {
                            SettingsLauncherFactory.createSettingsLauncher()
                                    .launchSettingsActivity(
                                            getActivity(), GoogleServicesSettings.class);
                        });
        NoUnderlineClickableSpan accountSettingsLink =
                new NoUnderlineClickableSpan(
                        getContext(),
                        v -> {
                            SettingsLauncherFactory.createSettingsLauncher()
                                    .launchSettingsActivity(
                                            getActivity(),
                                            ManageSyncSettings.class,
                                            ManageSyncSettings.createArguments(false));
                        });
        if (ChromeFeatureList.isEnabled(
                ChromeFeatureList.REPLACE_SYNC_PROMOS_WITH_SIGN_IN_PROMOS)) {
            if (IdentityServicesProvider.get()
                            .getIdentityManager(getProfile())
                            .getPrimaryAccountInfo(ConsentLevel.SIGNIN)
                    == null) {
                // User is signed out, show the string with one link to "Google Services".
                return SpanApplier.applySpans(
                        getString(
                                R.string.privacy_chrome_data_and_google_services_signed_out_footer),
                        new SpanApplier.SpanInfo("<link>", "</link>", servicesLink));
            }
            // Otherwise, show the string with both links to account settings and "Google Services".
            return SpanApplier.applySpans(
                    getString(R.string.privacy_chrome_data_and_google_services_footer),
                    new SpanApplier.SpanInfo("<link1>", "</link1>", accountSettingsLink),
                    new SpanApplier.SpanInfo("<link2>", "</link2>", servicesLink));
        }
        if (IdentityServicesProvider.get()
                        .getIdentityManager(getProfile())
                        .getPrimaryAccountInfo(ConsentLevel.SYNC)
                == null) {
            // Sync is off, show the string with one link to "Google Services".
            return SpanApplier.applySpans(
                    getString(R.string.privacy_sync_and_services_link_sync_off),
                    new SpanApplier.SpanInfo("<link>", "</link>", servicesLink));
        }
        // Otherwise, show the string with both links to "Sync" and "Google Services".
        return SpanApplier.applySpans(
                getString(R.string.privacy_sync_and_services_link_sync_on),
                new SpanApplier.SpanInfo("<link1>", "</link1>", accountSettingsLink),
                new SpanApplier.SpanInfo("<link2>", "</link2>", servicesLink));
    }

    @Override
    public boolean onPreferenceChange(Preference preference, Object newValue) {
        String key = preference.getKey();
        if (PREF_CAN_MAKE_PAYMENT.equals(key)) {
            UserPrefs.get(getProfile())
                    .setBoolean(Pref.CAN_MAKE_PAYMENT_ENABLED, (boolean) newValue);
        } else if (PREF_HTTPS_FIRST_MODE.equals(key)) {
            UserPrefs.get(getProfile())
                    .setBoolean(Pref.HTTPS_ONLY_MODE_ENABLED, (boolean) newValue);
        }
        return true;
    }

    @Override
    public void onResume() {
        super.onResume();
        updatePreferences();
    }

    /** Updates the preferences. */
    public void updatePreferences() {
        ChromeSwitchPreference canMakePaymentPref =
                (ChromeSwitchPreference) findPreference(PREF_CAN_MAKE_PAYMENT);
        if (canMakePaymentPref != null) {
            canMakePaymentPref.setChecked(
                    UserPrefs.get(getProfile()).getBoolean(Pref.CAN_MAKE_PAYMENT_ENABLED));
        }

        Preference doNotTrackPref = findPreference(PREF_DO_NOT_TRACK);
        if (doNotTrackPref != null) {
            doNotTrackPref.setSummary(
                    UserPrefs.get(getProfile()).getBoolean(Pref.ENABLE_DO_NOT_TRACK)
                            ? R.string.text_on
                            : R.string.text_off);
        }

        Preference ipProtectionPref = findPreference(PREF_IP_PROTECTION);
        if (ipProtectionPref != null) {
            ipProtectionPref.setSummary(
                    UserPrefs.get(getProfile()).getBoolean(Pref.IP_PROTECTION_ENABLED)
                            ? R.string.text_on
                            : R.string.text_off);
        }

        Preference fpProtectionPref = findPreference(PREF_FP_PROTECTION);
        if (fpProtectionPref != null) {
            fpProtectionPref.setSummary(
                    UserPrefs.get(getProfile()).getBoolean(Pref.FINGERPRINTING_PROTECTION_ENABLED)
                            ? R.string.text_on
                            : R.string.text_off);
        }

        Preference preloadPagesPreference = findPreference(PREF_PRELOAD_PAGES);
        if (preloadPagesPreference != null) {
            preloadPagesPreference.setSummary(
                    PreloadPagesSettingsFragment.getPreloadPagesSummaryString(
                            getContext(), getProfile()));
        }

        Preference secureDnsPref = findPreference(PREF_SECURE_DNS);
        if (secureDnsPref != null && secureDnsPref.isVisible()) {
            secureDnsPref.setSummary(SecureDnsSettings.getSummary(getContext()));
        }

        Preference safeBrowsingPreference = findPreference(PREF_SAFE_BROWSING);
        if (safeBrowsingPreference != null && safeBrowsingPreference.isVisible()) {
            safeBrowsingPreference.setSummary(
                    SafeBrowsingSettingsFragment.getSafeBrowsingSummaryString(
                            getContext(), getProfile()));
        }

        Preference usageStatsPref = findPreference(PREF_USAGE_STATS);
        if (usageStatsPref != null) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q
                    && UserPrefs.get(getProfile()).getBoolean(Pref.USAGE_STATS_ENABLED)) {
                usageStatsPref.setOnPreferenceClickListener(
                        preference -> {
                            UsageStatsConsentDialog.create(
                                            getActivity(),
                                            getProfile(),
                                            true,
                                            (didConfirm) -> {
                                                if (didConfirm) {
                                                    updatePreferences();
                                                }
                                            })
                                    .show();
                            return true;
                        });
            } else {
                getPreferenceScreen().removePreference(usageStatsPref);
            }
        }

        mIncognitoLockSettings.updateIncognitoReauthPreferenceIfNeeded(getActivity());

        Preference thirdPartyCookies = findPreference(PREF_THIRD_PARTY_COOKIES);
        if (thirdPartyCookies != null) {
            thirdPartyCookies.setSummary(
                    ContentSettingsResources.getThirdPartyCookieListSummary(
                            UserPrefs.get(getProfile()).getInteger(COOKIE_CONTROLS_MODE)));
        }
    }

    private boolean showTrackingProtectionUI() {
        return UserPrefs.get(getProfile()).getBoolean(Pref.TRACKING_PROTECTION3PCD_ENABLED)
                || ChromeFeatureList.isEnabled(ChromeFeatureList.TRACKING_PROTECTION_3PCD);
    }

    private boolean shouldShowIpProtectionUI() {
        return !showTrackingProtectionUI()
                && ChromeFeatureList.isEnabled(ChromeFeatureList.IP_PROTECTION_UX);
    }

    private boolean shouldShowFpProtectionUI() {
        return !showTrackingProtectionUI()
                && ChromeFeatureList.isEnabled(ChromeFeatureList.FINGERPRINTING_PROTECTION_UX);
    }

    @Override
    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
        menu.clear();
        MenuItem help =
                menu.add(Menu.NONE, R.id.menu_id_targeted_help, Menu.NONE, R.string.menu_help);
        help.setIcon(
                TraceEventVectorDrawableCompat.create(
                        getResources(), R.drawable.ic_help_and_feedback, getActivity().getTheme()));
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        if (item.getItemId() == R.id.menu_id_targeted_help) {
            getHelpAndFeedbackLauncher()
                    .show(getActivity(), getString(R.string.help_context_privacy), null);
            return true;
        }
        return false;
    }

    @Override
    public void onDestroy() {
        if (mIncognitoLockSettings != null) {
            mIncognitoLockSettings.destroy();
        }
        super.onDestroy();
    }
}