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 static com.android.internal.net.VpnProfile.isLegacyType;
20 
21 import android.content.Context;
22 import android.content.DialogInterface;
23 import android.content.pm.PackageManager;
24 import android.net.Proxy;
25 import android.net.ProxyInfo;
26 import android.os.Bundle;
27 import android.os.SystemProperties;
28 import android.security.Credentials;
29 import android.security.KeyStore;
30 import android.text.Editable;
31 import android.text.TextWatcher;
32 import android.view.View;
33 import android.view.WindowManager;
34 import android.widget.AdapterView;
35 import android.widget.ArrayAdapter;
36 import android.widget.CheckBox;
37 import android.widget.CompoundButton;
38 import android.widget.Spinner;
39 import android.widget.TextView;
40 
41 import androidx.appcompat.app.AlertDialog;
42 
43 import com.android.internal.net.VpnProfile;
44 import com.android.settings.R;
45 
46 import java.net.InetAddress;
47 import java.util.ArrayList;
48 import java.util.Arrays;
49 import java.util.List;
50 
51 /**
52  * Dialog showing information about a VPN configuration. The dialog
53  * can be launched to either edit or prompt for credentials to connect
54  * to a user-added VPN.
55  *
56  * {@see AppDialog}
57  */
58 class ConfigDialog extends AlertDialog implements TextWatcher,
59         View.OnClickListener, AdapterView.OnItemSelectedListener,
60         CompoundButton.OnCheckedChangeListener {
61     private final KeyStore mKeyStore = KeyStore.getInstance();
62     private final DialogInterface.OnClickListener mListener;
63     private final VpnProfile mProfile;
64 
65     private boolean mEditing;
66     private boolean mExists;
67 
68     private View mView;
69 
70     private TextView mName;
71     private Spinner mType;
72     private TextView mServer;
73     private TextView mUsername;
74     private TextView mPassword;
75     private TextView mSearchDomains;
76     private TextView mDnsServers;
77     private TextView mRoutes;
78     private Spinner mProxySettings;
79     private TextView mProxyHost;
80     private TextView mProxyPort;
81     private CheckBox mMppe;
82     private TextView mL2tpSecret;
83     private TextView mIpsecIdentifier;
84     private TextView mIpsecSecret;
85     private Spinner mIpsecUserCert;
86     private Spinner mIpsecCaCert;
87     private Spinner mIpsecServerCert;
88     private CheckBox mSaveLogin;
89     private CheckBox mShowOptions;
90     private CheckBox mAlwaysOnVpn;
91     private TextView mAlwaysOnInvalidReason;
92 
ConfigDialog(Context context, DialogInterface.OnClickListener listener, VpnProfile profile, boolean editing, boolean exists)93     ConfigDialog(Context context, DialogInterface.OnClickListener listener,
94             VpnProfile profile, boolean editing, boolean exists) {
95         super(context);
96 
97         mListener = listener;
98         mProfile = profile;
99         mEditing = editing;
100         mExists = exists;
101     }
102 
103     @Override
onCreate(Bundle savedState)104     protected void onCreate(Bundle savedState) {
105         mView = getLayoutInflater().inflate(R.layout.vpn_dialog, null);
106         setView(mView);
107 
108         Context context = getContext();
109 
110         // First, find out all the fields.
111         mName = (TextView) mView.findViewById(R.id.name);
112         mType = (Spinner) mView.findViewById(R.id.type);
113         mServer = (TextView) mView.findViewById(R.id.server);
114         mUsername = (TextView) mView.findViewById(R.id.username);
115         mPassword = (TextView) mView.findViewById(R.id.password);
116         mSearchDomains = (TextView) mView.findViewById(R.id.search_domains);
117         mDnsServers = (TextView) mView.findViewById(R.id.dns_servers);
118         mRoutes = (TextView) mView.findViewById(R.id.routes);
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         mMppe = (CheckBox) mView.findViewById(R.id.mppe);
123         mL2tpSecret = (TextView) mView.findViewById(R.id.l2tp_secret);
124         mIpsecIdentifier = (TextView) mView.findViewById(R.id.ipsec_identifier);
125         mIpsecSecret = (TextView) mView.findViewById(R.id.ipsec_secret);
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         mName.setText(mProfile.name);
136         setTypesByFeature(mType);
137         mType.setSelection(mProfile.type);
138         mServer.setText(mProfile.server);
139         if (mProfile.saveLogin) {
140             mUsername.setText(mProfile.username);
141             mPassword.setText(mProfile.password);
142         }
143         mSearchDomains.setText(mProfile.searchDomains);
144         mDnsServers.setText(mProfile.dnsServers);
145         mRoutes.setText(mProfile.routes);
146         if (mProfile.proxy != null) {
147             mProxyHost.setText(mProfile.proxy.getHost());
148             int port = mProfile.proxy.getPort();
149             mProxyPort.setText(port == 0 ? "" : Integer.toString(port));
150         }
151         mMppe.setChecked(mProfile.mppe);
152         mL2tpSecret.setText(mProfile.l2tpSecret);
153         mL2tpSecret.setTextAppearance(android.R.style.TextAppearance_DeviceDefault_Medium);
154         mIpsecIdentifier.setText(mProfile.ipsecIdentifier);
155         mIpsecSecret.setText(mProfile.ipsecSecret);
156         loadCertificates(mIpsecUserCert, Credentials.USER_PRIVATE_KEY, 0, mProfile.ipsecUserCert);
157         loadCertificates(mIpsecCaCert, Credentials.CA_CERTIFICATE,
158                 R.string.vpn_no_ca_cert, mProfile.ipsecCaCert);
159         loadCertificates(mIpsecServerCert, Credentials.USER_CERTIFICATE,
160                 R.string.vpn_no_server_cert, mProfile.ipsecServerCert);
161         mSaveLogin.setChecked(mProfile.saveLogin);
162         mAlwaysOnVpn.setChecked(mProfile.key.equals(VpnUtils.getLockdownVpn()));
163         mPassword.setTextAppearance(android.R.style.TextAppearance_DeviceDefault_Medium);
164 
165         // Hide lockdown VPN on devices that require IMS authentication
166         if (SystemProperties.getBoolean("persist.radio.imsregrequired", false)) {
167             mAlwaysOnVpn.setVisibility(View.GONE);
168         }
169 
170         // Third, add listeners to required fields.
171         mName.addTextChangedListener(this);
172         mType.setOnItemSelectedListener(this);
173         mServer.addTextChangedListener(this);
174         mUsername.addTextChangedListener(this);
175         mPassword.addTextChangedListener(this);
176         mDnsServers.addTextChangedListener(this);
177         mRoutes.addTextChangedListener(this);
178         mProxySettings.setOnItemSelectedListener(this);
179         mProxyHost.addTextChangedListener(this);
180         mProxyPort.addTextChangedListener(this);
181         mIpsecIdentifier.addTextChangedListener(this);
182         mIpsecSecret.addTextChangedListener(this);
183         mIpsecUserCert.setOnItemSelectedListener(this);
184         mShowOptions.setOnClickListener(this);
185         mAlwaysOnVpn.setOnCheckedChangeListener(this);
186 
187         // Fourth, determine whether to do editing or connecting.
188         mEditing = mEditing || !validate(true /*editing*/);
189 
190         if (mEditing) {
191             setTitle(R.string.vpn_edit);
192 
193             // Show common fields.
194             mView.findViewById(R.id.editor).setVisibility(View.VISIBLE);
195 
196             // Show type-specific fields.
197             changeType(mProfile.type);
198 
199             // Hide 'save login' when we are editing.
200             mSaveLogin.setVisibility(View.GONE);
201 
202             configureAdvancedOptionsVisibility();
203 
204             // Create a button to forget the profile if it has already been saved..
205             if (mExists) {
206                 setButton(DialogInterface.BUTTON_NEUTRAL,
207                         context.getString(R.string.vpn_forget), mListener);
208             }
209 
210             // Create a button to save the profile.
211             setButton(DialogInterface.BUTTON_POSITIVE,
212                     context.getString(R.string.vpn_save), mListener);
213         } else {
214             setTitle(context.getString(R.string.vpn_connect_to, mProfile.name));
215 
216             setUsernamePasswordVisibility(mProfile.type);
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(position);
271         } else if (parent == mProxySettings) {
272             updateProxyFieldsVisibility(position);
273         }
274         updateUiControls();
275     }
276 
277     @Override
onNothingSelected(AdapterView<?> parent)278     public void onNothingSelected(AdapterView<?> parent) {
279     }
280 
281     @Override
onCheckedChanged(CompoundButton compoundButton, boolean b)282     public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
283         if (compoundButton == mAlwaysOnVpn) {
284             updateUiControls();
285         }
286     }
287 
isVpnAlwaysOn()288     public boolean isVpnAlwaysOn() {
289         return mAlwaysOnVpn.isChecked();
290     }
291 
292     /**
293      * Updates the UI according to the current configuration entered by the user.
294      *
295      * These include:
296      * "Always-on VPN" checkbox
297      * Reason for "Always-on VPN" being disabled, when necessary
298      * Proxy info if manually configured
299      * "Save account information" checkbox
300      * "Save" and "Connect" buttons
301      */
updateUiControls()302     private void updateUiControls() {
303         VpnProfile profile = getProfile();
304 
305         // Always-on VPN
306         if (profile.isValidLockdownProfile()) {
307             mAlwaysOnVpn.setEnabled(true);
308             mAlwaysOnInvalidReason.setVisibility(View.GONE);
309         } else {
310             mAlwaysOnVpn.setChecked(false);
311             mAlwaysOnVpn.setEnabled(false);
312             if (!profile.isTypeValidForLockdown()) {
313                 mAlwaysOnInvalidReason.setText(R.string.vpn_always_on_invalid_reason_type);
314             } else if (isLegacyType(profile.type) && !profile.isServerAddressNumeric()) {
315                 mAlwaysOnInvalidReason.setText(R.string.vpn_always_on_invalid_reason_server);
316             } else if (isLegacyType(profile.type) && !profile.hasDns()) {
317                 mAlwaysOnInvalidReason.setText(R.string.vpn_always_on_invalid_reason_no_dns);
318             } else if (isLegacyType(profile.type) && !profile.areDnsAddressesNumeric()) {
319                 mAlwaysOnInvalidReason.setText(R.string.vpn_always_on_invalid_reason_dns);
320             } else {
321                 mAlwaysOnInvalidReason.setText(R.string.vpn_always_on_invalid_reason_other);
322             }
323             mAlwaysOnInvalidReason.setVisibility(View.VISIBLE);
324         }
325 
326         // Show proxy fields if any proxy field is filled.
327         if (mProfile.proxy != null && (!mProfile.proxy.getHost().isEmpty() ||
328                 mProfile.proxy.getPort() != 0)) {
329             mProxySettings.setSelection(VpnProfile.PROXY_MANUAL);
330             updateProxyFieldsVisibility(VpnProfile.PROXY_MANUAL);
331         }
332 
333         // Save account information
334         if (mAlwaysOnVpn.isChecked()) {
335             mSaveLogin.setChecked(true);
336             mSaveLogin.setEnabled(false);
337         } else {
338             mSaveLogin.setChecked(mProfile.saveLogin);
339             mSaveLogin.setEnabled(true);
340         }
341 
342         // Save or Connect button
343         getButton(DialogInterface.BUTTON_POSITIVE).setEnabled(validate(mEditing));
344     }
345 
updateProxyFieldsVisibility(int position)346     private void updateProxyFieldsVisibility(int position) {
347         final int visible = position == VpnProfile.PROXY_MANUAL ? View.VISIBLE : View.GONE;
348         mView.findViewById(R.id.vpn_proxy_fields).setVisibility(visible);
349     }
350 
isAdvancedOptionsEnabled()351     private boolean isAdvancedOptionsEnabled() {
352         return mSearchDomains.getText().length() > 0 || mDnsServers.getText().length() > 0 ||
353                     mRoutes.getText().length() > 0 || mProxyHost.getText().length() > 0
354                     || mProxyPort.getText().length() > 0;
355     }
356 
configureAdvancedOptionsVisibility()357     private void configureAdvancedOptionsVisibility() {
358         if (mShowOptions.isChecked() || isAdvancedOptionsEnabled()) {
359             mView.findViewById(R.id.options).setVisibility(View.VISIBLE);
360             mShowOptions.setVisibility(View.GONE);
361 
362             // Configure networking option visibility
363             // TODO(b/149070123): Add ability for platform VPNs to support DNS & routes
364             final int visibility =
365                     isLegacyType(mType.getSelectedItemPosition()) ? View.VISIBLE : View.GONE;
366             mView.findViewById(R.id.network_options).setVisibility(visibility);
367         } else {
368             mView.findViewById(R.id.options).setVisibility(View.GONE);
369             mShowOptions.setVisibility(View.VISIBLE);
370         }
371     }
372 
changeType(int type)373     private void changeType(int type) {
374         // First, hide everything.
375         mMppe.setVisibility(View.GONE);
376         mView.findViewById(R.id.l2tp).setVisibility(View.GONE);
377         mView.findViewById(R.id.ipsec_psk).setVisibility(View.GONE);
378         mView.findViewById(R.id.ipsec_user).setVisibility(View.GONE);
379         mView.findViewById(R.id.ipsec_peer).setVisibility(View.GONE);
380         mView.findViewById(R.id.options_ipsec_identity).setVisibility(View.GONE);
381 
382         setUsernamePasswordVisibility(type);
383 
384         // Always enable identity for IKEv2/IPsec profiles.
385         if (!isLegacyType(type)) {
386             mView.findViewById(R.id.options_ipsec_identity).setVisibility(View.VISIBLE);
387         }
388 
389         // Then, unhide type-specific fields.
390         switch (type) {
391             case VpnProfile.TYPE_PPTP:
392                 mMppe.setVisibility(View.VISIBLE);
393                 break;
394 
395             case VpnProfile.TYPE_L2TP_IPSEC_PSK:
396                 mView.findViewById(R.id.l2tp).setVisibility(View.VISIBLE);
397                 // fall through
398             case VpnProfile.TYPE_IKEV2_IPSEC_PSK: // fall through
399             case VpnProfile.TYPE_IPSEC_XAUTH_PSK:
400                 mView.findViewById(R.id.ipsec_psk).setVisibility(View.VISIBLE);
401                 mView.findViewById(R.id.options_ipsec_identity).setVisibility(View.VISIBLE);
402                 break;
403 
404             case VpnProfile.TYPE_L2TP_IPSEC_RSA:
405                 mView.findViewById(R.id.l2tp).setVisibility(View.VISIBLE);
406                 // fall through
407             case VpnProfile.TYPE_IKEV2_IPSEC_RSA: // fall through
408             case VpnProfile.TYPE_IPSEC_XAUTH_RSA:
409                 mView.findViewById(R.id.ipsec_user).setVisibility(View.VISIBLE);
410                 // fall through
411             case VpnProfile.TYPE_IKEV2_IPSEC_USER_PASS: // fall through
412             case VpnProfile.TYPE_IPSEC_HYBRID_RSA:
413                 mView.findViewById(R.id.ipsec_peer).setVisibility(View.VISIBLE);
414                 break;
415         }
416 
417         configureAdvancedOptionsVisibility();
418     }
419 
validate(boolean editing)420     private boolean validate(boolean editing) {
421         if (mAlwaysOnVpn.isChecked() && !getProfile().isValidLockdownProfile()) {
422             return false;
423         }
424 
425         final int type = mType.getSelectedItemPosition();
426         if (!editing && requiresUsernamePassword(type)) {
427             return mUsername.getText().length() != 0 && mPassword.getText().length() != 0;
428         }
429         if (mName.getText().length() == 0 || mServer.getText().length() == 0) {
430             return false;
431         }
432 
433         // TODO(b/149070123): Add ability for platform VPNs to support DNS & routes
434         if (isLegacyType(mProfile.type)
435                 && (!validateAddresses(mDnsServers.getText().toString(), false)
436                         || !validateAddresses(mRoutes.getText().toString(), true))) {
437             return false;
438         }
439 
440         // All IKEv2 methods require an identifier
441         if (!isLegacyType(mProfile.type) && mIpsecIdentifier.getText().length() == 0) {
442             return false;
443         }
444 
445         if (!validateProxy()) {
446             return false;
447         }
448 
449         switch (type) {
450             case VpnProfile.TYPE_PPTP: // fall through
451             case VpnProfile.TYPE_IPSEC_HYBRID_RSA: // fall through
452             case VpnProfile.TYPE_IKEV2_IPSEC_USER_PASS:
453                 return true;
454 
455             case VpnProfile.TYPE_IKEV2_IPSEC_PSK: // fall through
456             case VpnProfile.TYPE_L2TP_IPSEC_PSK: // fall through
457             case VpnProfile.TYPE_IPSEC_XAUTH_PSK:
458                 return mIpsecSecret.getText().length() != 0;
459 
460             case VpnProfile.TYPE_IKEV2_IPSEC_RSA: // fall through
461             case VpnProfile.TYPE_L2TP_IPSEC_RSA: // fall through
462             case VpnProfile.TYPE_IPSEC_XAUTH_RSA:
463                 return mIpsecUserCert.getSelectedItemPosition() != 0;
464         }
465         return false;
466     }
467 
validateAddresses(String addresses, boolean cidr)468     private boolean validateAddresses(String addresses, boolean cidr) {
469         try {
470             for (String address : addresses.split(" ")) {
471                 if (address.isEmpty()) {
472                     continue;
473                 }
474                 // Legacy VPN currently only supports IPv4.
475                 int prefixLength = 32;
476                 if (cidr) {
477                     String[] parts = address.split("/", 2);
478                     address = parts[0];
479                     prefixLength = Integer.parseInt(parts[1]);
480                 }
481                 byte[] bytes = InetAddress.parseNumericAddress(address).getAddress();
482                 int integer = (bytes[3] & 0xFF) | (bytes[2] & 0xFF) << 8 |
483                         (bytes[1] & 0xFF) << 16 | (bytes[0] & 0xFF) << 24;
484                 if (bytes.length != 4 || prefixLength < 0 || prefixLength > 32 ||
485                         (prefixLength < 32 && (integer << prefixLength) != 0)) {
486                     return false;
487                 }
488             }
489         } catch (Exception e) {
490             return false;
491         }
492         return true;
493     }
494 
setTypesByFeature(Spinner typeSpinner)495     private void setTypesByFeature(Spinner typeSpinner) {
496         String[] types = getContext().getResources().getStringArray(R.array.vpn_types);
497         if (!getContext().getPackageManager().hasSystemFeature(
498                 PackageManager.FEATURE_IPSEC_TUNNELS)) {
499             final List<String> typesList = new ArrayList<>(Arrays.asList(types));
500 
501             // This must be removed from back to front in order to ensure index consistency
502             typesList.remove(VpnProfile.TYPE_IKEV2_IPSEC_RSA);
503             typesList.remove(VpnProfile.TYPE_IKEV2_IPSEC_PSK);
504             typesList.remove(VpnProfile.TYPE_IKEV2_IPSEC_USER_PASS);
505 
506             types = typesList.toArray(new String[0]);
507         }
508         final ArrayAdapter<String> adapter = new ArrayAdapter<String>(
509                 getContext(), android.R.layout.simple_spinner_item, types);
510         adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
511         typeSpinner.setAdapter(adapter);
512     }
513 
loadCertificates(Spinner spinner, String prefix, int firstId, String selected)514     private void loadCertificates(Spinner spinner, String prefix, int firstId, String selected) {
515         Context context = getContext();
516         String first = (firstId == 0) ? "" : context.getString(firstId);
517         String[] certificates = mKeyStore.list(prefix);
518 
519         if (certificates == null || certificates.length == 0) {
520             certificates = new String[] {first};
521         } else {
522             String[] array = new String[certificates.length + 1];
523             array[0] = first;
524             System.arraycopy(certificates, 0, array, 1, certificates.length);
525             certificates = array;
526         }
527 
528         ArrayAdapter<String> adapter = new ArrayAdapter<String>(
529                 context, android.R.layout.simple_spinner_item, certificates);
530         adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
531         spinner.setAdapter(adapter);
532 
533         for (int i = 1; i < certificates.length; ++i) {
534             if (certificates[i].equals(selected)) {
535                 spinner.setSelection(i);
536                 break;
537             }
538         }
539     }
540 
setUsernamePasswordVisibility(int type)541     private void setUsernamePasswordVisibility(int type) {
542         mView.findViewById(R.id.userpass).setVisibility(
543                 requiresUsernamePassword(type) ? View.VISIBLE : View.GONE);
544     }
545 
requiresUsernamePassword(int type)546     private boolean requiresUsernamePassword(int type) {
547         switch (type) {
548             case VpnProfile.TYPE_IKEV2_IPSEC_RSA: // fall through
549             case VpnProfile.TYPE_IKEV2_IPSEC_PSK:
550                 return false;
551             default:
552                 return true;
553         }
554     }
555 
isEditing()556     boolean isEditing() {
557         return mEditing;
558     }
559 
hasProxy()560     boolean hasProxy() {
561         return mProxySettings.getSelectedItemPosition() == VpnProfile.PROXY_MANUAL;
562     }
563 
getProfile()564     VpnProfile getProfile() {
565         // First, save common fields.
566         VpnProfile profile = new VpnProfile(mProfile.key);
567         profile.name = mName.getText().toString();
568         profile.type = mType.getSelectedItemPosition();
569         profile.server = mServer.getText().toString().trim();
570         profile.username = mUsername.getText().toString();
571         profile.password = mPassword.getText().toString();
572 
573         // Save fields based on VPN type.
574         if (isLegacyType(profile.type)) {
575             // TODO(b/149070123): Add ability for platform VPNs to support DNS & routes
576             profile.searchDomains = mSearchDomains.getText().toString().trim();
577             profile.dnsServers = mDnsServers.getText().toString().trim();
578             profile.routes = mRoutes.getText().toString().trim();
579         } else {
580             profile.ipsecIdentifier = mIpsecIdentifier.getText().toString();
581         }
582 
583         if (hasProxy()) {
584             String proxyHost = mProxyHost.getText().toString().trim();
585             String proxyPort = mProxyPort.getText().toString().trim();
586             // 0 is a last resort default, but the interface validates that the proxy port is
587             // present and non-zero.
588             int port = proxyPort.isEmpty() ? 0 : Integer.parseInt(proxyPort);
589             profile.proxy = new ProxyInfo(proxyHost, port, null);
590         } else {
591             profile.proxy = null;
592         }
593         // Then, save type-specific fields.
594         switch (profile.type) {
595             case VpnProfile.TYPE_PPTP:
596                 profile.mppe = mMppe.isChecked();
597                 break;
598 
599             case VpnProfile.TYPE_L2TP_IPSEC_PSK:
600                 profile.l2tpSecret = mL2tpSecret.getText().toString();
601                 // fall through
602             case VpnProfile.TYPE_IKEV2_IPSEC_PSK: // fall through
603             case VpnProfile.TYPE_IPSEC_XAUTH_PSK:
604                 profile.ipsecIdentifier = mIpsecIdentifier.getText().toString();
605                 profile.ipsecSecret = mIpsecSecret.getText().toString();
606                 break;
607 
608             case VpnProfile.TYPE_L2TP_IPSEC_RSA:
609                 profile.l2tpSecret = mL2tpSecret.getText().toString();
610                 // fall through
611             case VpnProfile.TYPE_IKEV2_IPSEC_RSA: // fall through
612             case VpnProfile.TYPE_IPSEC_XAUTH_RSA:
613                 if (mIpsecUserCert.getSelectedItemPosition() != 0) {
614                     profile.ipsecUserCert = (String) mIpsecUserCert.getSelectedItem();
615                 }
616                 // fall through
617             case VpnProfile.TYPE_IKEV2_IPSEC_USER_PASS: // fall through
618             case VpnProfile.TYPE_IPSEC_HYBRID_RSA:
619                 if (mIpsecCaCert.getSelectedItemPosition() != 0) {
620                     profile.ipsecCaCert = (String) mIpsecCaCert.getSelectedItem();
621                 }
622                 if (mIpsecServerCert.getSelectedItemPosition() != 0) {
623                     profile.ipsecServerCert = (String) mIpsecServerCert.getSelectedItem();
624                 }
625                 break;
626         }
627 
628         final boolean hasLogin = !profile.username.isEmpty() || !profile.password.isEmpty();
629         profile.saveLogin = mSaveLogin.isChecked() || (mEditing && hasLogin);
630         return profile;
631     }
632 
validateProxy()633     private boolean validateProxy() {
634         if (!hasProxy()) {
635             return true;
636         }
637 
638         final String host = mProxyHost.getText().toString().trim();
639         final String port = mProxyPort.getText().toString().trim();
640         return Proxy.validate(host, port, "") == Proxy.PROXY_VALID;
641     }
642 
643 }
644