chromium/chrome/browser/privacy/java/src/org/chromium/chrome/browser/privacy/secure_dns/SecureDnsSettings.java

// Copyright 2020 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.secure_dns;

import android.content.Context;
import android.os.Bundle;

import androidx.preference.Preference;

import org.chromium.base.supplier.ObservableSupplier;
import org.chromium.base.supplier.ObservableSupplierImpl;
import org.chromium.chrome.browser.net.SecureDnsManagementMode;
import org.chromium.chrome.browser.privacy.secure_dns.SecureDnsProviderPreference.State;
import org.chromium.chrome.browser.settings.ChromeBaseSettingsFragment;
import org.chromium.chrome.browser.settings.ChromeManagedPreferenceDelegate;
import org.chromium.components.browser_ui.settings.ChromeSwitchPreference;
import org.chromium.components.browser_ui.settings.SettingsUtils;
import org.chromium.net.SecureDnsMode;

import java.util.List;

/**
 * Fragment to manage Secure DNS preference. It consists of a toggle switch and, if the switch is
 * enabled, a SecureDnsControl.
 */
public class SecureDnsSettings extends ChromeBaseSettingsFragment {
    // Must match keys in secure_dns_settings.xml.
    private static final String PREF_SECURE_DNS_SWITCH = "secure_dns_switch";
    private static final String PREF_SECURE_DNS_PROVIDER = "secure_dns_provider";

    private ChromeSwitchPreference mSecureDnsSwitch;
    private SecureDnsProviderPreference mSecureDnsProviderPreference;
    private final ObservableSupplierImpl<String> mPageTitle = new ObservableSupplierImpl<>();

    /**
     * @return A summary for use in the Preference that opens this fragment.
     */
    public static String getSummary(Context context) {
        @SecureDnsMode int mode = SecureDnsBridge.getMode();
        if (mode == SecureDnsMode.OFF) {
            return context.getString(R.string.text_off);
        } else if (mode == SecureDnsMode.AUTOMATIC) {
            return context.getString(R.string.settings_automatic_mode_summary);
        } else {
            String config = SecureDnsBridge.getConfig();
            List<SecureDnsBridge.Entry> providers = SecureDnsBridge.getProviders();
            String serverName = config;
            for (int i = 0; i < providers.size(); i++) {
                SecureDnsBridge.Entry entry = providers.get(i);
                if (entry.config.equals(config)) {
                    serverName = entry.name;
                    break;
                }
            }
            return String.format("%s - %s", context.getString(R.string.text_on), serverName);
        }
    }

    @Override
    public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
        mPageTitle.set(getString(R.string.settings_secure_dns_title));
        SettingsUtils.addPreferencesFromResource(this, R.xml.secure_dns_settings);

        // Set up preferences inside the activity.
        mSecureDnsSwitch = (ChromeSwitchPreference) findPreference(PREF_SECURE_DNS_SWITCH);
        mSecureDnsSwitch.setManagedPreferenceDelegate(
                new ChromeManagedPreferenceDelegate(getProfile()) {
                    @Override
                    public boolean isPreferenceControlledByPolicy(Preference preference) {
                        return SecureDnsBridge.isModeManaged();
                    }
                });
        mSecureDnsSwitch.setOnPreferenceChangeListener(
                (preference, enabled) -> {
                    storePreferenceState(
                            (boolean) enabled, mSecureDnsProviderPreference.getState());
                    loadPreferenceState();
                    return true;
                });

        if (!SecureDnsBridge.isModeManaged()) {
            // If the mode isn't managed directly, we still need to disable the controls
            // if we detect a managed system configuration, or any parental control software.
            // However, we don't want to show the managed setting icon in this case, because the
            // setting is not directly controlled by a policy.
            @SecureDnsManagementMode int managementMode = SecureDnsBridge.getManagementMode();
            if (managementMode != SecureDnsManagementMode.NO_OVERRIDE) {
                mSecureDnsSwitch.setEnabled(false);
                boolean parentalControls =
                        managementMode == SecureDnsManagementMode.DISABLED_PARENTAL_CONTROLS;
                mSecureDnsSwitch.setSummaryOff(
                        parentalControls
                                ? R.string.settings_secure_dns_disabled_for_parental_control
                                : R.string.settings_secure_dns_disabled_for_managed_environment);
            }
        }

        mSecureDnsProviderPreference =
                (SecureDnsProviderPreference) findPreference(PREF_SECURE_DNS_PROVIDER);
        mSecureDnsProviderPreference.setOnPreferenceChangeListener(
                (preference, value) -> {
                    State controlState = (State) value;
                    boolean valid =
                            storePreferenceState(mSecureDnsSwitch.isChecked(), controlState);
                    if (valid != controlState.valid) {
                        mSecureDnsProviderPreference.setState(controlState.withValid(valid));
                        // Cancel the change to controlState.
                        return false;
                    }
                    return true;
                });

        // Update preference views and state.
        loadPreferenceState();
    }

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

    /**
     * @param enabled Whether the toggle switch is enabled
     * @param controlState The state from SecureDnsControl.
     * @return True if the state was successfully stored.
     */
    private boolean storePreferenceState(boolean enabled, State controlState) {
        if (!enabled) {
            SecureDnsBridge.setMode(SecureDnsMode.OFF);
            SecureDnsBridge.setConfig("");
        } else if (!controlState.secure) {
            SecureDnsBridge.setMode(SecureDnsMode.AUTOMATIC);
            SecureDnsBridge.setConfig("");
        } else {
            if (controlState.config.isEmpty() || !SecureDnsBridge.setConfig(controlState.config)) {
                return false;
            }
            SecureDnsBridge.setMode(SecureDnsMode.SECURE);
        }
        return true;
    }

    private void loadPreferenceState() {
        @SecureDnsMode int mode = SecureDnsBridge.getMode();
        boolean enabled = mode != SecureDnsMode.OFF;
        boolean enforced =
                SecureDnsBridge.isModeManaged()
                        || SecureDnsBridge.getManagementMode()
                                != SecureDnsManagementMode.NO_OVERRIDE;
        mSecureDnsSwitch.setChecked(enabled);
        mSecureDnsProviderPreference.setEnabled(enabled && !enforced);

        boolean secure = mode == SecureDnsMode.SECURE;
        String config = SecureDnsBridge.getConfig();
        boolean valid = true; // States loaded from storage are presumed valid.
        mSecureDnsProviderPreference.setState(new State(secure, config, valid));
    }

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