Commit f384aa73 authored by Ben Schwartz's avatar Ben Schwartz Committed by Commit Bot

Secure DNS UI for Android

This replicates the desktop UI, with platform-appropriate adjustments.

Bug: 1040146
Change-Id: I33cea7dd37086d87580e149404ed3619a828ee45
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2240456Reviewed-by: default avatarPeter Conn <peconn@chromium.org>
Reviewed-by: default avatarNatalie Chouinard <chouinard@chromium.org>
Commit-Queue: Ben Schwartz <bemasc@chromium.org>
Auto-Submit: Ben Schwartz <bemasc@chromium.org>
Cr-Commit-Position: refs/heads/master@{#782049}
parent 10d15b44
...@@ -1002,6 +1002,8 @@ chrome_java_resources = [ ...@@ -1002,6 +1002,8 @@ chrome_java_resources = [
"java/res/layout/search_engine.xml", "java/res/layout/search_engine.xml",
"java/res/layout/search_engine_recent_title.xml", "java/res/layout/search_engine_recent_title.xml",
"java/res/layout/search_widget_template.xml", "java/res/layout/search_widget_template.xml",
"java/res/layout/secure_dns_provider_preference.xml",
"java/res/layout/secure_dns_provider_spinner_item.xml",
"java/res/layout/send_tab_to_self_device_picker_item.xml", "java/res/layout/send_tab_to_self_device_picker_item.xml",
"java/res/layout/send_tab_to_self_device_picker_list.xml", "java/res/layout/send_tab_to_self_device_picker_list.xml",
"java/res/layout/send_tab_to_self_device_picker_toolbar.xml", "java/res/layout/send_tab_to_self_device_picker_toolbar.xml",
...@@ -1119,6 +1121,7 @@ chrome_java_resources = [ ...@@ -1119,6 +1121,7 @@ chrome_java_resources = [
"java/res/xml/notifications_preferences.xml", "java/res/xml/notifications_preferences.xml",
"java/res/xml/privacy_preferences.xml", "java/res/xml/privacy_preferences.xml",
"java/res/xml/search_widget_info.xml", "java/res/xml/search_widget_info.xml",
"java/res/xml/secure_dns_settings.xml",
"java/res/xml/sync_and_services_preferences.xml", "java/res/xml/sync_and_services_preferences.xml",
"java/res/xml/theme_preferences.xml", "java/res/xml/theme_preferences.xml",
"java/res/xml/tracing_preferences.xml", "java/res/xml/tracing_preferences.xml",
......
...@@ -1336,6 +1336,8 @@ chrome_java_sources = [ ...@@ -1336,6 +1336,8 @@ chrome_java_sources = [
"java/src/org/chromium/chrome/browser/privacy/settings/DoNotTrackSettings.java", "java/src/org/chromium/chrome/browser/privacy/settings/DoNotTrackSettings.java",
"java/src/org/chromium/chrome/browser/privacy/settings/PrivacyPreferencesManager.java", "java/src/org/chromium/chrome/browser/privacy/settings/PrivacyPreferencesManager.java",
"java/src/org/chromium/chrome/browser/privacy/settings/PrivacySettings.java", "java/src/org/chromium/chrome/browser/privacy/settings/PrivacySettings.java",
"java/src/org/chromium/chrome/browser/privacy/settings/SecureDnsProviderPreference.java",
"java/src/org/chromium/chrome/browser/privacy/settings/SecureDnsSettings.java",
"java/src/org/chromium/chrome/browser/provider/BaseColumns.java", "java/src/org/chromium/chrome/browser/provider/BaseColumns.java",
"java/src/org/chromium/chrome/browser/provider/BookmarkColumns.java", "java/src/org/chromium/chrome/browser/provider/BookmarkColumns.java",
"java/src/org/chromium/chrome/browser/provider/ChromeBrowserProvider.java", "java/src/org/chromium/chrome/browser/provider/ChromeBrowserProvider.java",
......
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2020 The Chromium Authors. All rights reserved.
Use of this source code is governed by a BSD-style license that can be
found in the LICENSE file. -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?android:attr/selectableItemBackground"
android:gravity="center_vertical"
android:orientation="vertical"
android:paddingBottom="8dp"
android:focusable="false">
<org.chromium.components.browser_ui.widget.RadioButtonWithDescriptionLayout
android:id="@+id/mode_group"
android:layout_width="match_parent"
android:layout_height="match_parent">
<org.chromium.components.browser_ui.widget.RadioButtonWithDescription
android:id="@+id/automatic"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="@dimen/min_touch_target_size"
android:background="?attr/selectableItemBackground"
app:primaryText="@string/settings_automatic_mode_label"
app:descriptionText="@string/settings_automatic_mode_description"/>
<org.chromium.components.browser_ui.widget.RadioButtonWithDescription
android:id="@+id/secure"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="@dimen/min_touch_target_size"
android:background="?attr/selectableItemBackground"
app:primaryText="@string/settings_secure_dropdown_mode_description"/>
</org.chromium.components.browser_ui.widget.RadioButtonWithDescriptionLayout>
<!-- This container is relocated into @id/secure as an accessory view. -->
<LinearLayout
android:id="@+id/selection_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?android:attr/selectableItemBackground"
android:paddingStart="@dimen/radio_button_accessory_view_padding_start"
android:paddingEnd="@dimen/radio_button_accessory_view_padding_end"
android:gravity="start"
android:orientation="vertical"
android:focusable="false">
<Spinner
android:id="@+id/dropdown_spinner"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="4dp"/>
<TextView
android:id="@+id/privacy_policy"
android:paddingStart="4dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="@style/TextAppearance.TextMedium.Secondary"
android:text="@string/settings_secure_dropdown_mode_privacy_policy"/>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/custom_server_layout"
android:labelFor="@id/custom_server"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<EditText
tools:ignore="LabelFor"
android:id="@+id/custom_server"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:singleLine="true"
android:textAppearance="@style/TextAppearance.TextLarge.Primary"
android:inputType="textUri"
android:imeOptions="actionDone"
android:hint="@string/settings_secure_dns_custom_placeholder"/>
</com.google.android.material.textfield.TextInputLayout>
</LinearLayout>
</LinearLayout>
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2020 The Chromium Authors. All rights reserved.
Use of this source code is governed by a BSD-style license that can be
found in the LICENSE file. -->
<!-- Copy of @layout/preference_spinner_single_line_item with padding removed. -->
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
style="?android:attr/spinnerItemStyle"
android:maxLines="1"
android:textAppearance="@style/TextAppearance.TextLarge.Primary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="marquee"
android:textAlignment="inherit"
android:paddingStart="0dp"/>
...@@ -23,6 +23,10 @@ ...@@ -23,6 +23,10 @@
android:fragment="org.chromium.chrome.browser.privacy.settings.DoNotTrackSettings" android:fragment="org.chromium.chrome.browser.privacy.settings.DoNotTrackSettings"
android:key="do_not_track" android:key="do_not_track"
android:title="@string/do_not_track_title" /> android:title="@string/do_not_track_title" />
<org.chromium.components.browser_ui.settings.ChromeBasePreference
android:key="secure_dns"
android:title="@string/settings_secure_dns_title"
android:fragment="org.chromium.chrome.browser.privacy.settings.SecureDnsSettings" />
<Preference <Preference
android:key="clear_browsing_data" android:key="clear_browsing_data"
android:title="@string/clear_browsing_data_title" android:title="@string/clear_browsing_data_title"
......
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2020 The Chromium Authors. All rights reserved.
Use of this source code is governed by a BSD-style license that can be
found in the LICENSE file. -->
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android">
<org.chromium.components.browser_ui.settings.ChromeSwitchPreference
android:key="secure_dns_switch"
android:persistent="false"
android:title="@string/settings_secure_dns_title"
android:summary="@string/settings_secure_dns_description" />
<org.chromium.chrome.browser.privacy.settings.SecureDnsProviderPreference
android:key="secure_dns_provider" />
</PreferenceScreen>
...@@ -9,6 +9,7 @@ import android.content.Context; ...@@ -9,6 +9,7 @@ import android.content.Context;
import android.net.ConnectivityManager; import android.net.ConnectivityManager;
import android.net.NetworkInfo; import android.net.NetworkInfo;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting; import androidx.annotation.VisibleForTesting;
import org.chromium.base.CommandLine; import org.chromium.base.CommandLine;
...@@ -41,19 +42,19 @@ public class PrivacyPreferencesManager implements CrashReportingPermissionManage ...@@ -41,19 +42,19 @@ public class PrivacyPreferencesManager implements CrashReportingPermissionManage
* for display in the UI. * for display in the UI.
*/ */
static class DohEntry { static class DohEntry {
final String mName; // Display name public final @NonNull String name; // Display name
final String mTemplate; // URI template, or "" for the custom entry. public final @NonNull String template; // URI template, or "" for the custom entry.
final String mPrivacy; // Privacy policy link public final @NonNull String privacy; // Privacy policy link
DohEntry(String name, String template, String privacy) { DohEntry(String name, String template, String privacy) {
mName = name; this.name = name;
mTemplate = template; this.template = template;
mPrivacy = privacy; this.privacy = privacy;
} }
@Override @Override
public String toString() { public String toString() {
return mName; return name;
} }
} }
...@@ -368,7 +369,7 @@ public class PrivacyPreferencesManager implements CrashReportingPermissionManage ...@@ -368,7 +369,7 @@ public class PrivacyPreferencesManager implements CrashReportingPermissionManage
* *
* @param mode The desired new Secure DNS mode. * @param mode The desired new Secure DNS mode.
*/ */
public void setSecureDnsModeMode(@SecureDnsMode int mode) { public void setSecureDnsMode(@SecureDnsMode int mode) {
PrivacyPreferencesManagerJni.get().setSecureDnsMode(mode); PrivacyPreferencesManagerJni.get().setSecureDnsMode(mode);
} }
...@@ -392,6 +393,10 @@ public class PrivacyPreferencesManager implements CrashReportingPermissionManage ...@@ -392,6 +393,10 @@ public class PrivacyPreferencesManager implements CrashReportingPermissionManage
} }
/** /**
* Get the raw preference value, which can represent multiple templates separated
* by whitespace. The raw value is needed in order to allow direct editing while
* preserving whitespace.
*
* @return The templates (separated by spaces) of the DoH server * @return The templates (separated by spaces) of the DoH server
* currently selected for use in "secure" mode, or "" if there is none. * currently selected for use in "secure" mode, or "" if there is none.
*/ */
...@@ -425,7 +430,7 @@ public class PrivacyPreferencesManager implements CrashReportingPermissionManage ...@@ -425,7 +430,7 @@ public class PrivacyPreferencesManager implements CrashReportingPermissionManage
*/ */
public void updateDohDropdownHistograms(DohEntry oldEntry, DohEntry newEntry) { public void updateDohDropdownHistograms(DohEntry oldEntry, DohEntry newEntry) {
PrivacyPreferencesManagerJni.get().updateDohDropdownHistograms( PrivacyPreferencesManagerJni.get().updateDohDropdownHistograms(
oldEntry.mTemplate, newEntry.mTemplate); oldEntry.template, newEntry.template);
} }
/** /**
......
...@@ -19,6 +19,7 @@ import org.chromium.chrome.R; ...@@ -19,6 +19,7 @@ import org.chromium.chrome.R;
import org.chromium.chrome.browser.help.HelpAndFeedback; import org.chromium.chrome.browser.help.HelpAndFeedback;
import org.chromium.chrome.browser.preferences.Pref; import org.chromium.chrome.browser.preferences.Pref;
import org.chromium.chrome.browser.preferences.PrefServiceBridge; import org.chromium.chrome.browser.preferences.PrefServiceBridge;
import org.chromium.chrome.browser.privacy.settings.PrivacyPreferencesManager.DohEntry;
import org.chromium.chrome.browser.profiles.Profile; import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.chrome.browser.settings.ChromeManagedPreferenceDelegate; import org.chromium.chrome.browser.settings.ChromeManagedPreferenceDelegate;
import org.chromium.chrome.browser.settings.SettingsLauncher; import org.chromium.chrome.browser.settings.SettingsLauncher;
...@@ -28,9 +29,12 @@ import org.chromium.chrome.browser.usage_stats.UsageStatsConsentDialog; ...@@ -28,9 +29,12 @@ import org.chromium.chrome.browser.usage_stats.UsageStatsConsentDialog;
import org.chromium.components.browser_ui.settings.ChromeBaseCheckBoxPreference; import org.chromium.components.browser_ui.settings.ChromeBaseCheckBoxPreference;
import org.chromium.components.browser_ui.settings.ManagedPreferenceDelegate; import org.chromium.components.browser_ui.settings.ManagedPreferenceDelegate;
import org.chromium.components.browser_ui.settings.SettingsUtils; import org.chromium.components.browser_ui.settings.SettingsUtils;
import org.chromium.net.SecureDnsMode;
import org.chromium.ui.text.NoUnderlineClickableSpan; import org.chromium.ui.text.NoUnderlineClickableSpan;
import org.chromium.ui.text.SpanApplier; import org.chromium.ui.text.SpanApplier;
import java.util.List;
/** /**
* Fragment to keep track of the all the privacy related preferences. * Fragment to keep track of the all the privacy related preferences.
*/ */
...@@ -38,6 +42,7 @@ public class PrivacySettings ...@@ -38,6 +42,7 @@ public class PrivacySettings
extends PreferenceFragmentCompat implements Preference.OnPreferenceChangeListener { extends PreferenceFragmentCompat implements Preference.OnPreferenceChangeListener {
private static final String PREF_CAN_MAKE_PAYMENT = "can_make_payment"; private static final String PREF_CAN_MAKE_PAYMENT = "can_make_payment";
private static final String PREF_NETWORK_PREDICTIONS = "preload_pages"; private static final String PREF_NETWORK_PREDICTIONS = "preload_pages";
private static final String PREF_SECURE_DNS = "secure_dns";
private static final String PREF_USAGE_STATS = "usage_stats_reporting"; 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_DO_NOT_TRACK = "do_not_track";
private static final String PREF_SYNC_AND_SERVICES_LINK = "sync_and_services_link"; private static final String PREF_SYNC_AND_SERVICES_LINK = "sync_and_services_link";
...@@ -65,6 +70,9 @@ public class PrivacySettings ...@@ -65,6 +70,9 @@ public class PrivacySettings
networkPredictionPref.setOnPreferenceChangeListener(this); networkPredictionPref.setOnPreferenceChangeListener(this);
networkPredictionPref.setManagedPreferenceDelegate(mManagedPreferenceDelegate); networkPredictionPref.setManagedPreferenceDelegate(mManagedPreferenceDelegate);
Preference secureDnsPref = findPreference(PREF_SECURE_DNS);
secureDnsPref.setVisible(privacyPrefManager.isDnsOverHttpsUiEnabled());
Preference syncAndServicesLink = findPreference(PREF_SYNC_AND_SERVICES_LINK); Preference syncAndServicesLink = findPreference(PREF_SYNC_AND_SERVICES_LINK);
NoUnderlineClickableSpan linkSpan = new NoUnderlineClickableSpan(getResources(), view -> { NoUnderlineClickableSpan linkSpan = new NoUnderlineClickableSpan(getResources(), view -> {
SettingsLauncher settingsLauncher = new SettingsLauncherImpl(); SettingsLauncher settingsLauncher = new SettingsLauncherImpl();
...@@ -117,6 +125,31 @@ public class PrivacySettings ...@@ -117,6 +125,31 @@ public class PrivacySettings
: R.string.text_off); : R.string.text_off);
} }
Preference secureDnsPref = findPreference(PREF_SECURE_DNS);
if (secureDnsPref != null && secureDnsPref.isVisible()) {
PrivacyPreferencesManager manager = PrivacyPreferencesManager.getInstance();
@SecureDnsMode
int mode = manager.getSecureDnsMode();
if (mode == SecureDnsMode.OFF) {
secureDnsPref.setSummary(R.string.text_off);
} else if (mode == SecureDnsMode.AUTOMATIC) {
secureDnsPref.setSummary(R.string.settings_automatic_mode_summary);
} else {
String templateGroup = manager.getDnsOverHttpsTemplates();
List<DohEntry> providers = manager.getDohProviders();
String serverName = templateGroup;
for (int i = 0; i < providers.size(); i++) {
DohEntry entry = providers.get(i);
if (entry.template.equals(templateGroup)) {
serverName = entry.name;
break;
}
}
secureDnsPref.setSummary(
String.format("%s - %s", getString(R.string.text_on), serverName));
}
}
Preference usageStatsPref = findPreference(PREF_USAGE_STATS); Preference usageStatsPref = findPreference(PREF_USAGE_STATS);
if (usageStatsPref != null) { if (usageStatsPref != null) {
if (BuildInfo.isAtLeastQ() && prefServiceBridge.getBoolean(Pref.USAGE_STATS_ENABLED)) { if (BuildInfo.isAtLeastQ() && prefServiceBridge.getBoolean(Pref.USAGE_STATS_ENABLED)) {
......
// Copyright 2020 The Chromium Authors. All rights reserved.
// 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 android.content.Context;
import android.text.Editable;
import android.text.Html;
import android.text.TextWatcher;
import android.text.method.LinkMovementMethod;
import android.util.AttributeSet;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.EditText;
import android.widget.RadioGroup;
import android.widget.Spinner;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.preference.Preference;
import androidx.preference.PreferenceViewHolder;
import com.google.android.material.textfield.TextInputLayout;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.privacy.settings.PrivacyPreferencesManager.DohEntry;
import org.chromium.components.browser_ui.widget.RadioButtonWithDescription;
import org.chromium.components.browser_ui.widget.RadioButtonWithDescriptionLayout;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* SecureDnsProviderPreference is the user interface that is shown when Secure DNS is enabled.
* When Secure DNS is disabled, the SecureDnsProviderPreference is hidden.
*/
public class SecureDnsProviderPreference
extends Preference implements RadioGroup.OnCheckedChangeListener,
AdapterView.OnItemSelectedListener, TextWatcher {
// UI strings, loaded from the context.
private final String mPrivacyTemplate;
private final String mInvalidWarning;
private final String mProbeWarning;
// Server menu entries.
private final List<DohEntry> mOptions;
// UI elements. These fields are assigned only once, in onBindViewHolder.
private RadioButtonWithDescriptionLayout mGroup;
private RadioButtonWithDescription mAutomaticButton;
private RadioButtonWithDescription mSecureButton;
private Spinner mServerMenu;
private TextView mPrivacyPolicy;
private EditText mCustomServer;
private TextInputLayout mCustomServerLayout;
// All variable UI state for SecureDnsProviderPreference is encapsulated in this field.
// To ensure that the UI is updated whenever the state changes, this field
// should only be modified by setState().
private State mState;
// Checks whether the current template is actually reachable, and updates
// mCustomServerLayout's error state.
private final Runnable mProbeRunner = this::startServerProbe;
/**
* State is an immutable representation of the control's current UI state. It can represent
* states that are invalid, which are required when editing the template or changing modes.
*/
static class State {
// Indicates that secure mode is selected.
public final boolean secure;
// The selected or entered DoH template(s), if any.
public final @NonNull String template;
// Whether the selected template is valid.
public final boolean valid;
State(boolean secure, @NonNull String template, boolean valid) {
this.secure = secure;
this.template = template;
this.valid = valid;
}
@Override
public boolean equals(Object obj) {
if (obj instanceof State) {
State other = (State) obj;
return other.secure == secure && other.template.equals(template)
&& other.valid == valid;
}
return false;
}
@Override
public int hashCode() {
// This method is not used, but is defined here for consistency with equals().
return toString().hashCode();
}
State withSecure(boolean secure) {
return new State(secure, template, valid);
}
State withTemplate(@NonNull String template) {
return new State(secure, template, valid);
}
State withValid(boolean valid) {
return new State(secure, template, valid);
}
@Override
public @NonNull String toString() {
return String.format("State(%b, %s, %b)", secure, template, valid);
}
}
public SecureDnsProviderPreference(Context context, AttributeSet attrs) {
super(context, attrs);
// Inflating from XML.
setLayoutResource(R.layout.secure_dns_provider_preference);
// Preload strings from disk.
mPrivacyTemplate = context.getString(R.string.settings_secure_dropdown_mode_privacy_policy);
mInvalidWarning = context.getString(R.string.settings_secure_dns_custom_format_error);
mProbeWarning = context.getString(R.string.settings_secure_dns_custom_connection_error);
mOptions = makeOptions(context);
}
private static List<DohEntry> makeOptions(Context context) {
List<DohEntry> entries = PrivacyPreferencesManager.getInstance().getDohProviders();
// The Spinner's options consist of an entry called "Custom", followed
// by the providers in random order.
List<DohEntry> options = new ArrayList<>(entries.size() + 1);
String customEntryName = context.getString(R.string.settings_custom);
options.add(new DohEntry(customEntryName, "", ""));
Collections.shuffle(entries);
options.addAll(entries);
return options;
}
@Override
public void onBindViewHolder(PreferenceViewHolder holder) {
super.onBindViewHolder(holder);
mGroup = (RadioButtonWithDescriptionLayout) holder.findViewById(R.id.mode_group);
mGroup.setOnCheckedChangeListener(this);
mAutomaticButton = (RadioButtonWithDescription) holder.findViewById(R.id.automatic);
mSecureButton = (RadioButtonWithDescription) holder.findViewById(R.id.secure);
View selectionContainer = holder.findViewById(R.id.selection_container);
mServerMenu = selectionContainer.findViewById(R.id.dropdown_spinner);
mServerMenu.setOnItemSelectedListener(this);
Context context = selectionContainer.getContext();
ArrayAdapter<DohEntry> adapter =
new ArrayAdapter<>(context, R.layout.secure_dns_provider_spinner_item, mOptions);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
mServerMenu.setAdapter(adapter);
mPrivacyPolicy = selectionContainer.findViewById(R.id.privacy_policy);
mPrivacyPolicy.setMovementMethod(LinkMovementMethod.getInstance());
mCustomServer = selectionContainer.findViewById(R.id.custom_server);
mCustomServer.addTextChangedListener(this);
mCustomServerLayout = selectionContainer.findViewById(R.id.custom_server_layout);
mGroup.attachAccessoryView(selectionContainer, mSecureButton);
updateView();
}
public void setState(State state) {
if (!state.equals(mState)) {
mState = state;
updateView();
}
}
public State getState() {
return mState;
}
// Returns the index of the dropdown entry that matches the current template,
// or 0 if none match (i.e. a custom template).
private int matchingDropdownIndex() {
for (int i = 1; i < mServerMenu.getCount(); ++i) {
DohEntry entry = (DohEntry) mServerMenu.getItemAtPosition(i);
if (entry.template.equals(mState.template)) {
return i;
}
}
return 0;
}
/**
* Updates the view to match mState.
*/
private void updateView() {
if (mGroup == null) {
// Not yet bound to view holder.
return;
}
if (mSecureButton.isChecked() != mState.secure) {
mSecureButton.setChecked(mState.secure);
}
boolean automaticMode = !mState.secure;
if (mAutomaticButton.isChecked() != automaticMode) {
mAutomaticButton.setChecked(automaticMode);
}
int position = matchingDropdownIndex();
if (mServerMenu.getSelectedItemPosition() != position) {
mServerMenu.setSelection(position);
}
// Position 0 is the custom server. Other positions are actual server entries.
if (position > 0) {
// Selected server mode.
DohEntry entry = (DohEntry) mServerMenu.getSelectedItem();
String html = mPrivacyTemplate.replace("$1", entry.privacy);
mPrivacyPolicy.setText(Html.fromHtml(html));
mPrivacyPolicy.setVisibility(View.VISIBLE);
mCustomServerLayout.setVisibility(View.GONE);
} else {
// Custom server mode.
if (!mCustomServer.getText().toString().equals(mState.template)) {
mCustomServer.setText(mState.template);
mCustomServer.removeCallbacks(mProbeRunner);
if (mState.secure) {
mCustomServer.requestFocus();
// If the custom server field is idle for one second, run a probe.
// Any changes to the field will cancel this probe and start another.
mCustomServer.postDelayed(mProbeRunner, 1000);
}
}
// Show a warning if the input is invalid and is not the start of a valid URL.
boolean showWarning = !mState.valid && !"https://".startsWith(mState.template);
mCustomServerLayout.setError(showWarning ? mInvalidWarning : null);
mCustomServerLayout.setVisibility(mState.secure ? View.VISIBLE : View.INVISIBLE);
mPrivacyPolicy.setVisibility(View.GONE);
}
PrivacyPreferencesManager.getInstance().updateDohValidationHistogram(mState.valid);
}
private void startServerProbe() {
String group = mState.template;
if (group.isEmpty() || !mState.valid || !mState.secure) {
return;
}
// probeDohServer() is a blocking network call that uses WaitableEvent, so it cannot run
// on the UI thread, nor via the Java PostTask bindings, which do not expose
// base::WithBaseSyncPrimitives. Instead, it runs on a fresh Java thread.
new Thread(() -> {
PrivacyPreferencesManager manager = PrivacyPreferencesManager.getInstance();
for (String template : manager.splitDohTemplateGroup(group)) {
if (manager.probeDohServer(template)) {
return;
}
}
mCustomServer.post(() -> { // Send the state change back to the UI thread.
// Check that the setting hasn't been changed.
if (mState.template.contentEquals(group)) {
mCustomServerLayout.setError(mProbeWarning);
}
});
}).start();
}
@Override
public void onCheckedChanged(RadioGroup group, int checkedId) {
boolean secure = checkedId == R.id.secure;
if (mState.secure != secure) {
tryUpdate(mState.withSecure(secure));
}
}
@Override
public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) {
int oldPos = matchingDropdownIndex();
if (oldPos == pos) {
// This is the same item that was already in effect. Ignore spurious event.
// This check is required to avoid overwriting the custom template, because
// attaching an adapter triggers a spurious onItemSelected event.
return;
}
DohEntry oldEntry = (DohEntry) parent.getItemAtPosition(oldPos);
DohEntry entry = (DohEntry) parent.getItemAtPosition(pos);
tryUpdate(mState.withTemplate(entry.template));
PrivacyPreferencesManager.getInstance().updateDohDropdownHistograms(oldEntry, entry);
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
// In this UI, one radio button is always selected.
}
private void tryUpdate(State newState) {
if (callChangeListener(newState)) {
setState(newState);
} else {
updateView();
}
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
@Override
public void onTextChanged(CharSequence s, int start, int count, int after) {}
@Override
public void afterTextChanged(Editable s) {
tryUpdate(mState.withTemplate(s.toString()));
mCustomServer.removeCallbacks(mProbeRunner);
mCustomServer.postDelayed(mProbeRunner, 1000);
}
}
// Copyright 2020 The Chromium Authors. All rights reserved.
// 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 android.os.Bundle;
import androidx.preference.PreferenceFragmentCompat;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.net.SecureDnsManagementMode;
import org.chromium.chrome.browser.privacy.settings.SecureDnsProviderPreference.State;
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;
/**
* Fragment to manage Secure DNS preference. It consists of a toggle switch and,
* if the switch is enabled, a SecureDnsControl.
*/
public class SecureDnsSettings extends PreferenceFragmentCompat {
// 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 PrivacyPreferencesManager mManager;
private ChromeSwitchPreference mSecureDnsSwitch;
private SecureDnsProviderPreference mSecureDnsProviderPreference;
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
getActivity().setTitle(R.string.settings_secure_dns_title);
SettingsUtils.addPreferencesFromResource(this, R.xml.secure_dns_settings);
mManager = PrivacyPreferencesManager.getInstance();
// Set up preferences inside the activity.
mSecureDnsSwitch = (ChromeSwitchPreference) findPreference(PREF_SECURE_DNS_SWITCH);
mSecureDnsSwitch.setManagedPreferenceDelegate(
(ChromeManagedPreferenceDelegate) preference -> mManager.isSecureDnsModeManaged());
mSecureDnsSwitch.setOnPreferenceChangeListener((preference, enabled) -> {
storePreferenceState((boolean) enabled, mSecureDnsProviderPreference.getState());
loadPreferenceState();
return true;
});
if (!mManager.isSecureDnsModeManaged()) {
// 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 = mManager.getSecureDnsManagementMode();
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();
}
/**
* @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) {
mManager.setSecureDnsMode(SecureDnsMode.OFF);
mManager.setDnsOverHttpsTemplates("");
} else if (!controlState.secure) {
mManager.setSecureDnsMode(SecureDnsMode.AUTOMATIC);
mManager.setDnsOverHttpsTemplates("");
} else {
if (controlState.template.isEmpty()
|| !mManager.setDnsOverHttpsTemplates(controlState.template)) {
return false;
}
mManager.setSecureDnsMode(SecureDnsMode.SECURE);
}
return true;
}
private void loadPreferenceState() {
@SecureDnsMode
int mode = mManager.getSecureDnsMode();
boolean enabled = mode != SecureDnsMode.OFF;
boolean enforced = mManager.isSecureDnsModeManaged()
|| mManager.getSecureDnsManagementMode() != SecureDnsManagementMode.NO_OVERRIDE;
mSecureDnsSwitch.setChecked(enabled);
mSecureDnsProviderPreference.setEnabled(enabled && !enforced);
boolean secure = mode == SecureDnsMode.SECURE;
String template = mManager.getDnsOverHttpsTemplates();
boolean valid = true; // States loaded from storage are presumed valid.
mSecureDnsProviderPreference.setState(new State(secure, template, valid));
}
@Override
public void onResume() {
super.onResume();
loadPreferenceState();
}
}
...@@ -726,6 +726,45 @@ For example, some websites may respond to this request by showing you ads that a ...@@ -726,6 +726,45 @@ For example, some websites may respond to this request by showing you ads that a
<message name="IDS_CAN_MAKE_PAYMENT_TITLE" desc="Title for preference to allow websites to know whether you have payment methods available through PaymentRequest.CanMakePayment interface"> <message name="IDS_CAN_MAKE_PAYMENT_TITLE" desc="Title for preference to allow websites to know whether you have payment methods available through PaymentRequest.CanMakePayment interface">
Access payment methods Access payment methods
</message> </message>
<message name="IDS_SETTINGS_CUSTOM" desc="Label for a custom option in a dropdown menu.">
Custom
</message>
<message name="IDS_SETTINGS_SECURE_DNS_TITLE" desc="Title of the Secure DNS settings option">
Use secure DNS
</message>
<message name="IDS_SETTINGS_SECURE_DNS_DESCRIPTION" desc="Secondary, continued explanation of secure DNS in Privacy options">
Determines how to connect to websites over a secure connection
</message>
<message name="IDS_SETTINGS_AUTOMATIC_MODE_LABEL" desc="Text of the radio button that puts secure DNS in auto-upgrade mode">
Use your current service provider
</message>
<message name="IDS_SETTINGS_AUTOMATIC_MODE_DESCRIPTION" desc="Descriptive text that appears below IDS_SETTINGS_AUTOMATIC_MODE_LABEL.">
Secure DNS may not be available all the time
</message>
<message name="IDS_SETTINGS_AUTOMATIC_MODE_SUMMARY" desc="Short description of automatic mode">
Automatic
</message>
<message name="IDS_SETTINGS_SECURE_DROPDOWN_MODE_DESCRIPTION" desc="Text of the radio button that allows a secure resolver to be selected from a dropdown menu">
Choose another provider:
</message>
<message name="IDS_SETTINGS_SECURE_DROPDOWN_MODE_PRIVACY_POLICY" desc="Text that displays a link to the privacy policy of the resolver selected from a dropdown menu">
See this provider's <ph name="BEGIN_LINK">&lt;a target="_blank" href="$1<ex>https://google.com/</ex>"&gt;</ph>privacy policy<ph name="END_LINK">&lt;/a&gt;</ph>
</message>
<message name="IDS_SETTINGS_SECURE_DNS_DISABLED_FOR_MANAGED_ENVIRONMENT" desc="Substring of the secure DNS setting when secure DNS is disabled due to detection of a managed environment">
This setting is disabled on managed browsers
</message>
<message name="IDS_SETTINGS_SECURE_DNS_DISABLED_FOR_PARENTAL_CONTROL" desc="Substring of the secure DNS setting when secure DNS is disabled due to detection of OS-level parental controls">
This setting is disabled because parental controls are on
</message>
<message name="IDS_SETTINGS_SECURE_DNS_CUSTOM_PLACEHOLDER" desc="Placeholder text for a textbox where users can enter a custom secure DNS provider">
Provider URL
</message>
<message name="IDS_SETTINGS_SECURE_DNS_CUSTOM_FORMAT_ERROR" desc="Error text for an incorrectly formatted entry for the custom secure DNS provider">
Enter a correctly formatted URL
</message>
<message name="IDS_SETTINGS_SECURE_DNS_CUSTOM_CONNECTION_ERROR" desc="Error text for a custom secure DNS provider entry to which a probe connection fails">
Please verify that this is a valid provider or try again later
</message>
<message name="IDS_CLEAR_BROWSING_DATA_TITLE" desc="Title of the Clear Browsing Data screen. [CHAR-LIMIT=32]"> <message name="IDS_CLEAR_BROWSING_DATA_TITLE" desc="Title of the Clear Browsing Data screen. [CHAR-LIMIT=32]">
Clear browsing data Clear browsing data
</message> </message>
......
07470847c57193b5abe3c19d409f40fe03c64ac4
\ No newline at end of file
864e7cae4749082cf375f59b5f80b7a1bbcc3544
\ No newline at end of file
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment