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.ProxyInfo;
25 import android.os.Bundle;
26 import android.os.SystemProperties;
27 import android.text.Editable;
28 import android.text.TextWatcher;
29 import android.util.Log;
30 import android.view.View;
31 import android.view.WindowManager;
32 import android.widget.AdapterView;
33 import android.widget.ArrayAdapter;
34 import android.widget.CheckBox;
35 import android.widget.CompoundButton;
36 import android.widget.Spinner;
37 import android.widget.TextView;
38 
39 import androidx.appcompat.app.AlertDialog;
40 
41 import com.android.internal.net.VpnProfile;
42 import com.android.net.module.util.ProxyUtils;
43 import com.android.settings.R;
44 import com.android.settings.utils.AndroidKeystoreAliasLoader;
45 
46 import java.net.InetAddress;
47 import java.util.ArrayList;
48 import java.util.Arrays;
49 import java.util.Collection;
50 import java.util.List;
51 
52 /**
53  * Dialog showing information about a VPN configuration. The dialog
54  * can be launched to either edit or prompt for credentials to connect
55  * to a user-added VPN.
56  *
57  * {@see AppDialog}
58  */
59 class ConfigDialog extends AlertDialog implements TextWatcher,
60         View.OnClickListener, AdapterView.OnItemSelectedListener,
61         CompoundButton.OnCheckedChangeListener {
62     private static final String TAG = "ConfigDialog";
63     private final DialogInterface.OnClickListener mListener;
64     private final VpnProfile mProfile;
65 
66     private boolean mEditing;
67     private boolean mExists;
68     private List<String> mTotalTypes;
69     private List<String> mAllowedTypes;
70 
71     private View mView;
72 
73     private TextView mName;
74     private Spinner mType;
75     private TextView mServer;
76     private TextView mUsername;
77     private TextView mPassword;
78     private TextView mSearchDomains;
79     private TextView mDnsServers;
80     private TextView mRoutes;
81     private Spinner mProxySettings;
82     private TextView mProxyHost;
83     private TextView mProxyPort;
84     private CheckBox mMppe;
85     private TextView mL2tpSecret;
86     private TextView mIpsecIdentifier;
87     private TextView mIpsecSecret;
88     private Spinner mIpsecUserCert;
89     private Spinner mIpsecCaCert;
90     private Spinner mIpsecServerCert;
91     private CheckBox mSaveLogin;
92     private CheckBox mShowOptions;
93     private CheckBox mAlwaysOnVpn;
94     private TextView mAlwaysOnInvalidReason;
95 
ConfigDialog(Context context, DialogInterface.OnClickListener listener, VpnProfile profile, boolean editing, boolean exists)96     ConfigDialog(Context context, DialogInterface.OnClickListener listener,
97             VpnProfile profile, boolean editing, boolean exists) {
98         super(context);
99 
100         mListener = listener;
101         mProfile = profile;
102         mEditing = editing;
103         mExists = exists;
104     }
105 
106     @Override
onCreate(Bundle savedState)107     protected void onCreate(Bundle savedState) {
108         mView = getLayoutInflater().inflate(R.layout.vpn_dialog, null);
109         setView(mView);
110 
111         Context context = getContext();
112 
113         // First, find out all the fields.
114         mName = (TextView) mView.findViewById(R.id.name);
115         mType = (Spinner) mView.findViewById(R.id.type);
116         mServer = (TextView) mView.findViewById(R.id.server);
117         mUsername = (TextView) mView.findViewById(R.id.username);
118         mPassword = (TextView) mView.findViewById(R.id.password);
119         mSearchDomains = (TextView) mView.findViewById(R.id.search_domains);
120         mDnsServers = (TextView) mView.findViewById(R.id.dns_servers);
121         mRoutes = (TextView) mView.findViewById(R.id.routes);
122         mProxySettings = (Spinner) mView.findViewById(R.id.vpn_proxy_settings);
123         mProxyHost = (TextView) mView.findViewById(R.id.vpn_proxy_host);
124         mProxyPort = (TextView) mView.findViewById(R.id.vpn_proxy_port);
125         mMppe = (CheckBox) mView.findViewById(R.id.mppe);
126         mL2tpSecret = (TextView) mView.findViewById(R.id.l2tp_secret);
127         mIpsecIdentifier = (TextView) mView.findViewById(R.id.ipsec_identifier);
128         mIpsecSecret = (TextView) mView.findViewById(R.id.ipsec_secret);
129         mIpsecUserCert = (Spinner) mView.findViewById(R.id.ipsec_user_cert);
130         mIpsecCaCert = (Spinner) mView.findViewById(R.id.ipsec_ca_cert);
131         mIpsecServerCert = (Spinner) mView.findViewById(R.id.ipsec_server_cert);
132         mSaveLogin = (CheckBox) mView.findViewById(R.id.save_login);
133         mShowOptions = (CheckBox) mView.findViewById(R.id.show_options);
134         mAlwaysOnVpn = (CheckBox) mView.findViewById(R.id.always_on_vpn);
135         mAlwaysOnInvalidReason = (TextView) mView.findViewById(R.id.always_on_invalid_reason);
136 
137         // Second, copy values from the profile.
138         mName.setText(mProfile.name);
139         setTypesByFeature(mType);
140         // Not all types will be available to the user. Find the index corresponding to the
141         // string of the profile's type.
142         if (mAllowedTypes != null && mTotalTypes != null) {
143             mType.setSelection(mAllowedTypes.indexOf(mTotalTypes.get(mProfile.type)));
144         } else {
145             Log.w(TAG, "Allowed or Total vpn types not initialized when setting initial selection");
146         }
147         mServer.setText(mProfile.server);
148         if (mProfile.saveLogin) {
149             mUsername.setText(mProfile.username);
150             mPassword.setText(mProfile.password);
151         }
152         mSearchDomains.setText(mProfile.searchDomains);
153         mDnsServers.setText(mProfile.dnsServers);
154         mRoutes.setText(mProfile.routes);
155         if (mProfile.proxy != null) {
156             mProxyHost.setText(mProfile.proxy.getHost());
157             int port = mProfile.proxy.getPort();
158             mProxyPort.setText(port == 0 ? "" : Integer.toString(port));
159         }
160         mMppe.setChecked(mProfile.mppe);
161         mL2tpSecret.setText(mProfile.l2tpSecret);
162         mL2tpSecret.setTextAppearance(android.R.style.TextAppearance_DeviceDefault_Medium);
163         mIpsecIdentifier.setText(mProfile.ipsecIdentifier);
164         mIpsecSecret.setText(mProfile.ipsecSecret);
165         final AndroidKeystoreAliasLoader androidKeystoreAliasLoader =
166                 new AndroidKeystoreAliasLoader(null);
167         loadCertificates(mIpsecUserCert, androidKeystoreAliasLoader.getKeyCertAliases(), 0,
168                 mProfile.ipsecUserCert);
169         loadCertificates(mIpsecCaCert, androidKeystoreAliasLoader.getCaCertAliases(),
170                 R.string.vpn_no_ca_cert, mProfile.ipsecCaCert);
171         loadCertificates(mIpsecServerCert, androidKeystoreAliasLoader.getKeyCertAliases(),
172                 R.string.vpn_no_server_cert, mProfile.ipsecServerCert);
173         mSaveLogin.setChecked(mProfile.saveLogin);
174         mAlwaysOnVpn.setChecked(mProfile.key.equals(VpnUtils.getLockdownVpn()));
175         mPassword.setTextAppearance(android.R.style.TextAppearance_DeviceDefault_Medium);
176 
177         // Hide lockdown VPN on devices that require IMS authentication
178         if (SystemProperties.getBoolean("persist.radio.imsregrequired", false)) {
179             mAlwaysOnVpn.setVisibility(View.GONE);
180         }
181 
182         // Third, add listeners to required fields.
183         mName.addTextChangedListener(this);
184         mType.setOnItemSelectedListener(this);
185         mServer.addTextChangedListener(this);
186         mUsername.addTextChangedListener(this);
187         mPassword.addTextChangedListener(this);
188         mDnsServers.addTextChangedListener(this);
189         mRoutes.addTextChangedListener(this);
190         mProxySettings.setOnItemSelectedListener(this);
191         mProxyHost.addTextChangedListener(this);
192         mProxyPort.addTextChangedListener(this);
193         mIpsecIdentifier.addTextChangedListener(this);
194         mIpsecSecret.addTextChangedListener(this);
195         mIpsecUserCert.setOnItemSelectedListener(this);
196         mShowOptions.setOnClickListener(this);
197         mAlwaysOnVpn.setOnCheckedChangeListener(this);
198 
199         // Fourth, determine whether to do editing or connecting.
200         mEditing = mEditing || !validate(true /*editing*/);
201 
202         if (mEditing) {
203             setTitle(R.string.vpn_edit);
204 
205             // Show common fields.
206             mView.findViewById(R.id.editor).setVisibility(View.VISIBLE);
207 
208             // Show type-specific fields.
209             changeType(mProfile.type);
210 
211             // Hide 'save login' when we are editing.
212             mSaveLogin.setVisibility(View.GONE);
213 
214             configureAdvancedOptionsVisibility();
215 
216             if (mExists) {
217                 // Create a button to forget the profile if it has already been saved..
218                 setButton(DialogInterface.BUTTON_NEUTRAL,
219                         context.getString(R.string.vpn_forget), mListener);
220 
221                 // Display warning subtitle if the existing VPN is an insecure type...
222                 if (VpnProfile.isLegacyType(mProfile.type)) {
223                     TextView subtitle = mView.findViewById(R.id.dialog_alert_subtitle);
224                     subtitle.setVisibility(View.VISIBLE);
225                 }
226             }
227 
228             // Create a button to save the profile.
229             setButton(DialogInterface.BUTTON_POSITIVE,
230                     context.getString(R.string.vpn_save), mListener);
231         } else {
232             setTitle(context.getString(R.string.vpn_connect_to, mProfile.name));
233 
234             setUsernamePasswordVisibility(mProfile.type);
235 
236             // Create a button to connect the network.
237             setButton(DialogInterface.BUTTON_POSITIVE,
238                     context.getString(R.string.vpn_connect), mListener);
239         }
240 
241         // Always provide a cancel button.
242         setButton(DialogInterface.BUTTON_NEGATIVE,
243                 context.getString(R.string.vpn_cancel), mListener);
244 
245         // Let AlertDialog create everything.
246         super.onCreate(savedState);
247 
248         // Update UI controls according to the current configuration.
249         updateUiControls();
250 
251         // Workaround to resize the dialog for the input method.
252         getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE |
253                 WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE);
254     }
255 
256     @Override
onRestoreInstanceState(Bundle savedState)257     public void onRestoreInstanceState(Bundle savedState) {
258         super.onRestoreInstanceState(savedState);
259 
260         // Visibility isn't restored by super.onRestoreInstanceState, so re-show the advanced
261         // options here if they were already revealed or set.
262         configureAdvancedOptionsVisibility();
263     }
264 
265     @Override
afterTextChanged(Editable field)266     public void afterTextChanged(Editable field) {
267         updateUiControls();
268     }
269 
270     @Override
beforeTextChanged(CharSequence s, int start, int count, int after)271     public void beforeTextChanged(CharSequence s, int start, int count, int after) {
272     }
273 
274     @Override
onTextChanged(CharSequence s, int start, int before, int count)275     public void onTextChanged(CharSequence s, int start, int before, int count) {
276     }
277 
278     @Override
onClick(View view)279     public void onClick(View view) {
280         if (view == mShowOptions) {
281             configureAdvancedOptionsVisibility();
282         }
283     }
284 
285     @Override
onItemSelected(AdapterView<?> parent, View view, int position, long id)286     public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
287         if (parent == mType) {
288             // Because the spinner may not display all available types,
289             // convert the selected position into the actual vpn profile type integer.
290             final int profileType = convertAllowedIndexToProfileType(position);
291             changeType(profileType);
292         } else if (parent == mProxySettings) {
293             updateProxyFieldsVisibility(position);
294         }
295         updateUiControls();
296     }
297 
298     @Override
onNothingSelected(AdapterView<?> parent)299     public void onNothingSelected(AdapterView<?> parent) {
300     }
301 
302     @Override
onCheckedChanged(CompoundButton compoundButton, boolean b)303     public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
304         if (compoundButton == mAlwaysOnVpn) {
305             updateUiControls();
306         }
307     }
308 
isVpnAlwaysOn()309     public boolean isVpnAlwaysOn() {
310         return mAlwaysOnVpn.isChecked();
311     }
312 
313     /**
314      * Updates the UI according to the current configuration entered by the user.
315      *
316      * These include:
317      * "Always-on VPN" checkbox
318      * Reason for "Always-on VPN" being disabled, when necessary
319      * Proxy info if manually configured
320      * "Save account information" checkbox
321      * "Save" and "Connect" buttons
322      */
updateUiControls()323     private void updateUiControls() {
324         VpnProfile profile = getProfile();
325 
326         // Always-on VPN
327         if (profile.isValidLockdownProfile()) {
328             mAlwaysOnVpn.setEnabled(true);
329             mAlwaysOnInvalidReason.setVisibility(View.GONE);
330         } else {
331             mAlwaysOnVpn.setChecked(false);
332             mAlwaysOnVpn.setEnabled(false);
333             if (!profile.isTypeValidForLockdown()) {
334                 mAlwaysOnInvalidReason.setText(R.string.vpn_always_on_invalid_reason_type);
335             } else if (isLegacyType(profile.type) && !profile.isServerAddressNumeric()) {
336                 mAlwaysOnInvalidReason.setText(R.string.vpn_always_on_invalid_reason_server);
337             } else if (isLegacyType(profile.type) && !profile.hasDns()) {
338                 mAlwaysOnInvalidReason.setText(R.string.vpn_always_on_invalid_reason_no_dns);
339             } else if (isLegacyType(profile.type) && !profile.areDnsAddressesNumeric()) {
340                 mAlwaysOnInvalidReason.setText(R.string.vpn_always_on_invalid_reason_dns);
341             } else {
342                 mAlwaysOnInvalidReason.setText(R.string.vpn_always_on_invalid_reason_other);
343             }
344             mAlwaysOnInvalidReason.setVisibility(View.VISIBLE);
345         }
346 
347         // Show proxy fields if any proxy field is filled.
348         if (mProfile.proxy != null && (!mProfile.proxy.getHost().isEmpty() ||
349                 mProfile.proxy.getPort() != 0)) {
350             mProxySettings.setSelection(VpnProfile.PROXY_MANUAL);
351             updateProxyFieldsVisibility(VpnProfile.PROXY_MANUAL);
352         }
353 
354         // Save account information
355         if (mAlwaysOnVpn.isChecked()) {
356             mSaveLogin.setChecked(true);
357             mSaveLogin.setEnabled(false);
358         } else {
359             mSaveLogin.setChecked(mProfile.saveLogin);
360             mSaveLogin.setEnabled(true);
361         }
362 
363         // Save or Connect button
364         getButton(DialogInterface.BUTTON_POSITIVE).setEnabled(validate(mEditing));
365     }
366 
updateProxyFieldsVisibility(int position)367     private void updateProxyFieldsVisibility(int position) {
368         final int visible = position == VpnProfile.PROXY_MANUAL ? View.VISIBLE : View.GONE;
369         mView.findViewById(R.id.vpn_proxy_fields).setVisibility(visible);
370     }
371 
isAdvancedOptionsEnabled()372     private boolean isAdvancedOptionsEnabled() {
373         return mSearchDomains.getText().length() > 0 || mDnsServers.getText().length() > 0 ||
374                     mRoutes.getText().length() > 0 || mProxyHost.getText().length() > 0
375                     || mProxyPort.getText().length() > 0;
376     }
377 
configureAdvancedOptionsVisibility()378     private void configureAdvancedOptionsVisibility() {
379         if (mShowOptions.isChecked() || isAdvancedOptionsEnabled()) {
380             mView.findViewById(R.id.options).setVisibility(View.VISIBLE);
381             mShowOptions.setVisibility(View.GONE);
382 
383             // Configure networking option visibility
384             // TODO(b/149070123): Add ability for platform VPNs to support DNS & routes
385             final int visibility =
386                     isLegacyType(getSelectedVpnType()) ? View.VISIBLE : View.GONE;
387             mView.findViewById(R.id.network_options).setVisibility(visibility);
388         } else {
389             mView.findViewById(R.id.options).setVisibility(View.GONE);
390             mShowOptions.setVisibility(View.VISIBLE);
391         }
392     }
393 
changeType(int type)394     private void changeType(int type) {
395         // First, hide everything.
396         mMppe.setVisibility(View.GONE);
397         mView.findViewById(R.id.l2tp).setVisibility(View.GONE);
398         mView.findViewById(R.id.ipsec_psk).setVisibility(View.GONE);
399         mView.findViewById(R.id.ipsec_user).setVisibility(View.GONE);
400         mView.findViewById(R.id.ipsec_peer).setVisibility(View.GONE);
401         mView.findViewById(R.id.options_ipsec_identity).setVisibility(View.GONE);
402 
403         setUsernamePasswordVisibility(type);
404 
405         // Always enable identity for IKEv2/IPsec profiles.
406         if (!isLegacyType(type)) {
407             mView.findViewById(R.id.options_ipsec_identity).setVisibility(View.VISIBLE);
408         }
409 
410         // Then, unhide type-specific fields.
411         switch (type) {
412             case VpnProfile.TYPE_PPTP:
413                 mMppe.setVisibility(View.VISIBLE);
414                 break;
415 
416             case VpnProfile.TYPE_L2TP_IPSEC_PSK:
417                 mView.findViewById(R.id.l2tp).setVisibility(View.VISIBLE);
418                 // fall through
419             case VpnProfile.TYPE_IKEV2_IPSEC_PSK: // fall through
420             case VpnProfile.TYPE_IPSEC_XAUTH_PSK:
421                 mView.findViewById(R.id.ipsec_psk).setVisibility(View.VISIBLE);
422                 mView.findViewById(R.id.options_ipsec_identity).setVisibility(View.VISIBLE);
423                 break;
424 
425             case VpnProfile.TYPE_L2TP_IPSEC_RSA:
426                 mView.findViewById(R.id.l2tp).setVisibility(View.VISIBLE);
427                 // fall through
428             case VpnProfile.TYPE_IKEV2_IPSEC_RSA: // fall through
429             case VpnProfile.TYPE_IPSEC_XAUTH_RSA:
430                 mView.findViewById(R.id.ipsec_user).setVisibility(View.VISIBLE);
431                 // fall through
432             case VpnProfile.TYPE_IKEV2_IPSEC_USER_PASS: // fall through
433             case VpnProfile.TYPE_IPSEC_HYBRID_RSA:
434                 mView.findViewById(R.id.ipsec_peer).setVisibility(View.VISIBLE);
435                 break;
436         }
437 
438         configureAdvancedOptionsVisibility();
439     }
440 
validate(boolean editing)441     private boolean validate(boolean editing) {
442         if (mAlwaysOnVpn.isChecked() && !getProfile().isValidLockdownProfile()) {
443             return false;
444         }
445 
446         final int type = getSelectedVpnType();
447         if (!editing && requiresUsernamePassword(type)) {
448             return mUsername.getText().length() != 0 && mPassword.getText().length() != 0;
449         }
450         if (mName.getText().length() == 0 || mServer.getText().length() == 0) {
451             return false;
452         }
453 
454         // TODO(b/149070123): Add ability for platform VPNs to support DNS & routes
455         if (isLegacyType(mProfile.type)
456                 && (!validateAddresses(mDnsServers.getText().toString(), false)
457                         || !validateAddresses(mRoutes.getText().toString(), true))) {
458             return false;
459         }
460 
461         // All IKEv2 methods require an identifier
462         if (!isLegacyType(mProfile.type) && mIpsecIdentifier.getText().length() == 0) {
463             return false;
464         }
465 
466         if (!validateProxy()) {
467             return false;
468         }
469 
470         switch (type) {
471             case VpnProfile.TYPE_PPTP: // fall through
472             case VpnProfile.TYPE_IPSEC_HYBRID_RSA: // fall through
473             case VpnProfile.TYPE_IKEV2_IPSEC_USER_PASS:
474                 return true;
475 
476             case VpnProfile.TYPE_IKEV2_IPSEC_PSK: // fall through
477             case VpnProfile.TYPE_L2TP_IPSEC_PSK: // fall through
478             case VpnProfile.TYPE_IPSEC_XAUTH_PSK:
479                 return mIpsecSecret.getText().length() != 0;
480 
481             case VpnProfile.TYPE_IKEV2_IPSEC_RSA: // fall through
482             case VpnProfile.TYPE_L2TP_IPSEC_RSA: // fall through
483             case VpnProfile.TYPE_IPSEC_XAUTH_RSA:
484                 return mIpsecUserCert.getSelectedItemPosition() != 0;
485         }
486         return false;
487     }
488 
validateAddresses(String addresses, boolean cidr)489     private boolean validateAddresses(String addresses, boolean cidr) {
490         try {
491             for (String address : addresses.split(" ")) {
492                 if (address.isEmpty()) {
493                     continue;
494                 }
495                 // Legacy VPN currently only supports IPv4.
496                 int prefixLength = 32;
497                 if (cidr) {
498                     String[] parts = address.split("/", 2);
499                     address = parts[0];
500                     prefixLength = Integer.parseInt(parts[1]);
501                 }
502                 byte[] bytes = InetAddress.parseNumericAddress(address).getAddress();
503                 int integer = (bytes[3] & 0xFF) | (bytes[2] & 0xFF) << 8 |
504                         (bytes[1] & 0xFF) << 16 | (bytes[0] & 0xFF) << 24;
505                 if (bytes.length != 4 || prefixLength < 0 || prefixLength > 32 ||
506                         (prefixLength < 32 && (integer << prefixLength) != 0)) {
507                     return false;
508                 }
509             }
510         } catch (Exception e) {
511             return false;
512         }
513         return true;
514     }
515 
setTypesByFeature(Spinner typeSpinner)516     private void setTypesByFeature(Spinner typeSpinner) {
517         String[] types = getContext().getResources().getStringArray(R.array.vpn_types);
518         mTotalTypes = new ArrayList<>(Arrays.asList(types));
519         mAllowedTypes = new ArrayList<>(Arrays.asList(types));
520 
521         // Although FEATURE_IPSEC_TUNNELS should always be present in android S and beyond,
522         // keep this check here just to be safe.
523         if (!getContext().getPackageManager().hasSystemFeature(
524                 PackageManager.FEATURE_IPSEC_TUNNELS)) {
525             Log.wtf(TAG, "FEATURE_IPSEC_TUNNELS missing from system");
526         }
527         // If the vpn is new or is not already a legacy type,
528         // don't allow the user to set the type to a legacy option.
529 
530         // Set the mProfile.type to TYPE_IKEV2_IPSEC_USER_PASS if the VPN not exist
531         if (!mExists) {
532             mProfile.type = VpnProfile.TYPE_IKEV2_IPSEC_USER_PASS;
533         }
534 
535         // Remove all types which are legacy types from the typesList
536         if (!VpnProfile.isLegacyType(mProfile.type)) {
537             for (int i = mAllowedTypes.size() - 1; i >= 0; i--) {
538                 // This must be removed from back to front in order to ensure index consistency
539                 if (VpnProfile.isLegacyType(i)) {
540                     mAllowedTypes.remove(i);
541                 }
542             }
543 
544             types = mAllowedTypes.toArray(new String[0]);
545         }
546         final ArrayAdapter<String> adapter = new ArrayAdapter<String>(
547                 getContext(), android.R.layout.simple_spinner_item, types);
548         adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
549         typeSpinner.setAdapter(adapter);
550     }
551 
loadCertificates(Spinner spinner, Collection<String> choices, int firstId, String selected)552     private void loadCertificates(Spinner spinner, Collection<String> choices, int firstId,
553             String selected) {
554         Context context = getContext();
555         String first = (firstId == 0) ? "" : context.getString(firstId);
556         String[] myChoices;
557 
558         if (choices == null || choices.size() == 0) {
559             myChoices = new String[] {first};
560         } else {
561             myChoices = new String[choices.size() + 1];
562             myChoices[0] = first;
563             int i = 1;
564             for (String c : choices) {
565                 myChoices[i++] = c;
566             }
567         }
568 
569         ArrayAdapter<String> adapter = new ArrayAdapter<String>(
570                 context, android.R.layout.simple_spinner_item, myChoices);
571         adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
572         spinner.setAdapter(adapter);
573 
574         for (int i = 1; i < myChoices.length; ++i) {
575             if (myChoices[i].equals(selected)) {
576                 spinner.setSelection(i);
577                 break;
578             }
579         }
580     }
581 
setUsernamePasswordVisibility(int type)582     private void setUsernamePasswordVisibility(int type) {
583         mView.findViewById(R.id.userpass).setVisibility(
584                 requiresUsernamePassword(type) ? View.VISIBLE : View.GONE);
585     }
586 
requiresUsernamePassword(int type)587     private boolean requiresUsernamePassword(int type) {
588         switch (type) {
589             case VpnProfile.TYPE_IKEV2_IPSEC_RSA: // fall through
590             case VpnProfile.TYPE_IKEV2_IPSEC_PSK:
591                 return false;
592             default:
593                 return true;
594         }
595     }
596 
isEditing()597     boolean isEditing() {
598         return mEditing;
599     }
600 
hasProxy()601     boolean hasProxy() {
602         return mProxySettings.getSelectedItemPosition() == VpnProfile.PROXY_MANUAL;
603     }
604 
getProfile()605     VpnProfile getProfile() {
606         // First, save common fields.
607         VpnProfile profile = new VpnProfile(mProfile.key);
608         profile.name = mName.getText().toString();
609         profile.type = getSelectedVpnType();
610         profile.server = mServer.getText().toString().trim();
611         profile.username = mUsername.getText().toString();
612         profile.password = mPassword.getText().toString();
613 
614         // Save fields based on VPN type.
615         if (isLegacyType(profile.type)) {
616             // TODO(b/149070123): Add ability for platform VPNs to support DNS & routes
617             profile.searchDomains = mSearchDomains.getText().toString().trim();
618             profile.dnsServers = mDnsServers.getText().toString().trim();
619             profile.routes = mRoutes.getText().toString().trim();
620         } else {
621             profile.ipsecIdentifier = mIpsecIdentifier.getText().toString();
622         }
623 
624         if (hasProxy()) {
625             String proxyHost = mProxyHost.getText().toString().trim();
626             String proxyPort = mProxyPort.getText().toString().trim();
627             // 0 is a last resort default, but the interface validates that the proxy port is
628             // present and non-zero.
629             int port = proxyPort.isEmpty() ? 0 : Integer.parseInt(proxyPort);
630             profile.proxy = ProxyInfo.buildDirectProxy(proxyHost, port);
631         } else {
632             profile.proxy = null;
633         }
634         // Then, save type-specific fields.
635         switch (profile.type) {
636             case VpnProfile.TYPE_PPTP:
637                 profile.mppe = mMppe.isChecked();
638                 break;
639 
640             case VpnProfile.TYPE_L2TP_IPSEC_PSK:
641                 profile.l2tpSecret = mL2tpSecret.getText().toString();
642                 // fall through
643             case VpnProfile.TYPE_IKEV2_IPSEC_PSK: // fall through
644             case VpnProfile.TYPE_IPSEC_XAUTH_PSK:
645                 profile.ipsecIdentifier = mIpsecIdentifier.getText().toString();
646                 profile.ipsecSecret = mIpsecSecret.getText().toString();
647                 break;
648 
649             case VpnProfile.TYPE_L2TP_IPSEC_RSA:
650                 profile.l2tpSecret = mL2tpSecret.getText().toString();
651                 // fall through
652             case VpnProfile.TYPE_IKEV2_IPSEC_RSA: // fall through
653             case VpnProfile.TYPE_IPSEC_XAUTH_RSA:
654                 if (mIpsecUserCert.getSelectedItemPosition() != 0) {
655                     profile.ipsecUserCert = (String) mIpsecUserCert.getSelectedItem();
656                 }
657                 // fall through
658             case VpnProfile.TYPE_IKEV2_IPSEC_USER_PASS: // fall through
659             case VpnProfile.TYPE_IPSEC_HYBRID_RSA:
660                 if (mIpsecCaCert.getSelectedItemPosition() != 0) {
661                     profile.ipsecCaCert = (String) mIpsecCaCert.getSelectedItem();
662                 }
663                 if (mIpsecServerCert.getSelectedItemPosition() != 0) {
664                     profile.ipsecServerCert = (String) mIpsecServerCert.getSelectedItem();
665                 }
666                 break;
667         }
668 
669         final boolean hasLogin = !profile.username.isEmpty() || !profile.password.isEmpty();
670         profile.saveLogin = mSaveLogin.isChecked() || (mEditing && hasLogin);
671         return profile;
672     }
673 
validateProxy()674     private boolean validateProxy() {
675         if (!hasProxy()) {
676             return true;
677         }
678 
679         final String host = mProxyHost.getText().toString().trim();
680         final String port = mProxyPort.getText().toString().trim();
681         return ProxyUtils.validate(host, port, "") == ProxyUtils.PROXY_VALID;
682     }
683 
getSelectedVpnType()684     private int getSelectedVpnType() {
685         return convertAllowedIndexToProfileType(mType.getSelectedItemPosition());
686     }
687 
convertAllowedIndexToProfileType(int allowedSelectedPosition)688     private int convertAllowedIndexToProfileType(int allowedSelectedPosition) {
689         if (mAllowedTypes != null && mTotalTypes != null) {
690             final String typeString = mAllowedTypes.get(allowedSelectedPosition);
691             final int profileType = mTotalTypes.indexOf(typeString);
692             return profileType;
693         } else {
694             Log.w(TAG, "Allowed or Total vpn types not initialized when converting protileType");
695             return allowedSelectedPosition;
696         }
697     }
698 
699 }
700