• 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.app.AlertDialog;
20 import android.content.Context;
21 import android.content.DialogInterface;
22 import android.os.Bundle;
23 import android.os.SystemProperties;
24 import android.security.Credentials;
25 import android.security.KeyStore;
26 import android.text.Editable;
27 import android.text.TextWatcher;
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 com.android.internal.net.VpnProfile;
38 import com.android.settings.R;
39 
40 import java.net.InetAddress;
41 
42 /**
43  * Dialog showing information about a VPN configuration. The dialog
44  * can be launched to either edit or prompt for credentials to connect
45  * to a user-added VPN.
46  *
47  * {@see AppDialog}
48  */
49 class ConfigDialog extends AlertDialog implements TextWatcher,
50         View.OnClickListener, AdapterView.OnItemSelectedListener,
51         CompoundButton.OnCheckedChangeListener {
52     private final KeyStore mKeyStore = KeyStore.getInstance();
53     private final DialogInterface.OnClickListener mListener;
54     private final VpnProfile mProfile;
55 
56     private boolean mEditing;
57     private boolean mExists;
58 
59     private View mView;
60 
61     private TextView mName;
62     private Spinner mType;
63     private TextView mServer;
64     private TextView mUsername;
65     private TextView mPassword;
66     private TextView mSearchDomains;
67     private TextView mDnsServers;
68     private TextView mRoutes;
69     private CheckBox mMppe;
70     private TextView mL2tpSecret;
71     private TextView mIpsecIdentifier;
72     private TextView mIpsecSecret;
73     private Spinner mIpsecUserCert;
74     private Spinner mIpsecCaCert;
75     private Spinner mIpsecServerCert;
76     private CheckBox mSaveLogin;
77     private CheckBox mShowOptions;
78     private CheckBox mAlwaysOnVpn;
79     private TextView mAlwaysOnInvalidReason;
80 
ConfigDialog(Context context, DialogInterface.OnClickListener listener, VpnProfile profile, boolean editing, boolean exists)81     ConfigDialog(Context context, DialogInterface.OnClickListener listener,
82             VpnProfile profile, boolean editing, boolean exists) {
83         super(context);
84 
85         mListener = listener;
86         mProfile = profile;
87         mEditing = editing;
88         mExists = exists;
89     }
90 
91     @Override
onCreate(Bundle savedState)92     protected void onCreate(Bundle savedState) {
93         mView = getLayoutInflater().inflate(R.layout.vpn_dialog, null);
94         setView(mView);
95 
96         Context context = getContext();
97 
98         // First, find out all the fields.
99         mName = (TextView) mView.findViewById(R.id.name);
100         mType = (Spinner) mView.findViewById(R.id.type);
101         mServer = (TextView) mView.findViewById(R.id.server);
102         mUsername = (TextView) mView.findViewById(R.id.username);
103         mPassword = (TextView) mView.findViewById(R.id.password);
104         mSearchDomains = (TextView) mView.findViewById(R.id.search_domains);
105         mDnsServers = (TextView) mView.findViewById(R.id.dns_servers);
106         mRoutes = (TextView) mView.findViewById(R.id.routes);
107         mMppe = (CheckBox) mView.findViewById(R.id.mppe);
108         mL2tpSecret = (TextView) mView.findViewById(R.id.l2tp_secret);
109         mIpsecIdentifier = (TextView) mView.findViewById(R.id.ipsec_identifier);
110         mIpsecSecret = (TextView) mView.findViewById(R.id.ipsec_secret);
111         mIpsecUserCert = (Spinner) mView.findViewById(R.id.ipsec_user_cert);
112         mIpsecCaCert = (Spinner) mView.findViewById(R.id.ipsec_ca_cert);
113         mIpsecServerCert = (Spinner) mView.findViewById(R.id.ipsec_server_cert);
114         mSaveLogin = (CheckBox) mView.findViewById(R.id.save_login);
115         mShowOptions = (CheckBox) mView.findViewById(R.id.show_options);
116         mAlwaysOnVpn = (CheckBox) mView.findViewById(R.id.always_on_vpn);
117         mAlwaysOnInvalidReason = (TextView) mView.findViewById(R.id.always_on_invalid_reason);
118 
119         // Second, copy values from the profile.
120         mName.setText(mProfile.name);
121         mType.setSelection(mProfile.type);
122         mServer.setText(mProfile.server);
123         if (mProfile.saveLogin) {
124             mUsername.setText(mProfile.username);
125             mPassword.setText(mProfile.password);
126         }
127         mSearchDomains.setText(mProfile.searchDomains);
128         mDnsServers.setText(mProfile.dnsServers);
129         mRoutes.setText(mProfile.routes);
130         mMppe.setChecked(mProfile.mppe);
131         mL2tpSecret.setText(mProfile.l2tpSecret);
132         mIpsecIdentifier.setText(mProfile.ipsecIdentifier);
133         mIpsecSecret.setText(mProfile.ipsecSecret);
134         loadCertificates(mIpsecUserCert, Credentials.USER_PRIVATE_KEY, 0, mProfile.ipsecUserCert);
135         loadCertificates(mIpsecCaCert, Credentials.CA_CERTIFICATE,
136                 R.string.vpn_no_ca_cert, mProfile.ipsecCaCert);
137         loadCertificates(mIpsecServerCert, Credentials.USER_CERTIFICATE,
138                 R.string.vpn_no_server_cert, mProfile.ipsecServerCert);
139         mSaveLogin.setChecked(mProfile.saveLogin);
140         mAlwaysOnVpn.setChecked(mProfile.key.equals(VpnUtils.getLockdownVpn()));
141 
142         // Hide lockdown VPN on devices that require IMS authentication
143         if (SystemProperties.getBoolean("persist.radio.imsregrequired", false)) {
144             mAlwaysOnVpn.setVisibility(View.GONE);
145         }
146 
147         // Third, add listeners to required fields.
148         mName.addTextChangedListener(this);
149         mType.setOnItemSelectedListener(this);
150         mServer.addTextChangedListener(this);
151         mUsername.addTextChangedListener(this);
152         mPassword.addTextChangedListener(this);
153         mDnsServers.addTextChangedListener(this);
154         mRoutes.addTextChangedListener(this);
155         mIpsecSecret.addTextChangedListener(this);
156         mIpsecUserCert.setOnItemSelectedListener(this);
157         mShowOptions.setOnClickListener(this);
158         mAlwaysOnVpn.setOnCheckedChangeListener(this);
159 
160         // Fourth, determine whether to do editing or connecting.
161         mEditing = mEditing || !validate(true /*editing*/);
162 
163         if (mEditing) {
164             setTitle(R.string.vpn_edit);
165 
166             // Show common fields.
167             mView.findViewById(R.id.editor).setVisibility(View.VISIBLE);
168 
169             // Show type-specific fields.
170             changeType(mProfile.type);
171 
172             // Hide 'save login' when we are editing.
173             mSaveLogin.setVisibility(View.GONE);
174 
175             // Switch to advanced view immediately if any advanced options are on
176             if (!mProfile.searchDomains.isEmpty() || !mProfile.dnsServers.isEmpty() ||
177                     !mProfile.routes.isEmpty()) {
178                 showAdvancedOptions();
179             }
180 
181             // Create a button to forget the profile if it has already been saved..
182             if (mExists) {
183                 setButton(DialogInterface.BUTTON_NEUTRAL,
184                         context.getString(R.string.vpn_forget), mListener);
185             }
186 
187             // Create a button to save the profile.
188             setButton(DialogInterface.BUTTON_POSITIVE,
189                     context.getString(R.string.vpn_save), mListener);
190         } else {
191             setTitle(context.getString(R.string.vpn_connect_to, mProfile.name));
192 
193             // Create a button to connect the network.
194             setButton(DialogInterface.BUTTON_POSITIVE,
195                     context.getString(R.string.vpn_connect), mListener);
196         }
197 
198         // Always provide a cancel button.
199         setButton(DialogInterface.BUTTON_NEGATIVE,
200                 context.getString(R.string.vpn_cancel), mListener);
201 
202         // Let AlertDialog create everything.
203         super.onCreate(savedState);
204 
205         // Update UI controls according to the current configuration.
206         updateUiControls();
207 
208         // Workaround to resize the dialog for the input method.
209         getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE |
210                 WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE);
211     }
212 
213     @Override
onRestoreInstanceState(Bundle savedState)214     public void onRestoreInstanceState(Bundle savedState) {
215         super.onRestoreInstanceState(savedState);
216 
217         // Visibility isn't restored by super.onRestoreInstanceState, so re-show the advanced
218         // options here if they were already revealed or set.
219         if (mShowOptions.isChecked()) {
220             showAdvancedOptions();
221         }
222     }
223 
224     @Override
afterTextChanged(Editable field)225     public void afterTextChanged(Editable field) {
226         updateUiControls();
227     }
228 
229     @Override
beforeTextChanged(CharSequence s, int start, int count, int after)230     public void beforeTextChanged(CharSequence s, int start, int count, int after) {
231     }
232 
233     @Override
onTextChanged(CharSequence s, int start, int before, int count)234     public void onTextChanged(CharSequence s, int start, int before, int count) {
235     }
236 
237     @Override
onClick(View view)238     public void onClick(View view) {
239         if (view == mShowOptions) {
240             showAdvancedOptions();
241         }
242     }
243 
244     @Override
onItemSelected(AdapterView<?> parent, View view, int position, long id)245     public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
246         if (parent == mType) {
247             changeType(position);
248         }
249         updateUiControls();
250     }
251 
252     @Override
onNothingSelected(AdapterView<?> parent)253     public void onNothingSelected(AdapterView<?> parent) {
254     }
255 
256     @Override
onCheckedChanged(CompoundButton compoundButton, boolean b)257     public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
258         if (compoundButton == mAlwaysOnVpn) {
259             updateUiControls();
260         }
261     }
262 
isVpnAlwaysOn()263     public boolean isVpnAlwaysOn() {
264         return mAlwaysOnVpn.isChecked();
265     }
266 
267     /**
268      * Updates the UI according to the current configuration entered by the user.
269      *
270      * These include:
271      * "Always-on VPN" checkbox
272      * Reason for "Always-on VPN" being disabled, when necessary
273      * "Save account information" checkbox
274      * "Save" and "Connect" buttons
275      */
updateUiControls()276     private void updateUiControls() {
277         VpnProfile profile = getProfile();
278 
279         // Always-on VPN
280         if (profile.isValidLockdownProfile()) {
281             mAlwaysOnVpn.setEnabled(true);
282             mAlwaysOnInvalidReason.setVisibility(View.GONE);
283         } else {
284             mAlwaysOnVpn.setChecked(false);
285             mAlwaysOnVpn.setEnabled(false);
286             if (!profile.isTypeValidForLockdown()) {
287                 mAlwaysOnInvalidReason.setText(R.string.vpn_always_on_invalid_reason_type);
288             } else if (!profile.isServerAddressNumeric()) {
289                 mAlwaysOnInvalidReason.setText(R.string.vpn_always_on_invalid_reason_server);
290             } else if (!profile.hasDns()) {
291                 mAlwaysOnInvalidReason.setText(R.string.vpn_always_on_invalid_reason_no_dns);
292             } else if (!profile.areDnsAddressesNumeric()) {
293                 mAlwaysOnInvalidReason.setText(R.string.vpn_always_on_invalid_reason_dns);
294             } else {
295                 mAlwaysOnInvalidReason.setText(R.string.vpn_always_on_invalid_reason_other);
296             }
297             mAlwaysOnInvalidReason.setVisibility(View.VISIBLE);
298         }
299 
300         // Save account information
301         if (mAlwaysOnVpn.isChecked()) {
302             mSaveLogin.setChecked(true);
303             mSaveLogin.setEnabled(false);
304         } else {
305             mSaveLogin.setChecked(mProfile.saveLogin);
306             mSaveLogin.setEnabled(true);
307         }
308 
309         // Save or Connect button
310         getButton(DialogInterface.BUTTON_POSITIVE).setEnabled(validate(mEditing));
311     }
312 
showAdvancedOptions()313     private void showAdvancedOptions() {
314         mView.findViewById(R.id.options).setVisibility(View.VISIBLE);
315         mShowOptions.setVisibility(View.GONE);
316     }
317 
changeType(int type)318     private void changeType(int type) {
319         // First, hide everything.
320         mMppe.setVisibility(View.GONE);
321         mView.findViewById(R.id.l2tp).setVisibility(View.GONE);
322         mView.findViewById(R.id.ipsec_psk).setVisibility(View.GONE);
323         mView.findViewById(R.id.ipsec_user).setVisibility(View.GONE);
324         mView.findViewById(R.id.ipsec_peer).setVisibility(View.GONE);
325 
326         // Then, unhide type-specific fields.
327         switch (type) {
328             case VpnProfile.TYPE_PPTP:
329                 mMppe.setVisibility(View.VISIBLE);
330                 break;
331 
332             case VpnProfile.TYPE_L2TP_IPSEC_PSK:
333                 mView.findViewById(R.id.l2tp).setVisibility(View.VISIBLE);
334                 // fall through
335             case VpnProfile.TYPE_IPSEC_XAUTH_PSK:
336                 mView.findViewById(R.id.ipsec_psk).setVisibility(View.VISIBLE);
337                 break;
338 
339             case VpnProfile.TYPE_L2TP_IPSEC_RSA:
340                 mView.findViewById(R.id.l2tp).setVisibility(View.VISIBLE);
341                 // fall through
342             case VpnProfile.TYPE_IPSEC_XAUTH_RSA:
343                 mView.findViewById(R.id.ipsec_user).setVisibility(View.VISIBLE);
344                 // fall through
345             case VpnProfile.TYPE_IPSEC_HYBRID_RSA:
346                 mView.findViewById(R.id.ipsec_peer).setVisibility(View.VISIBLE);
347                 break;
348         }
349     }
350 
validate(boolean editing)351     private boolean validate(boolean editing) {
352         if (mAlwaysOnVpn.isChecked() && !getProfile().isValidLockdownProfile()) {
353             return false;
354         }
355         if (!editing) {
356             return mUsername.getText().length() != 0 && mPassword.getText().length() != 0;
357         }
358         if (mName.getText().length() == 0 || mServer.getText().length() == 0 ||
359                 !validateAddresses(mDnsServers.getText().toString(), false) ||
360                 !validateAddresses(mRoutes.getText().toString(), true)) {
361             return false;
362         }
363         switch (mType.getSelectedItemPosition()) {
364             case VpnProfile.TYPE_PPTP:
365             case VpnProfile.TYPE_IPSEC_HYBRID_RSA:
366                 return true;
367 
368             case VpnProfile.TYPE_L2TP_IPSEC_PSK:
369             case VpnProfile.TYPE_IPSEC_XAUTH_PSK:
370                 return mIpsecSecret.getText().length() != 0;
371 
372             case VpnProfile.TYPE_L2TP_IPSEC_RSA:
373             case VpnProfile.TYPE_IPSEC_XAUTH_RSA:
374                 return mIpsecUserCert.getSelectedItemPosition() != 0;
375         }
376         return false;
377     }
378 
validateAddresses(String addresses, boolean cidr)379     private boolean validateAddresses(String addresses, boolean cidr) {
380         try {
381             for (String address : addresses.split(" ")) {
382                 if (address.isEmpty()) {
383                     continue;
384                 }
385                 // Legacy VPN currently only supports IPv4.
386                 int prefixLength = 32;
387                 if (cidr) {
388                     String[] parts = address.split("/", 2);
389                     address = parts[0];
390                     prefixLength = Integer.parseInt(parts[1]);
391                 }
392                 byte[] bytes = InetAddress.parseNumericAddress(address).getAddress();
393                 int integer = (bytes[3] & 0xFF) | (bytes[2] & 0xFF) << 8 |
394                         (bytes[1] & 0xFF) << 16 | (bytes[0] & 0xFF) << 24;
395                 if (bytes.length != 4 || prefixLength < 0 || prefixLength > 32 ||
396                         (prefixLength < 32 && (integer << prefixLength) != 0)) {
397                     return false;
398                 }
399             }
400         } catch (Exception e) {
401             return false;
402         }
403         return true;
404     }
405 
loadCertificates(Spinner spinner, String prefix, int firstId, String selected)406     private void loadCertificates(Spinner spinner, String prefix, int firstId, String selected) {
407         Context context = getContext();
408         String first = (firstId == 0) ? "" : context.getString(firstId);
409         String[] certificates = mKeyStore.list(prefix);
410 
411         if (certificates == null || certificates.length == 0) {
412             certificates = new String[] {first};
413         } else {
414             String[] array = new String[certificates.length + 1];
415             array[0] = first;
416             System.arraycopy(certificates, 0, array, 1, certificates.length);
417             certificates = array;
418         }
419 
420         ArrayAdapter<String> adapter = new ArrayAdapter<String>(
421                 context, android.R.layout.simple_spinner_item, certificates);
422         adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
423         spinner.setAdapter(adapter);
424 
425         for (int i = 1; i < certificates.length; ++i) {
426             if (certificates[i].equals(selected)) {
427                 spinner.setSelection(i);
428                 break;
429             }
430         }
431     }
432 
isEditing()433     boolean isEditing() {
434         return mEditing;
435     }
436 
getProfile()437     VpnProfile getProfile() {
438         // First, save common fields.
439         VpnProfile profile = new VpnProfile(mProfile.key);
440         profile.name = mName.getText().toString();
441         profile.type = mType.getSelectedItemPosition();
442         profile.server = mServer.getText().toString().trim();
443         profile.username = mUsername.getText().toString();
444         profile.password = mPassword.getText().toString();
445         profile.searchDomains = mSearchDomains.getText().toString().trim();
446         profile.dnsServers = mDnsServers.getText().toString().trim();
447         profile.routes = mRoutes.getText().toString().trim();
448 
449         // Then, save type-specific fields.
450         switch (profile.type) {
451             case VpnProfile.TYPE_PPTP:
452                 profile.mppe = mMppe.isChecked();
453                 break;
454 
455             case VpnProfile.TYPE_L2TP_IPSEC_PSK:
456                 profile.l2tpSecret = mL2tpSecret.getText().toString();
457                 // fall through
458             case VpnProfile.TYPE_IPSEC_XAUTH_PSK:
459                 profile.ipsecIdentifier = mIpsecIdentifier.getText().toString();
460                 profile.ipsecSecret = mIpsecSecret.getText().toString();
461                 break;
462 
463             case VpnProfile.TYPE_L2TP_IPSEC_RSA:
464                 profile.l2tpSecret = mL2tpSecret.getText().toString();
465                 // fall through
466             case VpnProfile.TYPE_IPSEC_XAUTH_RSA:
467                 if (mIpsecUserCert.getSelectedItemPosition() != 0) {
468                     profile.ipsecUserCert = (String) mIpsecUserCert.getSelectedItem();
469                 }
470                 // fall through
471             case VpnProfile.TYPE_IPSEC_HYBRID_RSA:
472                 if (mIpsecCaCert.getSelectedItemPosition() != 0) {
473                     profile.ipsecCaCert = (String) mIpsecCaCert.getSelectedItem();
474                 }
475                 if (mIpsecServerCert.getSelectedItemPosition() != 0) {
476                     profile.ipsecServerCert = (String) mIpsecServerCert.getSelectedItem();
477                 }
478                 break;
479         }
480 
481         final boolean hasLogin = !profile.username.isEmpty() || !profile.password.isEmpty();
482         profile.saveLogin = mSaveLogin.isChecked() || (mEditing && hasLogin);
483         return profile;
484     }
485 }
486