• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2011 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 
17 package com.android.settings.vpn2;
18 
19 import android.content.Context;
20 import android.content.DialogInterface;
21 import android.content.pm.PackageManager;
22 import android.net.ProxyInfo;
23 import android.os.Bundle;
24 import android.os.SystemProperties;
25 import android.text.Editable;
26 import android.text.TextWatcher;
27 import android.util.Log;
28 import android.view.View;
29 import android.view.WindowManager;
30 import android.widget.AdapterView;
31 import android.widget.ArrayAdapter;
32 import android.widget.CheckBox;
33 import android.widget.CompoundButton;
34 import android.widget.Spinner;
35 import android.widget.TextView;
36 
37 import androidx.appcompat.app.AlertDialog;
38 
39 import com.android.internal.net.VpnProfile;
40 import com.android.net.module.util.ProxyUtils;
41 import com.android.settings.R;
42 import com.android.settings.utils.AndroidKeystoreAliasLoader;
43 import com.android.settings.wifi.utils.TextInputGroup;
44 
45 import java.util.Collection;
46 import java.util.List;
47 
48 /**
49  * Dialog showing information about a VPN configuration. The dialog
50  * can be launched to either edit or prompt for credentials to connect
51  * to a user-added VPN.
52  *
53  * {@see AppDialog}
54  */
55 class ConfigDialog extends AlertDialog implements TextWatcher,
56         View.OnClickListener, AdapterView.OnItemSelectedListener,
57         CompoundButton.OnCheckedChangeListener {
58     private static final String TAG = "ConfigDialog";
59     // Vpn profile constants to match with R.array.vpn_types.
60     private static final List<Integer> VPN_TYPES = List.of(
61             VpnProfile.TYPE_IKEV2_IPSEC_USER_PASS,
62             VpnProfile.TYPE_IKEV2_IPSEC_PSK,
63             VpnProfile.TYPE_IKEV2_IPSEC_RSA
64     );
65 
66     private final DialogInterface.OnClickListener mListener;
67     private final VpnProfile mProfile;
68 
69     private boolean mEditing;
70     private boolean mExists;
71 
72     private View mView;
73 
74     private TextInputGroup mNameInput;
75     private Spinner mType;
76     private TextInputGroup mServerInput;
77     private TextInputGroup mUsernameInput;
78     private TextInputGroup mPasswordInput;
79     private Spinner mProxySettings;
80     private TextView mProxyHost;
81     private TextView mProxyPort;
82     private TextInputGroup mIpsecIdentifierInput;
83     private TextInputGroup mIpsecSecretInput;
84     private Spinner mIpsecUserCert;
85     private Spinner mIpsecCaCert;
86     private Spinner mIpsecServerCert;
87     private CheckBox mSaveLogin;
88     private CheckBox mShowOptions;
89     private CheckBox mAlwaysOnVpn;
90     private TextView mAlwaysOnInvalidReason;
91 
ConfigDialog(Context context, DialogInterface.OnClickListener listener, VpnProfile profile, boolean editing, boolean exists)92     ConfigDialog(Context context, DialogInterface.OnClickListener listener,
93             VpnProfile profile, boolean editing, boolean exists) {
94         super(context);
95 
96         mListener = listener;
97         mProfile = profile;
98         mEditing = editing;
99         mExists = exists;
100     }
101 
102     @Override
onCreate(Bundle savedState)103     protected void onCreate(Bundle savedState) {
104         mView = getLayoutInflater().inflate(R.layout.vpn_dialog, null);
105         setView(mView);
106 
107         Context context = getContext();
108 
109         // First, find out all the fields.
110         mNameInput = new TextInputGroup(mView, R.id.name_layout, R.id.name,
111                 R.string.vpn_field_required);
112         mType = (Spinner) mView.findViewById(R.id.type);
113         mServerInput = new TextInputGroup(mView, R.id.server_layout, R.id.server,
114                 R.string.vpn_field_required);
115         mUsernameInput = new TextInputGroup(mView, R.id.username_layout, R.id.username,
116                 R.string.vpn_field_required);
117         mPasswordInput = new TextInputGroup(mView, R.id.password_layout, R.id.password,
118                 R.string.vpn_field_required);
119         mProxySettings = (Spinner) mView.findViewById(R.id.vpn_proxy_settings);
120         mProxyHost = (TextView) mView.findViewById(R.id.vpn_proxy_host);
121         mProxyPort = (TextView) mView.findViewById(R.id.vpn_proxy_port);
122         mIpsecIdentifierInput = new TextInputGroup(mView, R.id.ipsec_identifier_layout,
123                 R.id.ipsec_identifier, R.string.vpn_field_required);
124         mIpsecSecretInput = new TextInputGroup(mView, R.id.ipsec_secret_layout, R.id.ipsec_secret,
125                 R.string.vpn_field_required);
126         mIpsecUserCert = (Spinner) mView.findViewById(R.id.ipsec_user_cert);
127         mIpsecCaCert = (Spinner) mView.findViewById(R.id.ipsec_ca_cert);
128         mIpsecServerCert = (Spinner) mView.findViewById(R.id.ipsec_server_cert);
129         mSaveLogin = (CheckBox) mView.findViewById(R.id.save_login);
130         mShowOptions = (CheckBox) mView.findViewById(R.id.show_options);
131         mAlwaysOnVpn = (CheckBox) mView.findViewById(R.id.always_on_vpn);
132         mAlwaysOnInvalidReason = (TextView) mView.findViewById(R.id.always_on_invalid_reason);
133 
134         // Second, copy values from the profile.
135         mNameInput.setText(mProfile.name);
136         setTypesByFeature(mType);
137         mType.setSelection(convertVpnProfileConstantToTypeIndex(mProfile.type));
138         mServerInput.setText(mProfile.server);
139         if (mProfile.saveLogin) {
140             mUsernameInput.setText(mProfile.username);
141             mPasswordInput.setText(mProfile.password);
142         }
143         if (mProfile.proxy != null) {
144             mProxyHost.setText(mProfile.proxy.getHost());
145             int port = mProfile.proxy.getPort();
146             mProxyPort.setText(port == 0 ? "" : Integer.toString(port));
147         }
148         mIpsecIdentifierInput.setText(mProfile.ipsecIdentifier);
149         mIpsecSecretInput.setText(mProfile.ipsecSecret);
150         final AndroidKeystoreAliasLoader androidKeystoreAliasLoader =
151                 new AndroidKeystoreAliasLoader(null);
152         loadCertificates(mIpsecUserCert, androidKeystoreAliasLoader.getKeyCertAliases(), 0,
153                 mProfile.ipsecUserCert);
154         loadCertificates(mIpsecCaCert, androidKeystoreAliasLoader.getCaCertAliases(),
155                 R.string.vpn_no_ca_cert, mProfile.ipsecCaCert);
156         loadCertificates(mIpsecServerCert, androidKeystoreAliasLoader.getKeyCertAliases(),
157                 R.string.vpn_no_server_cert, mProfile.ipsecServerCert);
158         mSaveLogin.setChecked(mProfile.saveLogin);
159         mAlwaysOnVpn.setChecked(mProfile.key.equals(VpnUtils.getLockdownVpn()));
160         mPasswordInput.getEditText()
161                 .setTextAppearance(android.R.style.TextAppearance_DeviceDefault_Medium);
162 
163         // Hide lockdown VPN on devices that require IMS authentication
164         if (SystemProperties.getBoolean("persist.radio.imsregrequired", false)) {
165             mAlwaysOnVpn.setVisibility(View.GONE);
166         }
167 
168         // Third, add listeners to required fields.
169         mNameInput.addTextChangedListener(this);
170         mType.setOnItemSelectedListener(this);
171         mServerInput.addTextChangedListener(this);
172         mUsernameInput.addTextChangedListener(this);
173         mPasswordInput.addTextChangedListener(this);
174         mProxySettings.setOnItemSelectedListener(this);
175         mProxyHost.addTextChangedListener(this);
176         mProxyPort.addTextChangedListener(this);
177         mIpsecIdentifierInput.addTextChangedListener(this);
178         mIpsecSecretInput.addTextChangedListener(this);
179         mIpsecUserCert.setOnItemSelectedListener(this);
180         mShowOptions.setOnClickListener(this);
181         mAlwaysOnVpn.setOnCheckedChangeListener(this);
182 
183         // Fourth, determine whether to do editing or connecting.
184         mEditing = mEditing || !validate(true /*editing*/);
185 
186         if (mEditing) {
187             setTitle(R.string.vpn_edit);
188 
189             // Show common fields.
190             mView.findViewById(R.id.editor).setVisibility(View.VISIBLE);
191 
192             // Show type-specific fields.
193             changeType(mProfile.type);
194 
195             // Hide 'save login' when we are editing.
196             mSaveLogin.setVisibility(View.GONE);
197 
198             configureAdvancedOptionsVisibility();
199 
200             if (mExists) {
201                 // Create a button to forget the profile if it has already been saved..
202                 setButton(DialogInterface.BUTTON_NEUTRAL,
203                         context.getString(R.string.vpn_forget), mListener);
204             }
205 
206             // Create a button to save the profile.
207             setButton(DialogInterface.BUTTON_POSITIVE,
208                     context.getString(R.string.vpn_save), mListener);
209         } else {
210             setTitle(context.getString(R.string.vpn_connect_to, mProfile.name));
211 
212             setUsernamePasswordVisibility(mProfile.type);
213             mUsernameInput.setLabel(context.getString(R.string.vpn_username_required));
214             mUsernameInput.setHelperText(context.getString(R.string.vpn_field_required));
215             mPasswordInput.setLabel(context.getString(R.string.vpn_password_required));
216             mPasswordInput.setHelperText(context.getString(R.string.vpn_field_required));
217 
218             // Create a button to connect the network.
219             setButton(DialogInterface.BUTTON_POSITIVE,
220                     context.getString(R.string.vpn_connect), mListener);
221         }
222 
223         // Always provide a cancel button.
224         setButton(DialogInterface.BUTTON_NEGATIVE,
225                 context.getString(R.string.vpn_cancel), mListener);
226 
227         // Let AlertDialog create everything.
228         super.onCreate(savedState);
229 
230         // Update UI controls according to the current configuration.
231         updateUiControls();
232 
233         // Workaround to resize the dialog for the input method.
234         getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE |
235                 WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE);
236     }
237 
238     @Override
onRestoreInstanceState(Bundle savedState)239     public void onRestoreInstanceState(Bundle savedState) {
240         super.onRestoreInstanceState(savedState);
241 
242         // Visibility isn't restored by super.onRestoreInstanceState, so re-show the advanced
243         // options here if they were already revealed or set.
244         configureAdvancedOptionsVisibility();
245     }
246 
247     @Override
afterTextChanged(Editable field)248     public void afterTextChanged(Editable field) {
249         updateUiControls();
250     }
251 
252     @Override
beforeTextChanged(CharSequence s, int start, int count, int after)253     public void beforeTextChanged(CharSequence s, int start, int count, int after) {
254     }
255 
256     @Override
onTextChanged(CharSequence s, int start, int before, int count)257     public void onTextChanged(CharSequence s, int start, int before, int count) {
258     }
259 
260     @Override
onClick(View view)261     public void onClick(View view) {
262         if (view == mShowOptions) {
263             configureAdvancedOptionsVisibility();
264         }
265     }
266 
267     @Override
onItemSelected(AdapterView<?> parent, View view, int position, long id)268     public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
269         if (parent == mType) {
270             changeType(VPN_TYPES.get(position));
271         } else if (parent == mProxySettings) {
272             updateProxyFieldsVisibility(position);
273         }
274         updateUiControls();
275         mNameInput.setError("");
276         mServerInput.setError("");
277         mIpsecIdentifierInput.setError("");
278         mIpsecSecretInput.setError("");
279     }
280 
281     @Override
onNothingSelected(AdapterView<?> parent)282     public void onNothingSelected(AdapterView<?> parent) {
283     }
284 
285     @Override
onCheckedChanged(CompoundButton compoundButton, boolean b)286     public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
287         if (compoundButton == mAlwaysOnVpn) {
288             updateUiControls();
289         }
290     }
291 
isVpnAlwaysOn()292     public boolean isVpnAlwaysOn() {
293         return mAlwaysOnVpn.isChecked();
294     }
295 
296     /**
297      * Updates the UI according to the current configuration entered by the user.
298      *
299      * These include:
300      * "Always-on VPN" checkbox
301      * Reason for "Always-on VPN" being disabled, when necessary
302      * Proxy info if manually configured
303      * "Save account information" checkbox
304      * "Save" and "Connect" buttons
305      */
updateUiControls()306     private void updateUiControls() {
307         VpnProfile profile = getProfile();
308 
309         // Always-on VPN
310         if (profile.isValidLockdownProfile()) {
311             mAlwaysOnVpn.setEnabled(true);
312             mAlwaysOnInvalidReason.setVisibility(View.GONE);
313         } else {
314             mAlwaysOnVpn.setChecked(false);
315             mAlwaysOnVpn.setEnabled(false);
316             mAlwaysOnInvalidReason.setText(R.string.vpn_always_on_invalid_reason_other);
317             mAlwaysOnInvalidReason.setVisibility(View.VISIBLE);
318         }
319 
320         // Show proxy fields if any proxy field is filled.
321         if (mProfile.proxy != null && (!mProfile.proxy.getHost().isEmpty() ||
322                 mProfile.proxy.getPort() != 0)) {
323             mProxySettings.setSelection(VpnProfile.PROXY_MANUAL);
324             updateProxyFieldsVisibility(VpnProfile.PROXY_MANUAL);
325         }
326 
327         // Save account information
328         if (mAlwaysOnVpn.isChecked()) {
329             mSaveLogin.setChecked(true);
330             mSaveLogin.setEnabled(false);
331         } else {
332             mSaveLogin.setChecked(mProfile.saveLogin);
333             mSaveLogin.setEnabled(true);
334         }
335 
336         // Save or Connect button
337         getButton(DialogInterface.BUTTON_POSITIVE).setEnabled(validate(mEditing));
338     }
339 
updateProxyFieldsVisibility(int position)340     private void updateProxyFieldsVisibility(int position) {
341         final int visible = position == VpnProfile.PROXY_MANUAL ? View.VISIBLE : View.GONE;
342         mView.findViewById(R.id.vpn_proxy_fields).setVisibility(visible);
343     }
344 
isAdvancedOptionsEnabled()345     private boolean isAdvancedOptionsEnabled() {
346         return mProxyHost.getText().length() > 0 || mProxyPort.getText().length() > 0;
347     }
348 
configureAdvancedOptionsVisibility()349     private void configureAdvancedOptionsVisibility() {
350         if (mShowOptions.isChecked() || isAdvancedOptionsEnabled()) {
351             mView.findViewById(R.id.options).setVisibility(View.VISIBLE);
352             mShowOptions.setVisibility(View.GONE);
353             // TODO(b/149070123): Add ability for platform VPNs to support DNS & routes
354         } else {
355             mView.findViewById(R.id.options).setVisibility(View.GONE);
356             mShowOptions.setVisibility(View.VISIBLE);
357         }
358     }
359 
changeType(int type)360     private void changeType(int type) {
361         // First, hide everything.
362         mView.findViewById(R.id.ipsec_psk).setVisibility(View.GONE);
363         mView.findViewById(R.id.ipsec_user).setVisibility(View.GONE);
364         mView.findViewById(R.id.ipsec_peer).setVisibility(View.GONE);
365         mView.findViewById(R.id.options_ipsec_identity).setVisibility(View.GONE);
366 
367         setUsernamePasswordVisibility(type);
368 
369         // Always enable identity for IKEv2/IPsec profiles.
370         mView.findViewById(R.id.options_ipsec_identity).setVisibility(View.VISIBLE);
371 
372         // Then, unhide type-specific fields.
373         switch (type) {
374             case VpnProfile.TYPE_IKEV2_IPSEC_PSK:
375                 mView.findViewById(R.id.ipsec_psk).setVisibility(View.VISIBLE);
376                 mView.findViewById(R.id.options_ipsec_identity).setVisibility(View.VISIBLE);
377                 break;
378             case VpnProfile.TYPE_IKEV2_IPSEC_RSA:
379                 mView.findViewById(R.id.ipsec_user).setVisibility(View.VISIBLE);
380                 // fall through
381             case VpnProfile.TYPE_IKEV2_IPSEC_USER_PASS:
382                 mView.findViewById(R.id.ipsec_peer).setVisibility(View.VISIBLE);
383                 break;
384         }
385 
386         configureAdvancedOptionsVisibility();
387     }
388 
validate(boolean editing)389     private boolean validate(boolean editing) {
390         if (mAlwaysOnVpn.isChecked() && !getProfile().isValidLockdownProfile()) {
391             return false;
392         }
393 
394         if (!validateProxy()) {
395             return false;
396         }
397 
398         switch (getVpnType()) {
399             case VpnProfile.TYPE_IKEV2_IPSEC_USER_PASS:
400                 return true;
401 
402             case VpnProfile.TYPE_IKEV2_IPSEC_PSK:
403                 return true;
404 
405             case VpnProfile.TYPE_IKEV2_IPSEC_RSA:
406                 return mIpsecUserCert.getSelectedItemPosition() != 0;
407         }
408         return false;
409     }
410 
validate()411     public boolean validate() {
412         boolean isValidate = true;
413         int type = getVpnType();
414         if (!mEditing && requiresUsernamePassword(type)) {
415             if (!mUsernameInput.validate()) isValidate = false;
416             if (!mPasswordInput.validate()) isValidate = false;
417             return isValidate;
418         }
419 
420         if (!mNameInput.validate()) isValidate = false;
421         if (!mServerInput.validate()) isValidate = false;
422         if (!mIpsecIdentifierInput.validate()) isValidate = false;
423         if (type == VpnProfile.TYPE_IKEV2_IPSEC_PSK && !mIpsecSecretInput.validate()) {
424             isValidate = false;
425         }
426         if (!isValidate) Log.w(TAG, "Failed to validate VPN profile!");
427         return isValidate;
428     }
429 
getVpnType()430     private int getVpnType() {
431         return VPN_TYPES.get(mType.getSelectedItemPosition());
432     }
433 
setTypesByFeature(Spinner typeSpinner)434     private void setTypesByFeature(Spinner typeSpinner) {
435         String[] types = getContext().getResources().getStringArray(R.array.vpn_types);
436         if (types.length != VPN_TYPES.size()) {
437             Log.wtf(TAG, "VPN_TYPES array length does not match string array");
438         }
439         // Although FEATURE_IPSEC_TUNNELS should always be present in android S and beyond,
440         // keep this check here just to be safe.
441         if (!getContext().getPackageManager().hasSystemFeature(
442                 PackageManager.FEATURE_IPSEC_TUNNELS)) {
443             Log.wtf(TAG, "FEATURE_IPSEC_TUNNELS missing from system");
444         }
445         // If the vpn is new or is not already a legacy type,
446         // don't allow the user to set the type to a legacy option.
447 
448         // Set the mProfile.type to TYPE_IKEV2_IPSEC_USER_PASS if the VPN not exist
449         if (!mExists) {
450             mProfile.type = VpnProfile.TYPE_IKEV2_IPSEC_USER_PASS;
451         }
452 
453         final ArrayAdapter<String> adapter = new ArrayAdapter<String>(
454                 getContext(), android.R.layout.simple_spinner_item, types);
455         adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
456         typeSpinner.setAdapter(adapter);
457     }
458 
loadCertificates(Spinner spinner, Collection<String> choices, int firstId, String selected)459     private void loadCertificates(Spinner spinner, Collection<String> choices, int firstId,
460             String selected) {
461         Context context = getContext();
462         String first = (firstId == 0) ? "" : context.getString(firstId);
463         String[] myChoices;
464 
465         if (choices == null || choices.size() == 0) {
466             myChoices = new String[] {first};
467         } else {
468             myChoices = new String[choices.size() + 1];
469             myChoices[0] = first;
470             int i = 1;
471             for (String c : choices) {
472                 myChoices[i++] = c;
473             }
474         }
475 
476         ArrayAdapter<String> adapter = new ArrayAdapter<String>(
477                 context, android.R.layout.simple_spinner_item, myChoices);
478         adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
479         spinner.setAdapter(adapter);
480 
481         for (int i = 1; i < myChoices.length; ++i) {
482             if (myChoices[i].equals(selected)) {
483                 spinner.setSelection(i);
484                 break;
485             }
486         }
487     }
488 
setUsernamePasswordVisibility(int type)489     private void setUsernamePasswordVisibility(int type) {
490         mView.findViewById(R.id.userpass).setVisibility(
491                 requiresUsernamePassword(type) ? View.VISIBLE : View.GONE);
492     }
493 
requiresUsernamePassword(int type)494     private boolean requiresUsernamePassword(int type) {
495         switch (type) {
496             case VpnProfile.TYPE_IKEV2_IPSEC_RSA: // fall through
497             case VpnProfile.TYPE_IKEV2_IPSEC_PSK:
498                 return false;
499             default:
500                 return true;
501         }
502     }
503 
isEditing()504     boolean isEditing() {
505         return mEditing;
506     }
507 
hasProxy()508     boolean hasProxy() {
509         return mProxySettings.getSelectedItemPosition() == VpnProfile.PROXY_MANUAL;
510     }
511 
getProfile()512     VpnProfile getProfile() {
513         // First, save common fields.
514         VpnProfile profile = new VpnProfile(mProfile.key);
515         profile.name = mNameInput.getText();
516         profile.type = getVpnType();
517         profile.server = mServerInput.getText().trim();
518         profile.username = mUsernameInput.getText();
519         profile.password = mPasswordInput.getText();
520 
521         // Save fields based on VPN type.
522         profile.ipsecIdentifier = mIpsecIdentifierInput.getText();
523 
524         if (hasProxy()) {
525             String proxyHost = mProxyHost.getText().toString().trim();
526             String proxyPort = mProxyPort.getText().toString().trim();
527             // 0 is a last resort default, but the interface validates that the proxy port is
528             // present and non-zero.
529             int port = 0;
530             if (!proxyPort.isEmpty()) {
531                 try {
532                     port = Integer.parseInt(proxyPort);
533                 } catch (NumberFormatException e) {
534                     Log.e(TAG, "Could not parse proxy port integer ", e);
535                 }
536             }
537             profile.proxy = ProxyInfo.buildDirectProxy(proxyHost, port);
538         } else {
539             profile.proxy = null;
540         }
541         // Then, save type-specific fields.
542         switch (profile.type) {
543             case VpnProfile.TYPE_IKEV2_IPSEC_PSK:
544                 profile.ipsecSecret = mIpsecSecretInput.getText();
545                 break;
546 
547             case VpnProfile.TYPE_IKEV2_IPSEC_RSA:
548                 if (mIpsecUserCert.getSelectedItemPosition() != 0) {
549                     profile.ipsecUserCert = (String) mIpsecUserCert.getSelectedItem();
550                     profile.ipsecSecret = profile.ipsecUserCert;
551                 }
552                 // fall through
553             case VpnProfile.TYPE_IKEV2_IPSEC_USER_PASS:
554                 if (mIpsecCaCert.getSelectedItemPosition() != 0) {
555                     profile.ipsecCaCert = (String) mIpsecCaCert.getSelectedItem();
556                 }
557                 if (mIpsecServerCert.getSelectedItemPosition() != 0) {
558                     profile.ipsecServerCert = (String) mIpsecServerCert.getSelectedItem();
559                 }
560                 break;
561         }
562 
563         final boolean hasLogin = !profile.username.isEmpty() || !profile.password.isEmpty();
564         profile.saveLogin = mSaveLogin.isChecked() || (mEditing && hasLogin);
565         return profile;
566     }
567 
validateProxy()568     private boolean validateProxy() {
569         if (!hasProxy()) {
570             return true;
571         }
572 
573         final String host = mProxyHost.getText().toString().trim();
574         final String port = mProxyPort.getText().toString().trim();
575         return ProxyUtils.validate(host, port, "") == ProxyUtils.PROXY_VALID;
576     }
577 
convertVpnProfileConstantToTypeIndex(int vpnType)578     private int convertVpnProfileConstantToTypeIndex(int vpnType) {
579         final int typeIndex = VPN_TYPES.indexOf(vpnType);
580         if (typeIndex == -1) {
581             // Existing legacy profile type
582             Log.wtf(TAG, "Invalid existing profile type");
583             return 0;
584         }
585         return typeIndex;
586     }
587 }
588