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