Merge branch 'android-gui-updates'

Removes the progress dialogs while connecting/disconnecting, updates
the VPN profile editor (floating labels, helper texts) and allows
configuration of the remote identity (disables loose identity matching),
and selection of the local identity if certificates are used.

Also fixes an issue when redirected during IKE_AUTH and increases the
NAT-T keepalive interval.

Fixes #1403.
This commit is contained in:
Tobias Brunner 2016-05-02 18:39:26 +02:00
commit 5b85df674a
31 changed files with 1074 additions and 457 deletions

View File

@ -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.

View File

@ -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;

View File

@ -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;
}

View File

@ -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

View File

@ -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<TrustedCertificateEntry>
{
private final X509Certificate mCert;
@ -86,6 +91,40 @@ public class TrustedCertificateEntry implements Comparable<TrustedCertificateEnt
return mSubjectSecondary;
}
/**
* Get a sorted list of all rfc822Name, dnSName and iPAddress subjectAltNames
*
* @return sorted list of selected SANs
*/
public List<String> getSubjectAltNames()
{
List<String> list = new ArrayList<>();
try
{
Collection<List<?>> 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.
*

View File

@ -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);

View File

@ -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;

View File

@ -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)
{

View File

@ -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

View File

@ -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());
}
}

View File

@ -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;
}
}

View File

@ -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<String> 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 + " ";
}
}
}
}
}

View File

@ -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;
}
}

View File

@ -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<RemediationInstruction> instructions = mService.getRemediationInstructions();

View File

@ -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 <http://www.fsf.org/copyleft/gpl.txt>.
*
* 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<String>
{
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());
}
}
}

View File

@ -63,12 +63,18 @@ public class VpnProfileAdapter extends ArrayAdapter<VpnProfile>
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<VpnProfile>
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

View File

@ -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 <http://www.fsf.org/copyleft/gpl.txt>.
*
* 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();
}
}
}

View File

@ -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, \

View File

@ -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,

View File

@ -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,

View File

@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright (C) 2012 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
@ -15,37 +15,41 @@
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
for more details.
-->
<LinearLayout
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="match_parent"
android:layout_width="match_parent"
android:orientation="vertical"
android:padding="5dp"
xmlns:android="http://schemas.android.com/apk/res/android">
android:padding="10dp" >
<TextView
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:text="@string/profile_username_label"
android:textStyle="bold" />
<EditText
android:layout_height="wrap_content"
<android.support.design.widget.TextInputLayout
android:id="@+id/username_wrap"
android:layout_width="match_parent"
android:id="@+id/username"
android:enabled="false"
android:inputType="none" />
android:layout_height="wrap_content" >
<TextView
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:text="@string/profile_password_label"
android:textStyle="bold" />
<android.support.design.widget.TextInputEditText
android:id="@+id/username"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:enabled="false"
android:inputType="none"
android:hint="@string/profile_username_label" />
<EditText
android:layout_height="wrap_content"
</android.support.design.widget.TextInputLayout>
<android.support.design.widget.TextInputLayout
android:id="@+id/password_wrap"
android:layout_width="match_parent"
android:id="@+id/password"
android:inputType="textPassword|textNoSuggestions"
android:singleLine="true" />
android:layout_height="wrap_content"
android:layout_marginTop="4dp" >
<android.support.design.widget.TextInputEditText
android:id="@+id/password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:singleLine="true"
android:inputType="textPassword|textNoSuggestions"
android:hint="@string/profile_password_label" />
</android.support.design.widget.TextInputLayout>
</LinearLayout>

View File

@ -1,9 +1,9 @@
<?xml version="1.0" encoding="utf-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
@ -16,46 +16,39 @@
for more details.
-->
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="10dp" >
android:padding="10dp"
android:animateLayoutChanges="true" >
<org.strongswan.android.ui.widget.TextInputLayoutHelper
android:id="@+id/gateway_wrap"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
app:helper_text="@string/profile_gateway_hint" >
<android.support.design.widget.TextInputEditText
android:id="@+id/gateway"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:singleLine="true"
android:inputType="textNoSuggestions"
android:hint="@string/profile_gateway_label" />
</org.strongswan.android.ui.widget.TextInputLayoutHelper>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:text="@string/profile_name_label" />
<EditText
android:id="@+id/name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:singleLine="true"
android:inputType="textNoSuggestions"
android:hint="@string/profile_name_hint" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:text="@string/profile_gateway_label" />
<EditText
android:id="@+id/gateway"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:singleLine="true"
android:inputType="textNoSuggestions" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:layout_marginLeft="4dp"
android:textSize="12sp"
android:text="@string/profile_vpn_type_label" />
<Spinner
@ -76,32 +69,37 @@
android:layout_height="wrap_content"
android:orientation="vertical" >
<TextView
<org.strongswan.android.ui.widget.TextInputLayoutHelper
android:id="@+id/username_wrap"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:text="@string/profile_username_label" />
android:layout_height="wrap_content" >
<EditText
android:id="@+id/username"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:singleLine="true"
android:inputType="textNoSuggestions" />
<android.support.design.widget.TextInputEditText
android:id="@+id/username"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:singleLine="true"
android:inputType="textNoSuggestions"
android:hint="@string/profile_username_label" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:text="@string/profile_password_label" />
</org.strongswan.android.ui.widget.TextInputLayoutHelper>
<EditText
android:id="@+id/password"
<org.strongswan.android.ui.widget.TextInputLayoutHelper
android:id="@+id/password_wrap"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:singleLine="true"
android:inputType="textPassword|textNoSuggestions"
android:hint="@string/profile_password_hint" />
android:layout_marginTop="4dp"
app:helper_text="@string/profile_password_hint" >
<android.support.design.widget.TextInputEditText
android:id="@+id/password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:singleLine="true"
android:inputType="textPassword|textNoSuggestions"
android:hint="@string/profile_password_label" />
</org.strongswan.android.ui.widget.TextInputLayoutHelper>
</LinearLayout>
@ -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" >
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:layout_marginTop="4dp"
android:layout_marginLeft="4dp"
android:textSize="12sp"
android:text="@string/profile_user_certificate_label" />
<include
android:id="@+id/select_user_certificate"
layout="@layout/two_line_button" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:layout_marginLeft="4dp"
android:textSize="12sp"
android:text="@string/profile_user_select_id_label" />
<Spinner
android:id="@+id/select_user_id"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:spinnerMode="dropdown" />
</LinearLayout>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:layout_marginLeft="4dp"
android:textSize="12sp"
android:text="@string/profile_ca_label" />
<CheckBox
android:id="@+id/ca_auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="@string/profile_ca_auto_label" />
<include
android:id="@+id/select_certificate"
layout="@layout/two_line_button" />
<org.strongswan.android.ui.widget.TextInputLayoutHelper
android:id="@+id/name_wrap"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
app:helper_text="@string/profile_name_hint" >
<MultiAutoCompleteTextView
android:id="@+id/name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:singleLine="true"
android:inputType="textNoSuggestions"
android:completionThreshold="1"
android:hint="@string/profile_name_label" />
</org.strongswan.android.ui.widget.TextInputLayoutHelper>
<CheckBox
android:id="@+id/show_advanced"
android:layout_width="match_parent"
@ -155,34 +190,66 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:text="@string/profile_mtu_label" />
android:layout_marginLeft="4dp"
android:textSize="20sp"
android:text="@string/profile_advanced_label" />
<EditText
android:id="@+id/mtu"
<org.strongswan.android.ui.widget.TextInputLayoutHelper
android:id="@+id/remote_id_wrap"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:singleLine="true"
android:inputType="number|textNoSuggestions"
android:hint="@string/profile_use_default_hint" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:text="@string/profile_port_label" />
<EditText
android:id="@+id/port"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:singleLine="true"
android:inputType="number|textNoSuggestions"
android:hint="@string/profile_use_default_hint" />
android:layout_marginTop="10dp"
app:helper_text="@string/profile_remote_id_hint" >
<MultiAutoCompleteTextView
android:id="@+id/remote_id"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:singleLine="true"
android:inputType="textNoSuggestions"
android:completionThreshold="1"
android:hint="@string/profile_remote_id_label" />
</org.strongswan.android.ui.widget.TextInputLayoutHelper>
<org.strongswan.android.ui.widget.TextInputLayoutHelper
android:id="@+id/mtu_wrap"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:helper_text="@string/profile_mtu_hint" >
<android.support.design.widget.TextInputEditText
android:id="@+id/mtu"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:singleLine="true"
android:inputType="number|textNoSuggestions"
android:hint="@string/profile_mtu_label" />
</org.strongswan.android.ui.widget.TextInputLayoutHelper>
<org.strongswan.android.ui.widget.TextInputLayoutHelper
android:id="@+id/port_wrap"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:helper_text="@string/profile_port_hint" >
<android.support.design.widget.TextInputEditText
android:id="@+id/port"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:singleLine="true"
android:inputType="number|textNoSuggestions"
android:hint="@string/profile_port_label" />
</org.strongswan.android.ui.widget.TextInputLayoutHelper>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:layout_marginLeft="4dp"
android:textSize="12sp"
android:text="@string/profile_split_tunneling_label" />
<CheckBox
@ -198,6 +265,7 @@
android:text="@string/profile_split_tunnelingv6_title" />
</LinearLayout>
</LinearLayout>
</ScrollView>
</ScrollView>

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
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
@ -45,8 +45,8 @@
android:textIsSelectable="true"
android:textAppearance="?android:attr/textAppearanceMedium" />
<include
layout="@android:layout/list_content"
<FrameLayout
android:id="@+id/list_container"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"

View File

@ -14,7 +14,7 @@
for more details.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:strongswan="http://schemas.android.com/apk/res/org.strongswan.android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
@ -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" />
</LinearLayout>

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-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
@ -72,6 +72,17 @@
</TextView>
</GridLayout>
<ProgressBar
android:id="@+id/progress"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp"
android:indeterminate="true"
android:visibility="gone"
style="@style/Widget.AppCompat.ProgressBar.Horizontal" />
<Button
android:id="@+id/action"
android:layout_width="match_parent"

View File

@ -49,30 +49,40 @@
<!-- VPN profile details -->
<string name="profile_edit_save">Speichern</string>
<string name="profile_edit_cancel">Abbrechen</string>
<string name="profile_name_label">Profilname:</string>
<string name="profile_name_hint">(Server-Adresse verwenden)</string>
<string name="profile_gateway_label">Server:</string>
<string name="profile_vpn_type_label">Typ:</string>
<string name="profile_username_label">Benutzername:</string>
<string name="profile_password_label">Passwort:</string>
<string name="profile_password_hint">(anfordern wenn benötigt)</string>
<string name="profile_user_certificate_label">Benutzer-Zertifikat:</string>
<string name="profile_name_label">Profilname (optional)</string>
<string name="profile_name_hint">Standardwert ist der konfigurierte Server</string>
<string name="profile_name_hint_gateway">Standardwert ist \"%1$s\"</string>
<string name="profile_gateway_label">Server</string>
<string name="profile_gateway_hint">IP-Adresse oder Hostname des VPN Servers</string>
<string name="profile_vpn_type_label">VPN-Typ</string>
<string name="profile_username_label">Benutzername</string>
<string name="profile_password_label">Passwort (optional)</string>
<string name="profile_password_hint">Leer lassen, um bei Bedarf danach gefragt zu werden</string>
<string name="profile_user_certificate_label">Benutzer-Zertifikat</string>
<string name="profile_user_select_certificate_label">Benutzer-Zertifikat auswählen</string>
<string name="profile_user_select_certificate">Wählen Sie ein bestimmtes Benutzer-Zertifikat</string>
<string name="profile_ca_label">CA-Zertifikat:</string>
<string name="profile_user_select_id_label">Benutzer-Identität</string>
<string name="profile_user_select_id_init">Wählen Sie zuerst ein Benutzer-Zertifikat</string>
<string name="profile_user_select_id_default">Standardwert (%1$s)</string>
<string name="profile_ca_label">CA-Zertifikat</string>
<string name="profile_ca_auto_label">Automatisch wählen</string>
<string name="profile_ca_select_certificate_label">CA-Zertifikat auswählen</string>
<string name="profile_ca_select_certificate">Wählen Sie ein bestimmtes CA-Zertifikat</string>
<string name="profile_advanced_label">Erweiterte Einstellungen</string>
<string name="profile_show_advanced_label">Erweiterte Einstellungen anzeigen</string>
<string name="profile_mtu_label">MTU:</string>
<string name="profile_port_label">Server Port:</string>
<string name="profile_use_default_hint">(Standardwert verwenden)</string>
<string name="profile_split_tunneling_label">Split-Tunneling:</string>
<string name="profile_remote_id_label">Server-Identität</string>
<string name="profile_remote_id_hint">Standardwert ist der konfigurierte Server. Eigene Werte werden explizit and den Server gesendet und während der Authentifizierung erzwungen</string>
<string name="profile_remote_id_hint_gateway">Standardwert ist \"%1$s\". Eigene Werte werden explizit and den Server gesendet und während der Authentifizierung erzwungen</string>
<string name="profile_mtu_label">MTU des VPN Tunnel-Device</string>
<string name="profile_mtu_hint">Falls der Standardwert in einem bestimmten Netzwerk nicht geeignet ist</string>
<string name="profile_port_label">Server Port</string>
<string name="profile_port_hint">UDP-Port zu dem verbunden wird, falls dieser vom Standard-Port abweicht</string>
<string name="profile_split_tunneling_label">Split-Tunneling</string>
<string name="profile_split_tunnelingv4_title">Blockiere IPv4 Verkehr der nicht für das VPN bestimmt ist</string>
<string name="profile_split_tunnelingv6_title">Blockiere IPv6 Verkehr der nicht für das VPN bestimmt ist</string>
<!-- Warnings/Notifications in the details view -->
<string name="alert_text_no_input_gateway">Bitte geben Sie hier die Server-Adresse ein</string>
<string name="alert_text_no_input_username">Bitte geben Sie hier Ihren Benutzernamen ein</string>
<string name="alert_text_no_input_gateway">Ein Wert wird benötigt, um die Verbindung aufbauen zu können</string>
<string name="alert_text_no_input_username">Bitte geben Sie Ihren Benutzernamen ein</string>
<string name="alert_text_nocertfound_title">Kein CA-Zertifikat ausgewählt</string>
<string name="alert_text_nocertfound">Bitte wählen Sie eines aus oder aktivieren Sie <i>Automatisch wählen</i></string>
<string name="alert_text_out_of_range">Bitte geben Sie eine Nummer von %1$d - %2$d ein</string>
@ -122,8 +132,6 @@
<string name="error_auth_failed">Benutzerauthentifizierung ist fehlgeschlagen.</string>
<string name="error_assessment_failed">Sicherheitsassessment ist fehlgeschlagen.</string>
<string name="error_generic">Unbekannter Fehler während des Verbindens.</string>
<string name="connecting_title">Verbinden: %1$s</string>
<string name="connecting_message">Verbinde mit \""%1$s\".</string>
<string name="vpn_connected">VPN verbunden</string>
<string name="vpn_profile_connected">Dieses VPN Profil ist momentan verbunden!</string>
<string name="reconnect">Neu verbinden</string>

