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