]> git.ipfire.org Git - thirdparty/strongswan.git/blob - src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileDetailActivity.java
android: Make excluded subnets configurable in the GUI
[thirdparty/strongswan.git] / src / frontends / android / app / src / main / java / org / strongswan / android / ui / VpnProfileDetailActivity.java
1 /*
2 * Copyright (C) 2012-2016 Tobias Brunner
3 * Copyright (C) 2012 Giuliano Grassi
4 * Copyright (C) 2012 Ralf Sager
5 * HSR Hochschule fuer Technik Rapperswil
6 *
7 * This program is free software; you can redistribute it and/or modify it
8 * under the terms of the GNU General Public License as published by the
9 * Free Software Foundation; either version 2 of the License, or (at your
10 * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
11 *
12 * This program is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
14 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
15 * for more details.
16 */
17
18 package org.strongswan.android.ui;
19
20 import android.app.Dialog;
21 import android.content.Context;
22 import android.content.DialogInterface;
23 import android.content.Intent;
24 import android.os.AsyncTask;
25 import android.os.Bundle;
26 import android.security.KeyChain;
27 import android.security.KeyChainAliasCallback;
28 import android.security.KeyChainException;
29 import android.support.v4.content.LocalBroadcastManager;
30 import android.support.v7.app.AlertDialog;
31 import android.support.v7.app.AppCompatActivity;
32 import android.support.v7.app.AppCompatDialogFragment;
33 import android.text.Editable;
34 import android.text.Html;
35 import android.text.SpannableString;
36 import android.text.Spanned;
37 import android.text.TextUtils;
38 import android.text.TextWatcher;
39 import android.util.Log;
40 import android.view.Menu;
41 import android.view.MenuInflater;
42 import android.view.MenuItem;
43 import android.view.View;
44 import android.view.View.OnClickListener;
45 import android.view.ViewGroup;
46 import android.widget.AdapterView;
47 import android.widget.AdapterView.OnItemSelectedListener;
48 import android.widget.ArrayAdapter;
49 import android.widget.CheckBox;
50 import android.widget.CompoundButton;
51 import android.widget.CompoundButton.OnCheckedChangeListener;
52 import android.widget.EditText;
53 import android.widget.MultiAutoCompleteTextView;
54 import android.widget.RelativeLayout;
55 import android.widget.Spinner;
56 import android.widget.TextView;
57
58 import org.strongswan.android.R;
59 import org.strongswan.android.data.VpnProfile;
60 import org.strongswan.android.data.VpnProfileDataSource;
61 import org.strongswan.android.data.VpnType;
62 import org.strongswan.android.data.VpnType.VpnTypeFeature;
63 import org.strongswan.android.logic.TrustedCertificateManager;
64 import org.strongswan.android.security.TrustedCertificateEntry;
65 import org.strongswan.android.ui.adapter.CertificateIdentitiesAdapter;
66 import org.strongswan.android.ui.widget.TextInputLayoutHelper;
67 import org.strongswan.android.utils.Constants;
68 import org.strongswan.android.utils.IPRangeSet;
69
70 import java.security.cert.X509Certificate;
71 import java.util.UUID;
72
73 public class VpnProfileDetailActivity extends AppCompatActivity
74 {
75 private static final int SELECT_TRUSTED_CERTIFICATE = 0;
76
77 private VpnProfileDataSource mDataSource;
78 private Long mId;
79 private TrustedCertificateEntry mCertEntry;
80 private String mUserCertLoading;
81 private CertificateIdentitiesAdapter mSelectUserIdAdapter;
82 private String mSelectedUserId;
83 private TrustedCertificateEntry mUserCertEntry;
84 private VpnType mVpnType = VpnType.IKEV2_EAP;
85 private VpnProfile mProfile;
86 private MultiAutoCompleteTextView mName;
87 private TextInputLayoutHelper mNameWrap;
88 private EditText mGateway;
89 private TextInputLayoutHelper mGatewayWrap;
90 private Spinner mSelectVpnType;
91 private ViewGroup mUsernamePassword;
92 private EditText mUsername;
93 private TextInputLayoutHelper mUsernameWrap;
94 private EditText mPassword;
95 private ViewGroup mUserCertificate;
96 private RelativeLayout mSelectUserCert;
97 private Spinner mSelectUserId;
98 private CheckBox mCheckAuto;
99 private RelativeLayout mSelectCert;
100 private RelativeLayout mTncNotice;
101 private CheckBox mShowAdvanced;
102 private ViewGroup mAdvancedSettings;
103 private MultiAutoCompleteTextView mRemoteId;
104 private TextInputLayoutHelper mRemoteIdWrap;
105 private EditText mMTU;
106 private TextInputLayoutHelper mMTUWrap;
107 private EditText mPort;
108 private TextInputLayoutHelper mPortWrap;
109 private EditText mExcludedSubnets;
110 private TextInputLayoutHelper mExcludedSubnetsWrap;
111 private CheckBox mBlockIPv4;
112 private CheckBox mBlockIPv6;
113
114 @Override
115 public void onCreate(Bundle savedInstanceState)
116 {
117 super.onCreate(savedInstanceState);
118
119 /* the title is set when we load the profile, if any */
120 getSupportActionBar().setDisplayHomeAsUpEnabled(true);
121
122 mDataSource = new VpnProfileDataSource(this);
123 mDataSource.open();
124
125 setContentView(R.layout.profile_detail_view);
126
127 mName = (MultiAutoCompleteTextView)findViewById(R.id.name);
128 mNameWrap = (TextInputLayoutHelper)findViewById(R.id.name_wrap);
129 mGateway = (EditText)findViewById(R.id.gateway);
130 mGatewayWrap = (TextInputLayoutHelper) findViewById(R.id.gateway_wrap);
131 mSelectVpnType = (Spinner)findViewById(R.id.vpn_type);
132 mTncNotice = (RelativeLayout)findViewById(R.id.tnc_notice);
133
134 mUsernamePassword = (ViewGroup)findViewById(R.id.username_password_group);
135 mUsername = (EditText)findViewById(R.id.username);
136 mUsernameWrap = (TextInputLayoutHelper) findViewById(R.id.username_wrap);
137 mPassword = (EditText)findViewById(R.id.password);
138
139 mUserCertificate = (ViewGroup)findViewById(R.id.user_certificate_group);
140 mSelectUserCert = (RelativeLayout)findViewById(R.id.select_user_certificate);
141 mSelectUserId = (Spinner)findViewById(R.id.select_user_id);
142
143 mCheckAuto = (CheckBox)findViewById(R.id.ca_auto);
144 mSelectCert = (RelativeLayout)findViewById(R.id.select_certificate);
145
146 mShowAdvanced = (CheckBox)findViewById(R.id.show_advanced);
147 mAdvancedSettings = (ViewGroup)findViewById(R.id.advanced_settings);
148
149 mRemoteId = (MultiAutoCompleteTextView)findViewById(R.id.remote_id);
150 mRemoteIdWrap = (TextInputLayoutHelper) findViewById(R.id.remote_id_wrap);
151 mMTU = (EditText)findViewById(R.id.mtu);
152 mMTUWrap = (TextInputLayoutHelper) findViewById(R.id.mtu_wrap);
153 mPort = (EditText)findViewById(R.id.port);
154 mPortWrap = (TextInputLayoutHelper) findViewById(R.id.port_wrap);
155 mExcludedSubnets = (EditText)findViewById(R.id.excluded_subnets);
156 mExcludedSubnetsWrap = (TextInputLayoutHelper)findViewById(R.id.excluded_subnets_wrap);
157 mBlockIPv4 = (CheckBox)findViewById(R.id.split_tunneling_v4);
158 mBlockIPv6 = (CheckBox)findViewById(R.id.split_tunneling_v6);
159
160 final SpaceTokenizer spaceTokenizer = new SpaceTokenizer();
161 mName.setTokenizer(spaceTokenizer);
162 mRemoteId.setTokenizer(spaceTokenizer);
163 final ArrayAdapter<String> completeAdapter = new ArrayAdapter<>(this, android.R.layout.simple_dropdown_item_1line);
164 mName.setAdapter(completeAdapter);
165 mRemoteId.setAdapter(completeAdapter);
166
167 mGateway.addTextChangedListener(new TextWatcher() {
168 @Override
169 public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
170
171 @Override
172 public void onTextChanged(CharSequence s, int start, int before, int count) {}
173
174 @Override
175 public void afterTextChanged(Editable s)
176 {
177 completeAdapter.clear();
178 completeAdapter.add(mGateway.getText().toString());
179 if (TextUtils.isEmpty(mGateway.getText()))
180 {
181 mNameWrap.setHelperText(getString(R.string.profile_name_hint));
182 mRemoteIdWrap.setHelperText(getString(R.string.profile_remote_id_hint));
183 }
184 else
185 {
186 mNameWrap.setHelperText(String.format(getString(R.string.profile_name_hint_gateway), mGateway.getText()));
187 mRemoteIdWrap.setHelperText(String.format(getString(R.string.profile_remote_id_hint_gateway), mGateway.getText()));
188 }
189 }
190 });
191
192 mSelectVpnType.setOnItemSelectedListener(new OnItemSelectedListener() {
193 @Override
194 public void onItemSelected(AdapterView<?> parent, View view, int position, long id)
195 {
196 mVpnType = VpnType.values()[position];
197 updateCredentialView();
198 }
199
200 @Override
201 public void onNothingSelected(AdapterView<?> parent)
202 { /* should not happen */
203 mVpnType = VpnType.IKEV2_EAP;
204 updateCredentialView();
205 }
206 });
207
208 ((TextView)mTncNotice.findViewById(android.R.id.text1)).setText(R.string.tnc_notice_title);
209 ((TextView)mTncNotice.findViewById(android.R.id.text2)).setText(R.string.tnc_notice_subtitle);
210 mTncNotice.setOnClickListener(new OnClickListener() {
211 @Override
212 public void onClick(View v)
213 {
214 new TncNoticeDialog().show(VpnProfileDetailActivity.this.getSupportFragmentManager(), "TncNotice");
215 }
216 });
217
218 mSelectUserCert.setOnClickListener(new SelectUserCertOnClickListener());
219 mSelectUserIdAdapter = new CertificateIdentitiesAdapter(this);
220 mSelectUserId.setAdapter(mSelectUserIdAdapter);
221 mSelectUserId.setOnItemSelectedListener(new OnItemSelectedListener() {
222 @Override
223 public void onItemSelected(AdapterView<?> parent, View view, int position, long id)
224 {
225 if (mUserCertEntry != null)
226 { /* we don't store the subject DN as it is in the reverse order and the default anyway */
227 mSelectedUserId = position == 0 ? null : mSelectUserIdAdapter.getItem(position);
228 }
229 }
230
231 @Override
232 public void onNothingSelected(AdapterView<?> parent)
233 {
234 mSelectedUserId = null;
235 }
236 });
237
238 mCheckAuto.setOnCheckedChangeListener(new OnCheckedChangeListener() {
239 @Override
240 public void onCheckedChanged(CompoundButton buttonView, boolean isChecked)
241 {
242 updateCertificateSelector();
243 }
244 });
245
246 mSelectCert.setOnClickListener(new OnClickListener() {
247 @Override
248 public void onClick(View v)
249 {
250 Intent intent = new Intent(VpnProfileDetailActivity.this, TrustedCertificatesActivity.class);
251 intent.setAction(TrustedCertificatesActivity.SELECT_CERTIFICATE);
252 startActivityForResult(intent, SELECT_TRUSTED_CERTIFICATE);
253 }
254 });
255
256 mShowAdvanced.setOnCheckedChangeListener(new OnCheckedChangeListener() {
257 @Override
258 public void onCheckedChanged(CompoundButton buttonView, boolean isChecked)
259 {
260 updateAdvancedSettings();
261 }
262 });
263
264 mId = savedInstanceState == null ? null : savedInstanceState.getLong(VpnProfileDataSource.KEY_ID);
265 if (mId == null)
266 {
267 Bundle extras = getIntent().getExtras();
268 mId = extras == null ? null : extras.getLong(VpnProfileDataSource.KEY_ID);
269 }
270
271 loadProfileData(savedInstanceState);
272
273 updateCredentialView();
274 updateCertificateSelector();
275 updateAdvancedSettings();
276 }
277
278 @Override
279 protected void onDestroy()
280 {
281 super.onDestroy();
282 mDataSource.close();
283 }
284
285 @Override
286 protected void onSaveInstanceState(Bundle outState)
287 {
288 super.onSaveInstanceState(outState);
289 if (mId != null)
290 {
291 outState.putLong(VpnProfileDataSource.KEY_ID, mId);
292 }
293 if (mUserCertEntry != null)
294 {
295 outState.putString(VpnProfileDataSource.KEY_USER_CERTIFICATE, mUserCertEntry.getAlias());
296 }
297 if (mSelectedUserId != null)
298 {
299 outState.putString(VpnProfileDataSource.KEY_LOCAL_ID, mSelectedUserId);
300 }
301 if (mCertEntry != null)
302 {
303 outState.putString(VpnProfileDataSource.KEY_CERTIFICATE, mCertEntry.getAlias());
304 }
305 }
306
307 @Override
308 public boolean onCreateOptionsMenu(Menu menu)
309 {
310 MenuInflater inflater = getMenuInflater();
311 inflater.inflate(R.menu.profile_edit, menu);
312 return true;
313 }
314
315 @Override
316 public boolean onOptionsItemSelected(MenuItem item)
317 {
318 switch (item.getItemId())
319 {
320 case android.R.id.home:
321 case R.id.menu_cancel:
322 finish();
323 return true;
324 case R.id.menu_accept:
325 saveProfile();
326 return true;
327 default:
328 return super.onOptionsItemSelected(item);
329 }
330 }
331
332 @Override
333 protected void onActivityResult(int requestCode, int resultCode, Intent data)
334 {
335 switch (requestCode)
336 {
337 case SELECT_TRUSTED_CERTIFICATE:
338 if (resultCode == RESULT_OK)
339 {
340 String alias = data.getStringExtra(VpnProfileDataSource.KEY_CERTIFICATE);
341 X509Certificate certificate = TrustedCertificateManager.getInstance().getCACertificateFromAlias(alias);
342 mCertEntry = certificate == null ? null : new TrustedCertificateEntry(alias, certificate);
343 updateCertificateSelector();
344 }
345 break;
346 default:
347 super.onActivityResult(requestCode, resultCode, data);
348 }
349 }
350
351 /**
352 * Update the UI to enter credentials depending on the type of VPN currently selected
353 */
354 private void updateCredentialView()
355 {
356 mUsernamePassword.setVisibility(mVpnType.has(VpnTypeFeature.USER_PASS) ? View.VISIBLE : View.GONE);
357 mUserCertificate.setVisibility(mVpnType.has(VpnTypeFeature.CERTIFICATE) ? View.VISIBLE : View.GONE);
358 mTncNotice.setVisibility(mVpnType.has(VpnTypeFeature.BYOD) ? View.VISIBLE : View.GONE);
359
360 if (mVpnType.has(VpnTypeFeature.CERTIFICATE))
361 {
362 mSelectUserId.setEnabled(false);
363 if (mUserCertLoading != null)
364 {
365 ((TextView)mSelectUserCert.findViewById(android.R.id.text1)).setText(mUserCertLoading);
366 ((TextView)mSelectUserCert.findViewById(android.R.id.text2)).setText(R.string.loading);
367 }
368 else if (mUserCertEntry != null)
369 { /* clear any errors and set the new data */
370 ((TextView)mSelectUserCert.findViewById(android.R.id.text1)).setError(null);
371 ((TextView)mSelectUserCert.findViewById(android.R.id.text1)).setText(mUserCertEntry.getAlias());
372 ((TextView)mSelectUserCert.findViewById(android.R.id.text2)).setText(mUserCertEntry.getCertificate().getSubjectDN().toString());
373 mSelectUserIdAdapter.setCertificate(mUserCertEntry);
374 mSelectUserId.setSelection(mSelectUserIdAdapter.getPosition(mSelectedUserId));
375 mSelectUserId.setEnabled(true);
376 }
377 else
378 {
379 ((TextView)mSelectUserCert.findViewById(android.R.id.text1)).setText(R.string.profile_user_select_certificate_label);
380 ((TextView)mSelectUserCert.findViewById(android.R.id.text2)).setText(R.string.profile_user_select_certificate);
381 mSelectUserIdAdapter.setCertificate(null);
382 }
383 }
384 }
385
386 /**
387 * Show an alert in case the previously selected certificate is not found anymore
388 * or the user did not select a certificate in the spinner.
389 */
390 private void showCertificateAlert()
391 {
392 AlertDialog.Builder adb = new AlertDialog.Builder(VpnProfileDetailActivity.this);
393 adb.setTitle(R.string.alert_text_nocertfound_title);
394 adb.setMessage(R.string.alert_text_nocertfound);
395 adb.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
396 @Override
397 public void onClick(DialogInterface dialog, int id)
398 {
399 dialog.cancel();
400 }
401 });
402 adb.show();
403 }
404
405 /**
406 * Update the CA certificate selection UI depending on whether the
407 * certificate should be automatically selected or not.
408 */
409 private void updateCertificateSelector()
410 {
411 if (!mCheckAuto.isChecked())
412 {
413 mSelectCert.setEnabled(true);
414 mSelectCert.setVisibility(View.VISIBLE);
415
416 if (mCertEntry != null)
417 {
418 ((TextView)mSelectCert.findViewById(android.R.id.text1)).setText(mCertEntry.getSubjectPrimary());
419 ((TextView)mSelectCert.findViewById(android.R.id.text2)).setText(mCertEntry.getSubjectSecondary());
420 }
421 else
422 {
423 ((TextView)mSelectCert.findViewById(android.R.id.text1)).setText(R.string.profile_ca_select_certificate_label);
424 ((TextView)mSelectCert.findViewById(android.R.id.text2)).setText(R.string.profile_ca_select_certificate);
425 }
426 }
427 else
428 {
429 mSelectCert.setEnabled(false);
430 mSelectCert.setVisibility(View.GONE);
431 }
432 }
433
434 /**
435 * Update the advanced settings UI depending on whether any advanced
436 * settings have already been made.
437 */
438 private void updateAdvancedSettings()
439 {
440 boolean show = mShowAdvanced.isChecked();
441 if (!show && mProfile != null)
442 {
443 Integer st = mProfile.getSplitTunneling();
444 show = mProfile.getRemoteId() != null || mProfile.getMTU() != null ||
445 mProfile.getPort() != null || (st != null && st != 0) ||
446 mProfile.getExcludedSubnets() != null;
447 }
448 mShowAdvanced.setVisibility(!show ? View.VISIBLE : View.GONE);
449 mAdvancedSettings.setVisibility(show ? View.VISIBLE : View.GONE);
450 }
451
452 /**
453 * Save or update the profile depending on whether we actually have a
454 * profile object or not (this was created in updateProfileData)
455 */
456 private void saveProfile()
457 {
458 if (verifyInput())
459 {
460 if (mProfile != null)
461 {
462 updateProfileData();
463 if (mProfile.getUUID() == null)
464 {
465 mProfile.setUUID(UUID.randomUUID());
466 }
467 mDataSource.updateVpnProfile(mProfile);
468 }
469 else
470 {
471 mProfile = new VpnProfile();
472 updateProfileData();
473 mDataSource.insertProfile(mProfile);
474 }
475 Intent intent = new Intent(Constants.VPN_PROFILES_CHANGED);
476 intent.putExtra(Constants.VPN_PROFILES_SINGLE, mProfile.getId());
477 LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
478
479 setResult(RESULT_OK, new Intent().putExtra(VpnProfileDataSource.KEY_ID, mProfile.getId()));
480 finish();
481 }
482 }
483
484 /**
485 * Verify the user input and display error messages.
486 * @return true if the input is valid
487 */
488 private boolean verifyInput()
489 {
490 boolean valid = true;
491 if (mGateway.getText().toString().trim().isEmpty())
492 {
493 mGatewayWrap.setError(getString(R.string.alert_text_no_input_gateway));
494 valid = false;
495 }
496 if (mVpnType.has(VpnTypeFeature.USER_PASS))
497 {
498 if (mUsername.getText().toString().trim().isEmpty())
499 {
500 mUsernameWrap.setError(getString(R.string.alert_text_no_input_username));
501 valid = false;
502 }
503 }
504 if (mVpnType.has(VpnTypeFeature.CERTIFICATE) && mUserCertEntry == null)
505 { /* let's show an error icon */
506 ((TextView)mSelectUserCert.findViewById(android.R.id.text1)).setError("");
507 valid = false;
508 }
509 if (!mCheckAuto.isChecked() && mCertEntry == null)
510 {
511 showCertificateAlert();
512 valid = false;
513 }
514 if (!validateInteger(mMTU, Constants.MTU_MIN, Constants.MTU_MAX))
515 {
516 mMTUWrap.setError(String.format(getString(R.string.alert_text_out_of_range), Constants.MTU_MIN, Constants.MTU_MAX));
517 valid = false;
518 }
519 if (!validateSubnets(mExcludedSubnets))
520 {
521 mExcludedSubnetsWrap.setError(getString(R.string.alert_text_no_subnets));
522 valid = false;
523 }
524 if (!validateInteger(mPort, 1, 65535))
525 {
526 mPortWrap.setError(String.format(getString(R.string.alert_text_out_of_range), 1, 65535));
527 valid = false;
528 }
529 return valid;
530 }
531
532 /**
533 * Update the profile object with the data entered by the user
534 */
535 private void updateProfileData()
536 {
537 /* the name is optional, we default to the gateway if none is given */
538 String name = mName.getText().toString().trim();
539 String gateway = mGateway.getText().toString().trim();
540 mProfile.setName(name.isEmpty() ? gateway : name);
541 mProfile.setGateway(gateway);
542 mProfile.setVpnType(mVpnType);
543 if (mVpnType.has(VpnTypeFeature.USER_PASS))
544 {
545 mProfile.setUsername(mUsername.getText().toString().trim());
546 String password = mPassword.getText().toString().trim();
547 password = password.isEmpty() ? null : password;
548 mProfile.setPassword(password);
549 }
550 if (mVpnType.has(VpnTypeFeature.CERTIFICATE))
551 {
552 mProfile.setUserCertificateAlias(mUserCertEntry.getAlias());
553 mProfile.setLocalId(mSelectedUserId);
554 }
555 String certAlias = mCheckAuto.isChecked() ? null : mCertEntry.getAlias();
556 mProfile.setCertificateAlias(certAlias);
557 String remote_id = mRemoteId.getText().toString().trim();
558 mProfile.setRemoteId(remote_id.isEmpty() ? null : remote_id);
559 mProfile.setMTU(getInteger(mMTU));
560 mProfile.setPort(getInteger(mPort));
561 String excluded = mExcludedSubnets.getText().toString().trim();
562 mProfile.setExcludedSubnets(excluded.isEmpty() ? null : excluded);
563 int st = 0;
564 st |= mBlockIPv4.isChecked() ? VpnProfile.SPLIT_TUNNELING_BLOCK_IPV4 : 0;
565 st |= mBlockIPv6.isChecked() ? VpnProfile.SPLIT_TUNNELING_BLOCK_IPV6 : 0;
566 mProfile.setSplitTunneling(st == 0 ? null : st);
567 }
568
569 /**
570 * Load an existing profile if we got an ID
571 *
572 * @param savedInstanceState previously saved state
573 */
574 private void loadProfileData(Bundle savedInstanceState)
575 {
576 String useralias = null, local_id = null, alias = null;
577
578 getSupportActionBar().setTitle(R.string.add_profile);
579 if (mId != null && mId != 0)
580 {
581 mProfile = mDataSource.getVpnProfile(mId);
582 if (mProfile != null)
583 {
584 mName.setText(mProfile.getName());
585 mGateway.setText(mProfile.getGateway());
586 mVpnType = mProfile.getVpnType();
587 mUsername.setText(mProfile.getUsername());
588 mPassword.setText(mProfile.getPassword());
589 mRemoteId.setText(mProfile.getRemoteId());
590 mMTU.setText(mProfile.getMTU() != null ? mProfile.getMTU().toString() : null);
591 mPort.setText(mProfile.getPort() != null ? mProfile.getPort().toString() : null);
592 mExcludedSubnets.setText(mProfile.getExcludedSubnets());
593 mBlockIPv4.setChecked(mProfile.getSplitTunneling() != null && (mProfile.getSplitTunneling() & VpnProfile.SPLIT_TUNNELING_BLOCK_IPV4) != 0);
594 mBlockIPv6.setChecked(mProfile.getSplitTunneling() != null && (mProfile.getSplitTunneling() & VpnProfile.SPLIT_TUNNELING_BLOCK_IPV6) != 0);
595 useralias = mProfile.getUserCertificateAlias();
596 local_id = mProfile.getLocalId();
597 alias = mProfile.getCertificateAlias();
598 getSupportActionBar().setTitle(mProfile.getName());
599 }
600 else
601 {
602 Log.e(VpnProfileDetailActivity.class.getSimpleName(),
603 "VPN profile with id " + mId + " not found");
604 finish();
605 }
606 }
607
608 mSelectVpnType.setSelection(mVpnType.ordinal());
609
610 /* check if the user selected a user certificate previously */
611 useralias = savedInstanceState == null ? useralias : savedInstanceState.getString(VpnProfileDataSource.KEY_USER_CERTIFICATE);
612 local_id = savedInstanceState == null ? local_id : savedInstanceState.getString(VpnProfileDataSource.KEY_LOCAL_ID);
613 if (useralias != null)
614 {
615 UserCertificateLoader loader = new UserCertificateLoader(this, useralias);
616 mUserCertLoading = useralias;
617 mSelectedUserId = local_id;
618 loader.execute();
619 }
620
621 /* check if the user selected a CA certificate previously */
622 alias = savedInstanceState == null ? alias : savedInstanceState.getString(VpnProfileDataSource.KEY_CERTIFICATE);
623 mCheckAuto.setChecked(alias == null);
624 if (alias != null)
625 {
626 X509Certificate certificate = TrustedCertificateManager.getInstance().getCACertificateFromAlias(alias);
627 if (certificate != null)
628 {
629 mCertEntry = new TrustedCertificateEntry(alias, certificate);
630 }
631 else
632 { /* previously selected certificate is not here anymore */
633 showCertificateAlert();
634 mCertEntry = null;
635 }
636 }
637 }
638
639 /**
640 * Get the integer value in the given text box or null if empty
641 *
642 * @param view text box (numeric entry assumed)
643 */
644 private Integer getInteger(EditText view)
645 {
646 String value = view.getText().toString().trim();
647 try
648 {
649 return value.isEmpty() ? null : Integer.valueOf(value);
650 }
651 catch (NumberFormatException e)
652 {
653 return null;
654 }
655 }
656
657 /**
658 * Check that the value in the given text box is a valid integer in the given range
659 *
660 * @param view text box (numeric entry assumed)
661 * @param min minimum value (inclusive)
662 * @param max maximum value (inclusive)
663 */
664 private boolean validateInteger(EditText view, Integer min, Integer max)
665 {
666 String value = view.getText().toString().trim();
667 try
668 {
669 if (value.isEmpty())
670 {
671 return true;
672 }
673 Integer val = Integer.valueOf(value);
674 return min <= val && val <= max;
675 }
676 catch (NumberFormatException e)
677 {
678 return false;
679 }
680 }
681
682 /**
683 * Check that the value in the given text box is a valid list of subnets/ranges
684 *
685 * @param view text box
686 */
687 private boolean validateSubnets(EditText view)
688 {
689 String value = view.getText().toString().trim();
690 return value.isEmpty() || IPRangeSet.fromString(value) != null;
691 }
692
693 private class SelectUserCertOnClickListener implements OnClickListener, KeyChainAliasCallback
694 {
695 @Override
696 public void onClick(View v)
697 {
698 String useralias = mUserCertEntry != null ? mUserCertEntry.getAlias() : null;
699 KeyChain.choosePrivateKeyAlias(VpnProfileDetailActivity.this, this, new String[] { "RSA" }, null, null, -1, useralias);
700 }
701
702 @Override
703 public void alias(final String alias)
704 {
705 if (alias != null)
706 { /* otherwise the dialog was canceled, the request denied */
707 try
708 {
709 final X509Certificate[] chain = KeyChain.getCertificateChain(VpnProfileDetailActivity.this, alias);
710 /* alias() is not called from our main thread */
711 runOnUiThread(new Runnable() {
712 @Override
713 public void run()
714 {
715 if (chain != null && chain.length > 0)
716 {
717 mUserCertEntry = new TrustedCertificateEntry(alias, chain[0]);
718 }
719 updateCredentialView();
720 }
721 });
722 }
723 catch (KeyChainException | InterruptedException e)
724 {
725 e.printStackTrace();
726 }
727 }
728 }
729 }
730
731 /**
732 * Load the selected user certificate asynchronously. This cannot be done
733 * from the main thread as getCertificateChain() calls back to our main
734 * thread to bind to the KeyChain service resulting in a deadlock.
735 */
736 private class UserCertificateLoader extends AsyncTask<Void, Void, X509Certificate>
737 {
738 private final Context mContext;
739 private final String mAlias;
740
741 public UserCertificateLoader(Context context, String alias)
742 {
743 mContext = context;
744 mAlias = alias;
745 }
746
747 @Override
748 protected X509Certificate doInBackground(Void... params)
749 {
750 X509Certificate[] chain = null;
751 try
752 {
753 chain = KeyChain.getCertificateChain(mContext, mAlias);
754 }
755 catch (KeyChainException | InterruptedException e)
756 {
757 e.printStackTrace();
758 }
759 if (chain != null && chain.length > 0)
760 {
761 return chain[0];
762 }
763 return null;
764 }
765
766 @Override
767 protected void onPostExecute(X509Certificate result)
768 {
769 if (result != null)
770 {
771 mUserCertEntry = new TrustedCertificateEntry(mAlias, result);
772 }
773 else
774 { /* previously selected certificate is not here anymore */
775 ((TextView)mSelectUserCert.findViewById(android.R.id.text1)).setError("");
776 mUserCertEntry = null;
777 }
778 mUserCertLoading = null;
779 updateCredentialView();
780 }
781 }
782
783 /**
784 * Dialog with notification message if EAP-TNC is used.
785 */
786 public static class TncNoticeDialog extends AppCompatDialogFragment
787 {
788 @Override
789 public Dialog onCreateDialog(Bundle savedInstanceState)
790 {
791 return new AlertDialog.Builder(getActivity())
792 .setTitle(R.string.tnc_notice_title)
793 .setMessage(Html.fromHtml(getString(R.string.tnc_notice_details)))
794 .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
795 @Override
796 public void onClick(DialogInterface dialog, int id)
797 {
798 dialog.dismiss();
799 }
800 }).create();
801 }
802 }
803
804 /**
805 * Tokenizer implementation that separates by white-space
806 */
807 public static class SpaceTokenizer implements MultiAutoCompleteTextView.Tokenizer
808 {
809 @Override
810 public int findTokenStart(CharSequence text, int cursor)
811 {
812 int i = cursor;
813
814 while (i > 0 && !Character.isWhitespace(text.charAt(i - 1)))
815 {
816 i--;
817 }
818 return i;
819 }
820
821 @Override
822 public int findTokenEnd(CharSequence text, int cursor)
823 {
824 int i = cursor;
825 int len = text.length();
826
827 while (i < len)
828 {
829 if (Character.isWhitespace(text.charAt(i)))
830 {
831 return i;
832 }
833 else
834 {
835 i++;
836 }
837 }
838 return len;
839 }
840
841 @Override
842 public CharSequence terminateToken(CharSequence text)
843 {
844 int i = text.length();
845
846 if (i > 0 && Character.isWhitespace(text.charAt(i - 1)))
847 {
848 return text;
849 }
850 else
851 {
852 if (text instanceof Spanned)
853 {
854 SpannableString sp = new SpannableString(text + " ");
855 TextUtils.copySpansFrom((Spanned) text, 0, text.length(), Object.class, sp, 0);
856 return sp;
857 }
858 else
859 {
860 return text + " ";
861 }
862 }
863 }
864 }
865 }