View File

@ -49,36 +49,46 @@
<!-- VPN profile details -->
<string name="profile_edit_save">Zapisz</string>
<string name="profile_edit_cancel">Anuluj</string>
<string name="profile_name_label">Nazwa profilu:</string>
<string name="profile_name_hint">(użyj adresu serwer)</string>
<string name="profile_gateway_label">Serwer:</string>
<string name="profile_vpn_type_label">Typ:</string>
<string name="profile_username_label">Użytkownik:</string>
<string name="profile_password_label">Hasło:</string>
<string name="profile_password_hint">(w razie potrzeby zapromptuj)</string>
<string name="profile_user_certificate_label">Certyfikat użytkownika:</string>
<string name="profile_name_label">Nazwa profilu (opcjonalny)</string>
<string name="profile_name_hint">Defaults to the configured server</string>
<string name="profile_name_hint_gateway">Defaults to \"%1$s\"</string>
<string name="profile_gateway_label">Serwer</string>
<string name="profile_gateway_hint">IP address or hostname of the VPN server</string>
<string name="profile_vpn_type_label">Typ VPN</string>
<string name="profile_username_label">Użytkownik</string>
<string name="profile_password_label">Hasło (opcjonalny)</string>
<string name="profile_password_hint">Leave blank to get prompted on demand</string>
<string name="profile_user_certificate_label">Certyfikat użytkownika</string>
<string name="profile_user_select_certificate_label">Wybierz certyfikat użytkownika</string>
<string name="profile_user_select_certificate">>Wybierz określony certyfikat użytkownika</string>
<string name="profile_ca_label">Certyfikat CA:</string>
<string name="profile_user_select_id_label">User identity</string>
<string name="profile_user_select_id_init">Select a certificate first</string>
<string name="profile_user_select_id_default">Default (%1$s)</string>
<string name="profile_ca_label">Certyfikat CA</string>
<string name="profile_ca_auto_label">Wybierz automatycznie</string>
<string name="profile_ca_select_certificate_label">Wybierz certyfikat CA</string>
<string name="profile_ca_select_certificate">Wybierz określony certyfikat CA</string>
<string name="profile_advanced_label">Advanced settings</string>
<string name="profile_show_advanced_label">Show advanced settings</string>
<string name="profile_mtu_label">MTU:</string>
<string name="profile_port_label">Server port:</string>
<string name="profile_use_default_hint">(use default)</string>
<string name="profile_split_tunneling_label">Split tunneling:</string>
<string name="profile_remote_id_label">Server identity</string>
<string name="profile_remote_id_hint">Defaults to the configured server. Custom values are explicitly sent to the server and enforced during authentication</string>
<string name="profile_remote_id_hint_gateway">Defaults to \"%1$s\". Custom values are explicitly sent to the server and enforced during authentication</string>
<string name="profile_mtu_label">MTU of the VPN tunnel device</string>
<string name="profile_mtu_hint">In case the default value is unsuitable for a particular network</string>
<string name="profile_port_label">Server port</string>
<string name="profile_port_hint">UDP port to connect to, if different from the default</string>
<string name="profile_split_tunneling_label">Split tunneling</string>
<string name="profile_split_tunnelingv4_title">Block IPv4 traffic not destined for the VPN</string>
<string name="profile_split_tunnelingv6_title">Block IPv6 traffic not destined for the VPN</string>
<!-- Warnings/Notifications in the details view -->
<string name="alert_text_no_input_gateway">Wprowadź adres serwer</string>
<string name="alert_text_no_input_gateway">A value is required to initiate the connection</string>
<string name="alert_text_no_input_username">Wprowadź swoją nazwę użytkownika</string>
<string name="alert_text_nocertfound_title">Nie wybrano żadnego certyfikatu CA</string>
<string name="alert_text_nocertfound">Wybierz lub uaktywnij jeden <i>Wybierz automatycznie</i></string>
<string name="alert_text_out_of_range">Please enter a number in the range from %1$d - %2$d</string>
<string name="tnc_notice_title">EAP-TNC may affect your privacy</string>
<string name="tnc_notice_subtitle">Device data is sent to the server operator</string>
<string name="tnc_notice_details">&lt;p>Trusted Network Connect (TNC) allows server operators to assess the health of a client device.&lt;/p>&lt;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.&lt;/p>&lt;b>Any data will be sent only after verifying the server\'s identity.&lt;/b></string>
<string name="tnc_notice_details"><![CDATA[<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>]]></string>
<!-- Trusted certificate selection -->
<string name="trusted_certs_title">Certyfikaty CA</string>
@ -122,8 +132,6 @@
<string name="error_auth_failed">Błąd przy autoryzacji użytkownika</string>
<string name="error_assessment_failed">Security assessment failed.</string>
<string name="error_generic">Nieznany błąd w czasie połączenia</string>
<string name="connecting_title">Łączenie: %1$s</string>
<string name="connecting_message">Tworzenie tunelu VPN z \""%1$s\".</string>
<string name="vpn_connected">Połączenie z VPN</string>
<string name="vpn_profile_connected">Ten profil VPN jest obecnie połaczony!</string>
<string name="reconnect">Połączyć ponownie</string>

