Merge branch 'android-import'
Adds a VPN profile import feature.
This commit is contained in:
@@ -8,8 +8,8 @@ android {
|
|||||||
applicationId "org.strongswan.android"
|
applicationId "org.strongswan.android"
|
||||||
minSdkVersion 15
|
minSdkVersion 15
|
||||||
targetSdkVersion 22
|
targetSdkVersion 22
|
||||||
versionCode 36
|
versionCode 40
|
||||||
versionName "1.7.2"
|
versionName "1.8.0"
|
||||||
}
|
}
|
||||||
|
|
||||||
sourceSets.main {
|
sourceSets.main {
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
|
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:name=".logic.StrongSwanApplication"
|
android:name=".logic.StrongSwanApplication"
|
||||||
@@ -63,6 +64,36 @@
|
|||||||
<category android:name="android.intent.category.DEFAULT" />
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
<activity
|
||||||
|
android:name=".ui.VpnProfileImportActivity"
|
||||||
|
android:label="@string/profile_import"
|
||||||
|
android:taskAffinity=""
|
||||||
|
android:excludeFromRecents="true" >
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
<data android:scheme="http" />
|
||||||
|
<data android:scheme="https" />
|
||||||
|
<data android:scheme="file" />
|
||||||
|
<data android:scheme="content" />
|
||||||
|
<data android:mimeType="application/vnd.strongswan.profile" />
|
||||||
|
</intent-filter>
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
<data android:scheme="http" />
|
||||||
|
<data android:scheme="https" />
|
||||||
|
<data android:scheme="file" />
|
||||||
|
<data android:scheme="content" />
|
||||||
|
<data android:host="*" />
|
||||||
|
<data android:pathPattern=".*\\..*\\..*\\..*\\.sswan" />
|
||||||
|
<data android:pathPattern=".*\\..*\\..*\\.sswan" />
|
||||||
|
<data android:pathPattern=".*\\..*\\.sswan" />
|
||||||
|
<data android:pathPattern=".*\\.sswan" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.TrustedCertificateImportActivity"
|
android:name=".ui.TrustedCertificateImportActivity"
|
||||||
android:label="@string/import_certificate"
|
android:label="@string/import_certificate"
|
||||||
|
|||||||
+24
-1
@@ -18,6 +18,8 @@
|
|||||||
package org.strongswan.android.data;
|
package org.strongswan.android.data;
|
||||||
|
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
public class VpnProfile implements Cloneable
|
public class VpnProfile implements Cloneable
|
||||||
{
|
{
|
||||||
/* While storing this as EnumSet would be nicer this simplifies storing it in a database */
|
/* While storing this as EnumSet would be nicer this simplifies storing it in a database */
|
||||||
@@ -28,8 +30,14 @@ public class VpnProfile implements Cloneable
|
|||||||
private String mRemoteId, mLocalId;
|
private String mRemoteId, mLocalId;
|
||||||
private Integer mMTU, mPort, mSplitTunneling;
|
private Integer mMTU, mPort, mSplitTunneling;
|
||||||
private VpnType mVpnType;
|
private VpnType mVpnType;
|
||||||
|
private UUID mUUID;
|
||||||
private long mId = -1;
|
private long mId = -1;
|
||||||
|
|
||||||
|
public VpnProfile()
|
||||||
|
{
|
||||||
|
this.mUUID = UUID.randomUUID();
|
||||||
|
}
|
||||||
|
|
||||||
public long getId()
|
public long getId()
|
||||||
{
|
{
|
||||||
return mId;
|
return mId;
|
||||||
@@ -40,6 +48,16 @@ public class VpnProfile implements Cloneable
|
|||||||
this.mId = id;
|
this.mId = id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setUUID(UUID uuid)
|
||||||
|
{
|
||||||
|
this.mUUID = uuid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UUID getUUID()
|
||||||
|
{
|
||||||
|
return mUUID;
|
||||||
|
}
|
||||||
|
|
||||||
public String getName()
|
public String getName()
|
||||||
{
|
{
|
||||||
return mName;
|
return mName;
|
||||||
@@ -171,7 +189,12 @@ public class VpnProfile implements Cloneable
|
|||||||
{
|
{
|
||||||
if (o != null && o instanceof VpnProfile)
|
if (o != null && o instanceof VpnProfile)
|
||||||
{
|
{
|
||||||
return this.mId == ((VpnProfile)o).getId();
|
VpnProfile other = (VpnProfile)o;
|
||||||
|
if (this.mUUID != null && other.getUUID() != null)
|
||||||
|
{
|
||||||
|
return this.mUUID.equals(other.getUUID());
|
||||||
|
}
|
||||||
|
return this.mId == other.getId();
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
+43
-1
@@ -19,6 +19,7 @@ package org.strongswan.android.data;
|
|||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
import android.content.ContentValues;
|
import android.content.ContentValues;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
@@ -33,6 +34,7 @@ public class VpnProfileDataSource
|
|||||||
{
|
{
|
||||||
private static final String TAG = VpnProfileDataSource.class.getSimpleName();
|
private static final String TAG = VpnProfileDataSource.class.getSimpleName();
|
||||||
public static final String KEY_ID = "_id";
|
public static final String KEY_ID = "_id";
|
||||||
|
public static final String KEY_UUID = "_uuid";
|
||||||
public static final String KEY_NAME = "name";
|
public static final String KEY_NAME = "name";
|
||||||
public static final String KEY_GATEWAY = "gateway";
|
public static final String KEY_GATEWAY = "gateway";
|
||||||
public static final String KEY_VPN_TYPE = "vpn_type";
|
public static final String KEY_VPN_TYPE = "vpn_type";
|
||||||
@@ -53,11 +55,12 @@ public class VpnProfileDataSource
|
|||||||
private static final String DATABASE_NAME = "strongswan.db";
|
private static final String DATABASE_NAME = "strongswan.db";
|
||||||
private static final String TABLE_VPNPROFILE = "vpnprofile";
|
private static final String TABLE_VPNPROFILE = "vpnprofile";
|
||||||
|
|
||||||
private static final int DATABASE_VERSION = 8;
|
private static final int DATABASE_VERSION = 9;
|
||||||
|
|
||||||
public static final String DATABASE_CREATE =
|
public static final String DATABASE_CREATE =
|
||||||
"CREATE TABLE " + TABLE_VPNPROFILE + " (" +
|
"CREATE TABLE " + TABLE_VPNPROFILE + " (" +
|
||||||
KEY_ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
|
KEY_ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
|
||||||
|
KEY_UUID + " TEXT UNIQUE," +
|
||||||
KEY_NAME + " TEXT NOT NULL," +
|
KEY_NAME + " TEXT NOT NULL," +
|
||||||
KEY_GATEWAY + " TEXT NOT NULL," +
|
KEY_GATEWAY + " TEXT NOT NULL," +
|
||||||
KEY_VPN_TYPE + " TEXT NOT NULL," +
|
KEY_VPN_TYPE + " TEXT NOT NULL," +
|
||||||
@@ -73,6 +76,7 @@ public class VpnProfileDataSource
|
|||||||
");";
|
");";
|
||||||
private static final String[] ALL_COLUMNS = new String[] {
|
private static final String[] ALL_COLUMNS = new String[] {
|
||||||
KEY_ID,
|
KEY_ID,
|
||||||
|
KEY_UUID,
|
||||||
KEY_NAME,
|
KEY_NAME,
|
||||||
KEY_GATEWAY,
|
KEY_GATEWAY,
|
||||||
KEY_VPN_TYPE,
|
KEY_VPN_TYPE,
|
||||||
@@ -141,6 +145,12 @@ public class VpnProfileDataSource
|
|||||||
db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_REMOTE_ID +
|
db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_REMOTE_ID +
|
||||||
" TEXT;");
|
" TEXT;");
|
||||||
}
|
}
|
||||||
|
if (oldVersion < 9)
|
||||||
|
{
|
||||||
|
db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_UUID +
|
||||||
|
" TEXT;");
|
||||||
|
updateColumns(db);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateColumns(SQLiteDatabase db)
|
private void updateColumns(SQLiteDatabase db)
|
||||||
@@ -261,6 +271,24 @@ public class VpnProfileDataSource
|
|||||||
return profile;
|
return profile;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a single VPN profile from the database by its UUID.
|
||||||
|
* @param uuid the UUID of the VPN profile
|
||||||
|
* @return the profile or null, if not found
|
||||||
|
*/
|
||||||
|
public VpnProfile getVpnProfile(UUID uuid)
|
||||||
|
{
|
||||||
|
VpnProfile profile = null;
|
||||||
|
Cursor cursor = mDatabase.query(TABLE_VPNPROFILE, ALL_COLUMNS,
|
||||||
|
KEY_UUID + "='" + uuid.toString() + "'", null, null, null, null);
|
||||||
|
if (cursor.moveToFirst())
|
||||||
|
{
|
||||||
|
profile = VpnProfileFromCursor(cursor);
|
||||||
|
}
|
||||||
|
cursor.close();
|
||||||
|
return profile;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a list of all VPN profiles stored in the database.
|
* Get a list of all VPN profiles stored in the database.
|
||||||
* @return list of VPN profiles
|
* @return list of VPN profiles
|
||||||
@@ -285,6 +313,7 @@ public class VpnProfileDataSource
|
|||||||
{
|
{
|
||||||
VpnProfile profile = new VpnProfile();
|
VpnProfile profile = new VpnProfile();
|
||||||
profile.setId(cursor.getLong(cursor.getColumnIndex(KEY_ID)));
|
profile.setId(cursor.getLong(cursor.getColumnIndex(KEY_ID)));
|
||||||
|
profile.setUUID(getUUID(cursor, cursor.getColumnIndex(KEY_UUID)));
|
||||||
profile.setName(cursor.getString(cursor.getColumnIndex(KEY_NAME)));
|
profile.setName(cursor.getString(cursor.getColumnIndex(KEY_NAME)));
|
||||||
profile.setGateway(cursor.getString(cursor.getColumnIndex(KEY_GATEWAY)));
|
profile.setGateway(cursor.getString(cursor.getColumnIndex(KEY_GATEWAY)));
|
||||||
profile.setVpnType(VpnType.fromIdentifier(cursor.getString(cursor.getColumnIndex(KEY_VPN_TYPE))));
|
profile.setVpnType(VpnType.fromIdentifier(cursor.getString(cursor.getColumnIndex(KEY_VPN_TYPE))));
|
||||||
@@ -303,6 +332,7 @@ public class VpnProfileDataSource
|
|||||||
private ContentValues ContentValuesFromVpnProfile(VpnProfile profile)
|
private ContentValues ContentValuesFromVpnProfile(VpnProfile profile)
|
||||||
{
|
{
|
||||||
ContentValues values = new ContentValues();
|
ContentValues values = new ContentValues();
|
||||||
|
values.put(KEY_UUID, profile.getUUID() != null ? profile.getUUID().toString() : null);
|
||||||
values.put(KEY_NAME, profile.getName());
|
values.put(KEY_NAME, profile.getName());
|
||||||
values.put(KEY_GATEWAY, profile.getGateway());
|
values.put(KEY_GATEWAY, profile.getGateway());
|
||||||
values.put(KEY_VPN_TYPE, profile.getVpnType().getIdentifier());
|
values.put(KEY_VPN_TYPE, profile.getVpnType().getIdentifier());
|
||||||
@@ -322,4 +352,16 @@ public class VpnProfileDataSource
|
|||||||
{
|
{
|
||||||
return cursor.isNull(columnIndex) ? null : cursor.getInt(columnIndex);
|
return cursor.isNull(columnIndex) ? null : cursor.getInt(columnIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private UUID getUUID(Cursor cursor, int columnIndex)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return cursor.isNull(columnIndex) ? null : UUID.fromString(cursor.getString(columnIndex));
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+17
-16
@@ -26,6 +26,7 @@ import android.os.Bundle;
|
|||||||
import android.security.KeyChain;
|
import android.security.KeyChain;
|
||||||
import android.security.KeyChainAliasCallback;
|
import android.security.KeyChainAliasCallback;
|
||||||
import android.security.KeyChainException;
|
import android.security.KeyChainException;
|
||||||
|
import android.support.v4.content.LocalBroadcastManager;
|
||||||
import android.support.v7.app.AlertDialog;
|
import android.support.v7.app.AlertDialog;
|
||||||
import android.support.v7.app.AppCompatActivity;
|
import android.support.v7.app.AppCompatActivity;
|
||||||
import android.support.v7.app.AppCompatDialogFragment;
|
import android.support.v7.app.AppCompatDialogFragment;
|
||||||
@@ -63,14 +64,14 @@ import org.strongswan.android.logic.TrustedCertificateManager;
|
|||||||
import org.strongswan.android.security.TrustedCertificateEntry;
|
import org.strongswan.android.security.TrustedCertificateEntry;
|
||||||
import org.strongswan.android.ui.adapter.CertificateIdentitiesAdapter;
|
import org.strongswan.android.ui.adapter.CertificateIdentitiesAdapter;
|
||||||
import org.strongswan.android.ui.widget.TextInputLayoutHelper;
|
import org.strongswan.android.ui.widget.TextInputLayoutHelper;
|
||||||
|
import org.strongswan.android.utils.Constants;
|
||||||
|
|
||||||
import java.security.cert.X509Certificate;
|
import java.security.cert.X509Certificate;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
public class VpnProfileDetailActivity extends AppCompatActivity
|
public class VpnProfileDetailActivity extends AppCompatActivity
|
||||||
{
|
{
|
||||||
private static final int SELECT_TRUSTED_CERTIFICATE = 0;
|
private static final int SELECT_TRUSTED_CERTIFICATE = 0;
|
||||||
private static final int MTU_MIN = 1280;
|
|
||||||
private static final int MTU_MAX = 1500;
|
|
||||||
|
|
||||||
private VpnProfileDataSource mDataSource;
|
private VpnProfileDataSource mDataSource;
|
||||||
private Long mId;
|
private Long mId;
|
||||||
@@ -453,6 +454,10 @@ public class VpnProfileDetailActivity extends AppCompatActivity
|
|||||||
if (mProfile != null)
|
if (mProfile != null)
|
||||||
{
|
{
|
||||||
updateProfileData();
|
updateProfileData();
|
||||||
|
if (mProfile.getUUID() == null)
|
||||||
|
{
|
||||||
|
mProfile.setUUID(UUID.randomUUID());
|
||||||
|
}
|
||||||
mDataSource.updateVpnProfile(mProfile);
|
mDataSource.updateVpnProfile(mProfile);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -461,6 +466,10 @@ public class VpnProfileDetailActivity extends AppCompatActivity
|
|||||||
updateProfileData();
|
updateProfileData();
|
||||||
mDataSource.insertProfile(mProfile);
|
mDataSource.insertProfile(mProfile);
|
||||||
}
|
}
|
||||||
|
Intent intent = new Intent(Constants.VPN_PROFILES_CHANGED);
|
||||||
|
intent.putExtra(Constants.VPN_PROFILES_SINGLE, mProfile.getId());
|
||||||
|
LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
|
||||||
|
|
||||||
setResult(RESULT_OK, new Intent().putExtra(VpnProfileDataSource.KEY_ID, mProfile.getId()));
|
setResult(RESULT_OK, new Intent().putExtra(VpnProfileDataSource.KEY_ID, mProfile.getId()));
|
||||||
finish();
|
finish();
|
||||||
}
|
}
|
||||||
@@ -496,9 +505,9 @@ public class VpnProfileDetailActivity extends AppCompatActivity
|
|||||||
showCertificateAlert();
|
showCertificateAlert();
|
||||||
valid = false;
|
valid = false;
|
||||||
}
|
}
|
||||||
if (!validateInteger(mMTU, MTU_MIN, MTU_MAX))
|
if (!validateInteger(mMTU, Constants.MTU_MIN, Constants.MTU_MAX))
|
||||||
{
|
{
|
||||||
mMTUWrap.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), Constants.MTU_MIN, Constants.MTU_MAX));
|
||||||
valid = false;
|
valid = false;
|
||||||
}
|
}
|
||||||
if (!validateInteger(mPort, 1, 65535))
|
if (!validateInteger(mPort, 1, 65535))
|
||||||
@@ -567,8 +576,8 @@ public class VpnProfileDetailActivity extends AppCompatActivity
|
|||||||
mRemoteId.setText(mProfile.getRemoteId());
|
mRemoteId.setText(mProfile.getRemoteId());
|
||||||
mMTU.setText(mProfile.getMTU() != null ? mProfile.getMTU().toString() : null);
|
mMTU.setText(mProfile.getMTU() != null ? mProfile.getMTU().toString() : null);
|
||||||
mPort.setText(mProfile.getPort() != null ? mProfile.getPort().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);
|
mBlockIPv4.setChecked(mProfile.getSplitTunneling() != null && (mProfile.getSplitTunneling() & VpnProfile.SPLIT_TUNNELING_BLOCK_IPV4) != 0);
|
||||||
mBlockIPv6.setChecked(mProfile.getSplitTunneling() != null ? (mProfile.getSplitTunneling() & VpnProfile.SPLIT_TUNNELING_BLOCK_IPV6) != 0 : false);
|
mBlockIPv6.setChecked(mProfile.getSplitTunneling() != null && (mProfile.getSplitTunneling() & VpnProfile.SPLIT_TUNNELING_BLOCK_IPV6) != 0);
|
||||||
useralias = mProfile.getUserCertificateAlias();
|
useralias = mProfile.getUserCertificateAlias();
|
||||||
local_id = mProfile.getLocalId();
|
local_id = mProfile.getLocalId();
|
||||||
alias = mProfile.getCertificateAlias();
|
alias = mProfile.getCertificateAlias();
|
||||||
@@ -686,11 +695,7 @@ public class VpnProfileDetailActivity extends AppCompatActivity
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
catch (KeyChainException e)
|
catch (KeyChainException | InterruptedException e)
|
||||||
{
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
catch (InterruptedException e)
|
|
||||||
{
|
{
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
@@ -722,11 +727,7 @@ public class VpnProfileDetailActivity extends AppCompatActivity
|
|||||||
{
|
{
|
||||||
chain = KeyChain.getCertificateChain(mContext, mAlias);
|
chain = KeyChain.getCertificateChain(mContext, mAlias);
|
||||||
}
|
}
|
||||||
catch (KeyChainException e)
|
catch (KeyChainException | InterruptedException e)
|
||||||
{
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
catch (InterruptedException e)
|
|
||||||
{
|
{
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
|
|||||||
+811
@@ -0,0 +1,811 @@
|
|||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.app.LoaderManager;
|
||||||
|
import android.app.ProgressDialog;
|
||||||
|
import android.content.AsyncTaskLoader;
|
||||||
|
import android.content.ContentResolver;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.DialogInterface;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.Loader;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.security.KeyChain;
|
||||||
|
import android.security.KeyChainAliasCallback;
|
||||||
|
import android.security.KeyChainException;
|
||||||
|
import android.support.v4.content.LocalBroadcastManager;
|
||||||
|
import android.support.v7.app.AppCompatActivity;
|
||||||
|
import android.util.Base64;
|
||||||
|
import android.view.Menu;
|
||||||
|
import android.view.MenuInflater;
|
||||||
|
import android.view.MenuItem;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.Button;
|
||||||
|
import android.widget.EditText;
|
||||||
|
import android.widget.RelativeLayout;
|
||||||
|
import android.widget.TextView;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import org.json.JSONException;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
import org.strongswan.android.R;
|
||||||
|
import org.strongswan.android.data.VpnProfile;
|
||||||
|
import org.strongswan.android.data.VpnProfileDataSource;
|
||||||
|
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.widget.TextInputLayoutHelper;
|
||||||
|
import org.strongswan.android.utils.Constants;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.net.UnknownHostException;
|
||||||
|
import java.security.KeyStore;
|
||||||
|
import java.security.KeyStoreException;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.cert.CertificateException;
|
||||||
|
import java.security.cert.CertificateFactory;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import javax.net.ssl.SSLHandshakeException;
|
||||||
|
|
||||||
|
public class VpnProfileImportActivity extends AppCompatActivity
|
||||||
|
{
|
||||||
|
private static final String PKCS12_INSTALLED = "PKCS12_INSTALLED";
|
||||||
|
private static final int INSTALL_PKCS12 = 0;
|
||||||
|
private static final int PROFILE_LOADER = 0;
|
||||||
|
private static final int USER_CERT_LOADER = 1;
|
||||||
|
|
||||||
|
private VpnProfileDataSource mDataSource;
|
||||||
|
private ParsedVpnProfile mProfile;
|
||||||
|
private VpnProfile mExisting;
|
||||||
|
private TrustedCertificateEntry mCertEntry;
|
||||||
|
private TrustedCertificateEntry mUserCertEntry;
|
||||||
|
private String mUserCertLoading;
|
||||||
|
private boolean mHideImport;
|
||||||
|
private ProgressDialog mProgress;
|
||||||
|
private TextView mExistsWarning;
|
||||||
|
private ViewGroup mBasicDataGroup;
|
||||||
|
private TextView mName;
|
||||||
|
private TextView mGateway;
|
||||||
|
private TextView mSelectVpnType;
|
||||||
|
private ViewGroup mUsernamePassword;
|
||||||
|
private EditText mUsername;
|
||||||
|
private TextInputLayoutHelper mUsernameWrap;
|
||||||
|
private EditText mPassword;
|
||||||
|
private ViewGroup mUserCertificate;
|
||||||
|
private RelativeLayout mSelectUserCert;
|
||||||
|
private Button mImportUserCert;
|
||||||
|
private ViewGroup mRemoteCertificate;
|
||||||
|
private RelativeLayout mRemoteCert;
|
||||||
|
|
||||||
|
private LoaderManager.LoaderCallbacks<ProfileLoadResult> mProfileLoaderCallbacks = new LoaderManager.LoaderCallbacks<ProfileLoadResult>()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public Loader<ProfileLoadResult> onCreateLoader(int id, Bundle args)
|
||||||
|
{
|
||||||
|
return new ProfileLoader(VpnProfileImportActivity.this, getIntent().getData());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLoadFinished(Loader<ProfileLoadResult> loader, ProfileLoadResult data)
|
||||||
|
{
|
||||||
|
handleProfile(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLoaderReset(Loader<ProfileLoadResult> loader)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private LoaderManager.LoaderCallbacks<TrustedCertificateEntry> mUserCertificateLoaderCallbacks = new LoaderManager.LoaderCallbacks<TrustedCertificateEntry>()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public Loader<TrustedCertificateEntry> onCreateLoader(int id, Bundle args)
|
||||||
|
{
|
||||||
|
return new UserCertificateLoader(VpnProfileImportActivity.this, mUserCertLoading);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLoadFinished(Loader<TrustedCertificateEntry> loader, TrustedCertificateEntry data)
|
||||||
|
{
|
||||||
|
handleUserCertificate(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLoaderReset(Loader<TrustedCertificateEntry> loader)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstanceState)
|
||||||
|
{
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
getSupportActionBar().setHomeAsUpIndicator(R.drawable.ic_close_white_24dp);
|
||||||
|
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||||
|
|
||||||
|
mDataSource = new VpnProfileDataSource(this);
|
||||||
|
mDataSource.open();
|
||||||
|
|
||||||
|
setContentView(R.layout.profile_import_view);
|
||||||
|
|
||||||
|
mExistsWarning = (TextView)findViewById(R.id.exists_warning);
|
||||||
|
mBasicDataGroup = (ViewGroup)findViewById(R.id.basic_data_group);
|
||||||
|
mName = (TextView)findViewById(R.id.name);
|
||||||
|
mGateway = (TextView)findViewById(R.id.gateway);
|
||||||
|
mSelectVpnType = (TextView)findViewById(R.id.vpn_type);
|
||||||
|
|
||||||
|
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);
|
||||||
|
mImportUserCert = (Button)findViewById(R.id.import_user_certificate);
|
||||||
|
|
||||||
|
mRemoteCertificate = (ViewGroup)findViewById(R.id.remote_certificate_group);
|
||||||
|
mRemoteCert = (RelativeLayout)findViewById(R.id.remote_certificate);
|
||||||
|
|
||||||
|
mExistsWarning.setVisibility(View.GONE);
|
||||||
|
mBasicDataGroup.setVisibility(View.GONE);
|
||||||
|
mUsernamePassword.setVisibility(View.GONE);
|
||||||
|
mUserCertificate.setVisibility(View.GONE);
|
||||||
|
mRemoteCertificate.setVisibility(View.GONE);
|
||||||
|
|
||||||
|
mSelectUserCert.setOnClickListener(new SelectUserCertOnClickListener());
|
||||||
|
mImportUserCert.setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v)
|
||||||
|
{
|
||||||
|
Intent intent = KeyChain.createInstallIntent();
|
||||||
|
intent.putExtra(KeyChain.EXTRA_NAME, getString(R.string.profile_cert_alias, mProfile.getName()));
|
||||||
|
intent.putExtra(KeyChain.EXTRA_PKCS12, mProfile.PKCS12);
|
||||||
|
startActivityForResult(intent, INSTALL_PKCS12);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Intent intent = getIntent();
|
||||||
|
String action = intent.getAction();
|
||||||
|
if (Intent.ACTION_VIEW.equals(action))
|
||||||
|
{
|
||||||
|
mProgress = ProgressDialog.show(this, null, getString(R.string.loading),
|
||||||
|
true, true, new DialogInterface.OnCancelListener() {
|
||||||
|
@Override
|
||||||
|
public void onCancel(DialogInterface dialog)
|
||||||
|
{
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
getLoaderManager().initLoader(PROFILE_LOADER, null, mProfileLoaderCallbacks);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (savedInstanceState != null)
|
||||||
|
{
|
||||||
|
mUserCertLoading = savedInstanceState.getString(VpnProfileDataSource.KEY_USER_CERTIFICATE);
|
||||||
|
if (mUserCertLoading != null)
|
||||||
|
{
|
||||||
|
getLoaderManager().initLoader(USER_CERT_LOADER, null, mUserCertificateLoaderCallbacks);
|
||||||
|
}
|
||||||
|
mImportUserCert.setEnabled(!savedInstanceState.getBoolean(PKCS12_INSTALLED));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDestroy()
|
||||||
|
{
|
||||||
|
super.onDestroy();
|
||||||
|
mDataSource.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onSaveInstanceState(Bundle outState)
|
||||||
|
{
|
||||||
|
super.onSaveInstanceState(outState);
|
||||||
|
if (mUserCertEntry != null)
|
||||||
|
{
|
||||||
|
outState.putString(VpnProfileDataSource.KEY_USER_CERTIFICATE, mUserCertEntry.getAlias());
|
||||||
|
}
|
||||||
|
outState.putBoolean(PKCS12_INSTALLED, !mImportUserCert.isEnabled());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCreateOptionsMenu(Menu menu)
|
||||||
|
{
|
||||||
|
MenuInflater inflater = getMenuInflater();
|
||||||
|
inflater.inflate(R.menu.profile_import, menu);
|
||||||
|
if (mHideImport)
|
||||||
|
{
|
||||||
|
MenuItem item = menu.findItem(R.id.menu_accept);
|
||||||
|
item.setVisible(false);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onOptionsItemSelected(MenuItem item)
|
||||||
|
{
|
||||||
|
switch (item.getItemId())
|
||||||
|
{
|
||||||
|
case android.R.id.home:
|
||||||
|
finish();
|
||||||
|
return true;
|
||||||
|
case R.id.menu_accept:
|
||||||
|
saveProfile();
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return super.onOptionsItemSelected(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onActivityResult(int requestCode, int resultCode, Intent data)
|
||||||
|
{
|
||||||
|
super.onActivityResult(requestCode, resultCode, data);
|
||||||
|
switch (requestCode)
|
||||||
|
{
|
||||||
|
case INSTALL_PKCS12:
|
||||||
|
if (resultCode == Activity.RESULT_OK)
|
||||||
|
{ /* no need to import twice */
|
||||||
|
mImportUserCert.setEnabled(false);
|
||||||
|
mSelectUserCert.performClick();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void handleProfile(ProfileLoadResult data)
|
||||||
|
{
|
||||||
|
mProgress.dismiss();
|
||||||
|
|
||||||
|
mProfile = null;
|
||||||
|
if (data != null && data.ThrownException == null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
JSONObject obj = new JSONObject(data.Profile);
|
||||||
|
mProfile = parseProfile(obj);
|
||||||
|
}
|
||||||
|
catch (JSONException e)
|
||||||
|
{
|
||||||
|
mExistsWarning.setVisibility(View.VISIBLE);
|
||||||
|
mExistsWarning.setText(e.getLocalizedMessage());
|
||||||
|
mHideImport = true;
|
||||||
|
invalidateOptionsMenu();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (mProfile == null)
|
||||||
|
{
|
||||||
|
String error = null;
|
||||||
|
if (data.ThrownException != null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
throw data.ThrownException;
|
||||||
|
}
|
||||||
|
catch (FileNotFoundException e)
|
||||||
|
{
|
||||||
|
error = getString(R.string.profile_import_failed_not_found);
|
||||||
|
}
|
||||||
|
catch (UnknownHostException e)
|
||||||
|
{
|
||||||
|
error = getString(R.string.profile_import_failed_host);
|
||||||
|
}
|
||||||
|
catch (SSLHandshakeException e)
|
||||||
|
{
|
||||||
|
error = getString(R.string.profile_import_failed_tls);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (error != null)
|
||||||
|
{
|
||||||
|
Toast.makeText(this, getString(R.string.profile_import_failed_detail, error), Toast.LENGTH_LONG).show();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Toast.makeText(this, R.string.profile_import_failed, Toast.LENGTH_LONG).show();
|
||||||
|
}
|
||||||
|
finish();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mExisting = mDataSource.getVpnProfile(mProfile.getUUID());
|
||||||
|
mExistsWarning.setVisibility(mExisting != null ? View.VISIBLE : View.GONE);
|
||||||
|
|
||||||
|
mBasicDataGroup.setVisibility(View.VISIBLE);
|
||||||
|
mName.setText(mProfile.getName());
|
||||||
|
mGateway.setText(mProfile.getGateway());
|
||||||
|
mSelectVpnType.setText(getResources().getStringArray(R.array.vpn_types)[mProfile.getVpnType().ordinal()]);
|
||||||
|
|
||||||
|
mUsernamePassword.setVisibility(mProfile.getVpnType().has(VpnTypeFeature.USER_PASS) ? View.VISIBLE : View.GONE);
|
||||||
|
if (mProfile.getVpnType().has(VpnTypeFeature.USER_PASS))
|
||||||
|
{
|
||||||
|
mUsername.setText(mProfile.getUsername());
|
||||||
|
if (mProfile.getUsername() != null && !mProfile.getUsername().isEmpty())
|
||||||
|
{
|
||||||
|
mUsername.setEnabled(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mUserCertificate.setVisibility(mProfile.getVpnType().has(VpnTypeFeature.CERTIFICATE) ? View.VISIBLE : View.GONE);
|
||||||
|
mRemoteCertificate.setVisibility(mProfile.Certificate != null ? View.VISIBLE : View.GONE);
|
||||||
|
mImportUserCert.setVisibility(mProfile.PKCS12 != null ? View.VISIBLE : View.GONE);
|
||||||
|
|
||||||
|
updateUserCertView();
|
||||||
|
|
||||||
|
if (mProfile.Certificate != null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
CertificateFactory factory = CertificateFactory.getInstance("X.509");
|
||||||
|
X509Certificate certificate = (X509Certificate)factory.generateCertificate(new ByteArrayInputStream(mProfile.Certificate));
|
||||||
|
KeyStore store = KeyStore.getInstance("LocalCertificateStore");
|
||||||
|
store.load(null, null);
|
||||||
|
String alias = store.getCertificateAlias(certificate);
|
||||||
|
mCertEntry = new TrustedCertificateEntry(alias, certificate);
|
||||||
|
((TextView)mRemoteCert.findViewById(android.R.id.text1)).setText(mCertEntry.getSubjectPrimary());
|
||||||
|
((TextView)mRemoteCert.findViewById(android.R.id.text2)).setText(mCertEntry.getSubjectSecondary());
|
||||||
|
}
|
||||||
|
catch (CertificateException | NoSuchAlgorithmException | KeyStoreException | IOException e)
|
||||||
|
{
|
||||||
|
e.printStackTrace();
|
||||||
|
mRemoteCertificate.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleUserCertificate(TrustedCertificateEntry data)
|
||||||
|
{
|
||||||
|
mUserCertEntry = data;
|
||||||
|
mUserCertLoading = null;
|
||||||
|
updateUserCertView();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateUserCertView()
|
||||||
|
{
|
||||||
|
if (mUserCertLoading != null)
|
||||||
|
{
|
||||||
|
((TextView)mSelectUserCert.findViewById(android.R.id.text1)).setText(mUserCertLoading);
|
||||||
|
((TextView)mSelectUserCert.findViewById(android.R.id.text2)).setText(R.string.loading);
|
||||||
|
}
|
||||||
|
else if (mUserCertEntry != null)
|
||||||
|
{ /* clear any errors and set the new data */
|
||||||
|
((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());
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ParsedVpnProfile parseProfile(JSONObject obj) throws JSONException
|
||||||
|
{
|
||||||
|
UUID uuid;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
uuid = UUID.fromString(obj.getString("uuid"));
|
||||||
|
}
|
||||||
|
catch (IllegalArgumentException e)
|
||||||
|
{
|
||||||
|
e.printStackTrace();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
ParsedVpnProfile profile = new ParsedVpnProfile();
|
||||||
|
|
||||||
|
profile.setUUID(uuid);
|
||||||
|
profile.setName(obj.getString("name"));
|
||||||
|
VpnType type = VpnType.fromIdentifier(obj.getString("type"));
|
||||||
|
profile.setVpnType(type);
|
||||||
|
|
||||||
|
JSONObject remote = obj.getJSONObject("remote");
|
||||||
|
profile.setGateway(remote.getString("addr"));
|
||||||
|
profile.setPort(getInteger(remote, "port", 1, 65535));
|
||||||
|
profile.setRemoteId(remote.optString("id", null));
|
||||||
|
profile.Certificate = decodeBase64(remote.optString("cert", null));
|
||||||
|
|
||||||
|
JSONObject local = obj.optJSONObject("local");
|
||||||
|
if (local != null)
|
||||||
|
{
|
||||||
|
if (type.has(VpnTypeFeature.USER_PASS))
|
||||||
|
{
|
||||||
|
profile.setUsername(local.optString("eap_id", null));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type.has(VpnTypeFeature.CERTIFICATE))
|
||||||
|
{
|
||||||
|
profile.setLocalId(local.optString("id", null));
|
||||||
|
profile.PKCS12 = decodeBase64(local.optString("p12", null));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
profile.setMTU(getInteger(obj, "mtu", Constants.MTU_MIN, Constants.MTU_MAX));
|
||||||
|
JSONObject split = obj.optJSONObject("split-tunneling");
|
||||||
|
if (split != null)
|
||||||
|
{
|
||||||
|
int st = 0;
|
||||||
|
st |= split.optBoolean("block-ipv4") ? VpnProfile.SPLIT_TUNNELING_BLOCK_IPV4 : 0;
|
||||||
|
st |= split.optBoolean("block-ipv6") ? VpnProfile.SPLIT_TUNNELING_BLOCK_IPV6 : 0;
|
||||||
|
profile.setSplitTunneling(st == 0 ? null : st);
|
||||||
|
}
|
||||||
|
return profile;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Integer getInteger(JSONObject obj, String key, int min, int max)
|
||||||
|
{
|
||||||
|
Integer res = obj.optInt(key);
|
||||||
|
return res < min || res > max ? null : res;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save or update the profile depending on whether we actually have a
|
||||||
|
* profile object or not (this was created in updateProfileData)
|
||||||
|
*/
|
||||||
|
private void saveProfile()
|
||||||
|
{
|
||||||
|
if (verifyInput())
|
||||||
|
{
|
||||||
|
updateProfileData();
|
||||||
|
if (mExisting != null)
|
||||||
|
{
|
||||||
|
mProfile.setId(mExisting.getId());
|
||||||
|
mDataSource.updateVpnProfile(mProfile);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mDataSource.insertProfile(mProfile);
|
||||||
|
}
|
||||||
|
if (mCertEntry != null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{ /* store the CA/server certificate */
|
||||||
|
KeyStore store = KeyStore.getInstance("LocalCertificateStore");
|
||||||
|
store.load(null, null);
|
||||||
|
store.setCertificateEntry(null, mCertEntry.getCertificate());
|
||||||
|
TrustedCertificateManager.getInstance().reset();
|
||||||
|
}
|
||||||
|
catch (KeyStoreException | CertificateException | NoSuchAlgorithmException | IOException e)
|
||||||
|
{
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Intent intent = new Intent(Constants.VPN_PROFILES_CHANGED);
|
||||||
|
intent.putExtra(Constants.VPN_PROFILES_SINGLE, mProfile.getId());
|
||||||
|
LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
|
||||||
|
|
||||||
|
intent = new Intent(this, MainActivity.class);
|
||||||
|
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||||
|
startActivity(intent);
|
||||||
|
|
||||||
|
setResult(RESULT_OK, new Intent().putExtra(VpnProfileDataSource.KEY_ID, mProfile.getId()));
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify the user input and display error messages.
|
||||||
|
* @return true if the input is valid
|
||||||
|
*/
|
||||||
|
private boolean verifyInput()
|
||||||
|
{
|
||||||
|
boolean valid = true;
|
||||||
|
if (mProfile.getVpnType().has(VpnTypeFeature.USER_PASS))
|
||||||
|
{
|
||||||
|
if (mUsername.getText().toString().trim().isEmpty())
|
||||||
|
{
|
||||||
|
mUsernameWrap.setError(getString(R.string.alert_text_no_input_username));
|
||||||
|
valid = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (mProfile.getVpnType().has(VpnTypeFeature.CERTIFICATE) && mUserCertEntry == null)
|
||||||
|
{ /* let's show an error icon */
|
||||||
|
((TextView)mSelectUserCert.findViewById(android.R.id.text1)).setError("");
|
||||||
|
valid = false;
|
||||||
|
}
|
||||||
|
return valid;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the profile object with the data entered by the user
|
||||||
|
*/
|
||||||
|
private void updateProfileData()
|
||||||
|
{
|
||||||
|
if (mProfile.getVpnType().has(VpnTypeFeature.USER_PASS))
|
||||||
|
{
|
||||||
|
mProfile.setUsername(mUsername.getText().toString().trim());
|
||||||
|
String password = mPassword.getText().toString().trim();
|
||||||
|
password = password.isEmpty() ? null : password;
|
||||||
|
mProfile.setPassword(password);
|
||||||
|
}
|
||||||
|
if (mProfile.getVpnType().has(VpnTypeFeature.CERTIFICATE))
|
||||||
|
{
|
||||||
|
mProfile.setUserCertificateAlias(mUserCertEntry.getAlias());
|
||||||
|
}
|
||||||
|
if (mCertEntry != null)
|
||||||
|
{
|
||||||
|
mProfile.setCertificateAlias(mCertEntry.getAlias());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load the JSON-encoded VPN profile from the given URI
|
||||||
|
*/
|
||||||
|
private static class ProfileLoader extends AsyncTaskLoader<ProfileLoadResult>
|
||||||
|
{
|
||||||
|
private final Uri mUri;
|
||||||
|
private ProfileLoadResult mData;
|
||||||
|
|
||||||
|
public ProfileLoader(Context context, Uri uri)
|
||||||
|
{
|
||||||
|
super(context);
|
||||||
|
mUri = uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ProfileLoadResult loadInBackground()
|
||||||
|
{
|
||||||
|
ProfileLoadResult result = new ProfileLoadResult();
|
||||||
|
InputStream in = null;
|
||||||
|
|
||||||
|
if (ContentResolver.SCHEME_CONTENT.equals(mUri.getScheme()) ||
|
||||||
|
ContentResolver.SCHEME_FILE.equals(mUri.getScheme()))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
in = getContext().getContentResolver().openInputStream(mUri);
|
||||||
|
}
|
||||||
|
catch (FileNotFoundException e)
|
||||||
|
{
|
||||||
|
result.ThrownException = e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
URL url = new URL(mUri.toString());
|
||||||
|
in = url.openStream();
|
||||||
|
}
|
||||||
|
catch (IOException e)
|
||||||
|
{
|
||||||
|
result.ThrownException = e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (in != null)
|
||||||
|
{
|
||||||
|
result.Profile = streamToString(in);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onStartLoading()
|
||||||
|
{
|
||||||
|
if (mData != null)
|
||||||
|
{ /* if we have data ready, deliver it directly */
|
||||||
|
deliverResult(mData);
|
||||||
|
}
|
||||||
|
if (takeContentChanged() || mData == null)
|
||||||
|
{
|
||||||
|
forceLoad();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void deliverResult(ProfileLoadResult data)
|
||||||
|
{
|
||||||
|
if (isReset())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mData = data;
|
||||||
|
if (isStarted())
|
||||||
|
{ /* if it is started we deliver the data directly,
|
||||||
|
* otherwise this is handled in onStartLoading */
|
||||||
|
super.deliverResult(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onReset()
|
||||||
|
{
|
||||||
|
mData = null;
|
||||||
|
super.onReset();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String streamToString(InputStream in)
|
||||||
|
{
|
||||||
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
|
byte[] buf = new byte[1024];
|
||||||
|
int len;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
while ((len = in.read(buf)) != -1)
|
||||||
|
{
|
||||||
|
out.write(buf, 0, len);
|
||||||
|
}
|
||||||
|
return out.toString("UTF-8");
|
||||||
|
}
|
||||||
|
catch (IOException e)
|
||||||
|
{
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class ProfileLoadResult
|
||||||
|
{
|
||||||
|
public String Profile;
|
||||||
|
public Exception ThrownException;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ask the user to select an available certificate.
|
||||||
|
*/
|
||||||
|
private class SelectUserCertOnClickListener implements View.OnClickListener, KeyChainAliasCallback
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void onClick(View v)
|
||||||
|
{
|
||||||
|
String alias = null;
|
||||||
|
if (mUserCertEntry != null)
|
||||||
|
{
|
||||||
|
alias = mUserCertEntry.getAlias();
|
||||||
|
mUserCertEntry = null;
|
||||||
|
}
|
||||||
|
else if (mProfile != null)
|
||||||
|
{
|
||||||
|
alias = getString(R.string.profile_cert_alias, mProfile.getName());
|
||||||
|
}
|
||||||
|
KeyChain.choosePrivateKeyAlias(VpnProfileImportActivity.this, this, new String[] { "RSA" }, null, null, -1, alias);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void alias(final String alias)
|
||||||
|
{
|
||||||
|
/* alias() is not called from our main thread */
|
||||||
|
runOnUiThread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run()
|
||||||
|
{
|
||||||
|
mUserCertLoading = alias;
|
||||||
|
updateUserCertView();
|
||||||
|
if (alias != null)
|
||||||
|
{ /* otherwise the dialog was canceled, the request denied */
|
||||||
|
getLoaderManager().restartLoader(USER_CERT_LOADER, null, mUserCertificateLoaderCallbacks);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load the selected user certificate asynchronously. This cannot be done
|
||||||
|
* from the main thread as getCertificateChain() calls back to our main
|
||||||
|
* thread to bind to the KeyChain service resulting in a deadlock.
|
||||||
|
*/
|
||||||
|
private static class UserCertificateLoader extends AsyncTaskLoader<TrustedCertificateEntry>
|
||||||
|
{
|
||||||
|
private final String mAlias;
|
||||||
|
private TrustedCertificateEntry mData;
|
||||||
|
|
||||||
|
public UserCertificateLoader(Context context, String alias)
|
||||||
|
{
|
||||||
|
super(context);
|
||||||
|
mAlias = alias;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TrustedCertificateEntry loadInBackground()
|
||||||
|
{
|
||||||
|
X509Certificate[] chain = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
chain = KeyChain.getCertificateChain(getContext(), mAlias);
|
||||||
|
}
|
||||||
|
catch (KeyChainException | InterruptedException e)
|
||||||
|
{
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
if (chain != null && chain.length > 0)
|
||||||
|
{
|
||||||
|
return new TrustedCertificateEntry(mAlias, chain[0]);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onStartLoading()
|
||||||
|
{
|
||||||
|
if (mData != null)
|
||||||
|
{ /* if we have data ready, deliver it directly */
|
||||||
|
deliverResult(mData);
|
||||||
|
}
|
||||||
|
if (takeContentChanged() || mData == null)
|
||||||
|
{
|
||||||
|
forceLoad();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void deliverResult(TrustedCertificateEntry data)
|
||||||
|
{
|
||||||
|
if (isReset())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mData = data;
|
||||||
|
if (isStarted())
|
||||||
|
{ /* if it is started we deliver the data directly,
|
||||||
|
* otherwise this is handled in onStartLoading */
|
||||||
|
super.deliverResult(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onReset()
|
||||||
|
{
|
||||||
|
mData = null;
|
||||||
|
super.onReset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] decodeBase64(String encoded)
|
||||||
|
{
|
||||||
|
if (encoded == null || encoded.isEmpty())
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
byte[] data = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
data = Base64.decode(encoded, Base64.DEFAULT);
|
||||||
|
}
|
||||||
|
catch (IllegalArgumentException e)
|
||||||
|
{
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ParsedVpnProfile extends VpnProfile
|
||||||
|
{
|
||||||
|
public byte[] Certificate;
|
||||||
|
public byte[] PKCS12;
|
||||||
|
}
|
||||||
|
}
|
||||||
+55
-31
@@ -17,12 +17,14 @@
|
|||||||
|
|
||||||
package org.strongswan.android.ui;
|
package org.strongswan.android.ui;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.content.BroadcastReceiver;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.content.IntentFilter;
|
||||||
import android.content.res.TypedArray;
|
import android.content.res.TypedArray;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.v4.app.Fragment;
|
import android.support.v4.app.Fragment;
|
||||||
|
import android.support.v4.content.LocalBroadcastManager;
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
import android.view.ActionMode;
|
import android.view.ActionMode;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
@@ -41,9 +43,11 @@ import org.strongswan.android.R;
|
|||||||
import org.strongswan.android.data.VpnProfile;
|
import org.strongswan.android.data.VpnProfile;
|
||||||
import org.strongswan.android.data.VpnProfileDataSource;
|
import org.strongswan.android.data.VpnProfileDataSource;
|
||||||
import org.strongswan.android.ui.adapter.VpnProfileAdapter;
|
import org.strongswan.android.ui.adapter.VpnProfileAdapter;
|
||||||
|
import org.strongswan.android.utils.Constants;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class VpnProfileListFragment extends Fragment
|
public class VpnProfileListFragment extends Fragment
|
||||||
@@ -58,11 +62,47 @@ public class VpnProfileListFragment extends Fragment
|
|||||||
private OnVpnProfileSelectedListener mListener;
|
private OnVpnProfileSelectedListener mListener;
|
||||||
private boolean mReadOnly;
|
private boolean mReadOnly;
|
||||||
|
|
||||||
|
private BroadcastReceiver mProfilesChanged = new BroadcastReceiver()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void onReceive(Context context, Intent intent)
|
||||||
|
{
|
||||||
|
long id, ids[];
|
||||||
|
if ((id = intent.getLongExtra(Constants.VPN_PROFILES_SINGLE, 0)) > 0)
|
||||||
|
{
|
||||||
|
VpnProfile profile = mDataSource.getVpnProfile(id);
|
||||||
|
if (profile != null)
|
||||||
|
{ /* in case this was an edit, we remove it first */
|
||||||
|
mVpnProfiles.remove(profile);
|
||||||
|
mVpnProfiles.add(profile);
|
||||||
|
mListAdapter.notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if ((ids = intent.getLongArrayExtra(Constants.VPN_PROFILES_MULTIPLE)) != null)
|
||||||
|
{
|
||||||
|
for (long i : ids)
|
||||||
|
{
|
||||||
|
Iterator<VpnProfile> profiles = mVpnProfiles.iterator();
|
||||||
|
while (profiles.hasNext())
|
||||||
|
{
|
||||||
|
VpnProfile profile = profiles.next();
|
||||||
|
if (profile.getId() == i)
|
||||||
|
{
|
||||||
|
profiles.remove();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mListAdapter.notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The activity containing this fragment should implement this interface
|
* The activity containing this fragment should implement this interface
|
||||||
*/
|
*/
|
||||||
public interface OnVpnProfileSelectedListener {
|
public interface OnVpnProfileSelectedListener {
|
||||||
public void onVpnProfileSelected(VpnProfile profile);
|
void onVpnProfileSelected(VpnProfile profile);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -116,6 +156,9 @@ public class VpnProfileListFragment extends Fragment
|
|||||||
mVpnProfiles = mDataSource.getAllVpnProfiles();
|
mVpnProfiles = mDataSource.getAllVpnProfiles();
|
||||||
|
|
||||||
mListAdapter = new VpnProfileAdapter(getActivity(), R.layout.profile_list_item, mVpnProfiles);
|
mListAdapter = new VpnProfileAdapter(getActivity(), R.layout.profile_list_item, mVpnProfiles);
|
||||||
|
|
||||||
|
IntentFilter profileChangesFilter = new IntentFilter(Constants.VPN_PROFILES_CHANGED);
|
||||||
|
LocalBroadcastManager.getInstance(getActivity()).registerReceiver(mProfilesChanged, profileChangesFilter);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -123,6 +166,7 @@ public class VpnProfileListFragment extends Fragment
|
|||||||
{
|
{
|
||||||
super.onDestroy();
|
super.onDestroy();
|
||||||
mDataSource.close();
|
mDataSource.close();
|
||||||
|
LocalBroadcastManager.getInstance(getActivity()).unregisterReceiver(mProfilesChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -157,30 +201,6 @@ public class VpnProfileListFragment extends Fragment
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onActivityResult(int requestCode, int resultCode, Intent data)
|
|
||||||
{
|
|
||||||
switch (requestCode)
|
|
||||||
{
|
|
||||||
case ADD_REQUEST:
|
|
||||||
case EDIT_REQUEST:
|
|
||||||
if (resultCode != Activity.RESULT_OK)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
long id = data.getLongExtra(VpnProfileDataSource.KEY_ID, 0);
|
|
||||||
VpnProfile profile = mDataSource.getVpnProfile(id);
|
|
||||||
if (profile != null)
|
|
||||||
{ /* in case this was an edit, we remove it first */
|
|
||||||
mVpnProfiles.remove(profile);
|
|
||||||
mVpnProfiles.add(profile);
|
|
||||||
mListAdapter.notifyDataSetChanged();
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
super.onActivityResult(requestCode, resultCode, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
private final OnItemClickListener mVpnProfileClicked = new OnItemClickListener() {
|
private final OnItemClickListener mVpnProfileClicked = new OnItemClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onItemClick(AdapterView<?> a, View v, int position, long id)
|
public void onItemClick(AdapterView<?> a, View v, int position, long id)
|
||||||
@@ -213,7 +233,7 @@ public class VpnProfileListFragment extends Fragment
|
|||||||
MenuInflater inflater = mode.getMenuInflater();
|
MenuInflater inflater = mode.getMenuInflater();
|
||||||
inflater.inflate(R.menu.profile_list_context, menu);
|
inflater.inflate(R.menu.profile_list_context, menu);
|
||||||
mEditProfile = menu.findItem(R.id.edit_profile);
|
mEditProfile = menu.findItem(R.id.edit_profile);
|
||||||
mSelected = new HashSet<Integer>();
|
mSelected = new HashSet<>();
|
||||||
mode.setTitle(R.string.select_profiles);
|
mode.setTitle(R.string.select_profiles);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -234,17 +254,21 @@ public class VpnProfileListFragment extends Fragment
|
|||||||
}
|
}
|
||||||
case R.id.delete_profile:
|
case R.id.delete_profile:
|
||||||
{
|
{
|
||||||
ArrayList<VpnProfile> profiles = new ArrayList<VpnProfile>();
|
ArrayList<VpnProfile> profiles = new ArrayList<>();
|
||||||
for (int position : mSelected)
|
for (int position : mSelected)
|
||||||
{
|
{
|
||||||
profiles.add((VpnProfile)mListView.getItemAtPosition(position));
|
profiles.add((VpnProfile)mListView.getItemAtPosition(position));
|
||||||
}
|
}
|
||||||
for (VpnProfile profile : profiles)
|
long ids[] = new long[profiles.size()];
|
||||||
|
for (int i = 0; i < profiles.size(); i++)
|
||||||
{
|
{
|
||||||
|
VpnProfile profile = profiles.get(i);
|
||||||
|
ids[i] = profile.getId();
|
||||||
mDataSource.deleteVpnProfile(profile);
|
mDataSource.deleteVpnProfile(profile);
|
||||||
mVpnProfiles.remove(profile);
|
|
||||||
}
|
}
|
||||||
mListAdapter.notifyDataSetChanged();
|
Intent intent = new Intent(Constants.VPN_PROFILES_CHANGED);
|
||||||
|
intent.putExtra(Constants.VPN_PROFILES_MULTIPLE, ids);
|
||||||
|
LocalBroadcastManager.getInstance(getActivity()).sendBroadcast(intent);
|
||||||
Toast.makeText(VpnProfileListFragment.this.getActivity(),
|
Toast.makeText(VpnProfileListFragment.this.getActivity(),
|
||||||
R.string.profiles_deleted, Toast.LENGTH_SHORT).show();
|
R.string.profiles_deleted, Toast.LENGTH_SHORT).show();
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016 Tobias Brunner
|
||||||
|
* 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.utils;
|
||||||
|
|
||||||
|
public final class Constants
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Intent action used to notify about changes to the VPN profiles
|
||||||
|
*/
|
||||||
|
public static final String VPN_PROFILES_CHANGED = "org.strongswan.android.VPN_PROFILES_CHANGED";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used in the intent above to notify about edits or inserts of a VPN profile (long)
|
||||||
|
*/
|
||||||
|
public static final String VPN_PROFILES_SINGLE = "org.strongswan.android.VPN_PROFILES_SINGLE";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used in the intent above to notify about the deletion of multiple VPN profiles (array of longs)
|
||||||
|
*/
|
||||||
|
public static final String VPN_PROFILES_MULTIPLE = "org.strongswan.android.VPN_PROFILES_MULTIPLE";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Limits for MTU
|
||||||
|
*/
|
||||||
|
public static final int MTU_MAX = 1500;
|
||||||
|
public static final int MTU_MIN = 1280;
|
||||||
|
}
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 221 B |
Binary file not shown.
|
After Width: | Height: | Size: 175 B |
Binary file not shown.
|
After Width: | Height: | Size: 257 B |
@@ -0,0 +1,196 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
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.
|
||||||
|
-->
|
||||||
|
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
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:animateLayoutChanges="true" >
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/exists_warning"
|
||||||
|
android:background="@drawable/state_background"
|
||||||
|
android:padding="8dp"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="8dp"
|
||||||
|
android:drawableLeft="@android:drawable/ic_dialog_alert"
|
||||||
|
android:drawableStart="@android:drawable/ic_dialog_alert"
|
||||||
|
android:drawablePadding="8dp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:text="@string/profile_import_exists"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||||
|
android:textColor="?android:attr/textColorPrimary" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/basic_data_group"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical" >
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginLeft="4dp"
|
||||||
|
android:layout_marginStart="4dp"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:text="@string/profile_name_label_simple" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/name"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginLeft="4dp"
|
||||||
|
android:layout_marginStart="4dp"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||||
|
android:textColor="?android:attr/textColorPrimary" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="6dp"
|
||||||
|
android:layout_marginLeft="4dp"
|
||||||
|
android:layout_marginStart="4dp"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:text="@string/profile_gateway_label" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/gateway"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginLeft="4dp"
|
||||||
|
android:layout_marginStart="4dp"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||||
|
android:textColor="?android:attr/textColorPrimary" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="6dp"
|
||||||
|
android:layout_marginLeft="4dp"
|
||||||
|
android:layout_marginStart="4dp"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:text="@string/profile_vpn_type_label" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/vpn_type"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="4dp"
|
||||||
|
android:layout_marginLeft="4dp"
|
||||||
|
android:layout_marginStart="4dp"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||||
|
android:textColor="?android:attr/textColorPrimary" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/username_password_group"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_marginTop="6dp">
|
||||||
|
|
||||||
|
<org.strongswan.android.ui.widget.TextInputLayoutHelper
|
||||||
|
android:id="@+id/username_wrap"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content" >
|
||||||
|
|
||||||
|
<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" />
|
||||||
|
|
||||||
|
</org.strongswan.android.ui.widget.TextInputLayoutHelper>
|
||||||
|
|
||||||
|
<org.strongswan.android.ui.widget.TextInputLayoutHelper
|
||||||
|
android:id="@+id/password_wrap"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
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>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
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="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" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/import_user_certificate"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginLeft="4dp"
|
||||||
|
android:layout_marginRight="4dp"
|
||||||
|
android:text="@string/profile_cert_import" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/remote_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_marginLeft="4dp"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:text="@string/profile_ca_label" />
|
||||||
|
|
||||||
|
<include
|
||||||
|
android:id="@+id/remote_certificate"
|
||||||
|
layout="@layout/two_line_button" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</ScrollView>
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
Copyright (C) 2016 Tobias Brunner
|
||||||
|
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.
|
||||||
|
-->
|
||||||
|
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/menu_accept"
|
||||||
|
android:title="@string/profile_edit_import"
|
||||||
|
app:showAsAction="always|withText" />
|
||||||
|
|
||||||
|
</menu>
|
||||||
@@ -48,8 +48,10 @@
|
|||||||
|
|
||||||
<!-- VPN profile details -->
|
<!-- VPN profile details -->
|
||||||
<string name="profile_edit_save">Speichern</string>
|
<string name="profile_edit_save">Speichern</string>
|
||||||
|
<string name="profile_edit_import">Importieren</string>
|
||||||
<string name="profile_edit_cancel">Abbrechen</string>
|
<string name="profile_edit_cancel">Abbrechen</string>
|
||||||
<string name="profile_name_label">Profilname (optional)</string>
|
<string name="profile_name_label">Profilname (optional)</string>
|
||||||
|
<string name="profile_name_label_simple">Profilname</string>
|
||||||
<string name="profile_name_hint">Standardwert ist der konfigurierte Server</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_name_hint_gateway">Standardwert ist \"%1$s\"</string>
|
||||||
<string name="profile_gateway_label">Server</string>
|
<string name="profile_gateway_label">Server</string>
|
||||||
@@ -80,6 +82,15 @@
|
|||||||
<string name="profile_split_tunneling_label">Split-Tunneling</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_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>
|
<string name="profile_split_tunnelingv6_title">Blockiere IPv6 Verkehr der nicht für das VPN bestimmt ist</string>
|
||||||
|
<string name="profile_import">VPN Profile importieren</string>
|
||||||
|
<string name="profile_import_failed">VPN Profil-Import fehlgeschlagen</string>
|
||||||
|
<string name="profile_import_failed_detail">VPN Profil-Import fehlgeschlagen: %1$s</string>
|
||||||
|
<string name="profile_import_failed_not_found">Datei nicht gefunden</string>
|
||||||
|
<string name="profile_import_failed_host">Host unbekannt</string>
|
||||||
|
<string name="profile_import_failed_tls">TLS-Handshake fehlgeschlagen</string>
|
||||||
|
<string name="profile_import_exists">Dieses VPN Profil existiert bereits, die bestehenden Einstellungen werden ersetzt.</string>
|
||||||
|
<string name="profile_cert_import">Zertifikat aus VPN Profil importieren</string>
|
||||||
|
<string name="profile_cert_alias">Zertifikat für \"%1$s\"</string>
|
||||||
<!-- Warnings/Notifications in the details view -->
|
<!-- Warnings/Notifications in the details view -->
|
||||||
<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_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_no_input_username">Bitte geben Sie Ihren Benutzernamen ein</string>
|
||||||
|
|||||||
@@ -48,8 +48,10 @@
|
|||||||
|
|
||||||
<!-- VPN profile details -->
|
<!-- VPN profile details -->
|
||||||
<string name="profile_edit_save">Zapisz</string>
|
<string name="profile_edit_save">Zapisz</string>
|
||||||
|
<string name="profile_edit_import">Import</string>
|
||||||
<string name="profile_edit_cancel">Anuluj</string>
|
<string name="profile_edit_cancel">Anuluj</string>
|
||||||
<string name="profile_name_label">Nazwa profilu (opcjonalny)</string>
|
<string name="profile_name_label">Nazwa profilu (opcjonalny)</string>
|
||||||
|
<string name="profile_name_label_simple">Nazwa profilu</string>
|
||||||
<string name="profile_name_hint">Defaults to the configured server</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_name_hint_gateway">Defaults to \"%1$s\"</string>
|
||||||
<string name="profile_gateway_label">Serwer</string>
|
<string name="profile_gateway_label">Serwer</string>
|
||||||
@@ -80,6 +82,15 @@
|
|||||||
<string name="profile_split_tunneling_label">Split tunneling</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_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>
|
<string name="profile_split_tunnelingv6_title">Block IPv6 traffic not destined for the VPN</string>
|
||||||
|
<string name="profile_import">Import VPN profile</string>
|
||||||
|
<string name="profile_import_failed">Failed to import VPN profile</string>
|
||||||
|
<string name="profile_import_failed_detail">Failed to import VPN profile: %1$s</string>
|
||||||
|
<string name="profile_import_failed_not_found">File not found</string>
|
||||||
|
<string name="profile_import_failed_host">Host unknown</string>
|
||||||
|
<string name="profile_import_failed_tls">TLS handshake failed</string>
|
||||||
|
<string name="profile_import_exists">This VPN profile already exists, its current settings will be replaced.</string>
|
||||||
|
<string name="profile_cert_import">Import certificate from VPN profile</string>
|
||||||
|
<string name="profile_cert_alias">Certificate for \"%1$s\"</string>
|
||||||
<!-- Warnings/Notifications in the details view -->
|
<!-- Warnings/Notifications in the details view -->
|
||||||
<string name="alert_text_no_input_gateway">A value is required to initiate the connection</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_no_input_username">Wprowadź swoją nazwę użytkownika</string>
|
||||||
|
|||||||
@@ -45,8 +45,10 @@
|
|||||||
|
|
||||||
<!-- VPN profile details -->
|
<!-- VPN profile details -->
|
||||||
<string name="profile_edit_save">Сохранить</string>
|
<string name="profile_edit_save">Сохранить</string>
|
||||||
|
<string name="profile_edit_import">Import</string>
|
||||||
<string name="profile_edit_cancel">Отмена</string>
|
<string name="profile_edit_cancel">Отмена</string>
|
||||||
<string name="profile_name_label">Название профиля (необязательный)</string>
|
<string name="profile_name_label">Название профиля (необязательный)</string>
|
||||||
|
<string name="profile_name_label_simple">Название профиля</string>
|
||||||
<string name="profile_name_hint">Defaults to the configured server</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_name_hint_gateway">Defaults to \"%1$s\"</string>
|
||||||
<string name="profile_gateway_label">Сервер</string>
|
<string name="profile_gateway_label">Сервер</string>
|
||||||
@@ -77,6 +79,15 @@
|
|||||||
<string name="profile_split_tunneling_label">Split tunneling</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_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>
|
<string name="profile_split_tunnelingv6_title">Block IPv6 traffic not destined for the VPN</string>
|
||||||
|
<string name="profile_import">Import VPN profile</string>
|
||||||
|
<string name="profile_import_failed">Failed to import VPN profile</string>
|
||||||
|
<string name="profile_import_failed_detail">Failed to import VPN profile: %1$s</string>
|
||||||
|
<string name="profile_import_failed_not_found">File not found</string>
|
||||||
|
<string name="profile_import_failed_host">Host unknown</string>
|
||||||
|
<string name="profile_import_failed_tls">TLS handshake failed</string>
|
||||||
|
<string name="profile_import_exists">This VPN profile already exists, its current settings will be replaced.</string>
|
||||||
|
<string name="profile_cert_import">Import certificate from VPN profile</string>
|
||||||
|
<string name="profile_cert_alias">Certificate for \"%1$s\"</string>
|
||||||
<!-- Warnings/Notifications in the details view -->
|
<!-- Warnings/Notifications in the details view -->
|
||||||
<string name="alert_text_no_input_gateway">A value is required to initiate the connection</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_no_input_username">Пожалуйста введите имя пользователя</string>
|
||||||
|
|||||||
@@ -46,8 +46,10 @@
|
|||||||
|
|
||||||
<!-- VPN profile details -->
|
<!-- VPN profile details -->
|
||||||
<string name="profile_edit_save">Зберегти</string>
|
<string name="profile_edit_save">Зберегти</string>
|
||||||
|
<string name="profile_edit_import">Import</string>
|
||||||
<string name="profile_edit_cancel">Відміна</string>
|
<string name="profile_edit_cancel">Відміна</string>
|
||||||
<string name="profile_name_label">Назва профілю (необов\'язковий)</string>
|
<string name="profile_name_label">Назва профілю (необов\'язковий)</string>
|
||||||
|
<string name="profile_name_label_simple">Назва профілю</string>
|
||||||
<string name="profile_name_hint">Defaults to the configured server</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_name_hint_gateway">Defaults to \"%1$s\"</string>
|
||||||
<string name="profile_gateway_label">Сервер</string>
|
<string name="profile_gateway_label">Сервер</string>
|
||||||
@@ -78,6 +80,15 @@
|
|||||||
<string name="profile_split_tunneling_label">Split tunneling</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_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>
|
<string name="profile_split_tunnelingv6_title">Block IPv6 traffic not destined for the VPN</string>
|
||||||
|
<string name="profile_import">Import VPN profile</string>
|
||||||
|
<string name="profile_import_failed">Failed to import VPN profile</string>
|
||||||
|
<string name="profile_import_failed_detail">Failed to import VPN profile: %1$s</string>
|
||||||
|
<string name="profile_import_failed_not_found">File not found</string>
|
||||||
|
<string name="profile_import_failed_host">Host unknown</string>
|
||||||
|
<string name="profile_import_failed_tls">TLS handshake failed</string>
|
||||||
|
<string name="profile_import_exists">This VPN profile already exists, its current settings will be replaced.</string>
|
||||||
|
<string name="profile_cert_import">Import certificate from VPN profile</string>
|
||||||
|
<string name="profile_cert_alias">Certificate for \"%1$s\"</string>
|
||||||
<!-- Warnings/Notifications in the details view -->
|
<!-- Warnings/Notifications in the details view -->
|
||||||
<string name="alert_text_no_input_gateway">A value is required to initiate the connection</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_no_input_username">Введіть ім\'я користувача </string>
|
||||||
|
|||||||
@@ -48,8 +48,10 @@
|
|||||||
|
|
||||||
<!-- VPN profile details -->
|
<!-- VPN profile details -->
|
||||||
<string name="profile_edit_save">Save</string>
|
<string name="profile_edit_save">Save</string>
|
||||||
|
<string name="profile_edit_import">Import</string>
|
||||||
<string name="profile_edit_cancel">Cancel</string>
|
<string name="profile_edit_cancel">Cancel</string>
|
||||||
<string name="profile_name_label">Profile name (optional)</string>
|
<string name="profile_name_label">Profile name (optional)</string>
|
||||||
|
<string name="profile_name_label_simple">Profile name</string>
|
||||||
<string name="profile_name_hint">Defaults to the configured server</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_name_hint_gateway">Defaults to \"%1$s\"</string>
|
||||||
<string name="profile_gateway_label">Server</string>
|
<string name="profile_gateway_label">Server</string>
|
||||||
@@ -80,6 +82,15 @@
|
|||||||
<string name="profile_split_tunneling_label">Split tunneling</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_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>
|
<string name="profile_split_tunnelingv6_title">Block IPv6 traffic not destined for the VPN</string>
|
||||||
|
<string name="profile_import">Import VPN profile</string>
|
||||||
|
<string name="profile_import_failed">Failed to import VPN profile</string>
|
||||||
|
<string name="profile_import_failed_detail">Failed to import VPN profile: %1$s</string>
|
||||||
|
<string name="profile_import_failed_not_found">File not found</string>
|
||||||
|
<string name="profile_import_failed_host">Host unknown</string>
|
||||||
|
<string name="profile_import_failed_tls">TLS handshake failed</string>
|
||||||
|
<string name="profile_import_exists">This VPN profile already exists, its current settings will be replaced.</string>
|
||||||
|
<string name="profile_cert_import">Import certificate from VPN profile</string>
|
||||||
|
<string name="profile_cert_alias">Certificate for \"%1$s\"</string>
|
||||||
<!-- Warnings/Notifications in the details view -->
|
<!-- Warnings/Notifications in the details view -->
|
||||||
<string name="alert_text_no_input_gateway">A value is required to initiate the connection</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_no_input_username">Please enter your username </string>
|
||||||
|
|||||||
Reference in New Issue
Block a user