Loading res/layout/private_dns_mode_dialog.xml 0 → 100644 +59 −0 Original line number Diff line number Diff line <?xml version="1.0" encoding="utf-8"?> <!-- Copyright (C) 2017 The Android Open Source Project Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. --> <RadioGroup xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:padding="8dip"> <RadioButton android:id="@+id/private_dns_mode_off" android:text="@string/private_dns_mode_off" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="8dip" /> <RadioButton android:id="@+id/private_dns_mode_opportunistic" android:text="@string/private_dns_mode_opportunistic" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="8dip" /> <RadioButton android:id="@+id/private_dns_mode_provider" android:text="@string/private_dns_mode_provider" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="8dip" /> <EditText android:id="@+id/private_dns_mode_provider_hostname" android:hint="@string/private_dns_mode_provider_hostname_hint" style="@android:style/Widget.CompoundButton.RadioButton" android:imeOptions="actionDone" android:inputType="textFilter|textUri" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginStart="8dip" android:layout_marginEnd="8dip" /> </RadioGroup> res/xml/development_prefs.xml +7 −4 Original line number Diff line number Diff line Loading @@ -257,10 +257,13 @@ android:entries="@array/bluetooth_a2dp_codec_ldac_playback_quality_titles" android:entryValues="@array/bluetooth_a2dp_codec_ldac_playback_quality_values" /> <SwitchPreference android:key="dns_tls" android:title="@string/dns_tls" android:summary="@string/dns_tls_summary" /> <com.android.settings.development.PrivateDnsModeDialogPreference android:key="select_private_dns_configuration" android:title="@string/select_private_dns_configuration_title" android:dialogTitle="@string/select_private_dns_configuration_dialog_title" android:dialogLayout="@layout/private_dns_mode_dialog" android:positiveButtonText="@string/save" android:negativeButtonText="@string/cancel" /> </PreferenceCategory> Loading src/com/android/settings/development/DevelopmentSettings.java +10 −10 Original line number Diff line number Diff line Loading @@ -217,7 +217,7 @@ public class DevelopmentSettings extends RestrictedSettingsFragment private static final String BLUETOOTH_SELECT_A2DP_CHANNEL_MODE_KEY = "bluetooth_select_a2dp_channel_mode"; private static final String BLUETOOTH_SELECT_A2DP_LDAC_PLAYBACK_QUALITY_KEY = "bluetooth_select_a2dp_ldac_playback_quality"; private static final String DNS_TLS_KEY = "dns_tls"; private static final String PRIVATE_DNS_PREF_KEY = "select_private_dns_configuration"; private static final String INACTIVE_APPS_KEY = "inactive_apps"; Loading Loading @@ -295,8 +295,6 @@ public class DevelopmentSettings extends RestrictedSettingsFragment private ListPreference mBluetoothSelectA2dpChannelMode; private ListPreference mBluetoothSelectA2dpLdacPlaybackQuality; private SwitchPreference mDnsTls; private SwitchPreference mOtaDisableAutomaticUpdate; private SwitchPreference mWifiAllowScansWithTraffic; private SwitchPreference mStrictMode; Loading Loading @@ -511,7 +509,7 @@ public class DevelopmentSettings extends RestrictedSettingsFragment mBluetoothSelectA2dpLdacPlaybackQuality = addListPreference(BLUETOOTH_SELECT_A2DP_LDAC_PLAYBACK_QUALITY_KEY); initBluetoothConfigurationValues(); mDnsTls = findAndInitSwitchPref(DNS_TLS_KEY); updatePrivateDnsSummary(); mWindowAnimationScale = addListPreference(WINDOW_ANIMATION_SCALE_KEY); mTransitionAnimationScale = addListPreference(TRANSITION_ANIMATION_SCALE_KEY); Loading Loading @@ -829,8 +827,7 @@ public class DevelopmentSettings extends RestrictedSettingsFragment updateBluetoothDisableAbsVolumeOptions(); updateBluetoothEnableInbandRingingOptions(); updateBluetoothA2dpConfigurationValues(); updateSwitchPreference(mDnsTls, Settings.Global.getInt(cr, Settings.Global.DNS_TLS_DISABLED, 0) == 0); updatePrivateDnsSummary(); } private void resetDangerousOptions() { Loading Loading @@ -2183,6 +2180,13 @@ public class DevelopmentSettings extends RestrictedSettingsFragment } } private void updatePrivateDnsSummary() { final String summary = PrivateDnsModeDialogPreference.getSummaryStringForModeFromSettings( getActivity().getContentResolver(), getActivity().getResources()); final Preference pref = findPreference(PRIVATE_DNS_PREF_KEY); pref.setSummary(summary); } private void writeImmediatelyDestroyActivitiesOptions() { try { ActivityManager.getService().setAlwaysFinish( Loading Loading @@ -2534,10 +2538,6 @@ public class DevelopmentSettings extends RestrictedSettingsFragment writeBluetoothDisableAbsVolumeOptions(); } else if (preference == mBluetoothEnableInbandRinging) { writeBluetoothEnableInbandRingingOptions(); } else if (preference == mDnsTls) { Settings.Global.putInt(getActivity().getContentResolver(), Settings.Global.DNS_TLS_DISABLED, mDnsTls.isChecked() ? 0 : 1); } else if (SHORTCUT_MANAGER_RESET_KEY.equals(preference.getKey())) { resetShortcutManagerThrottling(); } else { Loading src/com/android/settings/development/PrivateDnsModeDialogPreference.java 0 → 100644 +246 −0 Original line number Diff line number Diff line /* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.settings.development; import static android.net.ConnectivityManager.PRIVATE_DNS_DEFAULT_MODE; import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_OFF; import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_OPPORTUNISTIC; import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_PROVIDER_HOSTNAME; import android.app.AlertDialog; import android.app.Dialog; import android.content.ContentResolver; import android.content.Context; import android.content.DialogInterface; import android.content.res.Resources; import android.os.Bundle; import android.provider.Settings; import android.support.v14.preference.PreferenceDialogFragment; import android.support.v7.preference.DialogPreference; import android.text.Editable; import android.text.TextUtils; import android.text.TextWatcher; import android.util.AttributeSet; import android.util.Log; import android.view.inputmethod.EditorInfo; import android.view.KeyEvent; import android.view.View; import android.view.ViewGroup.LayoutParams; import android.view.ViewGroup.MarginLayoutParams; import android.widget.CompoundButton; import android.widget.CompoundButton.OnCheckedChangeListener; import android.widget.EditText; import android.widget.RadioButton; import android.widget.TextView; import android.widget.TextView.OnEditorActionListener; import com.android.settings.R; import com.android.settingslib.CustomDialogPreference; public class PrivateDnsModeDialogPreference extends CustomDialogPreference implements OnCheckedChangeListener, TextWatcher, OnEditorActionListener { private static final String TAG = PrivateDnsModeDialogPreference.class.getSimpleName(); private static final String MODE_KEY = Settings.Global.PRIVATE_DNS_MODE; private static final String HOSTNAME_KEY = Settings.Global.PRIVATE_DNS_SPECIFIER; private String mMode; private EditText mEditText; public static String getSummaryStringForModeFromSettings(ContentResolver cr, Resources res) { final String mode = getModeFromSettings(cr); switch (mode) { case PRIVATE_DNS_MODE_OFF: return res.getString(R.string.private_dns_mode_off); case PRIVATE_DNS_MODE_OPPORTUNISTIC: return res.getString(R.string.private_dns_mode_opportunistic); case PRIVATE_DNS_MODE_PROVIDER_HOSTNAME: return getHostnameFromSettings(cr); default: return "unknown"; } } public PrivateDnsModeDialogPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); } public PrivateDnsModeDialogPreference(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } public PrivateDnsModeDialogPreference(Context context, AttributeSet attrs) { super(context, attrs); } public PrivateDnsModeDialogPreference(Context context) { super(context); } // This is called first when the dialog is launched. @Override protected void onBindDialogView(View view) { final String mode = getModeFromSettings(); RadioButton rb = (RadioButton) view.findViewById(R.id.private_dns_mode_off); if (mode.equals(PRIVATE_DNS_MODE_OFF)) rb.setChecked(true); rb.setOnCheckedChangeListener(this); rb = (RadioButton) view.findViewById(R.id.private_dns_mode_opportunistic); if (mode.equals(PRIVATE_DNS_MODE_OPPORTUNISTIC)) rb.setChecked(true); rb.setOnCheckedChangeListener(this); rb = (RadioButton) view.findViewById(R.id.private_dns_mode_provider); if (mode.equals(PRIVATE_DNS_MODE_PROVIDER_HOSTNAME)) rb.setChecked(true); rb.setOnCheckedChangeListener(this); mEditText = (EditText) view.findViewById(R.id.private_dns_mode_provider_hostname); mEditText.setOnEditorActionListener(this); mEditText.addTextChangedListener(this); // (Mostly) Fix the EditText field's indentation to align underneath the // displayed radio button text, and not under the radio button itself. final int padding = rb.isLayoutRtl() ? rb.getCompoundPaddingRight() : rb.getCompoundPaddingLeft(); final MarginLayoutParams marginParams = (MarginLayoutParams) mEditText.getLayoutParams(); marginParams.setMarginStart(marginParams.getMarginStart() + padding); mEditText.setLayoutParams(marginParams); mEditText.setText(getHostnameFromSettings()); setDialogValue(mode); } @Override protected void onDialogClosed(boolean positiveResult) { if (!positiveResult) return; saveDialogValue(); setSummary(getSummaryStringForModeFromSettings( getContext().getContentResolver(), getContext().getResources())); } @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { if (!isChecked) return; switch (buttonView.getId()) { case R.id.private_dns_mode_off: setDialogValue(PRIVATE_DNS_MODE_OFF); break; case R.id.private_dns_mode_opportunistic: setDialogValue(PRIVATE_DNS_MODE_OPPORTUNISTIC); break; case R.id.private_dns_mode_provider: setDialogValue(PRIVATE_DNS_MODE_PROVIDER_HOSTNAME); break; default: // Unknown button; ignored. break; } } @Override public boolean onEditorAction(TextView tv, int actionId, KeyEvent k) { if (actionId == EditorInfo.IME_ACTION_DONE) { saveDialogValue(); getDialog().dismiss(); return true; } return false; } @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { return; } @Override public void onTextChanged(CharSequence s, int start, int before, int count) { return; } @Override public void afterTextChanged(Editable s) { final String hostname = s.toString(); final boolean appearsValid = isWeaklyValidatedHostname(hostname); // TODO: Disable the "positive button" ("Save") when appearsValid is false. } private void setDialogValue(String mode) { mMode = mode; final boolean txtEnabled = mMode.equals(PRIVATE_DNS_MODE_PROVIDER_HOSTNAME); mEditText.setEnabled(txtEnabled); } private void saveDialogValue() { if (!isValidMode(mMode)) { mMode = PRIVATE_DNS_DEFAULT_MODE; } if (mMode.equals(PRIVATE_DNS_MODE_PROVIDER_HOSTNAME)) { final String hostname = mEditText.getText().toString(); if (isWeaklyValidatedHostname(hostname)) { saveHostnameToSettings(hostname); } else { // TODO: Once quasi-validation of hostnames works and acceptable // user signaling is working, this can be deleted. mMode = PRIVATE_DNS_MODE_OPPORTUNISTIC; if (TextUtils.isEmpty(hostname)) saveHostnameToSettings(""); } } saveModeToSettings(mMode); } private String getModeFromSettings() { return getModeFromSettings(getContext().getContentResolver()); } private void saveModeToSettings(String value) { Settings.Global.putString(getContext().getContentResolver(), MODE_KEY, value); } private String getHostnameFromSettings() { return getHostnameFromSettings(getContext().getContentResolver()); } private void saveHostnameToSettings(String hostname) { Settings.Global.putString(getContext().getContentResolver(), HOSTNAME_KEY, hostname); } private static String getModeFromSettings(ContentResolver cr) { final String mode = Settings.Global.getString(cr, MODE_KEY); return isValidMode(mode) ? mode : PRIVATE_DNS_DEFAULT_MODE; } private static boolean isValidMode(String mode) { return !TextUtils.isEmpty(mode) && ( mode.equals(PRIVATE_DNS_MODE_OFF) || mode.equals(PRIVATE_DNS_MODE_OPPORTUNISTIC) || mode.equals(PRIVATE_DNS_MODE_PROVIDER_HOSTNAME)); } private static String getHostnameFromSettings(ContentResolver cr) { return Settings.Global.getString(cr, HOSTNAME_KEY); } private static boolean isWeaklyValidatedHostname(String hostname) { // TODO: Find and use a better validation method. Specifically: // [1] this should reject IP string literals, and // [2] do the best, simplest, future-proof verification that // the input approximates a DNS hostname. final String WEAK_HOSTNAME_REGEX = "^[a-zA-Z0-9_.-]+$"; return hostname.matches(WEAK_HOSTNAME_REGEX); } } Loading
res/layout/private_dns_mode_dialog.xml 0 → 100644 +59 −0 Original line number Diff line number Diff line <?xml version="1.0" encoding="utf-8"?> <!-- Copyright (C) 2017 The Android Open Source Project Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. --> <RadioGroup xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:padding="8dip"> <RadioButton android:id="@+id/private_dns_mode_off" android:text="@string/private_dns_mode_off" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="8dip" /> <RadioButton android:id="@+id/private_dns_mode_opportunistic" android:text="@string/private_dns_mode_opportunistic" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="8dip" /> <RadioButton android:id="@+id/private_dns_mode_provider" android:text="@string/private_dns_mode_provider" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="8dip" /> <EditText android:id="@+id/private_dns_mode_provider_hostname" android:hint="@string/private_dns_mode_provider_hostname_hint" style="@android:style/Widget.CompoundButton.RadioButton" android:imeOptions="actionDone" android:inputType="textFilter|textUri" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginStart="8dip" android:layout_marginEnd="8dip" /> </RadioGroup>
res/xml/development_prefs.xml +7 −4 Original line number Diff line number Diff line Loading @@ -257,10 +257,13 @@ android:entries="@array/bluetooth_a2dp_codec_ldac_playback_quality_titles" android:entryValues="@array/bluetooth_a2dp_codec_ldac_playback_quality_values" /> <SwitchPreference android:key="dns_tls" android:title="@string/dns_tls" android:summary="@string/dns_tls_summary" /> <com.android.settings.development.PrivateDnsModeDialogPreference android:key="select_private_dns_configuration" android:title="@string/select_private_dns_configuration_title" android:dialogTitle="@string/select_private_dns_configuration_dialog_title" android:dialogLayout="@layout/private_dns_mode_dialog" android:positiveButtonText="@string/save" android:negativeButtonText="@string/cancel" /> </PreferenceCategory> Loading
src/com/android/settings/development/DevelopmentSettings.java +10 −10 Original line number Diff line number Diff line Loading @@ -217,7 +217,7 @@ public class DevelopmentSettings extends RestrictedSettingsFragment private static final String BLUETOOTH_SELECT_A2DP_CHANNEL_MODE_KEY = "bluetooth_select_a2dp_channel_mode"; private static final String BLUETOOTH_SELECT_A2DP_LDAC_PLAYBACK_QUALITY_KEY = "bluetooth_select_a2dp_ldac_playback_quality"; private static final String DNS_TLS_KEY = "dns_tls"; private static final String PRIVATE_DNS_PREF_KEY = "select_private_dns_configuration"; private static final String INACTIVE_APPS_KEY = "inactive_apps"; Loading Loading @@ -295,8 +295,6 @@ public class DevelopmentSettings extends RestrictedSettingsFragment private ListPreference mBluetoothSelectA2dpChannelMode; private ListPreference mBluetoothSelectA2dpLdacPlaybackQuality; private SwitchPreference mDnsTls; private SwitchPreference mOtaDisableAutomaticUpdate; private SwitchPreference mWifiAllowScansWithTraffic; private SwitchPreference mStrictMode; Loading Loading @@ -511,7 +509,7 @@ public class DevelopmentSettings extends RestrictedSettingsFragment mBluetoothSelectA2dpLdacPlaybackQuality = addListPreference(BLUETOOTH_SELECT_A2DP_LDAC_PLAYBACK_QUALITY_KEY); initBluetoothConfigurationValues(); mDnsTls = findAndInitSwitchPref(DNS_TLS_KEY); updatePrivateDnsSummary(); mWindowAnimationScale = addListPreference(WINDOW_ANIMATION_SCALE_KEY); mTransitionAnimationScale = addListPreference(TRANSITION_ANIMATION_SCALE_KEY); Loading Loading @@ -829,8 +827,7 @@ public class DevelopmentSettings extends RestrictedSettingsFragment updateBluetoothDisableAbsVolumeOptions(); updateBluetoothEnableInbandRingingOptions(); updateBluetoothA2dpConfigurationValues(); updateSwitchPreference(mDnsTls, Settings.Global.getInt(cr, Settings.Global.DNS_TLS_DISABLED, 0) == 0); updatePrivateDnsSummary(); } private void resetDangerousOptions() { Loading Loading @@ -2183,6 +2180,13 @@ public class DevelopmentSettings extends RestrictedSettingsFragment } } private void updatePrivateDnsSummary() { final String summary = PrivateDnsModeDialogPreference.getSummaryStringForModeFromSettings( getActivity().getContentResolver(), getActivity().getResources()); final Preference pref = findPreference(PRIVATE_DNS_PREF_KEY); pref.setSummary(summary); } private void writeImmediatelyDestroyActivitiesOptions() { try { ActivityManager.getService().setAlwaysFinish( Loading Loading @@ -2534,10 +2538,6 @@ public class DevelopmentSettings extends RestrictedSettingsFragment writeBluetoothDisableAbsVolumeOptions(); } else if (preference == mBluetoothEnableInbandRinging) { writeBluetoothEnableInbandRingingOptions(); } else if (preference == mDnsTls) { Settings.Global.putInt(getActivity().getContentResolver(), Settings.Global.DNS_TLS_DISABLED, mDnsTls.isChecked() ? 0 : 1); } else if (SHORTCUT_MANAGER_RESET_KEY.equals(preference.getKey())) { resetShortcutManagerThrottling(); } else { Loading
src/com/android/settings/development/PrivateDnsModeDialogPreference.java 0 → 100644 +246 −0 Original line number Diff line number Diff line /* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.settings.development; import static android.net.ConnectivityManager.PRIVATE_DNS_DEFAULT_MODE; import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_OFF; import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_OPPORTUNISTIC; import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_PROVIDER_HOSTNAME; import android.app.AlertDialog; import android.app.Dialog; import android.content.ContentResolver; import android.content.Context; import android.content.DialogInterface; import android.content.res.Resources; import android.os.Bundle; import android.provider.Settings; import android.support.v14.preference.PreferenceDialogFragment; import android.support.v7.preference.DialogPreference; import android.text.Editable; import android.text.TextUtils; import android.text.TextWatcher; import android.util.AttributeSet; import android.util.Log; import android.view.inputmethod.EditorInfo; import android.view.KeyEvent; import android.view.View; import android.view.ViewGroup.LayoutParams; import android.view.ViewGroup.MarginLayoutParams; import android.widget.CompoundButton; import android.widget.CompoundButton.OnCheckedChangeListener; import android.widget.EditText; import android.widget.RadioButton; import android.widget.TextView; import android.widget.TextView.OnEditorActionListener; import com.android.settings.R; import com.android.settingslib.CustomDialogPreference; public class PrivateDnsModeDialogPreference extends CustomDialogPreference implements OnCheckedChangeListener, TextWatcher, OnEditorActionListener { private static final String TAG = PrivateDnsModeDialogPreference.class.getSimpleName(); private static final String MODE_KEY = Settings.Global.PRIVATE_DNS_MODE; private static final String HOSTNAME_KEY = Settings.Global.PRIVATE_DNS_SPECIFIER; private String mMode; private EditText mEditText; public static String getSummaryStringForModeFromSettings(ContentResolver cr, Resources res) { final String mode = getModeFromSettings(cr); switch (mode) { case PRIVATE_DNS_MODE_OFF: return res.getString(R.string.private_dns_mode_off); case PRIVATE_DNS_MODE_OPPORTUNISTIC: return res.getString(R.string.private_dns_mode_opportunistic); case PRIVATE_DNS_MODE_PROVIDER_HOSTNAME: return getHostnameFromSettings(cr); default: return "unknown"; } } public PrivateDnsModeDialogPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); } public PrivateDnsModeDialogPreference(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } public PrivateDnsModeDialogPreference(Context context, AttributeSet attrs) { super(context, attrs); } public PrivateDnsModeDialogPreference(Context context) { super(context); } // This is called first when the dialog is launched. @Override protected void onBindDialogView(View view) { final String mode = getModeFromSettings(); RadioButton rb = (RadioButton) view.findViewById(R.id.private_dns_mode_off); if (mode.equals(PRIVATE_DNS_MODE_OFF)) rb.setChecked(true); rb.setOnCheckedChangeListener(this); rb = (RadioButton) view.findViewById(R.id.private_dns_mode_opportunistic); if (mode.equals(PRIVATE_DNS_MODE_OPPORTUNISTIC)) rb.setChecked(true); rb.setOnCheckedChangeListener(this); rb = (RadioButton) view.findViewById(R.id.private_dns_mode_provider); if (mode.equals(PRIVATE_DNS_MODE_PROVIDER_HOSTNAME)) rb.setChecked(true); rb.setOnCheckedChangeListener(this); mEditText = (EditText) view.findViewById(R.id.private_dns_mode_provider_hostname); mEditText.setOnEditorActionListener(this); mEditText.addTextChangedListener(this); // (Mostly) Fix the EditText field's indentation to align underneath the // displayed radio button text, and not under the radio button itself. final int padding = rb.isLayoutRtl() ? rb.getCompoundPaddingRight() : rb.getCompoundPaddingLeft(); final MarginLayoutParams marginParams = (MarginLayoutParams) mEditText.getLayoutParams(); marginParams.setMarginStart(marginParams.getMarginStart() + padding); mEditText.setLayoutParams(marginParams); mEditText.setText(getHostnameFromSettings()); setDialogValue(mode); } @Override protected void onDialogClosed(boolean positiveResult) { if (!positiveResult) return; saveDialogValue(); setSummary(getSummaryStringForModeFromSettings( getContext().getContentResolver(), getContext().getResources())); } @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { if (!isChecked) return; switch (buttonView.getId()) { case R.id.private_dns_mode_off: setDialogValue(PRIVATE_DNS_MODE_OFF); break; case R.id.private_dns_mode_opportunistic: setDialogValue(PRIVATE_DNS_MODE_OPPORTUNISTIC); break; case R.id.private_dns_mode_provider: setDialogValue(PRIVATE_DNS_MODE_PROVIDER_HOSTNAME); break; default: // Unknown button; ignored. break; } } @Override public boolean onEditorAction(TextView tv, int actionId, KeyEvent k) { if (actionId == EditorInfo.IME_ACTION_DONE) { saveDialogValue(); getDialog().dismiss(); return true; } return false; } @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { return; } @Override public void onTextChanged(CharSequence s, int start, int before, int count) { return; } @Override public void afterTextChanged(Editable s) { final String hostname = s.toString(); final boolean appearsValid = isWeaklyValidatedHostname(hostname); // TODO: Disable the "positive button" ("Save") when appearsValid is false. } private void setDialogValue(String mode) { mMode = mode; final boolean txtEnabled = mMode.equals(PRIVATE_DNS_MODE_PROVIDER_HOSTNAME); mEditText.setEnabled(txtEnabled); } private void saveDialogValue() { if (!isValidMode(mMode)) { mMode = PRIVATE_DNS_DEFAULT_MODE; } if (mMode.equals(PRIVATE_DNS_MODE_PROVIDER_HOSTNAME)) { final String hostname = mEditText.getText().toString(); if (isWeaklyValidatedHostname(hostname)) { saveHostnameToSettings(hostname); } else { // TODO: Once quasi-validation of hostnames works and acceptable // user signaling is working, this can be deleted. mMode = PRIVATE_DNS_MODE_OPPORTUNISTIC; if (TextUtils.isEmpty(hostname)) saveHostnameToSettings(""); } } saveModeToSettings(mMode); } private String getModeFromSettings() { return getModeFromSettings(getContext().getContentResolver()); } private void saveModeToSettings(String value) { Settings.Global.putString(getContext().getContentResolver(), MODE_KEY, value); } private String getHostnameFromSettings() { return getHostnameFromSettings(getContext().getContentResolver()); } private void saveHostnameToSettings(String hostname) { Settings.Global.putString(getContext().getContentResolver(), HOSTNAME_KEY, hostname); } private static String getModeFromSettings(ContentResolver cr) { final String mode = Settings.Global.getString(cr, MODE_KEY); return isValidMode(mode) ? mode : PRIVATE_DNS_DEFAULT_MODE; } private static boolean isValidMode(String mode) { return !TextUtils.isEmpty(mode) && ( mode.equals(PRIVATE_DNS_MODE_OFF) || mode.equals(PRIVATE_DNS_MODE_OPPORTUNISTIC) || mode.equals(PRIVATE_DNS_MODE_PROVIDER_HOSTNAME)); } private static String getHostnameFromSettings(ContentResolver cr) { return Settings.Global.getString(cr, HOSTNAME_KEY); } private static boolean isWeaklyValidatedHostname(String hostname) { // TODO: Find and use a better validation method. Specifically: // [1] this should reject IP string literals, and // [2] do the best, simplest, future-proof verification that // the input approximates a DNS hostname. final String WEAK_HOSTNAME_REGEX = "^[a-zA-Z0-9_.-]+$"; return hostname.matches(WEAK_HOSTNAME_REGEX); } }