diff --git a/src/frontends/android/README.ndk b/src/frontends/android/README.ndk
index 7c8cd309e..22150dd55 100644
--- a/src/frontends/android/README.ndk
+++ b/src/frontends/android/README.ndk
@@ -1,13 +1,14 @@
-To build this within the NDK several things have to be added in the
-app/src/main/jni/ folder:
+To build this within the NDK the following things have to be done:
- - strongswan: The strongSwan sources. This can either be an extracted tarball,
- or a symlink to the Git repository. To build from the repository the sources
- have to be prepared first (see HACKING for a list of required tools):
+ - By default the strongSwan sources of the current Git tree are used. They have
+ to be prepared first (see HACKING for a list of required tools):
./autogen.sh && ./configure && make && make distclean
- - openssl: The OpenSSL sources. Since the sources need to be changed to be
- built on Android (and especially in the NDK), we provide a modified mirror
- of the official Android OpenSSL version on git.strongswan.org.
+ It is also possible to use the sources from a different directory (e.g. an
+ extracted tarball) by setting strongswan_DIR in app/src/main/jni/Android.mk.
+ - The OpenSSL or BoringSSL sources are expected in app/src/main/jni/openssl.
+ Since the sources need to be changed to be built on Android (and especially
+ in the NDK) we provide a modified mirror of the official Android repositories
+ on git.strongswan.org.
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfile.java b/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfile.java
index 5c64ad0e5..8a9d319b5 100644
--- a/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfile.java
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfile.java
@@ -1,8 +1,8 @@
/*
- * Copyright (C) 2012-2015 Tobias Brunner
+ * Copyright (C) 2012-2016 Tobias Brunner
* Copyright (C) 2012 Giuliano Grassi
* Copyright (C) 2012 Ralf Sager
- * Hochschule fuer Technik Rapperswil
+ * HSR Hochschule fuer Technik Rapperswil
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
@@ -25,6 +25,7 @@ public class VpnProfile implements Cloneable
public static final int SPLIT_TUNNELING_BLOCK_IPV6 = 2;
private String mName, mGateway, mUsername, mPassword, mCertificate, mUserCertificate;
+ private String mRemoteId, mLocalId;
private Integer mMTU, mPort, mSplitTunneling;
private VpnType mVpnType;
private long mId = -1;
@@ -109,6 +110,26 @@ public class VpnProfile implements Cloneable
this.mUserCertificate = alias;
}
+ public String getLocalId()
+ {
+ return mLocalId;
+ }
+
+ public void setLocalId(String localId)
+ {
+ this.mLocalId = localId;
+ }
+
+ public String getRemoteId()
+ {
+ return mRemoteId;
+ }
+
+ public void setRemoteId(String remoteId)
+ {
+ this.mRemoteId = remoteId;
+ }
+
public Integer getMTU()
{
return mMTU;
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfileDataSource.java b/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfileDataSource.java
index 45e9b8650..17026536b 100644
--- a/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfileDataSource.java
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfileDataSource.java
@@ -1,8 +1,8 @@
/*
- * Copyright (C) 2012-2015 Tobias Brunner
+ * Copyright (C) 2012-2016 Tobias Brunner
* Copyright (C) 2012 Giuliano Grassi
* Copyright (C) 2012 Ralf Sager
- * Hochschule fuer Technik Rapperswil
+ * HSR Hochschule fuer Technik Rapperswil
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
@@ -43,6 +43,8 @@ public class VpnProfileDataSource
public static final String KEY_MTU = "mtu";
public static final String KEY_PORT = "port";
public static final String KEY_SPLIT_TUNNELING = "split_tunneling";
+ public static final String KEY_LOCAL_ID = "local_id";
+ public static final String KEY_REMOTE_ID = "remote_id";
private DatabaseHelper mDbHelper;
private SQLiteDatabase mDatabase;
@@ -51,7 +53,7 @@ public class VpnProfileDataSource
private static final String DATABASE_NAME = "strongswan.db";
private static final String TABLE_VPNPROFILE = "vpnprofile";
- private static final int DATABASE_VERSION = 7;
+ private static final int DATABASE_VERSION = 8;
public static final String DATABASE_CREATE =
"CREATE TABLE " + TABLE_VPNPROFILE + " (" +
@@ -65,7 +67,9 @@ public class VpnProfileDataSource
KEY_USER_CERTIFICATE + " TEXT," +
KEY_MTU + " INTEGER," +
KEY_PORT + " INTEGER," +
- KEY_SPLIT_TUNNELING + " INTEGER" +
+ KEY_SPLIT_TUNNELING + " INTEGER," +
+ KEY_LOCAL_ID + " TEXT," +
+ KEY_REMOTE_ID + " TEXT" +
");";
private static final String[] ALL_COLUMNS = new String[] {
KEY_ID,
@@ -79,6 +83,8 @@ public class VpnProfileDataSource
KEY_MTU,
KEY_PORT,
KEY_SPLIT_TUNNELING,
+ KEY_LOCAL_ID,
+ KEY_REMOTE_ID,
};
private static class DatabaseHelper extends SQLiteOpenHelper
@@ -128,6 +134,13 @@ public class VpnProfileDataSource
db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_SPLIT_TUNNELING +
" INTEGER;");
}
+ if (oldVersion < 8)
+ {
+ db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_LOCAL_ID +
+ " TEXT;");
+ db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_REMOTE_ID +
+ " TEXT;");
+ }
}
private void updateColumns(SQLiteDatabase db)
@@ -282,6 +295,8 @@ public class VpnProfileDataSource
profile.setMTU(getInt(cursor, cursor.getColumnIndex(KEY_MTU)));
profile.setPort(getInt(cursor, cursor.getColumnIndex(KEY_PORT)));
profile.setSplitTunneling(getInt(cursor, cursor.getColumnIndex(KEY_SPLIT_TUNNELING)));
+ profile.setLocalId(cursor.getString(cursor.getColumnIndex(KEY_LOCAL_ID)));
+ profile.setRemoteId(cursor.getString(cursor.getColumnIndex(KEY_REMOTE_ID)));
return profile;
}
@@ -298,6 +313,8 @@ public class VpnProfileDataSource
values.put(KEY_MTU, profile.getMTU());
values.put(KEY_PORT, profile.getPort());
values.put(KEY_SPLIT_TUNNELING, profile.getSplitTunneling());
+ values.put(KEY_LOCAL_ID, profile.getLocalId());
+ values.put(KEY_REMOTE_ID, profile.getRemoteId());
return values;
}
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/logic/CharonVpnService.java b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/CharonVpnService.java
index 84c44c123..a6b9fc52d 100644
--- a/src/frontends/android/app/src/main/java/org/strongswan/android/logic/CharonVpnService.java
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/CharonVpnService.java
@@ -1,8 +1,8 @@
/*
- * Copyright (C) 2012-2015 Tobias Brunner
+ * Copyright (C) 2012-2016 Tobias Brunner
* Copyright (C) 2012 Giuliano Grassi
* Copyright (C) 2012 Ralf Sager
- * Hochschule fuer Technik Rapperswil
+ * HSR Hochschule fuer Technik Rapperswil
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
@@ -232,6 +232,8 @@ public class CharonVpnService extends VpnService implements Runnable
writer.setValue("connection.port", mCurrentProfile.getPort());
writer.setValue("connection.username", mCurrentProfile.getUsername());
writer.setValue("connection.password", mCurrentProfile.getPassword());
+ writer.setValue("connection.local_id", mCurrentProfile.getLocalId());
+ writer.setValue("connection.remote_id", mCurrentProfile.getRemoteId());
initiate(writer.serialize());
}
else
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/security/TrustedCertificateEntry.java b/src/frontends/android/app/src/main/java/org/strongswan/android/security/TrustedCertificateEntry.java
index 143741faf..5e9873d1b 100644
--- a/src/frontends/android/app/src/main/java/org/strongswan/android/security/TrustedCertificateEntry.java
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/security/TrustedCertificateEntry.java
@@ -1,6 +1,6 @@
/*
- * Copyright (C) 2012 Tobias Brunner
- * Hochschule fuer Technik Rapperswil
+ * Copyright (C) 2012-2016 Tobias Brunner
+ * HSR Hochschule fuer Technik Rapperswil
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
@@ -15,10 +15,15 @@
package org.strongswan.android.security;
-import java.security.cert.X509Certificate;
-
import android.net.http.SslCertificate;
+import java.security.cert.CertificateParsingException;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
public class TrustedCertificateEntry implements Comparable
{
private final X509Certificate mCert;
@@ -86,6 +91,40 @@ public class TrustedCertificateEntry implements Comparable getSubjectAltNames()
+ {
+ List list = new ArrayList<>();
+ try
+ {
+ Collection> sans = mCert.getSubjectAlternativeNames();
+ if (sans != null)
+ {
+ for (List> san : sans)
+ {
+ switch ((Integer)san.get(0))
+ {
+ case 1: /* rfc822Name */
+ case 2: /* dnSName */
+ case 7: /* iPAddress */
+ list.add((String)san.get(1));
+ break;
+ }
+ }
+ }
+ Collections.sort(list);
+ }
+ catch(CertificateParsingException ex)
+ {
+ ex.printStackTrace();
+ }
+ return list;
+ }
+
/**
* The alias associated with this certificate.
*
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/ImcStateFragment.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/ImcStateFragment.java
index 7328693f1..ab8dfc2de 100644
--- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/ImcStateFragment.java
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/ImcStateFragment.java
@@ -15,9 +15,6 @@
package org.strongswan.android.ui;
-import android.app.Fragment;
-import android.app.FragmentManager;
-import android.app.FragmentTransaction;
import android.app.Service;
import android.content.ComponentName;
import android.content.Context;
@@ -25,6 +22,9 @@ import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentManager;
+import android.support.v4.app.FragmentTransaction;
import android.support.v4.content.ContextCompat;
import android.view.GestureDetector;
import android.view.LayoutInflater;
@@ -66,7 +66,6 @@ public class ImcStateFragment extends Fragment implements VpnStateListener
{
mService = ((VpnStateService.LocalBinder)service).getService();
mService.registerListener(ImcStateFragment.this);
- updateView();
}
};
@@ -147,9 +146,9 @@ public class ImcStateFragment extends Fragment implements VpnStateListener
}
@Override
- public void onStart()
+ public void onResume()
{
- super.onStart();
+ super.onResume();
if (mService != null)
{
mService.registerListener(this);
@@ -158,9 +157,9 @@ public class ImcStateFragment extends Fragment implements VpnStateListener
}
@Override
- public void onStop()
+ public void onPause()
{
- super.onStop();
+ super.onPause();
if (mService != null)
{
mService.unregisterListener(this);
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/LogFragment.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/LogFragment.java
index 8740e0c46..2f6240726 100644
--- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/LogFragment.java
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/LogFragment.java
@@ -15,24 +15,24 @@
package org.strongswan.android.ui;
+import android.os.Bundle;
+import android.os.FileObserver;
+import android.os.Handler;
+import android.support.v4.app.Fragment;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import org.strongswan.android.R;
+import org.strongswan.android.logic.CharonVpnService;
+
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.StringReader;
-import org.strongswan.android.R;
-import org.strongswan.android.logic.CharonVpnService;
-
-import android.app.Fragment;
-import android.os.Bundle;
-import android.os.FileObserver;
-import android.os.Handler;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.TextView;
-
public class LogFragment extends Fragment implements Runnable
{
private String mLogFilePath;
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/MainActivity.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/MainActivity.java
index 3d5324056..890434255 100644
--- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/MainActivity.java
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/MainActivity.java
@@ -18,9 +18,6 @@
package org.strongswan.android.ui;
import android.app.Dialog;
-import android.app.Fragment;
-import android.app.FragmentManager;
-import android.app.FragmentTransaction;
import android.app.Service;
import android.content.ActivityNotFoundException;
import android.content.ComponentName;
@@ -31,6 +28,9 @@ import android.net.VpnService;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.IBinder;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentManager;
+import android.support.v4.app.FragmentTransaction;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
@@ -230,7 +230,7 @@ public class MainActivity extends AppCompatActivity implements OnVpnProfileSelec
removeFragmentByTag(DIALOG_TAG);
- if (mService != null && mService.getState() == State.CONNECTED)
+ if (mService != null && (mService.getState() == State.CONNECTED || mService.getState() == State.CONNECTING))
{
profileInfo.putBoolean(PROFILE_RECONNECT, mService.getProfile().getId() == profile.getId());
@@ -317,7 +317,7 @@ public class MainActivity extends AppCompatActivity implements OnVpnProfileSelec
*/
public void removeFragmentByTag(String tag)
{
- FragmentManager fm = getFragmentManager();
+ FragmentManager fm = getSupportFragmentManager();
Fragment login = fm.findFragmentByTag(tag);
if (login != null)
{
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/RemediationInstructionFragment.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/RemediationInstructionFragment.java
index 04c288bcf..c17c552a2 100644
--- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/RemediationInstructionFragment.java
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/RemediationInstructionFragment.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2013 Tobias Brunner
+ * Copyright (C) 2013-2016 Tobias Brunner
* Hochschule fuer Technik Rapperswil
*
* This program is free software; you can redistribute it and/or modify it
@@ -15,17 +15,18 @@
package org.strongswan.android.ui;
-import org.strongswan.android.R;
-import org.strongswan.android.logic.imc.RemediationInstruction;
-
-import android.app.ListFragment;
import android.os.Bundle;
+import android.support.v4.app.ListFragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
+import android.widget.FrameLayout;
import android.widget.TextView;
+import org.strongswan.android.R;
+import org.strongswan.android.logic.imc.RemediationInstruction;
+
public class RemediationInstructionFragment extends ListFragment
{
public static final String ARG_REMEDIATION_INSTRUCTION = "instruction";
@@ -37,7 +38,13 @@ public class RemediationInstructionFragment extends ListFragment
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
- return inflater.inflate(R.layout.remediation_instruction, container, false);
+ /* while the documentation recommends to include "@android:layout/list_content" to retain
+ * the default functionality, this does not actually work with the ListFragment provided by
+ * the support library as it builds the view manually and uses different IDs */
+ View layout = inflater.inflate(R.layout.remediation_instruction, container, false);
+ FrameLayout list = (FrameLayout)layout.findViewById(R.id.list_container);
+ list.addView(super.onCreateView(inflater, list, savedInstanceState));
+ return layout;
}
@Override
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/RemediationInstructionsActivity.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/RemediationInstructionsActivity.java
index 7e7e870be..7765a2e02 100644
--- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/RemediationInstructionsActivity.java
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/RemediationInstructionsActivity.java
@@ -38,7 +38,7 @@ public class RemediationInstructionsActivity extends AppCompatActivity implement
{ /* only update if we're not restoring */
return;
}
- RemediationInstructionsFragment frag = (RemediationInstructionsFragment)getFragmentManager().findFragmentById(R.id.remediation_instructions_fragment);
+ RemediationInstructionsFragment frag = (RemediationInstructionsFragment)getSupportFragmentManager().findFragmentById(R.id.remediation_instructions_fragment);
if (frag != null)
{ /* two-pane layout, update fragment */
Bundle extras = getIntent().getExtras();
@@ -49,7 +49,7 @@ public class RemediationInstructionsActivity extends AppCompatActivity implement
{ /* one-pane layout, create fragment */
frag = new RemediationInstructionsFragment();
frag.setArguments(getIntent().getExtras());
- getFragmentManager().beginTransaction().add(R.id.fragment_container, frag).commit();
+ getSupportFragmentManager().beginTransaction().add(R.id.fragment_container, frag).commit();
}
}
@@ -60,7 +60,7 @@ public class RemediationInstructionsActivity extends AppCompatActivity implement
{
case android.R.id.home:
/* one-pane layout, pop possible fragment from stack, finish otherwise */
- if (!getFragmentManager().popBackStackImmediate())
+ if (!getSupportFragmentManager().popBackStackImmediate())
{
finish();
}
@@ -74,7 +74,7 @@ public class RemediationInstructionsActivity extends AppCompatActivity implement
@Override
public void onRemediationInstructionSelected(RemediationInstruction instruction)
{
- RemediationInstructionFragment frag = (RemediationInstructionFragment)getFragmentManager().findFragmentById(R.id.remediation_instruction_fragment);
+ RemediationInstructionFragment frag = (RemediationInstructionFragment)getSupportFragmentManager().findFragmentById(R.id.remediation_instruction_fragment);
if (frag != null)
{ /* two-pane layout, update directly */
@@ -87,7 +87,7 @@ public class RemediationInstructionsActivity extends AppCompatActivity implement
args.putParcelable(RemediationInstructionFragment.ARG_REMEDIATION_INSTRUCTION, instruction);
frag.setArguments(args);
- getFragmentManager().beginTransaction().replace(R.id.fragment_container, frag).addToBackStack(null).commit();
+ getSupportFragmentManager().beginTransaction().replace(R.id.fragment_container, frag).addToBackStack(null).commit();
getSupportActionBar().setTitle(instruction.getTitle());
}
}
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/RemediationInstructionsFragment.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/RemediationInstructionsFragment.java
index 86467dc35..a2beffd94 100644
--- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/RemediationInstructionsFragment.java
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/RemediationInstructionsFragment.java
@@ -15,17 +15,17 @@
package org.strongswan.android.ui;
-import java.util.ArrayList;
+import android.content.Context;
+import android.os.Bundle;
+import android.support.v4.app.ListFragment;
+import android.view.View;
+import android.widget.ListView;
import org.strongswan.android.R;
import org.strongswan.android.logic.imc.RemediationInstruction;
import org.strongswan.android.ui.adapter.RemediationInstructionAdapter;
-import android.app.Activity;
-import android.app.ListFragment;
-import android.os.Bundle;
-import android.view.View;
-import android.widget.ListView;
+import java.util.ArrayList;
public class RemediationInstructionsFragment extends ListFragment
{
@@ -65,13 +65,13 @@ public class RemediationInstructionsFragment extends ListFragment
}
@Override
- public void onAttach(Activity activity)
+ public void onAttach(Context context)
{
- super.onAttach(activity);
+ super.onAttach(context);
- if (activity instanceof OnRemediationInstructionSelectedListener)
+ if (context instanceof OnRemediationInstructionSelectedListener)
{
- mListener = (OnRemediationInstructionSelectedListener)activity;
+ mListener = (OnRemediationInstructionSelectedListener)context;
}
}
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileDetailActivity.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileDetailActivity.java
index bcc226b1f..29124ced7 100644
--- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileDetailActivity.java
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileDetailActivity.java
@@ -1,8 +1,8 @@
/*
- * Copyright (C) 2012-2014 Tobias Brunner
+ * Copyright (C) 2012-2016 Tobias Brunner
* Copyright (C) 2012 Giuliano Grassi
* Copyright (C) 2012 Ralf Sager
- * Hochschule fuer Technik Rapperswil
+ * HSR Hochschule fuer Technik Rapperswil
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
@@ -29,7 +29,12 @@ import android.security.KeyChainException;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.app.AppCompatDialogFragment;
+import android.text.Editable;
import android.text.Html;
+import android.text.SpannableString;
+import android.text.Spanned;
+import android.text.TextUtils;
+import android.text.TextWatcher;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
@@ -39,10 +44,12 @@ import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemSelectedListener;
+import android.widget.ArrayAdapter;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.EditText;
+import android.widget.MultiAutoCompleteTextView;
import android.widget.RelativeLayout;
import android.widget.Spinner;
import android.widget.TextView;
@@ -54,6 +61,8 @@ import org.strongswan.android.data.VpnType;
import org.strongswan.android.data.VpnType.VpnTypeFeature;
import org.strongswan.android.logic.TrustedCertificateManager;
import org.strongswan.android.security.TrustedCertificateEntry;
+import org.strongswan.android.ui.adapter.CertificateIdentitiesAdapter;
+import org.strongswan.android.ui.widget.TextInputLayoutHelper;
import java.security.cert.X509Certificate;
@@ -67,24 +76,34 @@ public class VpnProfileDetailActivity extends AppCompatActivity
private Long mId;
private TrustedCertificateEntry mCertEntry;
private String mUserCertLoading;
+ private CertificateIdentitiesAdapter mSelectUserIdAdapter;
+ private String mSelectedUserId;
private TrustedCertificateEntry mUserCertEntry;
private VpnType mVpnType = VpnType.IKEV2_EAP;
private VpnProfile mProfile;
- private EditText mName;
+ private MultiAutoCompleteTextView mName;
+ private TextInputLayoutHelper mNameWrap;
private EditText mGateway;
+ private TextInputLayoutHelper mGatewayWrap;
private Spinner mSelectVpnType;
private ViewGroup mUsernamePassword;
private EditText mUsername;
+ private TextInputLayoutHelper mUsernameWrap;
private EditText mPassword;
private ViewGroup mUserCertificate;
private RelativeLayout mSelectUserCert;
+ private Spinner mSelectUserId;
private CheckBox mCheckAuto;
private RelativeLayout mSelectCert;
private RelativeLayout mTncNotice;
private CheckBox mShowAdvanced;
private ViewGroup mAdvancedSettings;
+ private MultiAutoCompleteTextView mRemoteId;
+ private TextInputLayoutHelper mRemoteIdWrap;
private EditText mMTU;
+ private TextInputLayoutHelper mMTUWrap;
private EditText mPort;
+ private TextInputLayoutHelper mPortWrap;
private CheckBox mBlockIPv4;
private CheckBox mBlockIPv6;
@@ -101,17 +120,21 @@ public class VpnProfileDetailActivity extends AppCompatActivity
setContentView(R.layout.profile_detail_view);
- mName = (EditText)findViewById(R.id.name);
+ mName = (MultiAutoCompleteTextView)findViewById(R.id.name);
+ mNameWrap = (TextInputLayoutHelper)findViewById(R.id.name_wrap);
mGateway = (EditText)findViewById(R.id.gateway);
+ mGatewayWrap = (TextInputLayoutHelper) findViewById(R.id.gateway_wrap);
mSelectVpnType = (Spinner)findViewById(R.id.vpn_type);
mTncNotice = (RelativeLayout)findViewById(R.id.tnc_notice);
mUsernamePassword = (ViewGroup)findViewById(R.id.username_password_group);
mUsername = (EditText)findViewById(R.id.username);
+ mUsernameWrap = (TextInputLayoutHelper) findViewById(R.id.username_wrap);
mPassword = (EditText)findViewById(R.id.password);
mUserCertificate = (ViewGroup)findViewById(R.id.user_certificate_group);
mSelectUserCert = (RelativeLayout)findViewById(R.id.select_user_certificate);
+ mSelectUserId = (Spinner)findViewById(R.id.select_user_id);
mCheckAuto = (CheckBox)findViewById(R.id.ca_auto);
mSelectCert = (RelativeLayout)findViewById(R.id.select_certificate);
@@ -119,11 +142,47 @@ public class VpnProfileDetailActivity extends AppCompatActivity
mShowAdvanced = (CheckBox)findViewById(R.id.show_advanced);
mAdvancedSettings = (ViewGroup)findViewById(R.id.advanced_settings);
+ mRemoteId = (MultiAutoCompleteTextView)findViewById(R.id.remote_id);
+ mRemoteIdWrap = (TextInputLayoutHelper) findViewById(R.id.remote_id_wrap);
mMTU = (EditText)findViewById(R.id.mtu);
+ mMTUWrap = (TextInputLayoutHelper) findViewById(R.id.mtu_wrap);
mPort = (EditText)findViewById(R.id.port);
+ mPortWrap = (TextInputLayoutHelper) findViewById(R.id.port_wrap);
mBlockIPv4 = (CheckBox)findViewById(R.id.split_tunneling_v4);
mBlockIPv6 = (CheckBox)findViewById(R.id.split_tunneling_v6);
+ final SpaceTokenizer spaceTokenizer = new SpaceTokenizer();
+ mName.setTokenizer(spaceTokenizer);
+ mRemoteId.setTokenizer(spaceTokenizer);
+ final ArrayAdapter completeAdapter = new ArrayAdapter<>(this, android.R.layout.simple_dropdown_item_1line);
+ mName.setAdapter(completeAdapter);
+ mRemoteId.setAdapter(completeAdapter);
+
+ mGateway.addTextChangedListener(new TextWatcher() {
+ @Override
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
+
+ @Override
+ public void onTextChanged(CharSequence s, int start, int before, int count) {}
+
+ @Override
+ public void afterTextChanged(Editable s)
+ {
+ completeAdapter.clear();
+ completeAdapter.add(mGateway.getText().toString());
+ if (TextUtils.isEmpty(mGateway.getText()))
+ {
+ mNameWrap.setHelperText(getString(R.string.profile_name_hint));
+ mRemoteIdWrap.setHelperText(getString(R.string.profile_remote_id_hint));
+ }
+ else
+ {
+ mNameWrap.setHelperText(String.format(getString(R.string.profile_name_hint_gateway), mGateway.getText()));
+ mRemoteIdWrap.setHelperText(String.format(getString(R.string.profile_remote_id_hint_gateway), mGateway.getText()));
+ }
+ }
+ });
+
mSelectVpnType.setOnItemSelectedListener(new OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView> parent, View view, int position, long id)
@@ -151,6 +210,24 @@ public class VpnProfileDetailActivity extends AppCompatActivity
});
mSelectUserCert.setOnClickListener(new SelectUserCertOnClickListener());
+ mSelectUserIdAdapter = new CertificateIdentitiesAdapter(this);
+ mSelectUserId.setAdapter(mSelectUserIdAdapter);
+ mSelectUserId.setOnItemSelectedListener(new OnItemSelectedListener() {
+ @Override
+ public void onItemSelected(AdapterView> parent, View view, int position, long id)
+ {
+ if (mUserCertEntry != null)
+ { /* we don't store the subject DN as it is in the reverse order and the default anyway */
+ mSelectedUserId = position == 0 ? null : mSelectUserIdAdapter.getItem(position);
+ }
+ }
+
+ @Override
+ public void onNothingSelected(AdapterView> parent)
+ {
+ mSelectedUserId = null;
+ }
+ });
mCheckAuto.setOnCheckedChangeListener(new OnCheckedChangeListener() {
@Override
@@ -211,6 +288,10 @@ public class VpnProfileDetailActivity extends AppCompatActivity
{
outState.putString(VpnProfileDataSource.KEY_USER_CERTIFICATE, mUserCertEntry.getAlias());
}
+ if (mSelectedUserId != null)
+ {
+ outState.putString(VpnProfileDataSource.KEY_LOCAL_ID, mSelectedUserId);
+ }
if (mCertEntry != null)
{
outState.putString(VpnProfileDataSource.KEY_CERTIFICATE, mCertEntry.getAlias());
@@ -272,6 +353,7 @@ public class VpnProfileDetailActivity extends AppCompatActivity
if (mVpnType.has(VpnTypeFeature.CERTIFICATE))
{
+ mSelectUserId.setEnabled(false);
if (mUserCertLoading != null)
{
((TextView)mSelectUserCert.findViewById(android.R.id.text1)).setText(mUserCertLoading);
@@ -282,11 +364,15 @@ public class VpnProfileDetailActivity extends AppCompatActivity
((TextView)mSelectUserCert.findViewById(android.R.id.text1)).setError(null);
((TextView)mSelectUserCert.findViewById(android.R.id.text1)).setText(mUserCertEntry.getAlias());
((TextView)mSelectUserCert.findViewById(android.R.id.text2)).setText(mUserCertEntry.getCertificate().getSubjectDN().toString());
+ mSelectUserIdAdapter.setCertificate(mUserCertEntry);
+ mSelectUserId.setSelection(mSelectUserIdAdapter.getPosition(mSelectedUserId));
+ mSelectUserId.setEnabled(true);
}
else
{
((TextView)mSelectUserCert.findViewById(android.R.id.text1)).setText(R.string.profile_user_select_certificate_label);
((TextView)mSelectUserCert.findViewById(android.R.id.text2)).setText(R.string.profile_user_select_certificate);
+ mSelectUserIdAdapter.setCertificate(null);
}
}
}
@@ -349,7 +435,8 @@ public class VpnProfileDetailActivity extends AppCompatActivity
if (!show && mProfile != null)
{
Integer st = mProfile.getSplitTunneling();
- show = mProfile.getMTU() != null || mProfile.getPort() != null || (st != null && st != 0);
+ show = mProfile.getRemoteId() != null || mProfile.getMTU() != null ||
+ mProfile.getPort() != null || (st != null && st != 0);
}
mShowAdvanced.setVisibility(!show ? View.VISIBLE : View.GONE);
mAdvancedSettings.setVisibility(show ? View.VISIBLE : View.GONE);
@@ -388,14 +475,14 @@ public class VpnProfileDetailActivity extends AppCompatActivity
boolean valid = true;
if (mGateway.getText().toString().trim().isEmpty())
{
- mGateway.setError(getString(R.string.alert_text_no_input_gateway));
+ mGatewayWrap.setError(getString(R.string.alert_text_no_input_gateway));
valid = false;
}
if (mVpnType.has(VpnTypeFeature.USER_PASS))
{
if (mUsername.getText().toString().trim().isEmpty())
{
- mUsername.setError(getString(R.string.alert_text_no_input_username));
+ mUsernameWrap.setError(getString(R.string.alert_text_no_input_username));
valid = false;
}
}
@@ -412,13 +499,13 @@ public class VpnProfileDetailActivity extends AppCompatActivity
Integer mtu = getInteger(mMTU);
if (mtu != null && (mtu < MTU_MIN || mtu > MTU_MAX))
{
- mMTU.setError(String.format(getString(R.string.alert_text_out_of_range), MTU_MIN, MTU_MAX));
+ mMTUWrap.setError(String.format(getString(R.string.alert_text_out_of_range), MTU_MIN, MTU_MAX));
valid = false;
}
Integer port = getInteger(mPort);
if (port != null && (port < 1 || port > 65535))
{
- mPort.setError(String.format(getString(R.string.alert_text_out_of_range), 1, 65535));
+ mPortWrap.setError(String.format(getString(R.string.alert_text_out_of_range), 1, 65535));
valid = false;
}
return valid;
@@ -445,9 +532,12 @@ public class VpnProfileDetailActivity extends AppCompatActivity
if (mVpnType.has(VpnTypeFeature.CERTIFICATE))
{
mProfile.setUserCertificateAlias(mUserCertEntry.getAlias());
+ mProfile.setLocalId(mSelectedUserId);
}
String certAlias = mCheckAuto.isChecked() ? null : mCertEntry.getAlias();
mProfile.setCertificateAlias(certAlias);
+ String remote_id = mRemoteId.getText().toString().trim();
+ mProfile.setRemoteId(remote_id.isEmpty() ? null : remote_id);
mProfile.setMTU(getInteger(mMTU));
mProfile.setPort(getInteger(mPort));
int st = 0;
@@ -463,7 +553,7 @@ public class VpnProfileDetailActivity extends AppCompatActivity
*/
private void loadProfileData(Bundle savedInstanceState)
{
- String useralias = null, alias = null;
+ String useralias = null, local_id = null, alias = null;
getSupportActionBar().setTitle(R.string.add_profile);
if (mId != null && mId != 0)
@@ -476,11 +566,13 @@ public class VpnProfileDetailActivity extends AppCompatActivity
mVpnType = mProfile.getVpnType();
mUsername.setText(mProfile.getUsername());
mPassword.setText(mProfile.getPassword());
+ mRemoteId.setText(mProfile.getRemoteId());
mMTU.setText(mProfile.getMTU() != null ? mProfile.getMTU().toString() : null);
mPort.setText(mProfile.getPort() != null ? mProfile.getPort().toString() : null);
mBlockIPv4.setChecked(mProfile.getSplitTunneling() != null ? (mProfile.getSplitTunneling() & VpnProfile.SPLIT_TUNNELING_BLOCK_IPV4) != 0 : false);
mBlockIPv6.setChecked(mProfile.getSplitTunneling() != null ? (mProfile.getSplitTunneling() & VpnProfile.SPLIT_TUNNELING_BLOCK_IPV6) != 0 : false);
useralias = mProfile.getUserCertificateAlias();
+ local_id = mProfile.getLocalId();
alias = mProfile.getCertificateAlias();
getSupportActionBar().setTitle(mProfile.getName());
}
@@ -495,11 +587,13 @@ public class VpnProfileDetailActivity extends AppCompatActivity
mSelectVpnType.setSelection(mVpnType.ordinal());
/* check if the user selected a user certificate previously */
- useralias = savedInstanceState == null ? useralias: savedInstanceState.getString(VpnProfileDataSource.KEY_USER_CERTIFICATE);
+ useralias = savedInstanceState == null ? useralias : savedInstanceState.getString(VpnProfileDataSource.KEY_USER_CERTIFICATE);
+ local_id = savedInstanceState == null ? local_id : savedInstanceState.getString(VpnProfileDataSource.KEY_LOCAL_ID);
if (useralias != null)
{
UserCertificateLoader loader = new UserCertificateLoader(this, useralias);
mUserCertLoading = useralias;
+ mSelectedUserId = local_id;
loader.execute();
}
@@ -650,4 +744,66 @@ public class VpnProfileDetailActivity extends AppCompatActivity
}).create();
}
}
+
+ /**
+ * Tokenizer implementation that separates by white-space
+ */
+ public static class SpaceTokenizer implements MultiAutoCompleteTextView.Tokenizer
+ {
+ @Override
+ public int findTokenStart(CharSequence text, int cursor)
+ {
+ int i = cursor;
+
+ while (i > 0 && !Character.isWhitespace(text.charAt(i - 1)))
+ {
+ i--;
+ }
+ return i;
+ }
+
+ @Override
+ public int findTokenEnd(CharSequence text, int cursor)
+ {
+ int i = cursor;
+ int len = text.length();
+
+ while (i < len)
+ {
+ if (Character.isWhitespace(text.charAt(i)))
+ {
+ return i;
+ }
+ else
+ {
+ i++;
+ }
+ }
+ return len;
+ }
+
+ @Override
+ public CharSequence terminateToken(CharSequence text)
+ {
+ int i = text.length();
+
+ if (i > 0 && Character.isWhitespace(text.charAt(i - 1)))
+ {
+ return text;
+ }
+ else
+ {
+ if (text instanceof Spanned)
+ {
+ SpannableString sp = new SpannableString(text + " ");
+ TextUtils.copySpansFrom((Spanned) text, 0, text.length(), Object.class, sp, 0);
+ return sp;
+ }
+ else
+ {
+ return text + " ";
+ }
+ }
+ }
+ }
}
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileListFragment.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileListFragment.java
index a23df054b..d8d99ff00 100644
--- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileListFragment.java
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileListFragment.java
@@ -17,21 +17,12 @@
package org.strongswan.android.ui;
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.List;
-
-import org.strongswan.android.R;
-import org.strongswan.android.data.VpnProfile;
-import org.strongswan.android.data.VpnProfileDataSource;
-import org.strongswan.android.ui.adapter.VpnProfileAdapter;
-
import android.app.Activity;
-import android.app.Fragment;
import android.content.Context;
import android.content.Intent;
import android.content.res.TypedArray;
import android.os.Bundle;
+import android.support.v4.app.Fragment;
import android.util.AttributeSet;
import android.view.ActionMode;
import android.view.LayoutInflater;
@@ -46,6 +37,15 @@ import android.widget.AdapterView.OnItemClickListener;
import android.widget.ListView;
import android.widget.Toast;
+import org.strongswan.android.R;
+import org.strongswan.android.data.VpnProfile;
+import org.strongswan.android.data.VpnProfileDataSource;
+import org.strongswan.android.ui.adapter.VpnProfileAdapter;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+
public class VpnProfileListFragment extends Fragment
{
private static final int ADD_REQUEST = 1;
@@ -66,10 +66,10 @@ public class VpnProfileListFragment extends Fragment
}
@Override
- public void onInflate(Activity activity, AttributeSet attrs, Bundle savedInstanceState)
+ public void onInflate(Context context, AttributeSet attrs, Bundle savedInstanceState)
{
- super.onInflate(activity, attrs, savedInstanceState);
- TypedArray a = activity.obtainStyledAttributes(attrs, R.styleable.Fragment);
+ super.onInflate(context, attrs, savedInstanceState);
+ TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.Fragment);
mReadOnly = a.getBoolean(R.styleable.Fragment_read_only, false);
a.recycle();
}
@@ -126,13 +126,13 @@ public class VpnProfileListFragment extends Fragment
}
@Override
- public void onAttach(Activity activity)
+ public void onAttach(Context context)
{
- super.onAttach(activity);
+ super.onAttach(context);
- if (activity instanceof OnVpnProfileSelectedListener)
+ if (context instanceof OnVpnProfileSelectedListener)
{
- mListener = (OnVpnProfileSelectedListener)activity;
+ mListener = (OnVpnProfileSelectedListener)context;
}
}
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnStateFragment.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnStateFragment.java
index c49e5cc7e..0b093d78f 100644
--- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnStateFragment.java
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnStateFragment.java
@@ -1,8 +1,8 @@
/*
- * Copyright (C) 2012-2013 Tobias Brunner
+ * Copyright (C) 2012-2016 Tobias Brunner
* Copyright (C) 2012 Giuliano Grassi
* Copyright (C) 2012 Ralf Sager
- * Hochschule fuer Technik Rapperswil
+ * HSR Hochschule fuer Technik Rapperswil
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
@@ -17,8 +17,6 @@
package org.strongswan.android.ui;
-import android.app.Fragment;
-import android.app.ProgressDialog;
import android.app.Service;
import android.content.ComponentName;
import android.content.Context;
@@ -27,6 +25,7 @@ import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
+import android.support.v4.app.Fragment;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AlertDialog;
import android.view.LayoutInflater;
@@ -34,6 +33,7 @@ import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.Button;
+import android.widget.ProgressBar;
import android.widget.TextView;
import org.strongswan.android.R;
@@ -60,9 +60,7 @@ public class VpnStateFragment extends Fragment implements VpnStateListener
private int mColorStateError;
private int mColorStateSuccess;
private Button mActionButton;
- private ProgressDialog mConnectDialog;
- private ProgressDialog mDisconnectDialog;
- private ProgressDialog mProgressDialog;
+ private ProgressBar mProgress;
private AlertDialog mErrorDialog;
private long mErrorConnectionID;
private long mDismissedConnectionID;
@@ -133,8 +131,9 @@ public class VpnStateFragment extends Fragment implements VpnStateListener
}
}
});
- enableActionButton(false);
+ enableActionButton(null);
+ mProgress = (ProgressBar)view.findViewById(R.id.progress);
mStateView = (TextView)view.findViewById(R.id.vpn_state);
mColorStateBase = mStateView.getCurrentTextColor();
mProfileView = (TextView)view.findViewById(R.id.vpn_profile_label);
@@ -163,7 +162,6 @@ public class VpnStateFragment extends Fragment implements VpnStateListener
mService.unregisterListener(this);
}
hideErrorDialog();
- hideProgressDialog();
}
@Override
@@ -202,33 +200,35 @@ public class VpnStateFragment extends Fragment implements VpnStateListener
return;
}
- enableActionButton(false);
mProfileNameView.setText(name);
switch (state)
{
case DISABLED:
showProfile(false);
- hideProgressDialog();
+ mProgress.setVisibility(View.GONE);
+ enableActionButton(null);
mStateView.setText(R.string.state_disabled);
mStateView.setTextColor(mColorStateBase);
break;
case CONNECTING:
showProfile(true);
- showConnectDialog(name, gateway);
+ mProgress.setVisibility(View.VISIBLE);
+ enableActionButton(getString(android.R.string.cancel));
mStateView.setText(R.string.state_connecting);
mStateView.setTextColor(mColorStateBase);
break;
case CONNECTED:
showProfile(true);
- hideProgressDialog();
- enableActionButton(true);
+ mProgress.setVisibility(View.GONE);
+ enableActionButton(getString(R.string.disconnect));
mStateView.setText(R.string.state_connected);
mStateView.setTextColor(mColorStateSuccess);
break;
case DISCONNECTING:
showProfile(true);
- showDisconnectDialog(name);
+ mProgress.setVisibility(View.VISIBLE);
+ enableActionButton(null);
mStateView.setText(R.string.state_disconnecting);
mStateView.setTextColor(mColorStateBase);
break;
@@ -254,10 +254,10 @@ public class VpnStateFragment extends Fragment implements VpnStateListener
{ /* we already show the dialog */
return true;
}
- hideProgressDialog();
mProfileNameView.setText(name);
showProfile(true);
- enableActionButton(false);
+ mProgress.setVisibility(View.GONE);
+ enableActionButton(null);
mStateView.setText(R.string.state_error);
mStateView.setTextColor(mColorStateError);
switch (error)
@@ -294,20 +294,11 @@ public class VpnStateFragment extends Fragment implements VpnStateListener
mProfileNameView.setVisibility(show ? View.VISIBLE : View.GONE);
}
- private void enableActionButton(boolean enable)
+ private void enableActionButton(String text)
{
- mActionButton.setEnabled(enable);
- mActionButton.setVisibility(enable ? View.VISIBLE : View.GONE);
- }
-
- private void hideProgressDialog()
- {
- if (mProgressDialog != null)
- {
- mProgressDialog.dismiss();
- mProgressDialog = null;
- mDisconnectDialog = mConnectDialog = null;
- }
+ mActionButton.setText(text);
+ mActionButton.setEnabled(text != null);
+ mActionButton.setVisibility(text != null ? View.VISIBLE : View.GONE);
}
private void hideErrorDialog()
@@ -329,50 +320,6 @@ public class VpnStateFragment extends Fragment implements VpnStateListener
updateView();
}
- private void showConnectDialog(String profile, String gateway)
- {
- if (mConnectDialog != null)
- { /* already showing the dialog */
- return;
- }
- hideProgressDialog();
- mConnectDialog = new ProgressDialog(getActivity());
- mConnectDialog.setTitle(String.format(getString(R.string.connecting_title), profile));
- mConnectDialog.setMessage(String.format(getString(R.string.connecting_message), gateway));
- mConnectDialog.setIndeterminate(true);
- mConnectDialog.setCancelable(false);
- mConnectDialog.setButton(DialogInterface.BUTTON_NEGATIVE,
- getString(android.R.string.cancel),
- new DialogInterface.OnClickListener()
- {
- @Override
- public void onClick(DialogInterface dialog, int which)
- {
- if (mService != null)
- {
- mService.disconnect();
- }
- }
- });
- mConnectDialog.show();
- mProgressDialog = mConnectDialog;
- }
-
- private void showDisconnectDialog(String profile)
- {
- if (mDisconnectDialog != null)
- { /* already showing the dialog */
- return;
- }
- hideProgressDialog();
- mDisconnectDialog = new ProgressDialog(getActivity());
- mDisconnectDialog.setMessage(getString(R.string.state_disconnecting));
- mDisconnectDialog.setIndeterminate(true);
- mDisconnectDialog.setCancelable(false);
- mDisconnectDialog.show();
- mProgressDialog = mDisconnectDialog;
- }
-
private void showErrorDialog(int textid)
{
final List instructions = mService.getRemediationInstructions();
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/adapter/CertificateIdentitiesAdapter.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/adapter/CertificateIdentitiesAdapter.java
new file mode 100644
index 000000000..c8e3df38b
--- /dev/null
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/adapter/CertificateIdentitiesAdapter.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2016 Tobias Brunner
+ * HSR Hochschule fuer Technik Rapperswil
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version. See .
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+package org.strongswan.android.ui.adapter;
+
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.TextView;
+
+import org.strongswan.android.R;
+import org.strongswan.android.security.TrustedCertificateEntry;
+
+import java.util.List;
+
+public class CertificateIdentitiesAdapter extends ArrayAdapter
+{
+ TrustedCertificateEntry mCertificate;
+
+ public CertificateIdentitiesAdapter(Context context)
+ {
+ super(context, android.R.layout.simple_dropdown_item_1line);
+ extractIdentities();
+ }
+
+ /**
+ * Set a new certificate for this adapter.
+ *
+ * @param certificate the certificate to extract identities from (null to clear)
+ */
+ public void setCertificate(TrustedCertificateEntry certificate)
+ {
+ mCertificate = certificate;
+ clear();
+ extractIdentities();
+ }
+
+ private void extractIdentities()
+ {
+ if (mCertificate == null)
+ {
+ add(getContext().getString(R.string.profile_user_select_id_init));
+ }
+ else
+ {
+ add(String.format(getContext().getString(R.string.profile_user_select_id_default),
+ mCertificate.getCertificate().getSubjectDN().getName()));
+ addAll(mCertificate.getSubjectAltNames());
+ }
+ }
+}
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/adapter/VpnProfileAdapter.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/adapter/VpnProfileAdapter.java
index f3bb271bc..58296a018 100644
--- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/adapter/VpnProfileAdapter.java
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/adapter/VpnProfileAdapter.java
@@ -63,12 +63,18 @@ public class VpnProfileAdapter extends ArrayAdapter
TextView tv = (TextView)vpnProfileView.findViewById(R.id.profile_item_name);
tv.setText(profile.getName());
tv = (TextView)vpnProfileView.findViewById(R.id.profile_item_gateway);
- tv.setText(getContext().getString(R.string.profile_gateway_label) + " " + profile.getGateway());
+ tv.setText(getContext().getString(R.string.profile_gateway_label) + ": " + profile.getGateway());
tv = (TextView)vpnProfileView.findViewById(R.id.profile_item_username);
if (profile.getVpnType().has(VpnTypeFeature.USER_PASS))
{ /* if the view is reused we make sure it is visible */
tv.setVisibility(View.VISIBLE);
- tv.setText(getContext().getString(R.string.profile_username_label) + " " + profile.getUsername());
+ tv.setText(getContext().getString(R.string.profile_username_label) + ": " + profile.getUsername());
+ }
+ else if (profile.getVpnType().has(VpnTypeFeature.CERTIFICATE) &&
+ profile.getLocalId() != null)
+ {
+ tv.setVisibility(View.VISIBLE);
+ tv.setText(getContext().getString(R.string.profile_user_select_id_label) + ": " + profile.getLocalId());
}
else
{
@@ -77,7 +83,7 @@ public class VpnProfileAdapter extends ArrayAdapter
tv = (TextView)vpnProfileView.findViewById(R.id.profile_item_certificate);
if (profile.getVpnType().has(VpnTypeFeature.CERTIFICATE))
{
- tv.setText(getContext().getString(R.string.profile_user_certificate_label) + " " + profile.getUserCertificateAlias());
+ tv.setText(getContext().getString(R.string.profile_user_certificate_label) + ": " + profile.getUserCertificateAlias());
tv.setVisibility(View.VISIBLE);
}
else
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/widget/TextInputLayoutHelper.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/widget/TextInputLayoutHelper.java
new file mode 100644
index 000000000..45b0ae592
--- /dev/null
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/widget/TextInputLayoutHelper.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2016 Tobias Brunner
+ * HSR Hochschule fuer Technik Rapperswil
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version. See .
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+package org.strongswan.android.ui.widget;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.support.annotation.Nullable;
+import android.support.design.widget.TextInputLayout;
+import android.support.v4.view.ViewCompat;
+import android.support.v4.view.ViewPropertyAnimatorListenerAdapter;
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.util.AttributeSet;
+import android.util.TypedValue;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.EditText;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import org.strongswan.android.R;
+
+/**
+ * Layout that extends {@link android.support.design.widget.TextInputLayout} with a helper text
+ * displayed below the text field when it receives the focus. Also, any error message shown with
+ * {@link #setError(CharSequence)} is hidden when the text field is changed (this mirrors the
+ * behavior of {@link android.widget.EditText}).
+ */
+public class TextInputLayoutHelper extends TextInputLayout
+{
+ private LinearLayout mHelperContainer;
+ private TextView mHelperText;
+
+ public TextInputLayoutHelper(Context context)
+ {
+ this(context, null);
+ }
+
+ public TextInputLayoutHelper(Context context, AttributeSet attrs)
+ {
+ this(context, attrs, 0);
+ }
+
+ public TextInputLayoutHelper(Context context, AttributeSet attrs, int defStyleAttr)
+ {
+ super(context, attrs);
+ TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TextInputLayoutHelper);
+ String helper = a.getString(R.styleable.TextInputLayoutHelper_helper_text);
+ a.recycle();
+ if (helper != null)
+ {
+ mHelperContainer = new LinearLayout(context);
+ mHelperContainer.setOrientation(LinearLayout.HORIZONTAL);
+ mHelperContainer.setVisibility(View.INVISIBLE);
+ addView(mHelperContainer, LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
+
+ mHelperText = new TextView(context);
+ mHelperText.setText(helper);
+ mHelperText.setTextSize(TypedValue.COMPLEX_UNIT_SP, 12);
+ a = context.obtainStyledAttributes(attrs, new int[]{android.R.attr.textColorSecondary});
+ mHelperText.setTextColor(a.getColor(0, mHelperText.getCurrentTextColor()));
+ a.recycle();
+
+ mHelperContainer.addView(mHelperText, LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
+ }
+ }
+
+ @Override
+ public void addView(View child, int index, ViewGroup.LayoutParams params)
+ {
+ super.addView(child, index, params);
+ if (child instanceof EditText)
+ {
+ EditText text = (EditText)child;
+ text.addTextChangedListener(new TextWatcher() {
+ @Override
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
+
+ @Override
+ public void onTextChanged(CharSequence s, int start, int before, int count) {}
+
+ @Override
+ public void afterTextChanged(Editable s)
+ {
+ if (getError() != null)
+ {
+ setError(null);
+ }
+ }
+ });
+ if (mHelperContainer != null)
+ {
+ text.setOnFocusChangeListener(new OnFocusChangeListener() {
+ @Override
+ public void onFocusChange(View v, boolean hasFocus)
+ {
+ showHelper(hasFocus);
+ }
+ });
+ ViewCompat.setPaddingRelative(mHelperContainer, ViewCompat.getPaddingStart(text),
+ 0, ViewCompat.getPaddingEnd(text), text.getPaddingBottom());
+ }
+ }
+ }
+
+ @Override
+ public void setError(@Nullable CharSequence error)
+ {
+ super.setError(error);
+ if (mHelperContainer != null)
+ {
+ if (error == null)
+ { /* this frees up space used by the now invisible error message */
+ setErrorEnabled(false);
+ }
+ else
+ { /* re-add the helper as the error message should be displayed directly under the textbox */
+ removeView(mHelperContainer);
+ addView(mHelperContainer, LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
+ }
+ }
+ }
+
+ /**
+ * Set the helper text to be displayed below the text field.
+ *
+ * @attr ref R.styleable#TextInputLayoutHelper_helper_text
+ */
+ public void setHelperText(CharSequence text)
+ {
+ mHelperText.setText(text);
+ }
+
+ private void showHelper(boolean show)
+ {
+ if (show == (mHelperContainer.getVisibility() == View.VISIBLE))
+ {
+ return;
+ }
+ if (show)
+ {
+ ViewCompat.animate(mHelperContainer)
+ .alpha(1f)
+ .setDuration(200)
+ .setListener(new ViewPropertyAnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(View view)
+ {
+ view.setVisibility(View.VISIBLE);
+ }
+ }).start();
+ }
+ else
+ {
+ ViewCompat.animate(mHelperContainer)
+ .alpha(0f)
+ .setDuration(200)
+ .setListener(new ViewPropertyAnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(View view)
+ {
+ view.setVisibility(View.INVISIBLE);
+ }
+ }).start();
+ }
+ }
+}
diff --git a/src/frontends/android/app/src/main/jni/Android.mk b/src/frontends/android/app/src/main/jni/Android.mk
index 9e81a14c5..5bbfeaa1b 100644
--- a/src/frontends/android/app/src/main/jni/Android.mk
+++ b/src/frontends/android/app/src/main/jni/Android.mk
@@ -16,12 +16,14 @@ endif
strongswan_PLUGINS := $(strongswan_CHARON_PLUGINS) \
$(strongswan_BYOD_PLUGINS)
-include $(LOCAL_PATH)/strongswan/Android.common.mk
+strongswan_DIR := ../../../../../../../
# includes
-strongswan_PATH := $(LOCAL_PATH)/strongswan
+strongswan_PATH := $(LOCAL_PATH)/$(strongswan_DIR)
openssl_PATH := $(LOCAL_PATH)/openssl/include
+include $(strongswan_PATH)/Android.common.mk
+
# CFLAGS (partially from a configure run using droid-gcc)
strongswan_CFLAGS := \
-Wall \
@@ -67,15 +69,15 @@ endif
strongswan_BUILD := \
openssl \
libandroidbridge \
- strongswan/src/libipsec \
- strongswan/src/libcharon \
- strongswan/src/libstrongswan
+ $(strongswan_DIR)/src/libipsec \
+ $(strongswan_DIR)/src/libcharon \
+ $(strongswan_DIR)/src/libstrongswan
ifneq ($(strongswan_USE_BYOD),)
strongswan_BUILD += \
- strongswan/src/libtnccs \
- strongswan/src/libtncif \
- strongswan/src/libimcv
+ $(strongswan_DIR)/src/libtnccs \
+ $(strongswan_DIR)/src/libtncif \
+ $(strongswan_DIR)/src/libimcv
endif
include $(addprefix $(LOCAL_PATH)/,$(addsuffix /Android.mk, \
diff --git a/src/frontends/android/app/src/main/jni/libandroidbridge/backend/android_service.c b/src/frontends/android/app/src/main/jni/libandroidbridge/backend/android_service.c
index b3e91d808..2532402bb 100644
--- a/src/frontends/android/app/src/main/jni/libandroidbridge/backend/android_service.c
+++ b/src/frontends/android/app/src/main/jni/libandroidbridge/backend/android_service.c
@@ -1,8 +1,8 @@
/*
- * Copyright (C) 2010-2015 Tobias Brunner
+ * Copyright (C) 2010-2016 Tobias Brunner
* Copyright (C) 2012 Giuliano Grassi
* Copyright (C) 2012 Ralf Sager
- * Hochschule fuer Technik Rapperswil
+ * HSR Hochschule fuer Technik Rapperswil
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
@@ -430,6 +430,84 @@ CALLBACK(reestablish, job_requeue_t,
return JOB_REQUEUE_NONE;
}
+METHOD(listener_t, ike_updown, bool,
+ private_android_service_t *this, ike_sa_t *ike_sa, bool up)
+{
+ /* this callback is only registered during initiation, so if the IKE_SA
+ * goes down we assume some kind of authentication error, more specific
+ * errors are catched in the alert() handler */
+ if (this->ike_sa == ike_sa && !up)
+ {
+ charonservice->update_status(charonservice,
+ CHARONSERVICE_AUTH_ERROR);
+ return FALSE;
+ }
+ return TRUE;
+}
+
+METHOD(listener_t, ike_rekey, bool,
+ private_android_service_t *this, ike_sa_t *old, ike_sa_t *new)
+{
+ if (this->ike_sa == old)
+ {
+ this->ike_sa = new;
+ }
+ return TRUE;
+}
+
+METHOD(listener_t, ike_reestablish_post_redirect, bool,
+ private_android_service_t *this, ike_sa_t *old, ike_sa_t *new,
+ bool initiated)
+{
+ if (this->ike_sa == old && initiated)
+ { /* if we get redirected during IKE_AUTH we just migrate to the new SA,
+ * we don't have a TUN device yet, so reinstalling it without DNS would
+ * fail (and using the DNS proxy is not required anyway) */
+ this->ike_sa = new;
+ }
+ return TRUE;
+}
+
+METHOD(listener_t, ike_reestablish_pre, bool,
+ private_android_service_t *this, ike_sa_t *old, ike_sa_t *new)
+{
+ if (this->ike_sa == old)
+ {
+ /* enable DNS proxy so hosts are properly resolved while the TUN device
+ * is still active */
+ this->lock->write_lock(this->lock);
+ this->use_dns_proxy = TRUE;
+ this->lock->unlock(this->lock);
+ /* if DNS servers are installed that are only reachable through the VPN
+ * the DNS proxy doesn't help, so uninstall DNS servers */
+ if (!setup_tun_device_without_dns(this))
+ {
+ DBG1(DBG_DMN, "failed to setup TUN device without DNS");
+ charonservice->update_status(charonservice,
+ CHARONSERVICE_GENERIC_ERROR);
+ }
+ }
+ return TRUE;
+}
+
+METHOD(listener_t, ike_reestablish_post, bool,
+ private_android_service_t *this, ike_sa_t *old, ike_sa_t *new,
+ bool initiated)
+{
+ if (this->ike_sa == old && initiated)
+ {
+ this->ike_sa = new;
+ /* re-register hook to detect initiation failures */
+ this->public.listener.ike_updown = _ike_updown;
+ /* if the IKE_SA got deleted by the responder we get the child_down()
+ * event on the old IKE_SA after this hook has been called, so they
+ * get ignored and thus we trigger the event here */
+ charonservice->update_status(charonservice,
+ CHARONSERVICE_CHILD_STATE_DOWN);
+ }
+ return TRUE;
+}
+
METHOD(listener_t, child_updown, bool,
private_android_service_t *this, ike_sa_t *ike_sa, child_sa_t *child_sa,
bool up)
@@ -440,6 +518,9 @@ METHOD(listener_t, child_updown, bool,
{
/* disable the hooks registered to catch initiation failures */
this->public.listener.ike_updown = NULL;
+ /* enable hooks to handle reauthentications */
+ this->public.listener.ike_reestablish_pre = _ike_reestablish_pre;
+ this->public.listener.ike_reestablish_post = _ike_reestablish_post;
/* CHILD_SA is up so we can disable the DNS proxy we enabled to
* reestablish the SA */
this->lock->write_lock(this->lock);
@@ -465,21 +546,6 @@ METHOD(listener_t, child_updown, bool,
return TRUE;
}
-METHOD(listener_t, ike_updown, bool,
- private_android_service_t *this, ike_sa_t *ike_sa, bool up)
-{
- /* this callback is only registered during initiation, so if the IKE_SA
- * goes down we assume some kind of authentication error, more specific
- * errors are catched in the alert() handler */
- if (this->ike_sa == ike_sa && !up)
- {
- charonservice->update_status(charonservice,
- CHARONSERVICE_AUTH_ERROR);
- return FALSE;
- }
- return TRUE;
-}
-
METHOD(listener_t, alert, bool,
private_android_service_t *this, ike_sa_t *ike_sa, alert_t alert,
va_list args)
@@ -554,62 +620,12 @@ METHOD(listener_t, alert, bool,
return TRUE;
}
-METHOD(listener_t, ike_rekey, bool,
- private_android_service_t *this, ike_sa_t *old, ike_sa_t *new)
-{
- if (this->ike_sa == old)
- {
- this->ike_sa = new;
- }
- return TRUE;
-}
-
-METHOD(listener_t, ike_reestablish_pre, bool,
- private_android_service_t *this, ike_sa_t *old, ike_sa_t *new)
-{
- if (this->ike_sa == old)
- {
- /* enable DNS proxy so hosts are properly resolved while the TUN device
- * is still active */
- this->lock->write_lock(this->lock);
- this->use_dns_proxy = TRUE;
- this->lock->unlock(this->lock);
- /* if DNS servers are installed that are only reachable through the VPN
- * the DNS proxy doesn't help, so uninstall DNS servers */
- if (!setup_tun_device_without_dns(this))
- {
- DBG1(DBG_DMN, "failed to setup TUN device without DNS");
- charonservice->update_status(charonservice,
- CHARONSERVICE_GENERIC_ERROR);
- }
- }
- return TRUE;
-}
-
-METHOD(listener_t, ike_reestablish_post, bool,
- private_android_service_t *this, ike_sa_t *old, ike_sa_t *new,
- bool initiated)
-{
- if (this->ike_sa == old && initiated)
- {
- this->ike_sa = new;
- /* re-register hook to detect initiation failures */
- this->public.listener.ike_updown = _ike_updown;
- /* if the IKE_SA got deleted by the responder we get the child_down()
- * event on the old IKE_SA after this hook has been called, so they
- * get ignored and thus we trigger the event here */
- charonservice->update_status(charonservice,
- CHARONSERVICE_CHILD_STATE_DOWN);
- }
- return TRUE;
-}
-
static void add_auth_cfg_pw(private_android_service_t *this,
peer_cfg_t *peer_cfg, bool byod)
{
- identification_t *user;
+ identification_t *user, *id = NULL;
auth_cfg_t *auth;
- char *username, *password;
+ char *username, *password, *local_id;
auth = auth_cfg_create();
auth->add(auth, AUTH_RULE_AUTH_CLASS, AUTH_CLASS_EAP);
@@ -622,8 +638,19 @@ static void add_auth_cfg_pw(private_android_service_t *this,
NULL);
password = this->settings->get_str(this->settings, "connection.password",
NULL);
+ local_id = this->settings->get_str(this->settings, "connection.local_id",
+ NULL);
user = identification_create_from_string(username);
- auth->add(auth, AUTH_RULE_IDENTITY, user);
+ auth->add(auth, AUTH_RULE_EAP_IDENTITY, user);
+ if (local_id)
+ {
+ id = identification_create_from_string(local_id);
+ }
+ if (!id)
+ {
+ id = user->clone(user);
+ }
+ auth->add(auth, AUTH_RULE_IDENTITY, id);
this->creds->add_username_password(this->creds, username, password);
peer_cfg->add_auth_cfg(peer_cfg, auth, TRUE);
@@ -633,9 +660,9 @@ static bool add_auth_cfg_cert(private_android_service_t *this,
peer_cfg_t *peer_cfg)
{
certificate_t *cert;
- identification_t *id;
+ identification_t *id = NULL;
auth_cfg_t *auth;
- char *type;
+ char *type, *local_id;
cert = this->creds->load_user_certificate(this->creds);
if (!cert)
@@ -649,8 +676,8 @@ static bool add_auth_cfg_cert(private_android_service_t *this,
{
auth->add(auth, AUTH_RULE_AUTH_CLASS, AUTH_CLASS_EAP);
auth->add(auth, AUTH_RULE_EAP_TYPE, EAP_TLS);
- id = identification_create_from_string("%any");
- auth->add(auth, AUTH_RULE_AAA_IDENTITY, id);
+ auth->add(auth, AUTH_RULE_AAA_IDENTITY,
+ identification_create_from_string("%any"));
}
else
{
@@ -658,15 +685,25 @@ static bool add_auth_cfg_cert(private_android_service_t *this,
}
auth->add(auth, AUTH_RULE_SUBJECT_CERT, cert);
- id = cert->get_subject(cert);
- auth->add(auth, AUTH_RULE_IDENTITY, id->clone(id));
+ local_id = this->settings->get_str(this->settings, "connection.local_id",
+ NULL);
+ if (local_id)
+ {
+ id = identification_create_from_string(local_id);
+ }
+ if (!id)
+ {
+ id = cert->get_subject(cert);
+ id = id->clone(id);
+ }
+ auth->add(auth, AUTH_RULE_IDENTITY, id);
peer_cfg->add_auth_cfg(peer_cfg, auth, TRUE);
return TRUE;
}
static job_requeue_t initiate(private_android_service_t *this)
{
- identification_t *gateway;
+ identification_t *gateway = NULL;
ike_cfg_t *ike_cfg;
peer_cfg_t *peer_cfg;
child_cfg_t *child_cfg;
@@ -692,7 +729,7 @@ static job_requeue_t initiate(private_android_service_t *this)
.dpd_action = ACTION_RESTART,
.close_action = ACTION_RESTART,
};
- char *type, *server;
+ char *type, *server, *remote_id;
int port;
server = this->settings->get_str(this->settings, "connection.server", NULL);
@@ -731,9 +768,20 @@ static job_requeue_t initiate(private_android_service_t *this)
/* remote auth config */
auth = auth_cfg_create();
- gateway = identification_create_from_string(server);
+ remote_id = this->settings->get_str(this->settings, "connection.remote_id",
+ NULL);
+ if (remote_id)
+ {
+ gateway = identification_create_from_string(remote_id);
+ }
+ if (!gateway || gateway->get_type(gateway) == ID_ANY)
+ {
+ DESTROY_IF(gateway);
+ gateway = identification_create_from_string(server);
+ /* only use this if remote ID was not configured explicitly */
+ auth->add(auth, AUTH_RULE_IDENTITY_LOOSE, TRUE);
+ }
auth->add(auth, AUTH_RULE_IDENTITY, gateway);
- auth->add(auth, AUTH_RULE_IDENTITY_LOOSE, TRUE);
auth->add(auth, AUTH_RULE_AUTH_CLASS, AUTH_CLASS_PUBKEY);
peer_cfg->add_auth_cfg(peer_cfg, auth, FALSE);
@@ -824,8 +872,7 @@ android_service_t *android_service_create(android_creds_t *creds,
.public = {
.listener = {
.ike_rekey = _ike_rekey,
- .ike_reestablish_pre = _ike_reestablish_pre,
- .ike_reestablish_post = _ike_reestablish_post,
+ .ike_reestablish_post = _ike_reestablish_post_redirect,
.ike_updown = _ike_updown,
.child_updown = _child_updown,
.alert = _alert,
diff --git a/src/frontends/android/app/src/main/jni/libandroidbridge/charonservice.c b/src/frontends/android/app/src/main/jni/libandroidbridge/charonservice.c
index 3bed6eb4d..fa9708963 100644
--- a/src/frontends/android/app/src/main/jni/libandroidbridge/charonservice.c
+++ b/src/frontends/android/app/src/main/jni/libandroidbridge/charonservice.c
@@ -43,6 +43,7 @@
#define ANDROID_RETRASNMIT_TRIES 3
#define ANDROID_RETRANSMIT_TIMEOUT 2.0
#define ANDROID_RETRANSMIT_BASE 1.4
+#define ANDROID_KEEPALIVE_INTERVAL 45
typedef struct private_charonservice_t private_charonservice_t;
@@ -466,6 +467,9 @@ static void set_options(char *logfile)
"charon.retransmit_timeout", ANDROID_RETRANSMIT_TIMEOUT);
lib->settings->set_double(lib->settings,
"charon.retransmit_base", ANDROID_RETRANSMIT_BASE);
+ /* increase NAT-T keepalive interval a bit to save battery power */
+ lib->settings->set_time(lib->settings,
+ "charon.keep_alive", ANDROID_KEEPALIVE_INTERVAL);
lib->settings->set_bool(lib->settings,
"charon.initiator_only", TRUE);
lib->settings->set_bool(lib->settings,
diff --git a/src/frontends/android/app/src/main/res/layout/login_dialog.xml b/src/frontends/android/app/src/main/res/layout/login_dialog.xml
index 0262af0a3..adfaac5e2 100644
--- a/src/frontends/android/app/src/main/res/layout/login_dialog.xml
+++ b/src/frontends/android/app/src/main/res/layout/login_dialog.xml
@@ -1,9 +1,9 @@
-
+ android:padding="10dp" >
-
-
-
+ android:layout_height="wrap_content" >
-
+
-
+
+
+ android:layout_height="wrap_content"
+ android:layout_marginTop="4dp" >
+
+
+
+
diff --git a/src/frontends/android/app/src/main/res/layout/profile_detail_view.xml b/src/frontends/android/app/src/main/res/layout/profile_detail_view.xml
index 57d5606ff..08881b38c 100644
--- a/src/frontends/android/app/src/main/res/layout/profile_detail_view.xml
+++ b/src/frontends/android/app/src/main/res/layout/profile_detail_view.xml
@@ -1,9 +1,9 @@
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" >
+ android:padding="10dp"
+ android:animateLayoutChanges="true" >
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
+ android:layout_height="wrap_content" >
-
+
-
+
-
+ android:layout_marginTop="4dp"
+ app:helper_text="@string/profile_password_hint" >
+
+
+
+
@@ -109,36 +107,73 @@
android:id="@+id/user_certificate_group"
android:layout_width="match_parent"
android:layout_height="wrap_content"
+ android:layout_marginBottom="4dp"
android:orientation="vertical" >
+
+
+
+
+
+
+
+
+
+
+ android:layout_marginLeft="4dp"
+ android:textSize="20sp"
+ android:text="@string/profile_advanced_label" />
-
+ android:layout_marginTop="10dp"
+ app:helper_text="@string/profile_remote_id_hint" >
+
+
-
+
+
+ app:helper_text="@string/profile_mtu_hint" >
-
+
+
+
+
+ app:helper_text="@string/profile_port_hint" >
+
+
+
+
+
-
\ No newline at end of file
+
diff --git a/src/frontends/android/app/src/main/res/layout/remediation_instruction.xml b/src/frontends/android/app/src/main/res/layout/remediation_instruction.xml
index 09c0d43a3..04fffaa4e 100644
--- a/src/frontends/android/app/src/main/res/layout/remediation_instruction.xml
+++ b/src/frontends/android/app/src/main/res/layout/remediation_instruction.xml
@@ -1,6 +1,6 @@
@@ -25,6 +25,6 @@
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
- strongswan:read_only="true" />
+ app:read_only="true" />
\ No newline at end of file
diff --git a/src/frontends/android/app/src/main/res/layout/vpn_state_fragment.xml b/src/frontends/android/app/src/main/res/layout/vpn_state_fragment.xml
index e347c4c4b..86a2f1321 100644
--- a/src/frontends/android/app/src/main/res/layout/vpn_state_fragment.xml
+++ b/src/frontends/android/app/src/main/res/layout/vpn_state_fragment.xml
@@ -1,6 +1,6 @@
Speichern
Abbrechen
- Profilname:
- (Server-Adresse verwenden)
- Server:
- Typ:
- Benutzername:
- Passwort:
- (anfordern wenn benötigt)
- Benutzer-Zertifikat:
+ Profilname (optional)
+ Standardwert ist der konfigurierte Server
+ Standardwert ist \"%1$s\"
+ Server
+ IP-Adresse oder Hostname des VPN Servers
+ VPN-Typ
+ Benutzername
+ Passwort (optional)
+ Leer lassen, um bei Bedarf danach gefragt zu werden
+ Benutzer-Zertifikat
Benutzer-Zertifikat auswählen
Wählen Sie ein bestimmtes Benutzer-Zertifikat
- CA-Zertifikat:
+ Benutzer-Identität
+ Wählen Sie zuerst ein Benutzer-Zertifikat
+ Standardwert (%1$s)
+ CA-Zertifikat
Automatisch wählen
CA-Zertifikat auswählen
Wählen Sie ein bestimmtes CA-Zertifikat
+ Erweiterte Einstellungen
Erweiterte Einstellungen anzeigen
- MTU:
- Server Port:
- (Standardwert verwenden)
- Split-Tunneling:
+ Server-Identität
+ Standardwert ist der konfigurierte Server. Eigene Werte werden explizit and den Server gesendet und während der Authentifizierung erzwungen
+ Standardwert ist \"%1$s\". Eigene Werte werden explizit and den Server gesendet und während der Authentifizierung erzwungen
+ MTU des VPN Tunnel-Device
+ Falls der Standardwert in einem bestimmten Netzwerk nicht geeignet ist
+ Server Port
+ UDP-Port zu dem verbunden wird, falls dieser vom Standard-Port abweicht
+ Split-Tunneling
Blockiere IPv4 Verkehr der nicht für das VPN bestimmt ist
Blockiere IPv6 Verkehr der nicht für das VPN bestimmt ist
- Bitte geben Sie hier die Server-Adresse ein
- Bitte geben Sie hier Ihren Benutzernamen ein
+ Ein Wert wird benötigt, um die Verbindung aufbauen zu können
+ Bitte geben Sie Ihren Benutzernamen ein
Kein CA-Zertifikat ausgewählt
Bitte wählen Sie eines aus oder aktivieren Sie Automatisch wählen
Bitte geben Sie eine Nummer von %1$d - %2$d ein
@@ -122,8 +132,6 @@
Benutzerauthentifizierung ist fehlgeschlagen.
Sicherheitsassessment ist fehlgeschlagen.
Unbekannter Fehler während des Verbindens.
- Verbinden: %1$s
- Verbinde mit \""%1$s\".
VPN verbunden
Dieses VPN Profil ist momentan verbunden!
Neu verbinden
diff --git a/src/frontends/android/app/src/main/res/values-pl/strings.xml b/src/frontends/android/app/src/main/res/values-pl/strings.xml
index 26387190b..c7aadb994 100644
--- a/src/frontends/android/app/src/main/res/values-pl/strings.xml
+++ b/src/frontends/android/app/src/main/res/values-pl/strings.xml
@@ -49,36 +49,46 @@
Zapisz
Anuluj
- Nazwa profilu:
- (użyj adresu serwer)
- Serwer:
- Typ:
- Użytkownik:
- Hasło:
- (w razie potrzeby zapromptuj)
- Certyfikat użytkownika:
+ Nazwa profilu (opcjonalny)
+ Defaults to the configured server
+ Defaults to \"%1$s\"
+ Serwer
+ IP address or hostname of the VPN server
+ Typ VPN
+ Użytkownik
+ Hasło (opcjonalny)
+ Leave blank to get prompted on demand
+ Certyfikat użytkownika
Wybierz certyfikat użytkownika
>Wybierz określony certyfikat użytkownika
- Certyfikat CA:
+ User identity
+ Select a certificate first
+ Default (%1$s)
+ Certyfikat CA
Wybierz automatycznie
Wybierz certyfikat CA
Wybierz określony certyfikat CA
+ Advanced settings
Show advanced settings
- MTU:
- Server port:
- (use default)
- Split tunneling:
+ Server identity
+ Defaults to the configured server. Custom values are explicitly sent to the server and enforced during authentication
+ Defaults to \"%1$s\". Custom values are explicitly sent to the server and enforced during authentication
+ MTU of the VPN tunnel device
+ In case the default value is unsuitable for a particular network
+ Server port
+ UDP port to connect to, if different from the default
+ Split tunneling
Block IPv4 traffic not destined for the VPN
Block IPv6 traffic not destined for the VPN
- Wprowadź adres serwer
+ A value is required to initiate the connection
Wprowadź swoją nazwę użytkownika
Nie wybrano żadnego certyfikatu CA
Wybierz lub uaktywnij jeden Wybierz automatycznie
Please enter a number in the range from %1$d - %2$d
EAP-TNC may affect your privacy
Device data is sent to the server operator
- <p>Trusted Network Connect (TNC) allows server operators to assess the health of a client device.</p><p>For that purpose the server operator may request data such as a unique identifier, a list of installed packages, system settings, or cryptographic checksums of files.</p><b>Any data will be sent only after verifying the server\'s identity.</b>
+ Trusted Network Connect (TNC) allows server operators to assess the health of a client device.
For that purpose the server operator may request data such as a unique identifier, a list of installed packages, system settings, or cryptographic checksums of files.
Any data will be sent only after verifying the server\'s identity.]]>
Certyfikaty CA
@@ -122,8 +132,6 @@
Błąd przy autoryzacji użytkownika
Security assessment failed.
Nieznany błąd w czasie połączenia
- Łączenie: %1$s
- Tworzenie tunelu VPN z \""%1$s\".
Połączenie z VPN
Ten profil VPN jest obecnie połaczony!
Połączyć ponownie
diff --git a/src/frontends/android/app/src/main/res/values-ru/strings.xml b/src/frontends/android/app/src/main/res/values-ru/strings.xml
index ec9d66334..c0a6484f8 100644
--- a/src/frontends/android/app/src/main/res/values-ru/strings.xml
+++ b/src/frontends/android/app/src/main/res/values-ru/strings.xml
@@ -46,29 +46,39 @@
Сохранить
Отмена
- Название профиля:
- (адрес cервер)
- Сервер:
- Тип:
- Логин:
- Пароль:
- (спросить если нужно)
- Сертификат пользователя:
+ Название профиля (необязательный)
+ Defaults to the configured server
+ Defaults to \"%1$s\"
+ Сервер
+ IP address or hostname of the VPN server
+ VPN Тип
+ Логин
+ Пароль (необязательный)
+ Leave blank to get prompted on demand
+ Сертификат пользователя
Выбрать сертификат пользователя
Выбрать сертификат пользователя
- Сертификат CA:
+ User identity
+ Select a certificate first
+ Default (%1$s)
+ Сертификат CA
Выбрать автоматически
Выбрать сертификат CA
Выбрать CA сертификат
+ Advanced settings
Show advanced settings
- MTU:
- Server port:
- (use default)
- Split tunneling:
+ Server identity
+ Defaults to the configured server. Custom values are explicitly sent to the server and enforced during authentication
+ Defaults to \"%1$s\". Custom values are explicitly sent to the server and enforced during authentication
+ MTU of the VPN tunnel device
+ In case the default value is unsuitable for a particular network
+ Server port
+ UDP port to connect to, if different from the default
+ Split tunneling
Block IPv4 traffic not destined for the VPN
Block IPv6 traffic not destined for the VPN
- Пожалуйста введите адрес cервер
+ A value is required to initiate the connection
Пожалуйста введите имя пользователя
Не выбран сертификат CA
Пожалуйста выберите один Выбрать автоматически
@@ -119,8 +129,6 @@
Ошибка авторизации пользователя.
Security assessment failed.
Неизвестная ошибка.
- Подключение: %1$s
- Подключение к VPN с \""%1$s\".
Соединение с VPN установлено
Подключение к этому профилю VPN уже существует!
Переподключить
@@ -129,4 +137,3 @@
Соединить
-
diff --git a/src/frontends/android/app/src/main/res/values-ua/strings.xml b/src/frontends/android/app/src/main/res/values-ua/strings.xml
index a5e668b84..a852dfdba 100644
--- a/src/frontends/android/app/src/main/res/values-ua/strings.xml
+++ b/src/frontends/android/app/src/main/res/values-ua/strings.xml
@@ -47,30 +47,40 @@
Зберегти
Відміна
- Назва профілю:
- (використовувати адресу cервер)
- Сервер:
- Тип:
- Логін:
- Пароль:
- (запитати якщо потрібно)
- Сертифікат користувача:
+ Назва профілю (необов\'язковий)
+ Defaults to the configured server
+ Defaults to \"%1$s\"
+ Сервер
+ IP address or hostname of the VPN server
+ VPN Тип
+ Логін
+ Пароль (необов\'язковий)
+ Leave blank to get prompted on demand
+ Сертифікат користувача
Виберіть сертифікат користувача
Вибрати спеціальний сертифікат користувача
- Сертифікат CA:
+ User identity
+ Select a certificate first
+ Default (%1$s)
+ Сертифікат CA
Вибрати автоматично
Вибрати сертифікат CA
Вибрати спеціальний сертифікат CA
+ Advanced settings
Show advanced settings
- MTU:
- Server port:
- (use default)
- Split tunneling:
+ Server identity
+ Defaults to the configured server. Custom values are explicitly sent to the server and enforced during authentication
+ Defaults to \"%1$s\". Custom values are explicitly sent to the server and enforced during authentication
+ MTU of the VPN tunnel device
+ In case the default value is unsuitable for a particular network
+ Server port
+ UDP port to connect to, if different from the default
+ Split tunneling
Block IPv4 traffic not destined for the VPN
Block IPv6 traffic not destined for the VPN
- Введіть адресу cервер тут
- Введіть ім\'я користувача тут
+ A value is required to initiate the connection
+ Введіть ім\'я користувача
Не вибрано сертифікат CA
Будь ласка виберіть один Вибрати автоматично
Please enter a number in the range from %1$d - %2$d
@@ -120,8 +130,6 @@
Помилка аутентифікації користувача.
Security assessment failed.
Невідома помилка під час підключення.
- Підключення: %1$s
- Підключення VPN з \""%1$s\".
VPN підключено
Цей VPN профіль зараз підключений!
Перепідключитися
@@ -130,4 +138,3 @@
Підключити
-
diff --git a/src/frontends/android/app/src/main/res/values/attrs.xml b/src/frontends/android/app/src/main/res/values/attrs.xml
index 6c3480b0e..592555c8c 100644
--- a/src/frontends/android/app/src/main/res/values/attrs.xml
+++ b/src/frontends/android/app/src/main/res/values/attrs.xml
@@ -1,7 +1,7 @@
Save
Cancel
- Profile Name:
- (use server address)
- Server:
- Type:
- Username:
- Password:
- (prompt when needed)
- User certificate:
+ Profile name (optional)
+ Defaults to the configured server
+ Defaults to \"%1$s\"
+ Server
+ IP address or hostname of the VPN server
+ VPN Type
+ Username
+ Password (optional)
+ Leave blank to get prompted on demand
+ User certificate
Select user certificate
Select a specific user certificate
- CA certificate:
+ User identity
+ Select a certificate first
+ Default (%1$s)
+ CA certificate
Select automatically
Select CA certificate
Select a specific CA certificate
+ Advanced settings
Show advanced settings
- MTU:
- Server port:
- (use default)
- Split tunneling:
+ Server identity
+ Defaults to the configured server. Custom values are explicitly sent to the server and enforced during authentication
+ Defaults to \"%1$s\". Custom values are explicitly sent to the server and enforced during authentication
+ MTU of the VPN tunnel device
+ In case the default value is unsuitable for a particular network
+ Server port
+ UDP port to connect to, if different from the default
+ Split tunneling
Block IPv4 traffic not destined for the VPN
Block IPv6 traffic not destined for the VPN
- Please enter the server address here
- Please enter your username here
+ A value is required to initiate the connection
+ Please enter your username
No CA certificate selected
Please select one or activate Select automatically
Please enter a number in the range from %1$d - %2$d
@@ -122,8 +132,6 @@
User authentication failed.
Security assessment failed.
Unspecified failure while connecting.
- Connecting: %1$s
- Establishing VPN with \""%1$s\".
VPN connected
This VPN profile is currently connected!
Reconnect