View File

@ -46,29 +46,39 @@
<!-- VPN profile details -->
<string name="profile_edit_save">Сохранить</string>
<string name="profile_edit_cancel">Отмена</string>
<string name="profile_name_label">Название профиля:</string>
<string name="profile_name_hint">(адрес cервер)</string>
<string name="profile_gateway_label">Сервер:</string>
<string name="profile_vpn_type_label">Тип:</string>
<string name="profile_username_label">Логин:</string>
<string name="profile_password_label">Пароль:</string>
<string name="profile_password_hint">(спросить если нужно)</string>
<string name="profile_user_certificate_label">Сертификат пользователя:</string>
<string name="profile_name_label">Название профиля (необязательный)</string>
<string name="profile_name_hint">Defaults to the configured server</string>
<string name="profile_name_hint_gateway">Defaults to \"%1$s\"</string>
<string name="profile_gateway_label">Сервер</string>
<string name="profile_gateway_hint">IP address or hostname of the VPN server</string>
<string name="profile_vpn_type_label">VPN Тип</string>
<string name="profile_username_label">Логин</string>
<string name="profile_password_label">Пароль (необязательный)</string>
<string name="profile_password_hint">Leave blank to get prompted on demand</string>
<string name="profile_user_certificate_label">Сертификат пользователя</string>
<string name="profile_user_select_certificate_label">Выбрать сертификат пользователя</string>
<string name="profile_user_select_certificate">Выбрать сертификат пользователя</string>
<string name="profile_ca_label">Сертификат CA:</string>
<string name="profile_user_select_id_label">User identity</string>
<string name="profile_user_select_id_init">Select a certificate first</string>
<string name="profile_user_select_id_default">Default (%1$s)</string>
<string name="profile_ca_label">Сертификат CA</string>
<string name="profile_ca_auto_label">Выбрать автоматически</string>
<string name="profile_ca_select_certificate_label">Выбрать сертификат CA</string>
<string name="profile_ca_select_certificate">Выбрать CA сертификат</string>
<string name="profile_advanced_label">Advanced settings</string>
<string name="profile_show_advanced_label">Show advanced settings</string>
<string name="profile_mtu_label">MTU:</string>
<string name="profile_port_label">Server port:</string>
<string name="profile_use_default_hint">(use default)</string>
<string name="profile_split_tunneling_label">Split tunneling:</string>
<string name="profile_remote_id_label">Server identity</string>
<string name="profile_remote_id_hint">Defaults to the configured server. Custom values are explicitly sent to the server and enforced during authentication</string>
<string name="profile_remote_id_hint_gateway">Defaults to \"%1$s\". Custom values are explicitly sent to the server and enforced during authentication</string>
<string name="profile_mtu_label">MTU of the VPN tunnel device</string>
<string name="profile_mtu_hint">In case the default value is unsuitable for a particular network</string>
<string name="profile_port_label">Server port</string>
<string name="profile_port_hint">UDP port to connect to, if different from the default</string>
<string name="profile_split_tunneling_label">Split tunneling</string>
<string name="profile_split_tunnelingv4_title">Block IPv4 traffic not destined for the VPN</string>
<string name="profile_split_tunnelingv6_title">Block IPv6 traffic not destined for the VPN</string>
<!-- Warnings/Notifications in the details view -->
<string name="alert_text_no_input_gateway">Пожалуйста введите адрес cервер</string>
<string name="alert_text_no_input_gateway">A value is required to initiate the connection</string>
<string name="alert_text_no_input_username">Пожалуйста введите имя пользователя</string>
<string name="alert_text_nocertfound_title">Не выбран сертификат CA</string>
<string name="alert_text_nocertfound">Пожалуйста выберите один <i>Выбрать автоматически</i></string>
@ -119,8 +129,6 @@
<string name="error_auth_failed">Ошибка авторизации пользователя.</string>
<string name="error_assessment_failed">Security assessment failed.</string>
<string name="error_generic">Неизвестная ошибка.</string>
<string name="connecting_title">Подключение: %1$s</string>
<string name="connecting_message">Подключение к VPN с \""%1$s\".</string>
<string name="vpn_connected">Соединение с VPN установлено</string>
<string name="vpn_profile_connected">Подключение к этому профилю VPN уже существует!</string>
<string name="reconnect">Переподключить</string>
@ -129,4 +137,3 @@
<string name="connect">Соединить</string>
</resources>

