]> git.ipfire.org Git - thirdparty/strongswan.git/blame - src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileDetailActivity.java
android: Show profile ID at bottom of advanced settings
[thirdparty/strongswan.git] / src / frontends / android / app / src / main / java / org / strongswan / android / ui / VpnProfileDetailActivity.java
CommitLineData
56a922b2 1/*
291ef58c 2 * Copyright (C) 2012-2017 Tobias Brunner
56a922b2
TB
3 * Copyright (C) 2012 Giuliano Grassi
4 * Copyright (C) 2012 Ralf Sager
ea15f20a 5 * HSR Hochschule fuer Technik Rapperswil
56a922b2
TB
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
18package org.strongswan.android.ui;
19
e1a98e79 20import android.app.Dialog;
f46da851 21import android.content.Context;
fcb54480 22import android.content.DialogInterface;
a3e2f127 23import android.content.Intent;
f46da851 24import android.os.AsyncTask;
98ab7572 25import android.os.Build;
56a922b2 26import android.os.Bundle;
f46da851
TB
27import android.security.KeyChain;
28import android.security.KeyChainAliasCallback;
29import android.security.KeyChainException;
cf6110f1 30import android.support.v4.content.LocalBroadcastManager;
77c1c28d 31import android.support.v7.app.AlertDialog;
6b318282 32import android.support.v7.app.AppCompatActivity;
77c1c28d 33import android.support.v7.app.AppCompatDialogFragment;
fd23ed8c 34import android.text.Editable;
e1a98e79 35import android.text.Html;
e7a12cc8
TB
36import android.text.SpannableString;
37import android.text.Spanned;
fd23ed8c
TB
38import android.text.TextUtils;
39import android.text.TextWatcher;
2d1f65fe 40import android.text.method.LinkMovementMethod;
56a922b2 41import android.util.Log;
a3e2f127
TB
42import android.view.Menu;
43import android.view.MenuInflater;
44import android.view.MenuItem;
fcb54480 45import android.view.View;
4db2d633 46import android.view.View.OnClickListener;
825c192d
TB
47import android.view.ViewGroup;
48import android.widget.AdapterView;
49import android.widget.AdapterView.OnItemSelectedListener;
e7a12cc8 50import android.widget.ArrayAdapter;
fcb54480
TB
51import android.widget.CheckBox;
52import android.widget.CompoundButton;
53import android.widget.CompoundButton.OnCheckedChangeListener;
56a922b2 54import android.widget.EditText;
e7a12cc8 55import android.widget.MultiAutoCompleteTextView;
d4bf6bfb 56import android.widget.RelativeLayout;
825c192d 57import android.widget.Spinner;
8ae7f8b7 58import android.widget.Switch;
d4bf6bfb 59import android.widget.TextView;
56a922b2 60
6b318282
TB
61import org.strongswan.android.R;
62import org.strongswan.android.data.VpnProfile;
291ef58c 63import org.strongswan.android.data.VpnProfile.SelectedAppsHandling;
6b318282
TB
64import org.strongswan.android.data.VpnProfileDataSource;
65import org.strongswan.android.data.VpnType;
66import org.strongswan.android.data.VpnType.VpnTypeFeature;
67import org.strongswan.android.logic.TrustedCertificateManager;
68import org.strongswan.android.security.TrustedCertificateEntry;
67fa05aa 69import org.strongswan.android.ui.adapter.CertificateIdentitiesAdapter;
ea15f20a 70import org.strongswan.android.ui.widget.TextInputLayoutHelper;
cf6110f1 71import org.strongswan.android.utils.Constants;
1a63e8e4 72import org.strongswan.android.utils.IPRangeSet;
9f962f6c 73import org.strongswan.android.utils.Utils;
6b318282
TB
74
75import java.security.cert.X509Certificate;
291ef58c
TB
76import java.util.ArrayList;
77import java.util.SortedSet;
78import java.util.TreeSet;
c4ab9af7 79import java.util.UUID;
6b318282
TB
80
81public class VpnProfileDetailActivity extends AppCompatActivity
56a922b2 82{
4db2d633 83 private static final int SELECT_TRUSTED_CERTIFICATE = 0;
291ef58c 84 private static final int SELECT_APPLICATIONS = 1;
4db2d633 85
56a922b2
TB
86 private VpnProfileDataSource mDataSource;
87 private Long mId;
4db2d633 88 private TrustedCertificateEntry mCertEntry;
f46da851 89 private String mUserCertLoading;
67fa05aa
TB
90 private CertificateIdentitiesAdapter mSelectUserIdAdapter;
91 private String mSelectedUserId;
f46da851 92 private TrustedCertificateEntry mUserCertEntry;
825c192d 93 private VpnType mVpnType = VpnType.IKEV2_EAP;
291ef58c
TB
94 private SelectedAppsHandling mSelectedAppsHandling = SelectedAppsHandling.SELECTED_APPS_DISABLE;
95 private SortedSet<String> mSelectedApps = new TreeSet<>();
56a922b2 96 private VpnProfile mProfile;
e7a12cc8 97 private MultiAutoCompleteTextView mName;
ea15f20a 98 private TextInputLayoutHelper mNameWrap;
56a922b2 99 private EditText mGateway;
ea15f20a 100 private TextInputLayoutHelper mGatewayWrap;
825c192d
TB
101 private Spinner mSelectVpnType;
102 private ViewGroup mUsernamePassword;
56a922b2 103 private EditText mUsername;
ea15f20a 104 private TextInputLayoutHelper mUsernameWrap;
56a922b2 105 private EditText mPassword;
f46da851 106 private ViewGroup mUserCertificate;
d4bf6bfb 107 private RelativeLayout mSelectUserCert;
67fa05aa 108 private Spinner mSelectUserId;
fcb54480 109 private CheckBox mCheckAuto;
d4bf6bfb
TB
110 private RelativeLayout mSelectCert;
111 private RelativeLayout mTncNotice;
c6822051
TB
112 private CheckBox mShowAdvanced;
113 private ViewGroup mAdvancedSettings;
e7a12cc8 114 private MultiAutoCompleteTextView mRemoteId;
c5fee223 115 private TextInputLayoutHelper mRemoteIdWrap;
c6822051 116 private EditText mMTU;
ea15f20a 117 private TextInputLayoutHelper mMTUWrap;
6c0ec35c 118 private EditText mPort;
ea15f20a 119 private TextInputLayoutHelper mPortWrap;
8ae7f8b7 120 private Switch mCertReq;
a2aa0ca0
TB
121 private EditText mNATKeepalive;
122 private TextInputLayoutHelper mNATKeepaliveWrap;
05c5e894
TB
123 private EditText mIncludedSubnets;
124 private TextInputLayoutHelper mIncludedSubnetsWrap;
1a63e8e4
TB
125 private EditText mExcludedSubnets;
126 private TextInputLayoutHelper mExcludedSubnetsWrap;
3ee84fa9
TB
127 private CheckBox mBlockIPv4;
128 private CheckBox mBlockIPv6;
291ef58c
TB
129 private Spinner mSelectSelectedAppsHandling;
130 private RelativeLayout mSelectApps;
2d1f65fe
TB
131 private TextInputLayoutHelper mIkeProposalWrap;
132 private EditText mIkeProposal;
133 private TextInputLayoutHelper mEspProposalWrap;
134 private EditText mEspProposal;
71f4a20a
TB
135 private TextView mProfileIdLabel;
136 private TextView mProfileId;
4db2d633 137
56a922b2
TB
138 @Override
139 public void onCreate(Bundle savedInstanceState)
140 {
141 super.onCreate(savedInstanceState);
142
143 /* the title is set when we load the profile, if any */
6b318282 144 getSupportActionBar().setDisplayHomeAsUpEnabled(true);
56a922b2
TB
145
146 mDataSource = new VpnProfileDataSource(this);
147 mDataSource.open();
148
149 setContentView(R.layout.profile_detail_view);
150
e7a12cc8 151 mName = (MultiAutoCompleteTextView)findViewById(R.id.name);
ea15f20a 152 mNameWrap = (TextInputLayoutHelper)findViewById(R.id.name_wrap);
56a922b2 153 mGateway = (EditText)findViewById(R.id.gateway);
ea15f20a 154 mGatewayWrap = (TextInputLayoutHelper) findViewById(R.id.gateway_wrap);
825c192d 155 mSelectVpnType = (Spinner)findViewById(R.id.vpn_type);
d4bf6bfb 156 mTncNotice = (RelativeLayout)findViewById(R.id.tnc_notice);
825c192d
TB
157
158 mUsernamePassword = (ViewGroup)findViewById(R.id.username_password_group);
56a922b2 159 mUsername = (EditText)findViewById(R.id.username);
ea15f20a 160 mUsernameWrap = (TextInputLayoutHelper) findViewById(R.id.username_wrap);
825c192d 161 mPassword = (EditText)findViewById(R.id.password);
56a922b2 162
f46da851 163 mUserCertificate = (ViewGroup)findViewById(R.id.user_certificate_group);
d4bf6bfb 164 mSelectUserCert = (RelativeLayout)findViewById(R.id.select_user_certificate);
67fa05aa 165 mSelectUserId = (Spinner)findViewById(R.id.select_user_id);
f46da851 166
fcb54480 167 mCheckAuto = (CheckBox)findViewById(R.id.ca_auto);
d4bf6bfb 168 mSelectCert = (RelativeLayout)findViewById(R.id.select_certificate);
825c192d 169
c6822051
TB
170 mShowAdvanced = (CheckBox)findViewById(R.id.show_advanced);
171 mAdvancedSettings = (ViewGroup)findViewById(R.id.advanced_settings);
172
e7a12cc8 173 mRemoteId = (MultiAutoCompleteTextView)findViewById(R.id.remote_id);
c5fee223 174 mRemoteIdWrap = (TextInputLayoutHelper) findViewById(R.id.remote_id_wrap);
c6822051 175 mMTU = (EditText)findViewById(R.id.mtu);
ea15f20a 176 mMTUWrap = (TextInputLayoutHelper) findViewById(R.id.mtu_wrap);
6c0ec35c 177 mPort = (EditText)findViewById(R.id.port);
ea15f20a 178 mPortWrap = (TextInputLayoutHelper) findViewById(R.id.port_wrap);
a2aa0ca0
TB
179 mNATKeepalive = (EditText)findViewById(R.id.nat_keepalive);
180 mNATKeepaliveWrap = (TextInputLayoutHelper) findViewById(R.id.nat_keepalive_wrap);
8ae7f8b7 181 mCertReq = (Switch)findViewById(R.id.cert_req);
05c5e894
TB
182 mIncludedSubnets = (EditText)findViewById(R.id.included_subnets);
183 mIncludedSubnetsWrap = (TextInputLayoutHelper)findViewById(R.id.included_subnets_wrap);
1a63e8e4
TB
184 mExcludedSubnets = (EditText)findViewById(R.id.excluded_subnets);
185 mExcludedSubnetsWrap = (TextInputLayoutHelper)findViewById(R.id.excluded_subnets_wrap);
3ee84fa9
TB
186 mBlockIPv4 = (CheckBox)findViewById(R.id.split_tunneling_v4);
187 mBlockIPv6 = (CheckBox)findViewById(R.id.split_tunneling_v6);
c6822051 188
291ef58c
TB
189 mSelectSelectedAppsHandling = (Spinner)findViewById(R.id.apps_handling);
190 mSelectApps = (RelativeLayout)findViewById(R.id.select_applications);
191
2d1f65fe
TB
192 mIkeProposal = (EditText)findViewById(R.id.ike_proposal);
193 mIkeProposalWrap = (TextInputLayoutHelper)findViewById(R.id.ike_proposal_wrap);
194 mEspProposal = (EditText)findViewById(R.id.esp_proposal);
195 mEspProposalWrap = (TextInputLayoutHelper)findViewById(R.id.esp_proposal_wrap);
196 /* make the link clickable */
197 ((TextView)findViewById(R.id.proposal_intro)).setMovementMethod(LinkMovementMethod.getInstance());
198
71f4a20a
TB
199 mProfileIdLabel = (TextView)findViewById(R.id.profile_id_label);
200 mProfileId = (TextView)findViewById(R.id.profile_id);
201
e7a12cc8
TB
202 final SpaceTokenizer spaceTokenizer = new SpaceTokenizer();
203 mName.setTokenizer(spaceTokenizer);
204 mRemoteId.setTokenizer(spaceTokenizer);
205 final ArrayAdapter<String> completeAdapter = new ArrayAdapter<>(this, android.R.layout.simple_dropdown_item_1line);
206 mName.setAdapter(completeAdapter);
207 mRemoteId.setAdapter(completeAdapter);
208
98ab7572
TB
209 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP)
210 {
211 findViewById(R.id.apps).setVisibility(View.GONE);
212 mSelectSelectedAppsHandling.setVisibility(View.GONE);
213 mSelectApps.setVisibility(View.GONE);
214 }
215
fd23ed8c
TB
216 mGateway.addTextChangedListener(new TextWatcher() {
217 @Override
218 public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
219
220 @Override
221 public void onTextChanged(CharSequence s, int start, int before, int count) {}
222
223 @Override
ea15f20a
TB
224 public void afterTextChanged(Editable s)
225 {
e7a12cc8
TB
226 completeAdapter.clear();
227 completeAdapter.add(mGateway.getText().toString());
fd23ed8c
TB
228 if (TextUtils.isEmpty(mGateway.getText()))
229 {
ea15f20a 230 mNameWrap.setHelperText(getString(R.string.profile_name_hint));
c5fee223 231 mRemoteIdWrap.setHelperText(getString(R.string.profile_remote_id_hint));
fd23ed8c
TB
232 }
233 else
234 {
ea15f20a 235 mNameWrap.setHelperText(String.format(getString(R.string.profile_name_hint_gateway), mGateway.getText()));
c5fee223 236 mRemoteIdWrap.setHelperText(String.format(getString(R.string.profile_remote_id_hint_gateway), mGateway.getText()));
fd23ed8c
TB
237 }
238 }
239 });
240
825c192d
TB
241 mSelectVpnType.setOnItemSelectedListener(new OnItemSelectedListener() {
242 @Override
243 public void onItemSelected(AdapterView<?> parent, View view, int position, long id)
244 {
245 mVpnType = VpnType.values()[position];
f46da851 246 updateCredentialView();
825c192d
TB
247 }
248
249 @Override
250 public void onNothingSelected(AdapterView<?> parent)
251 { /* should not happen */
252 mVpnType = VpnType.IKEV2_EAP;
f46da851 253 updateCredentialView();
825c192d
TB
254 }
255 });
256
d4bf6bfb
TB
257 ((TextView)mTncNotice.findViewById(android.R.id.text1)).setText(R.string.tnc_notice_title);
258 ((TextView)mTncNotice.findViewById(android.R.id.text2)).setText(R.string.tnc_notice_subtitle);
e1a98e79
TB
259 mTncNotice.setOnClickListener(new OnClickListener() {
260 @Override
261 public void onClick(View v)
262 {
77c1c28d 263 new TncNoticeDialog().show(VpnProfileDetailActivity.this.getSupportFragmentManager(), "TncNotice");
e1a98e79
TB
264 }
265 });
266
f46da851 267 mSelectUserCert.setOnClickListener(new SelectUserCertOnClickListener());
67fa05aa
TB
268 mSelectUserIdAdapter = new CertificateIdentitiesAdapter(this);
269 mSelectUserId.setAdapter(mSelectUserIdAdapter);
270 mSelectUserId.setOnItemSelectedListener(new OnItemSelectedListener() {
271 @Override
272 public void onItemSelected(AdapterView<?> parent, View view, int position, long id)
273 {
274 if (mUserCertEntry != null)
275 { /* we don't store the subject DN as it is in the reverse order and the default anyway */
276 mSelectedUserId = position == 0 ? null : mSelectUserIdAdapter.getItem(position);
277 }
278 }
279
280 @Override
281 public void onNothingSelected(AdapterView<?> parent)
282 {
283 mSelectedUserId = null;
284 }
285 });
f46da851 286
fcb54480
TB
287 mCheckAuto.setOnCheckedChangeListener(new OnCheckedChangeListener() {
288 @Override
289 public void onCheckedChanged(CompoundButton buttonView, boolean isChecked)
290 {
4db2d633
TB
291 updateCertificateSelector();
292 }
293 });
fcb54480 294
4db2d633
TB
295 mSelectCert.setOnClickListener(new OnClickListener() {
296 @Override
297 public void onClick(View v)
298 {
299 Intent intent = new Intent(VpnProfileDetailActivity.this, TrustedCertificatesActivity.class);
9c841b1f 300 intent.setAction(TrustedCertificatesActivity.SELECT_CERTIFICATE);
4db2d633 301 startActivityForResult(intent, SELECT_TRUSTED_CERTIFICATE);
fcb54480
TB
302 }
303 });
304
c6822051
TB
305 mShowAdvanced.setOnCheckedChangeListener(new OnCheckedChangeListener() {
306 @Override
307 public void onCheckedChanged(CompoundButton buttonView, boolean isChecked)
308 {
309 updateAdvancedSettings();
310 }
311 });
312
291ef58c
TB
313 mSelectSelectedAppsHandling.setOnItemSelectedListener(new OnItemSelectedListener() {
314 @Override
315 public void onItemSelected(AdapterView<?> parent, View view, int position, long id)
316 {
317 mSelectedAppsHandling = SelectedAppsHandling.values()[position];
318 updateAppsSelector();
319 }
320
321 @Override
322 public void onNothingSelected(AdapterView<?> parent)
323 { /* should not happen */
324 mSelectedAppsHandling = SelectedAppsHandling.SELECTED_APPS_DISABLE;
325 updateAppsSelector();
326 }
327 });
328
329 mSelectApps.setOnClickListener(new OnClickListener() {
330 @Override
331 public void onClick(View v)
332 {
333 Intent intent = new Intent(VpnProfileDetailActivity.this, SelectedApplicationsActivity.class);
334 intent.putExtra(VpnProfileDataSource.KEY_SELECTED_APPS_LIST, new ArrayList<>(mSelectedApps));
335 startActivityForResult(intent, SELECT_APPLICATIONS);
336 }
337 });
338
56a922b2
TB
339 mId = savedInstanceState == null ? null : savedInstanceState.getLong(VpnProfileDataSource.KEY_ID);
340 if (mId == null)
341 {
342 Bundle extras = getIntent().getExtras();
343 mId = extras == null ? null : extras.getLong(VpnProfileDataSource.KEY_ID);
344 }
345
4db2d633 346 loadProfileData(savedInstanceState);
fcb54480 347
f46da851 348 updateCredentialView();
4db2d633 349 updateCertificateSelector();
c6822051 350 updateAdvancedSettings();
291ef58c 351 updateAppsSelector();
56a922b2
TB
352 }
353
354 @Override
355 protected void onDestroy()
356 {
357 super.onDestroy();
358 mDataSource.close();
359 }
360
361 @Override
362 protected void onSaveInstanceState(Bundle outState)
363 {
364 super.onSaveInstanceState(outState);
cb431e12
TB
365 if (mId != null)
366 {
367 outState.putLong(VpnProfileDataSource.KEY_ID, mId);
368 }
f46da851
TB
369 if (mUserCertEntry != null)
370 {
371 outState.putString(VpnProfileDataSource.KEY_USER_CERTIFICATE, mUserCertEntry.getAlias());
372 }
67fa05aa
TB
373 if (mSelectedUserId != null)
374 {
375 outState.putString(VpnProfileDataSource.KEY_LOCAL_ID, mSelectedUserId);
376 }
4db2d633
TB
377 if (mCertEntry != null)
378 {
379 outState.putString(VpnProfileDataSource.KEY_CERTIFICATE, mCertEntry.getAlias());
380 }
291ef58c 381 outState.putStringArrayList(VpnProfileDataSource.KEY_SELECTED_APPS_LIST, new ArrayList<>(mSelectedApps));
56a922b2
TB
382 }
383
a3e2f127
TB
384 @Override
385 public boolean onCreateOptionsMenu(Menu menu)
386 {
387 MenuInflater inflater = getMenuInflater();
388 inflater.inflate(R.menu.profile_edit, menu);
389 return true;
390 }
391
392 @Override
393 public boolean onOptionsItemSelected(MenuItem item)
394 {
395 switch (item.getItemId())
396 {
397 case android.R.id.home:
398 case R.id.menu_cancel:
399 finish();
400 return true;
401 case R.id.menu_accept:
402 saveProfile();
403 return true;
404 default:
405 return super.onOptionsItemSelected(item);
406 }
407 }
408
4db2d633
TB
409 @Override
410 protected void onActivityResult(int requestCode, int resultCode, Intent data)
411 {
412 switch (requestCode)
413 {
414 case SELECT_TRUSTED_CERTIFICATE:
415 if (resultCode == RESULT_OK)
416 {
417 String alias = data.getStringExtra(VpnProfileDataSource.KEY_CERTIFICATE);
418 X509Certificate certificate = TrustedCertificateManager.getInstance().getCACertificateFromAlias(alias);
419 mCertEntry = certificate == null ? null : new TrustedCertificateEntry(alias, certificate);
420 updateCertificateSelector();
421 }
422 break;
291ef58c
TB
423 case SELECT_APPLICATIONS:
424 if (resultCode == RESULT_OK)
425 {
426 ArrayList<String> selection = data.getStringArrayListExtra(VpnProfileDataSource.KEY_SELECTED_APPS_LIST);
427 mSelectedApps = new TreeSet<>(selection);
428 updateAppsSelector();
429 }
430 break;
4db2d633
TB
431 default:
432 super.onActivityResult(requestCode, resultCode, data);
433 }
434 }
435
825c192d 436 /**
f46da851 437 * Update the UI to enter credentials depending on the type of VPN currently selected
825c192d 438 */
f46da851 439 private void updateCredentialView()
825c192d 440 {
a6408973
TB
441 mUsernamePassword.setVisibility(mVpnType.has(VpnTypeFeature.USER_PASS) ? View.VISIBLE : View.GONE);
442 mUserCertificate.setVisibility(mVpnType.has(VpnTypeFeature.CERTIFICATE) ? View.VISIBLE : View.GONE);
443 mTncNotice.setVisibility(mVpnType.has(VpnTypeFeature.BYOD) ? View.VISIBLE : View.GONE);
f46da851 444
a6408973 445 if (mVpnType.has(VpnTypeFeature.CERTIFICATE))
f46da851 446 {
67fa05aa 447 mSelectUserId.setEnabled(false);
f46da851
TB
448 if (mUserCertLoading != null)
449 {
d4bf6bfb
TB
450 ((TextView)mSelectUserCert.findViewById(android.R.id.text1)).setText(mUserCertLoading);
451 ((TextView)mSelectUserCert.findViewById(android.R.id.text2)).setText(R.string.loading);
f46da851
TB
452 }
453 else if (mUserCertEntry != null)
454 { /* clear any errors and set the new data */
d4bf6bfb
TB
455 ((TextView)mSelectUserCert.findViewById(android.R.id.text1)).setError(null);
456 ((TextView)mSelectUserCert.findViewById(android.R.id.text1)).setText(mUserCertEntry.getAlias());
457 ((TextView)mSelectUserCert.findViewById(android.R.id.text2)).setText(mUserCertEntry.getCertificate().getSubjectDN().toString());
67fa05aa
TB
458 mSelectUserIdAdapter.setCertificate(mUserCertEntry);
459 mSelectUserId.setSelection(mSelectUserIdAdapter.getPosition(mSelectedUserId));
460 mSelectUserId.setEnabled(true);
f46da851
TB
461 }
462 else
463 {
d4bf6bfb
TB
464 ((TextView)mSelectUserCert.findViewById(android.R.id.text1)).setText(R.string.profile_user_select_certificate_label);
465 ((TextView)mSelectUserCert.findViewById(android.R.id.text2)).setText(R.string.profile_user_select_certificate);
67fa05aa 466 mSelectUserIdAdapter.setCertificate(null);
f46da851
TB
467 }
468 }
825c192d
TB
469 }
470
fcb54480
TB
471 /**
472 * Show an alert in case the previously selected certificate is not found anymore
473 * or the user did not select a certificate in the spinner.
474 */
475 private void showCertificateAlert()
476 {
477 AlertDialog.Builder adb = new AlertDialog.Builder(VpnProfileDetailActivity.this);
478 adb.setTitle(R.string.alert_text_nocertfound_title);
479 adb.setMessage(R.string.alert_text_nocertfound);
480 adb.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
481 @Override
482 public void onClick(DialogInterface dialog, int id)
483 {
484 dialog.cancel();
485 }
486 });
487 adb.show();
488 }
489
490 /**
4db2d633
TB
491 * Update the CA certificate selection UI depending on whether the
492 * certificate should be automatically selected or not.
fcb54480 493 */
4db2d633 494 private void updateCertificateSelector()
fcb54480 495 {
4db2d633 496 if (!mCheckAuto.isChecked())
fcb54480 497 {
4db2d633
TB
498 mSelectCert.setEnabled(true);
499 mSelectCert.setVisibility(View.VISIBLE);
fcb54480 500
4db2d633
TB
501 if (mCertEntry != null)
502 {
d4bf6bfb
TB
503 ((TextView)mSelectCert.findViewById(android.R.id.text1)).setText(mCertEntry.getSubjectPrimary());
504 ((TextView)mSelectCert.findViewById(android.R.id.text2)).setText(mCertEntry.getSubjectSecondary());
4db2d633
TB
505 }
506 else
507 {
d4bf6bfb
TB
508 ((TextView)mSelectCert.findViewById(android.R.id.text1)).setText(R.string.profile_ca_select_certificate_label);
509 ((TextView)mSelectCert.findViewById(android.R.id.text2)).setText(R.string.profile_ca_select_certificate);
4db2d633 510 }
fcb54480 511 }
4db2d633 512 else
fcb54480 513 {
4db2d633
TB
514 mSelectCert.setEnabled(false);
515 mSelectCert.setVisibility(View.GONE);
fcb54480
TB
516 }
517 }
518
291ef58c
TB
519 /**
520 * Update the application selection UI
521 */
522 private void updateAppsSelector()
523 {
524 if (mSelectedAppsHandling == SelectedAppsHandling.SELECTED_APPS_DISABLE)
525 {
526 mSelectApps.setEnabled(false);
527 mSelectApps.setVisibility(View.GONE);
528
529 }
530 else
531 {
532 mSelectApps.setEnabled(true);
533 mSelectApps.setVisibility(View.VISIBLE);
534
535 ((TextView)mSelectApps.findViewById(android.R.id.text1)).setText(R.string.profile_select_apps);
536 String selected;
537 switch (mSelectedApps.size())
538 {
539 case 0:
540 selected = getString(R.string.profile_select_no_apps);
541 break;
542 case 1:
543 selected = getString(R.string.profile_select_one_app);
544 break;
545 default:
546 selected = getString(R.string.profile_select_x_apps, mSelectedApps.size());
547 break;
548 }
549 ((TextView)mSelectApps.findViewById(android.R.id.text2)).setText(selected);
550 }
551 }
552
c6822051
TB
553 /**
554 * Update the advanced settings UI depending on whether any advanced
555 * settings have already been made.
556 */
557 private void updateAdvancedSettings()
558 {
559 boolean show = mShowAdvanced.isChecked();
560 if (!show && mProfile != null)
561 {
8ae7f8b7 562 Integer st = mProfile.getSplitTunneling(), flags = mProfile.getFlags();
c5fee223 563 show = mProfile.getRemoteId() != null || mProfile.getMTU() != null ||
8ae7f8b7
TB
564 mProfile.getPort() != null || mProfile.getNATKeepAlive() != null ||
565 (flags != null && flags != 0) || (st != null && st != 0) ||
291ef58c 566 mProfile.getIncludedSubnets() != null || mProfile.getExcludedSubnets() != null ||
2d1f65fe
TB
567 mProfile.getSelectedAppsHandling() != SelectedAppsHandling.SELECTED_APPS_DISABLE ||
568 mProfile.getIkeProposal() != null || mProfile.getEspProposal() != null;
c6822051
TB
569 }
570 mShowAdvanced.setVisibility(!show ? View.VISIBLE : View.GONE);
571 mAdvancedSettings.setVisibility(show ? View.VISIBLE : View.GONE);
71f4a20a
TB
572
573 if (show && mProfile == null)
574 {
575 mProfileIdLabel.setVisibility(View.GONE);
576 mProfileId.setVisibility(View.GONE);
577 }
c6822051
TB
578 }
579
a3e2f127
TB
580 /**
581 * Save or update the profile depending on whether we actually have a
582 * profile object or not (this was created in updateProfileData)
583 */
584 private void saveProfile()
585 {
586 if (verifyInput())
587 {
588 if (mProfile != null)
589 {
590 updateProfileData();
c4ab9af7
TB
591 if (mProfile.getUUID() == null)
592 {
593 mProfile.setUUID(UUID.randomUUID());
594 }
a3e2f127
TB
595 mDataSource.updateVpnProfile(mProfile);
596 }
597 else
598 {
599 mProfile = new VpnProfile();
600 updateProfileData();
601 mDataSource.insertProfile(mProfile);
602 }
cf6110f1
TB
603 Intent intent = new Intent(Constants.VPN_PROFILES_CHANGED);
604 intent.putExtra(Constants.VPN_PROFILES_SINGLE, mProfile.getId());
605 LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
606
a3e2f127
TB
607 setResult(RESULT_OK, new Intent().putExtra(VpnProfileDataSource.KEY_ID, mProfile.getId()));
608 finish();
609 }
610 }
611
612 /**
613 * Verify the user input and display error messages.
614 * @return true if the input is valid
615 */
616 private boolean verifyInput()
617 {
618 boolean valid = true;
619 if (mGateway.getText().toString().trim().isEmpty())
620 {
ea15f20a 621 mGatewayWrap.setError(getString(R.string.alert_text_no_input_gateway));
a3e2f127
TB
622 valid = false;
623 }
a6408973 624 if (mVpnType.has(VpnTypeFeature.USER_PASS))
a3e2f127 625 {
825c192d
TB
626 if (mUsername.getText().toString().trim().isEmpty())
627 {
ea15f20a 628 mUsernameWrap.setError(getString(R.string.alert_text_no_input_username));
825c192d
TB
629 valid = false;
630 }
a3e2f127 631 }
a6408973 632 if (mVpnType.has(VpnTypeFeature.CERTIFICATE) && mUserCertEntry == null)
f46da851 633 { /* let's show an error icon */
d4bf6bfb 634 ((TextView)mSelectUserCert.findViewById(android.R.id.text1)).setError("");
f46da851
TB
635 valid = false;
636 }
4db2d633 637 if (!mCheckAuto.isChecked() && mCertEntry == null)
fcb54480
TB
638 {
639 showCertificateAlert();
640 valid = false;
641 }
cf6110f1 642 if (!validateInteger(mMTU, Constants.MTU_MIN, Constants.MTU_MAX))
c6822051 643 {
cf6110f1 644 mMTUWrap.setError(String.format(getString(R.string.alert_text_out_of_range), Constants.MTU_MIN, Constants.MTU_MAX));
c6822051
TB
645 valid = false;
646 }
05c5e894
TB
647 if (!validateSubnets(mIncludedSubnets))
648 {
649 mIncludedSubnetsWrap.setError(getString(R.string.alert_text_no_subnets));
650 valid = false;
651 }
1a63e8e4
TB
652 if (!validateSubnets(mExcludedSubnets))
653 {
654 mExcludedSubnetsWrap.setError(getString(R.string.alert_text_no_subnets));
655 valid = false;
656 }
ef2ad9db 657 if (!validateInteger(mPort, 1, 65535))
6c0ec35c 658 {
ea15f20a 659 mPortWrap.setError(String.format(getString(R.string.alert_text_out_of_range), 1, 65535));
6c0ec35c
TB
660 valid = false;
661 }
a2aa0ca0
TB
662 if (!validateInteger(mNATKeepalive, Constants.NAT_KEEPALIVE_MIN, Constants.NAT_KEEPALIVE_MAX))
663 {
664 mNATKeepaliveWrap.setError(String.format(getString(R.string.alert_text_out_of_range),
665 Constants.NAT_KEEPALIVE_MIN, Constants.NAT_KEEPALIVE_MAX));
666 valid = false;
667 }
9f962f6c
TB
668 if (!validateProposal(mIkeProposal, true))
669 {
670 mIkeProposalWrap.setError(getString(R.string.alert_text_no_proposal));
671 valid = false;
672 }
673 if (!validateProposal(mEspProposal, false))
674 {
675 mEspProposalWrap.setError(getString(R.string.alert_text_no_proposal));
676 valid = false;
677 }
a3e2f127
TB
678 return valid;
679 }
680
681 /**
682 * Update the profile object with the data entered by the user
683 */
684 private void updateProfileData()
685 {
686 /* the name is optional, we default to the gateway if none is given */
687 String name = mName.getText().toString().trim();
688 String gateway = mGateway.getText().toString().trim();
689 mProfile.setName(name.isEmpty() ? gateway : name);
690 mProfile.setGateway(gateway);
825c192d 691 mProfile.setVpnType(mVpnType);
a6408973 692 if (mVpnType.has(VpnTypeFeature.USER_PASS))
825c192d
TB
693 {
694 mProfile.setUsername(mUsername.getText().toString().trim());
695 String password = mPassword.getText().toString().trim();
696 password = password.isEmpty() ? null : password;
697 mProfile.setPassword(password);
698 }
a6408973 699 if (mVpnType.has(VpnTypeFeature.CERTIFICATE))
f46da851
TB
700 {
701 mProfile.setUserCertificateAlias(mUserCertEntry.getAlias());
67fa05aa 702 mProfile.setLocalId(mSelectedUserId);
f46da851 703 }
4db2d633
TB
704 String certAlias = mCheckAuto.isChecked() ? null : mCertEntry.getAlias();
705 mProfile.setCertificateAlias(certAlias);
c5fee223
TB
706 String remote_id = mRemoteId.getText().toString().trim();
707 mProfile.setRemoteId(remote_id.isEmpty() ? null : remote_id);
c6822051 708 mProfile.setMTU(getInteger(mMTU));
6c0ec35c 709 mProfile.setPort(getInteger(mPort));
a2aa0ca0 710 mProfile.setNATKeepAlive(getInteger(mNATKeepalive));
8ae7f8b7
TB
711 int flags = 0;
712 flags |= !mCertReq.isChecked() ? VpnProfile.FLAGS_SUPPRESS_CERT_REQS : 0;
713 mProfile.setFlags(flags);
05c5e894
TB
714 String included = mIncludedSubnets.getText().toString().trim();
715 mProfile.setIncludedSubnets(included.isEmpty() ? null : included);
1a63e8e4
TB
716 String excluded = mExcludedSubnets.getText().toString().trim();
717 mProfile.setExcludedSubnets(excluded.isEmpty() ? null : excluded);
3ee84fa9
TB
718 int st = 0;
719 st |= mBlockIPv4.isChecked() ? VpnProfile.SPLIT_TUNNELING_BLOCK_IPV4 : 0;
720 st |= mBlockIPv6.isChecked() ? VpnProfile.SPLIT_TUNNELING_BLOCK_IPV6 : 0;
721 mProfile.setSplitTunneling(st == 0 ? null : st);
291ef58c
TB
722 mProfile.setSelectedAppsHandling(mSelectedAppsHandling);
723 mProfile.setSelectedApps(mSelectedApps);
2d1f65fe
TB
724 String ike = mIkeProposal.getText().toString().trim();
725 mProfile.setIkeProposal(ike.isEmpty() ? null : ike);
726 String esp = mEspProposal.getText().toString().trim();
727 mProfile.setEspProposal(esp.isEmpty() ? null : esp);
a3e2f127
TB
728 }
729
56a922b2
TB
730 /**
731 * Load an existing profile if we got an ID
4db2d633
TB
732 *
733 * @param savedInstanceState previously saved state
56a922b2 734 */
4db2d633 735 private void loadProfileData(Bundle savedInstanceState)
56a922b2 736 {
67fa05aa 737 String useralias = null, local_id = null, alias = null;
8ae7f8b7 738 Integer flags = null;
4db2d633 739
6b318282 740 getSupportActionBar().setTitle(R.string.add_profile);
7fedacb2 741 if (mId != null && mId != 0)
56a922b2
TB
742 {
743 mProfile = mDataSource.getVpnProfile(mId);
744 if (mProfile != null)
745 {
746 mName.setText(mProfile.getName());
747 mGateway.setText(mProfile.getGateway());
825c192d 748 mVpnType = mProfile.getVpnType();
56a922b2
TB
749 mUsername.setText(mProfile.getUsername());
750 mPassword.setText(mProfile.getPassword());
c5fee223 751 mRemoteId.setText(mProfile.getRemoteId());
c6822051 752 mMTU.setText(mProfile.getMTU() != null ? mProfile.getMTU().toString() : null);
6c0ec35c 753 mPort.setText(mProfile.getPort() != null ? mProfile.getPort().toString() : null);
a2aa0ca0 754 mNATKeepalive.setText(mProfile.getNATKeepAlive() != null ? mProfile.getNATKeepAlive().toString() : null);
05c5e894 755 mIncludedSubnets.setText(mProfile.getIncludedSubnets());
1a63e8e4 756 mExcludedSubnets.setText(mProfile.getExcludedSubnets());
cf6110f1
TB
757 mBlockIPv4.setChecked(mProfile.getSplitTunneling() != null && (mProfile.getSplitTunneling() & VpnProfile.SPLIT_TUNNELING_BLOCK_IPV4) != 0);
758 mBlockIPv6.setChecked(mProfile.getSplitTunneling() != null && (mProfile.getSplitTunneling() & VpnProfile.SPLIT_TUNNELING_BLOCK_IPV6) != 0);
291ef58c
TB
759 mSelectedAppsHandling = mProfile.getSelectedAppsHandling();
760 mSelectedApps = mProfile.getSelectedAppsSet();
2d1f65fe
TB
761 mIkeProposal.setText(mProfile.getIkeProposal());
762 mEspProposal.setText(mProfile.getEspProposal());
71f4a20a 763 mProfileId.setText(mProfile.getUUID().toString());
8ae7f8b7 764 flags = mProfile.getFlags();
f46da851 765 useralias = mProfile.getUserCertificateAlias();
67fa05aa 766 local_id = mProfile.getLocalId();
4db2d633 767 alias = mProfile.getCertificateAlias();
6b318282 768 getSupportActionBar().setTitle(mProfile.getName());
56a922b2
TB
769 }
770 else
771 {
772 Log.e(VpnProfileDetailActivity.class.getSimpleName(),
773 "VPN profile with id " + mId + " not found");
774 finish();
775 }
776 }
4db2d633 777
825c192d 778 mSelectVpnType.setSelection(mVpnType.ordinal());
8ae7f8b7 779 mCertReq.setChecked(flags == null || (flags & VpnProfile.FLAGS_SUPPRESS_CERT_REQS) == 0);
825c192d 780
f46da851 781 /* check if the user selected a user certificate previously */
67fa05aa
TB
782 useralias = savedInstanceState == null ? useralias : savedInstanceState.getString(VpnProfileDataSource.KEY_USER_CERTIFICATE);
783 local_id = savedInstanceState == null ? local_id : savedInstanceState.getString(VpnProfileDataSource.KEY_LOCAL_ID);
f46da851
TB
784 if (useralias != null)
785 {
786 UserCertificateLoader loader = new UserCertificateLoader(this, useralias);
787 mUserCertLoading = useralias;
67fa05aa 788 mSelectedUserId = local_id;
f46da851
TB
789 loader.execute();
790 }
791
792 /* check if the user selected a CA certificate previously */
4db2d633
TB
793 alias = savedInstanceState == null ? alias : savedInstanceState.getString(VpnProfileDataSource.KEY_CERTIFICATE);
794 mCheckAuto.setChecked(alias == null);
795 if (alias != null)
796 {
797 X509Certificate certificate = TrustedCertificateManager.getInstance().getCACertificateFromAlias(alias);
798 if (certificate != null)
799 {
800 mCertEntry = new TrustedCertificateEntry(alias, certificate);
801 }
802 else
803 { /* previously selected certificate is not here anymore */
804 showCertificateAlert();
805 mCertEntry = null;
806 }
807 }
291ef58c
TB
808
809 mSelectSelectedAppsHandling.setSelection(mSelectedAppsHandling.ordinal());
810 if (savedInstanceState != null)
811 {
812 ArrayList<String> selectedApps = savedInstanceState.getStringArrayList(VpnProfileDataSource.KEY_SELECTED_APPS_LIST);
813 mSelectedApps = new TreeSet<>(selectedApps);
814 }
56a922b2 815 }
f46da851 816
c6822051
TB
817 /**
818 * Get the integer value in the given text box or null if empty
819 *
820 * @param view text box (numeric entry assumed)
821 */
822 private Integer getInteger(EditText view)
823 {
824 String value = view.getText().toString().trim();
6294f28b
TB
825 try
826 {
827 return value.isEmpty() ? null : Integer.valueOf(value);
828 }
829 catch (NumberFormatException e)
830 {
831 return null;
832 }
c6822051
TB
833 }
834
ef2ad9db
TB
835 /**
836 * Check that the value in the given text box is a valid integer in the given range
837 *
838 * @param view text box (numeric entry assumed)
839 * @param min minimum value (inclusive)
840 * @param max maximum value (inclusive)
841 */
842 private boolean validateInteger(EditText view, Integer min, Integer max)
843 {
844 String value = view.getText().toString().trim();
845 try
846 {
847 if (value.isEmpty())
848 {
849 return true;
850 }
851 Integer val = Integer.valueOf(value);
852 return min <= val && val <= max;
853 }
854 catch (NumberFormatException e)
855 {
856 return false;
857 }
858 }
859
1a63e8e4
TB
860 /**
861 * Check that the value in the given text box is a valid list of subnets/ranges
862 *
863 * @param view text box
864 */
865 private boolean validateSubnets(EditText view)
866 {
867 String value = view.getText().toString().trim();
868 return value.isEmpty() || IPRangeSet.fromString(value) != null;
869 }
870
9f962f6c
TB
871 /**
872 * Check that the value in the given text box is a valid proposal
873 *
874 * @param view text box
875 */
876 private boolean validateProposal(EditText view, boolean ike)
877 {
878 String value = view.getText().toString().trim();
879 return value.isEmpty() || Utils.isProposalValid(ike, value);
880 }
881
f46da851
TB
882 private class SelectUserCertOnClickListener implements OnClickListener, KeyChainAliasCallback
883 {
884 @Override
885 public void onClick(View v)
886 {
887 String useralias = mUserCertEntry != null ? mUserCertEntry.getAlias() : null;
888 KeyChain.choosePrivateKeyAlias(VpnProfileDetailActivity.this, this, new String[] { "RSA" }, null, null, -1, useralias);
889 }
890
891 @Override
892 public void alias(final String alias)
893 {
894 if (alias != null)
895 { /* otherwise the dialog was canceled, the request denied */
896 try
897 {
898 final X509Certificate[] chain = KeyChain.getCertificateChain(VpnProfileDetailActivity.this, alias);
899 /* alias() is not called from our main thread */
900 runOnUiThread(new Runnable() {
901 @Override
902 public void run()
903 {
904 if (chain != null && chain.length > 0)
905 {
906 mUserCertEntry = new TrustedCertificateEntry(alias, chain[0]);
907 }
908 updateCredentialView();
909 }
910 });
911 }
cf6110f1 912 catch (KeyChainException | InterruptedException e)
f46da851
TB
913 {
914 e.printStackTrace();
915 }
916 }
917 }
918 }
919
920 /**
921 * Load the selected user certificate asynchronously. This cannot be done
922 * from the main thread as getCertificateChain() calls back to our main
923 * thread to bind to the KeyChain service resulting in a deadlock.
924 */
925 private class UserCertificateLoader extends AsyncTask<Void, Void, X509Certificate>
926 {
927 private final Context mContext;
928 private final String mAlias;
929
930 public UserCertificateLoader(Context context, String alias)
931 {
932 mContext = context;
933 mAlias = alias;
934 }
935
936 @Override
937 protected X509Certificate doInBackground(Void... params)
938 {
939 X509Certificate[] chain = null;
940 try
941 {
942 chain = KeyChain.getCertificateChain(mContext, mAlias);
943 }
cf6110f1 944 catch (KeyChainException | InterruptedException e)
f46da851
TB
945 {
946 e.printStackTrace();
947 }
948 if (chain != null && chain.length > 0)
949 {
950 return chain[0];
951 }
952 return null;
953 }
954
955 @Override
956 protected void onPostExecute(X509Certificate result)
957 {
958 if (result != null)
959 {
960 mUserCertEntry = new TrustedCertificateEntry(mAlias, result);
961 }
962 else
963 { /* previously selected certificate is not here anymore */
d4bf6bfb 964 ((TextView)mSelectUserCert.findViewById(android.R.id.text1)).setError("");
f46da851
TB
965 mUserCertEntry = null;
966 }
967 mUserCertLoading = null;
968 updateCredentialView();
969 }
970 }
e1a98e79
TB
971
972 /**
973 * Dialog with notification message if EAP-TNC is used.
974 */
77c1c28d 975 public static class TncNoticeDialog extends AppCompatDialogFragment
e1a98e79
TB
976 {
977 @Override
978 public Dialog onCreateDialog(Bundle savedInstanceState)
979 {
980 return new AlertDialog.Builder(getActivity())
981 .setTitle(R.string.tnc_notice_title)
982 .setMessage(Html.fromHtml(getString(R.string.tnc_notice_details)))
983 .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
984 @Override
985 public void onClick(DialogInterface dialog, int id)
986 {
987 dialog.dismiss();
988 }
989 }).create();
990 }
991 }
e7a12cc8
TB
992
993 /**
994 * Tokenizer implementation that separates by white-space
995 */
996 public static class SpaceTokenizer implements MultiAutoCompleteTextView.Tokenizer
997 {
998 @Override
999 public int findTokenStart(CharSequence text, int cursor)
1000 {
1001 int i = cursor;
1002
1003 while (i > 0 && !Character.isWhitespace(text.charAt(i - 1)))
1004 {
1005 i--;
1006 }
1007 return i;
1008 }
1009
1010 @Override
1011 public int findTokenEnd(CharSequence text, int cursor)
1012 {
1013 int i = cursor;
1014 int len = text.length();
1015
1016 while (i < len)
1017 {
1018 if (Character.isWhitespace(text.charAt(i)))
1019 {
1020 return i;
1021 }
1022 else
1023 {
1024 i++;
1025 }
1026 }
1027 return len;
1028 }
1029
1030 @Override
1031 public CharSequence terminateToken(CharSequence text)
1032 {
1033 int i = text.length();
1034
1035 if (i > 0 && Character.isWhitespace(text.charAt(i - 1)))
1036 {
1037 return text;
1038 }
1039 else
1040 {
1041 if (text instanceof Spanned)
1042 {
1043 SpannableString sp = new SpannableString(text + " ");
1044 TextUtils.copySpansFrom((Spanned) text, 0, text.length(), Object.class, sp, 0);
1045 return sp;
1046 }
1047 else
1048 {
1049 return text + " ";
1050 }
1051 }
1052 }
1053 }
56a922b2 1054}