• 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.ConnectivitySettingsManager.PRIVATE_DNS_MODE_OFF;
19 import static android.net.ConnectivitySettingsManager.PRIVATE_DNS_MODE_OPPORTUNISTIC;
20 import static android.net.ConnectivitySettingsManager.PRIVATE_DNS_MODE_PROVIDER_HOSTNAME;
21 
22 import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
23 
24 import android.app.settings.SettingsEnums;
25 import android.content.ActivityNotFoundException;
26 import android.content.Context;
27 import android.content.DialogInterface;
28 import android.content.Intent;
29 import android.net.ConnectivitySettingsManager;
30 import android.os.UserHandle;
31 import android.os.UserManager;
32 import android.text.Editable;
33 import android.text.TextWatcher;
34 import android.text.method.LinkMovementMethod;
35 import android.util.AttributeSet;
36 import android.util.Log;
37 import android.view.View;
38 import android.widget.Button;
39 import android.widget.EditText;
40 import android.widget.RadioButton;
41 import android.widget.RadioGroup;
42 import android.widget.TextView;
43 
44 import androidx.annotation.VisibleForTesting;
45 import androidx.appcompat.app.AlertDialog;
46 import androidx.preference.PreferenceViewHolder;
47 
48 import com.android.settings.R;
49 import com.android.settings.overlay.FeatureFactory;
50 import com.android.settings.utils.AnnotationSpan;
51 import com.android.settingslib.CustomDialogPreferenceCompat;
52 import com.android.settingslib.HelpUtils;
53 import com.android.settingslib.RestrictedLockUtils;
54 import com.android.settingslib.RestrictedLockUtilsInternal;
55 
56 import com.google.android.material.textfield.TextInputLayout;
57 import com.google.common.net.InternetDomainName;
58 
59 import java.util.HashMap;
60 import java.util.Map;
61 
62 /**
63  * Dialog to set the Private DNS
64  */
65 public class PrivateDnsModeDialogPreference extends CustomDialogPreferenceCompat implements
66         RadioGroup.OnCheckedChangeListener, TextWatcher {
67 
68     public static final String ANNOTATION_URL = "url";
69 
70     private static final String TAG = "PrivateDnsModeDialog";
71     // DNS_MODE -> RadioButton id
72     private static final Map<Integer, Integer> PRIVATE_DNS_MAP;
73 
74     static {
75         PRIVATE_DNS_MAP = new HashMap<>();
PRIVATE_DNS_MAP.put(PRIVATE_DNS_MODE_OFF, R.id.private_dns_mode_off)76         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)77         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)78         PRIVATE_DNS_MAP.put(PRIVATE_DNS_MODE_PROVIDER_HOSTNAME, R.id.private_dns_mode_provider);
79     }
80 
81     @VisibleForTesting
82     TextInputLayout mHostnameLayout;
83     @VisibleForTesting
84     EditText mHostnameText;
85     @VisibleForTesting
86     RadioGroup mRadioGroup;
87     @VisibleForTesting
88     int mMode;
89 
PrivateDnsModeDialogPreference(Context context)90     public PrivateDnsModeDialogPreference(Context context) {
91         super(context);
92     }
93 
PrivateDnsModeDialogPreference(Context context, AttributeSet attrs)94     public PrivateDnsModeDialogPreference(Context context, AttributeSet attrs) {
95         super(context, attrs);
96     }
97 
PrivateDnsModeDialogPreference(Context context, AttributeSet attrs, int defStyleAttr)98     public PrivateDnsModeDialogPreference(Context context, AttributeSet attrs, int defStyleAttr) {
99         super(context, attrs, defStyleAttr);
100     }
101 
PrivateDnsModeDialogPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)102     public PrivateDnsModeDialogPreference(Context context, AttributeSet attrs, int defStyleAttr,
103             int defStyleRes) {
104         super(context, attrs, defStyleAttr, defStyleRes);
105     }
106 
107     private final AnnotationSpan.LinkInfo mUrlLinkInfo = new AnnotationSpan.LinkInfo(
108             ANNOTATION_URL, (widget) -> {
109         final Context context = widget.getContext();
110         final Intent intent = HelpUtils.getHelpIntent(context,
111                 context.getString(R.string.help_uri_private_dns),
112                 context.getClass().getName());
113         if (intent != null) {
114             try {
115                 widget.startActivityForResult(intent, 0);
116             } catch (ActivityNotFoundException e) {
117                 Log.w(TAG, "Activity was not found for intent, " + intent.toString());
118             }
119         }
120     });
121 
122     @Override
onBindViewHolder(PreferenceViewHolder holder)123     public void onBindViewHolder(PreferenceViewHolder holder) {
124         super.onBindViewHolder(holder);
125         if (isDisabledByAdmin()) {
126             // If the preference is disabled by the admin, set the inner item as enabled so
127             // it could act as a click target. The preference itself will have been disabled
128             // by the controller.
129             holder.itemView.setEnabled(true);
130         }
131 
132         setSaveButtonListener();
133     }
134 
135     @Override
onBindDialogView(View view)136     protected void onBindDialogView(View view) {
137         final Context context = getContext();
138         mMode = ConnectivitySettingsManager.getPrivateDnsMode(context);
139         mRadioGroup = view.findViewById(R.id.private_dns_radio_group);
140         mRadioGroup.check(PRIVATE_DNS_MAP.getOrDefault(mMode, R.id.private_dns_mode_opportunistic));
141         mRadioGroup.setOnCheckedChangeListener(this);
142 
143         // Initial radio button text
144         final RadioButton offRadioButton = view.findViewById(R.id.private_dns_mode_off);
145         offRadioButton.setText(com.android.settingslib.R.string.private_dns_mode_off);
146         final RadioButton opportunisticRadioButton =
147                 view.findViewById(R.id.private_dns_mode_opportunistic);
148         opportunisticRadioButton.setText(
149                 com.android.settingslib.R.string.private_dns_mode_opportunistic);
150         final RadioButton providerRadioButton = view.findViewById(R.id.private_dns_mode_provider);
151         providerRadioButton.setText(com.android.settingslib.R.string.private_dns_mode_provider);
152 
153         mHostnameLayout = view.findViewById(R.id.private_dns_mode_provider_hostname_layout);
154         mHostnameText = view.findViewById(R.id.private_dns_mode_provider_hostname);
155         if (mHostnameText != null) {
156             mHostnameText.setText(ConnectivitySettingsManager.getPrivateDnsHostname(context));
157             mHostnameText.addTextChangedListener(this);
158         }
159 
160         final TextView helpTextView = view.findViewById(R.id.private_dns_help_info);
161         helpTextView.setMovementMethod(LinkMovementMethod.getInstance());
162         final Intent helpIntent = HelpUtils.getHelpIntent(context,
163                 context.getString(R.string.help_uri_private_dns),
164                 context.getClass().getName());
165         final AnnotationSpan.LinkInfo linkInfo = new AnnotationSpan.LinkInfo(context,
166                 ANNOTATION_URL, helpIntent);
167         if (linkInfo.isActionable()) {
168             helpTextView.setText(AnnotationSpan.linkify(
169                     context.getText(R.string.private_dns_help_message), linkInfo));
170         } else {
171             helpTextView.setText("");
172         }
173 
174         updateDialogInfo();
175     }
176 
177     @Override
onCheckedChanged(RadioGroup group, int checkedId)178     public void onCheckedChanged(RadioGroup group, int checkedId) {
179         if (checkedId == R.id.private_dns_mode_off) {
180             mMode = PRIVATE_DNS_MODE_OFF;
181         } else if (checkedId == R.id.private_dns_mode_opportunistic) {
182             mMode = PRIVATE_DNS_MODE_OPPORTUNISTIC;
183         } else if (checkedId == R.id.private_dns_mode_provider) {
184             mMode = PRIVATE_DNS_MODE_PROVIDER_HOSTNAME;
185         }
186         updateDialogInfo();
187     }
188 
189     @Override
beforeTextChanged(CharSequence s, int start, int count, int after)190     public void beforeTextChanged(CharSequence s, int start, int count, int after) {
191     }
192 
193     @Override
onTextChanged(CharSequence s, int start, int before, int count)194     public void onTextChanged(CharSequence s, int start, int before, int count) {
195     }
196 
197     @Override
afterTextChanged(Editable s)198     public void afterTextChanged(Editable s) {
199         updateDialogInfo();
200     }
201 
202     @Override
performClick()203     public void performClick() {
204         EnforcedAdmin enforcedAdmin = getEnforcedAdmin();
205 
206         if (enforcedAdmin == null) {
207             // If the restriction is not restricted by admin, continue as usual.
208             super.performClick();
209         } else {
210             // Show a dialog explaining to the user why they cannot change the preference.
211             RestrictedLockUtils.sendShowAdminSupportDetailsIntent(getContext(), enforcedAdmin);
212         }
213     }
214 
getEnforcedAdmin()215     private EnforcedAdmin getEnforcedAdmin() {
216         return RestrictedLockUtilsInternal.checkIfRestrictionEnforced(
217                 getContext(), UserManager.DISALLOW_CONFIG_PRIVATE_DNS, UserHandle.myUserId());
218     }
219 
isDisabledByAdmin()220     private boolean isDisabledByAdmin() {
221         return getEnforcedAdmin() != null;
222     }
223 
updateDialogInfo()224     private void updateDialogInfo() {
225         final boolean modeProvider = PRIVATE_DNS_MODE_PROVIDER_HOSTNAME == mMode;
226         if (mHostnameLayout != null) {
227             mHostnameLayout.setEnabled(modeProvider);
228             mHostnameLayout.setErrorEnabled(false);
229         }
230     }
231 
setSaveButtonListener()232     private void setSaveButtonListener() {
233         View.OnClickListener onClickListener = v -> doSaveButton();
234         DialogInterface.OnShowListener onShowListener = dialog -> {
235             if (dialog == null) {
236                 Log.e(TAG, "The DialogInterface is null!");
237                 return;
238             }
239             Button saveButton = ((AlertDialog) dialog).getButton(DialogInterface.BUTTON_POSITIVE);
240             if (saveButton == null) {
241                 Log.e(TAG, "Can't get the save button!");
242                 return;
243             }
244             saveButton.setOnClickListener(onClickListener);
245         };
246         setOnShowListener(onShowListener);
247     }
248 
249     @VisibleForTesting
doSaveButton()250     void doSaveButton() {
251         Context context = getContext();
252         if (mMode == PRIVATE_DNS_MODE_PROVIDER_HOSTNAME) {
253             if (mHostnameLayout == null || mHostnameText == null) {
254                 Log.e(TAG, "Can't find hostname resources!");
255                 return;
256             }
257             if (mHostnameText.getText().isEmpty()) {
258                 mHostnameLayout.setError(context.getString(R.string.private_dns_field_require));
259                 Log.w(TAG, "The hostname is empty!");
260                 return;
261             }
262             if (!InternetDomainName.isValid(mHostnameText.getText().toString())) {
263                 mHostnameLayout.setError(context.getString(R.string.private_dns_hostname_invalid));
264                 Log.w(TAG, "The hostname is invalid!");
265                 return;
266             }
267 
268             ConnectivitySettingsManager.setPrivateDnsHostname(context,
269                     mHostnameText.getText().toString());
270         }
271 
272         ConnectivitySettingsManager.setPrivateDnsMode(context, mMode);
273 
274         FeatureFactory.getFeatureFactory().getMetricsFeatureProvider()
275                 .action(context, SettingsEnums.ACTION_PRIVATE_DNS_MODE, mMode);
276         getDialog().dismiss();
277     }
278 }
279