View File

@ -47,30 +47,40 @@
<!-- VPN profile details -->
<string name="profile_edit_save">Зберегти</string>
<string name="profile_edit_cancel">Відміна</string>
<string name="profile_name_label">Назва профілю:</string>
<string name="profile_name_hint">(використовувати адресу cервер)</string>
<string name="profile_gateway_label">Сервер:</string>
<string name="profile_vpn_type_label">Тип:</string>
<string name="profile_username_label">Логін:</string>
<string name="profile_password_label">Пароль:</string>
<string name="profile_password_hint">(запитати якщо потрібно)</string>
<string name="profile_user_certificate_label">Сертифікат користувача:</string>
<string name="profile_name_label">Назва профілю (необов\'язковий)</string>
<string name="profile_name_hint">Defaults to the configured server</string>
<string name="profile_name_hint_gateway">Defaults to \"%1$s\"</string>
<string name="profile_gateway_label">Сервер</string>
<string name="profile_gateway_hint">IP address or hostname of the VPN server</string>
<string name="profile_vpn_type_label">VPN Тип</string>
<string name="profile_username_label">Логін</string>
<string name="profile_password_label">Пароль (необов\'язковий)</string>
<string name="profile_password_hint">Leave blank to get prompted on demand</string>
<string name="profile_user_certificate_label">Сертифікат користувача</string>
<string name="profile_user_select_certificate_label">Виберіть сертифікат користувача</string>
<string name="profile_user_select_certificate">Вибрати спеціальний сертифікат користувача</string>
<string name="profile_ca_label">Сертифікат CA:</string>
<string name="profile_user_select_id_label">User identity</string>
<string name="profile_user_select_id_init">Select a certificate first</string>
<string name="profile_user_select_id_default">Default (%1$s)</string>
<string name="profile_ca_label">Сертифікат CA</string>
<string name="profile_ca_auto_label">Вибрати автоматично</string>
<string name="profile_ca_select_certificate_label">Вибрати сертифікат CA</string>
<string name="profile_ca_select_certificate">Вибрати спеціальний сертифікат CA</string>
<string name="profile_advanced_label">Advanced settings</string>
<string name="profile_show_advanced_label">Show advanced settings</string>
<string name="profile_mtu_label">MTU:</string>
<string name="profile_port_label">Server port:</string>
<string name="profile_use_default_hint">(use default)</string>
<string name="profile_split_tunneling_label">Split tunneling:</string>
<string name="profile_remote_id_label">Server identity</string>
<string name="profile_remote_id_hint">Defaults to the configured server. Custom values are explicitly sent to the server and enforced during authentication</string>
<string name="profile_remote_id_hint_gateway">Defaults to \"%1$s\". Custom values are explicitly sent to the server and enforced during authentication</string>
<string name="profile_mtu_label">MTU of the VPN tunnel device</string>
<string name="profile_mtu_hint">In case the default value is unsuitable for a particular network</string>
<string name="profile_port_label">Server port</string>
<string name="profile_port_hint">UDP port to connect to, if different from the default</string>
<string name="profile_split_tunneling_label">Split tunneling</string>
<string name="profile_split_tunnelingv4_title">Block IPv4 traffic not destined for the VPN</string>
<string name="profile_split_tunnelingv6_title">Block IPv6 traffic not destined for the VPN</string>
<!-- Warnings/Notifications in the details view -->
<string name="alert_text_no_input_gateway">Введіть адресу cервер тут</string>
<string name="alert_text_no_input_username">Введіть ім\'я користувача тут</string>
<string name="alert_text_no_input_gateway">A value is required to initiate the connection</string>
<string name="alert_text_no_input_username">Введіть ім\'я користувача </string>
<string name="alert_text_nocertfound_title">Не вибрано сертифікат CA</string>
<string name="alert_text_nocertfound">Будь ласка виберіть один <i>Вибрати автоматично</i></string>
<string name="alert_text_out_of_range">Please enter a number in the range from %1$d - %2$d</string>
@ -120,8 +130,6 @@
<string name="error_auth_failed">Помилка аутентифікації користувача.</string>
<string name="error_assessment_failed">Security assessment failed.</string>
<string name="error_generic">Невідома помилка під час підключення.</string>
<string name="connecting_title">Підключення: %1$s</string>
<string name="connecting_message">Підключення VPN з \""%1$s\".</string>
<string name="vpn_connected">VPN підключено</string>
<string name="vpn_profile_connected">Цей VPN профіль зараз підключений!</string>
<string name="reconnect">Перепідключитися</string>
@ -130,4 +138,3 @@
<string name="connect">Підключити</string>
</resources>

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
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
@ -17,4 +17,7 @@
<declare-styleable name="Fragment">
<attr name="read_only" format="boolean" />
</declare-styleable>
<declare-styleable name="TextInputLayoutHelper">
<attr name="helper_text" format="string" />
</declare-styleable>
</resources>

View File

@ -49,30 +49,40 @@
<!-- VPN profile details -->
<string name="profile_edit_save">Save</string>
<string name="profile_edit_cancel">Cancel</string>
<string name="profile_name_label">Profile Name:</string>
<string name="profile_name_hint">(use server address)</string>
<string name="profile_gateway_label">Server:</string>
<string name="profile_vpn_type_label">Type:</string>
<string name="profile_username_label">Username:</string>
<string name="profile_password_label">Password:</string>
<string name="profile_password_hint">(prompt when needed)</string>
<string name="profile_user_certificate_label">User certificate:</string>
<string name="profile_name_label">Profile name (optional)</string>
<string name="profile_name_hint">Defaults to the configured server</string>
<string name="profile_name_hint_gateway">Defaults to \"%1$s\"</string>
<string name="profile_gateway_label">Server</string>
<string name="profile_gateway_hint">IP address or hostname of the VPN server</string>
<string name="profile_vpn_type_label">VPN Type</string>
<string name="profile_username_label">Username</string>
<string name="profile_password_label">Password (optional)</string>
<string name="profile_password_hint">Leave blank to get prompted on demand</string>
<string name="profile_user_certificate_label">User certificate</string>
<string name="profile_user_select_certificate_label">Select user certificate</string>
<string name="profile_user_select_certificate">Select a specific user certificate</string>
<string name="profile_ca_label">CA certificate:</string>
<string name="profile_user_select_id_label">User identity</string>
<string name="profile_user_select_id_init">Select a certificate first</string>
<string name="profile_user_select_id_default">Default (%1$s)</string>
<string name="profile_ca_label">CA certificate</string>
<string name="profile_ca_auto_label">Select automatically</string>
<string name="profile_ca_select_certificate_label">Select CA certificate</string>
<string name="profile_ca_select_certificate">Select a specific CA certificate</string>
<string name="profile_advanced_label">Advanced settings</string>
<string name="profile_show_advanced_label">Show advanced settings</string>
<string name="profile_mtu_label">MTU:</string>
<string name="profile_port_label">Server port:</string>
<string name="profile_use_default_hint">(use default)</string>
<string name="profile_split_tunneling_label">Split tunneling:</string>
<string name="profile_remote_id_label">Server identity</string>
<string name="profile_remote_id_hint">Defaults to the configured server. Custom values are explicitly sent to the server and enforced during authentication</string>
<string name="profile_remote_id_hint_gateway">Defaults to \"%1$s\". Custom values are explicitly sent to the server and enforced during authentication</string>
<string name="profile_mtu_label">MTU of the VPN tunnel device</string>
<string name="profile_mtu_hint">In case the default value is unsuitable for a particular network</string>
<string name="profile_port_label">Server port</string>
<string name="profile_port_hint">UDP port to connect to, if different from the default</string>
<string name="profile_split_tunneling_label">Split tunneling</string>
<string name="profile_split_tunnelingv4_title">Block IPv4 traffic not destined for the VPN</string>
<string name="profile_split_tunnelingv6_title">Block IPv6 traffic not destined for the VPN</string>
<!-- Warnings/Notifications in the details view -->
<string name="alert_text_no_input_gateway">Please enter the server address here</string>
<string name="alert_text_no_input_username">Please enter your username here</string>
<string name="alert_text_no_input_gateway">A value is required to initiate the connection</string>
<string name="alert_text_no_input_username">Please enter your username </string>
<string name="alert_text_nocertfound_title">No CA certificate selected</string>
<string name="alert_text_nocertfound">Please select one or activate <i>Select automatically</i></string>
<string name="alert_text_out_of_range">Please enter a number in the range from %1$d - %2$d</string>
@ -122,8 +132,6 @@
<string name="error_auth_failed">User authentication failed.</string>
<string name="error_assessment_failed">Security assessment failed.</string>
<string name="error_generic">Unspecified failure while connecting.</string>
<string name="connecting_title">Connecting: %1$s</string>
<string name="connecting_message">Establishing VPN with \""%1$s\".</string>
<string name="vpn_connected">VPN connected</string>
<string name="vpn_profile_connected">This VPN profile is currently connected!</string>
<string name="reconnect">Reconnect</string>