• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.settings.network;
17 
18 import static android.net.ConnectivityManager.PRIVATE_DNS_DEFAULT_MODE_FALLBACK;
19 import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_OFF;
20 import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_OPPORTUNISTIC;
21 import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_PROVIDER_HOSTNAME;
22 
23 import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
24 
25 import android.app.settings.SettingsEnums;
26 import android.content.ActivityNotFoundException;
27 import android.content.ContentResolver;
28 import android.content.Context;
29 import android.content.DialogInterface;
30 import android.content.Intent;
31 import android.net.NetworkUtils;
32 import android.os.UserHandle;
33 import android.os.UserManager;
34 import android.provider.Settings;
35 import android.text.Editable;
36 import android.text.TextWatcher;
37 import android.text.method.LinkMovementMethod;
38 import android.util.AttributeSet;
39 import android.util.Log;
40 import android.view.View;
41 import android.widget.Button;
42 import android.widget.EditText;
43 import android.widget.RadioGroup;
44 import android.widget.TextView;
45 
46 import androidx.annotation.VisibleForTesting;
47 import androidx.appcompat.app.AlertDialog;
48 import androidx.preference.PreferenceViewHolder;
49 
50 import com.android.settings.R;
51 import com.android.settings.overlay.FeatureFactory;
52 import com.android.settings.utils.AnnotationSpan;
53 import com.android.settingslib.CustomDialogPreferenceCompat;
54 import com.android.settingslib.HelpUtils;
55 import com.android.settingslib.RestrictedLockUtils;
56 import com.android.settingslib.RestrictedLockUtilsInternal;
57 
58 import java.util.HashMap;
59 import java.util.Map;
60 
61 /**
62  * Dialog to set the Private DNS
63  */
64 public class PrivateDnsModeDialogPreference extends CustomDialogPreferenceCompat implements
65         DialogInterface.OnClickListener, RadioGroup.OnCheckedChangeListener, TextWatcher {
66 
67     public static final String ANNOTATION_URL = "url";
68 
69     private static final String TAG = "PrivateDnsModeDialog";
70     // DNS_MODE -> RadioButton id
71     private static final Map<String, Integer> PRIVATE_DNS_MAP;
72 
73     static {
74         PRIVATE_DNS_MAP = new HashMap<>();
PRIVATE_DNS_MAP.put(PRIVATE_DNS_MODE_OFF, R.id.private_dns_mode_off)75         PRIVATE_DNS_MAP.put(PRIVATE_DNS_MODE_OFF, R.id.private_dns_mode_off);
PRIVATE_DNS_MAP.put(PRIVATE_DNS_MODE_OPPORTUNISTIC, R.id.private_dns_mode_opportunistic)76         PRIVATE_DNS_MAP.put(PRIVATE_DNS_MODE_OPPORTUNISTIC, R.id.private_dns_mode_opportunistic);
PRIVATE_DNS_MAP.put(PRIVATE_DNS_MODE_PROVIDER_HOSTNAME, R.id.private_dns_mode_provider)77         PRIVATE_DNS_MAP.put(PRIVATE_DNS_MODE_PROVIDER_HOSTNAME, R.id.private_dns_mode_provider);
78     }
79 
80     @VisibleForTesting
81     static final String MODE_KEY = Settings.Global.PRIVATE_DNS_MODE;
82     @VisibleForTesting
83     static final String HOSTNAME_KEY = Settings.Global.PRIVATE_DNS_SPECIFIER;
84 
getModeFromSettings(ContentResolver cr)85     public static String getModeFromSettings(ContentResolver cr) {
86         String mode = Settings.Global.getString(cr, MODE_KEY);
87         if (!PRIVATE_DNS_MAP.containsKey(mode)) {
88             mode = Settings.Global.getString(cr, Settings.Global.PRIVATE_DNS_DEFAULT_MODE);
89         }
90         return PRIVATE_DNS_MAP.containsKey(mode) ? mode : PRIVATE_DNS_DEFAULT_MODE_FALLBACK;
91     }
92 
getHostnameFromSettings(ContentResolver cr)93     public static String getHostnameFromSettings(ContentResolver cr) {
94         return Settings.Global.getString(cr, HOSTNAME_KEY);
95     }
96 
97     @VisibleForTesting
98     EditText mEditText;
99     @VisibleForTesting
100     RadioGroup mRadioGroup;
101     @VisibleForTesting
102     String mMode;
103 
PrivateDnsModeDialogPreference(Context context)104     public PrivateDnsModeDialogPreference(Context context) {
105         super(context);
106         initialize();
107     }
108 
PrivateDnsModeDialogPreference(Context context, AttributeSet attrs)109     public PrivateDnsModeDialogPreference(Context context, AttributeSet attrs) {
110         super(context, attrs);
111         initialize();
112     }
113 
PrivateDnsModeDialogPreference(Context context, AttributeSet attrs, int defStyleAttr)114     public PrivateDnsModeDialogPreference(Context context, AttributeSet attrs, int defStyleAttr) {
115         super(context, attrs, defStyleAttr);
116         initialize();
117     }
118 
PrivateDnsModeDialogPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)119     public PrivateDnsModeDialogPreference(Context context, AttributeSet attrs, int defStyleAttr,
120             int defStyleRes) {
121         super(context, attrs, defStyleAttr, defStyleRes);
122         initialize();
123     }
124 
125     private final AnnotationSpan.LinkInfo mUrlLinkInfo = new AnnotationSpan.LinkInfo(
126             ANNOTATION_URL, (widget) -> {
127         final Context context = widget.getContext();
128         final Intent intent = HelpUtils.getHelpIntent(context,
129                 context.getString(R.string.help_uri_private_dns),
130                 context.getClass().getName());
131         if (intent != null) {
132             try {
133                 widget.startActivityForResult(intent, 0);
134             } catch (ActivityNotFoundException e) {
135                 Log.w(TAG, "Activity was not found for intent, " + intent.toString());
136             }
137         }
138     });
139 
initialize()140     private void initialize() {
141         // Add the "Restricted" icon resource so that if the preference is disabled by the
142         // admin, an information button will be shown.
143         setWidgetLayoutResource(R.layout.restricted_icon);
144     }
145 
146     @Override
onBindViewHolder(PreferenceViewHolder holder)147     public void onBindViewHolder(PreferenceViewHolder holder) {
148         super.onBindViewHolder(holder);
149         if (isDisabledByAdmin()) {
150             // If the preference is disabled by the admin, set the inner item as enabled so
151             // it could act as a click target. The preference itself will have been disabled
152             // by the controller.
153             holder.itemView.setEnabled(true);
154         }
155 
156         final View restrictedIcon = holder.findViewById(R.id.restricted_icon);
157         if (restrictedIcon != null) {
158             // Show the "Restricted" icon if, and only if, the preference was disabled by
159             // the admin.
160             restrictedIcon.setVisibility(isDisabledByAdmin() ? View.VISIBLE : View.GONE);
161         }
162     }
163 
164     @Override
onBindDialogView(View view)165     protected void onBindDialogView(View view) {
166         final Context context = getContext();
167         final ContentResolver contentResolver = context.getContentResolver();
168 
169         mMode = getModeFromSettings(context.getContentResolver());
170 
171         mEditText = view.findViewById(R.id.private_dns_mode_provider_hostname);
172         mEditText.addTextChangedListener(this);
173         mEditText.setText(getHostnameFromSettings(contentResolver));
174 
175         mRadioGroup = view.findViewById(R.id.private_dns_radio_group);
176         mRadioGroup.setOnCheckedChangeListener(this);
177         mRadioGroup.check(PRIVATE_DNS_MAP.getOrDefault(mMode, R.id.private_dns_mode_opportunistic));
178 
179         final TextView helpTextView = view.findViewById(R.id.private_dns_help_info);
180         helpTextView.setMovementMethod(LinkMovementMethod.getInstance());
181         final Intent helpIntent = HelpUtils.getHelpIntent(context,
182                 context.getString(R.string.help_uri_private_dns),
183                 context.getClass().getName());
184         final AnnotationSpan.LinkInfo linkInfo = new AnnotationSpan.LinkInfo(context,
185                 ANNOTATION_URL, helpIntent);
186         if (linkInfo.isActionable()) {
187             helpTextView.setText(AnnotationSpan.linkify(
188                     context.getText(R.string.private_dns_help_message), linkInfo));
189         }
190     }
191 
192     @Override
onClick(DialogInterface dialog, int which)193     public void onClick(DialogInterface dialog, int which) {
194         if (which == DialogInterface.BUTTON_POSITIVE) {
195             final Context context = getContext();
196             if (mMode.equals(PRIVATE_DNS_MODE_PROVIDER_HOSTNAME)) {
197                 // Only clickable if hostname is valid, so we could save it safely
198                 Settings.Global.putString(context.getContentResolver(), HOSTNAME_KEY,
199                         mEditText.getText().toString());
200             }
201 
202             FeatureFactory.getFactory(context).getMetricsFeatureProvider().action(context,
203                     SettingsEnums.ACTION_PRIVATE_DNS_MODE, mMode);
204             Settings.Global.putString(context.getContentResolver(), MODE_KEY, mMode);
205         }
206     }
207 
208     @Override
onCheckedChanged(RadioGroup group, int checkedId)209     public void onCheckedChanged(RadioGroup group, int checkedId) {
210         switch (checkedId) {
211             case R.id.private_dns_mode_off:
212                 mMode = PRIVATE_DNS_MODE_OFF;
213                 break;
214             case R.id.private_dns_mode_opportunistic:
215                 mMode = PRIVATE_DNS_MODE_OPPORTUNISTIC;
216                 break;
217             case R.id.private_dns_mode_provider:
218                 mMode = PRIVATE_DNS_MODE_PROVIDER_HOSTNAME;
219                 break;
220         }
221         updateDialogInfo();
222     }
223 
224     @Override
beforeTextChanged(CharSequence s, int start, int count, int after)225     public void beforeTextChanged(CharSequence s, int start, int count, int after) {
226     }
227 
228     @Override
onTextChanged(CharSequence s, int start, int before, int count)229     public void onTextChanged(CharSequence s, int start, int before, int count) {
230     }
231 
232     @Override
afterTextChanged(Editable s)233     public void afterTextChanged(Editable s) {
234         updateDialogInfo();
235     }
236 
237     @Override
performClick()238     public void performClick() {
239         EnforcedAdmin enforcedAdmin = getEnforcedAdmin();
240 
241         if (enforcedAdmin == null) {
242             // If the restriction is not restricted by admin, continue as usual.
243             super.performClick();
244         } else {
245             // Show a dialog explaining to the user why they cannot change the preference.
246             RestrictedLockUtils.sendShowAdminSupportDetailsIntent(getContext(), enforcedAdmin);
247         }
248     }
249 
getEnforcedAdmin()250     private EnforcedAdmin getEnforcedAdmin() {
251         return RestrictedLockUtilsInternal.checkIfRestrictionEnforced(
252                 getContext(), UserManager.DISALLOW_CONFIG_PRIVATE_DNS, UserHandle.myUserId());
253     }
254 
isDisabledByAdmin()255     private boolean isDisabledByAdmin() {
256         return getEnforcedAdmin() != null;
257     }
258 
getSaveButton()259     private Button getSaveButton() {
260         final AlertDialog dialog = (AlertDialog) getDialog();
261         if (dialog == null) {
262             return null;
263         }
264         return dialog.getButton(DialogInterface.BUTTON_POSITIVE);
265     }
266 
updateDialogInfo()267     private void updateDialogInfo() {
268         final boolean modeProvider = PRIVATE_DNS_MODE_PROVIDER_HOSTNAME.equals(mMode);
269         if (mEditText != null) {
270             mEditText.setEnabled(modeProvider);
271         }
272         final Button saveButton = getSaveButton();
273         if (saveButton != null) {
274             saveButton.setEnabled(modeProvider
275                     ? NetworkUtils.isWeaklyValidatedHostname(mEditText.getText().toString())
276                     : true);
277         }
278     }
279 }
280