summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorArne Schwabe <arne@rfc2549.org>2014-11-16 19:21:14 +0100
committerArne Schwabe <arne@rfc2549.org>2014-11-16 19:21:14 +0100
commit08f9700058b07b79908110baf4cef353d7eba5f4 (patch)
tree8827131a3397705f1b84809ab790657db52aae71
parent413e7689990867ae727bc5624787acf7aa789796 (diff)
Implement support for multiple connection/remote entries (closes issue #2)
--HG-- extra : rebase_source : 22f70cc3750ec5342e1c8b69f8978b92237710c6
-rw-r--r--main/src/main/java/de/blinkt/openvpn/VpnProfile.java54
-rw-r--r--main/src/main/java/de/blinkt/openvpn/activities/VPNPreferences.java3
-rw-r--r--main/src/main/java/de/blinkt/openvpn/core/ConfigParser.java217
-rw-r--r--main/src/main/java/de/blinkt/openvpn/core/Connection.java45
-rw-r--r--main/src/main/java/de/blinkt/openvpn/fragments/ConnectionsAdapter.java176
-rw-r--r--main/src/main/java/de/blinkt/openvpn/fragments/Settings_Basic.java14
-rw-r--r--main/src/main/java/de/blinkt/openvpn/fragments/Settings_Connections.java107
-rw-r--r--main/src/main/java/de/blinkt/openvpn/fragments/Utils.java1
-rw-r--r--main/src/main/java/de/blinkt/openvpn/fragments/VPNProfileList.java4
-rw-r--r--main/src/main/res/anim/fab_anim.xml23
-rw-r--r--main/src/main/res/drawable/oval_ripple.xml13
-rw-r--r--main/src/main/res/drawable/white_rect.xml11
-rw-r--r--main/src/main/res/layout-v21/connection_fab.xml23
-rw-r--r--main/src/main/res/layout/basic_settings.xml45
-rw-r--r--main/src/main/res/layout/connection_fab.xml11
-rw-r--r--main/src/main/res/layout/connections.xml52
-rw-r--r--main/src/main/res/layout/server_card.xml195
-rw-r--r--main/src/main/res/layout/server_layout172
-rw-r--r--main/src/main/res/layout/vpn_profile_list.xml116
-rw-r--r--main/src/main/res/menu/connections.xml15
-rw-r--r--main/src/main/res/values/dimens.xml7
-rw-r--r--main/src/main/res/xml/vpn_headers.xml3
22 files changed, 1112 insertions, 195 deletions
diff --git a/main/src/main/java/de/blinkt/openvpn/VpnProfile.java b/main/src/main/java/de/blinkt/openvpn/VpnProfile.java
index 6f21a847..aff2a362 100644
--- a/main/src/main/java/de/blinkt/openvpn/VpnProfile.java
+++ b/main/src/main/java/de/blinkt/openvpn/VpnProfile.java
@@ -47,6 +47,7 @@ import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
+import de.blinkt.openvpn.core.Connection;
import de.blinkt.openvpn.core.NativeUtils;
import de.blinkt.openvpn.core.OpenVPNService;
import de.blinkt.openvpn.core.VPNLaunchHelper;
@@ -67,7 +68,7 @@ public class VpnProfile implements Serializable {
private static final long serialVersionUID = 7085688938959334563L;
public static final int MAXLOGLEVEL = 4;
- public static final int CURRENT_PROFILE_VERSION = 2;
+ public static final int CURRENT_PROFILE_VERSION = 4;
public static final int DEFAULT_MSSFIX_SIZE = 1450;
public static String DEFAULT_DNS1 = "8.8.8.8";
public static String DEFAULT_DNS2 = "8.8.4.4";
@@ -102,12 +103,10 @@ public class VpnProfile implements Serializable {
public String mClientKeyFilename;
public String mCaFilename;
public boolean mUseLzo = true;
- public String mServerPort = "1194";
- public boolean mUseUdp = true;
public String mPKCS12Filename;
public String mPKCS12Password;
public boolean mUseTLSAuth = false;
- public String mServerName = "openvpn.blinkt.de";
+
public String mDNS1 = DEFAULT_DNS1;
public String mDNS2 = DEFAULT_DNS2;
public String mIPv4Address;
@@ -148,6 +147,13 @@ public class VpnProfile implements Serializable {
public String mExcludedRoutes;
public String mExcludedRoutesv6;
public int mMssFix =0; // -1 is default,
+ public Connection[] mConnections;
+ public boolean mRemoteRandom=false;
+
+ /* Options no long used in new profiles */
+ public String mServerName = "openvpn.blinkt.de";
+ public String mServerPort = "1194";
+ public boolean mUseUdp = true;
@@ -202,7 +208,25 @@ public class VpnProfile implements Serializable {
mAllowLocalLAN = Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT;
}
+
+ if (mProfileVersion < 4)
+ moveOptionsToConnection();
+
mProfileVersion= CURRENT_PROFILE_VERSION;
+
+ }
+
+ private void moveOptionsToConnection() {
+ mConnections = new Connection[1];
+ Connection conn = new Connection();
+
+ conn.mServerName = mServerName;
+ conn.mServerPort = mServerPort;
+ conn.mUseUdp = mUseUdp;
+ conn.mCustomConfiguration = "";
+
+ mConnections[0] = conn;
+
}
public String getConfigFile(Context context, boolean configForOvpn3) {
@@ -263,15 +287,19 @@ public class VpnProfile implements Serializable {
// We cannot use anything else than tun
cfg += "dev tun\n";
- // Server Address
- cfg += "remote ";
- cfg += mServerName;
- cfg += " ";
- cfg += mServerPort;
- if (mUseUdp)
- cfg += " udp\n";
- else
- cfg += " tcp-client\n";
+ if (mConnections.length==1) {
+ cfg += mConnections[0].getConnectionBlock();
+ } else {
+ if (mRemoteRandom)
+ cfg+="remote-random\n";
+ for (Connection conn : mConnections) {
+ if (conn.mEnabled) {
+ cfg += "<connection>\n";
+ cfg += conn.getConnectionBlock();
+ cfg += "</connection>\n";
+ }
+ }
+ }
switch (mAuthenticationType) {
diff --git a/main/src/main/java/de/blinkt/openvpn/activities/VPNPreferences.java b/main/src/main/java/de/blinkt/openvpn/activities/VPNPreferences.java
index 1f022545..1641a51b 100644
--- a/main/src/main/java/de/blinkt/openvpn/activities/VPNPreferences.java
+++ b/main/src/main/java/de/blinkt/openvpn/activities/VPNPreferences.java
@@ -25,6 +25,7 @@ import de.blinkt.openvpn.core.ProfileManager;
import de.blinkt.openvpn.fragments.Settings_Authentication;
import de.blinkt.openvpn.fragments.Settings_Basic;
import de.blinkt.openvpn.fragments.Settings_Behaviour;
+import de.blinkt.openvpn.fragments.Settings_Connections;
import de.blinkt.openvpn.fragments.Settings_IP;
import de.blinkt.openvpn.fragments.Settings_Obscure;
import de.blinkt.openvpn.fragments.Settings_Routing;
@@ -37,7 +38,7 @@ public class VPNPreferences extends PreferenceActivity {
static final Class validFragments[] = new Class[] {
Settings_Authentication.class, Settings_Basic.class, Settings_IP.class,
Settings_Obscure.class, Settings_Routing.class, ShowConfigFragment.class,
- Settings_Behaviour.class
+ Settings_Behaviour.class, Settings_Connections.class
};
private String mProfileUUID;
diff --git a/main/src/main/java/de/blinkt/openvpn/core/ConfigParser.java b/main/src/main/java/de/blinkt/openvpn/core/ConfigParser.java
index 9e99ba55..3015b6fe 100644
--- a/main/src/main/java/de/blinkt/openvpn/core/ConfigParser.java
+++ b/main/src/main/java/de/blinkt/openvpn/core/ConfigParser.java
@@ -5,9 +5,15 @@
package de.blinkt.openvpn.core;
+import android.text.TextUtils;
+import android.util.Pair;
+
import java.io.BufferedReader;
import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
import java.io.Reader;
+import java.io.StringReader;
import java.util.Collections;
import java.util.HashMap;
import java.util.Locale;
@@ -28,9 +34,6 @@ public class ConfigParser {
private HashMap<String, Vector<Vector<String>>> options = new HashMap<String, Vector<Vector<String>>>();
private HashMap<String, Vector<String>> meta = new HashMap<String, Vector<String>>();
-
- private boolean extraRemotesAsCustom=false;
-
public void parseConfig(Reader reader) throws IOException, ConfigParseError {
@@ -53,7 +56,7 @@ public class ConfigParser {
meta.put(metaarg.get(0),metaarg);
continue;
}
- Vector<String> args = parseline(line);
+ Vector<String> args = parseline(line);
if(args.size() ==0)
continue;
@@ -98,7 +101,7 @@ public class ConfigParser {
break;
else {
inlinefile+=line;
- inlinefile+= "\n";
+ inlinefile+= "\n";
}
} while(true);
@@ -132,7 +135,7 @@ public class ConfigParser {
// adapted openvpn's parse function to java
private Vector<String> parseline(String line) throws ConfigParseError {
- Vector<String> parameters = new Vector<String>();
+ Vector<String> parameters = new Vector<String>();
if (line.length()==0)
return parameters;
@@ -145,12 +148,12 @@ public class ConfigParser {
int pos=0;
String currentarg="";
- do {
+ do {
// Emulate the c parsing ...
char in;
if(pos < line.length())
in = line.charAt(pos);
- else
+ else
in = '\0';
if (!backslash && in == '\\' && state != linestate.readin_single_quote)
@@ -228,10 +231,7 @@ public class ConfigParser {
}
- final String[] unsupportedOptions = { "config",
- "connection",
- "proto-force",
- "remote-random",
+ final String[] unsupportedOptions = { "config",
"tls-server"
};
@@ -299,7 +299,7 @@ public class ConfigParser {
"remote",
"float",
"port",
-// "connect-retry",
+ "connect-retry",
"connect-timeout",
"connect-retry-max",
"link-mtu",
@@ -325,7 +325,7 @@ public class ConfigParser {
// This method is far too long
@SuppressWarnings("ConstantConditions")
- public VpnProfile convertProfile() throws ConfigParseError{
+ public VpnProfile convertProfile() throws ConfigParseError, IOException {
boolean noauthtypeset=true;
VpnProfile np = new VpnProfile(CONVERTED_PROFILE);
// Pull, client, tls-client
@@ -338,7 +338,7 @@ public class ConfigParser {
}
Vector<String> secret = getOption("secret", 1, 2);
- if(secret!=null)
+ if(secret!=null)
{
np.mAuthenticationType=VpnProfile.TYPE_STATICKEYS;
noauthtypeset=false;
@@ -362,7 +362,7 @@ public class ConfigParser {
if (route.size() >= 4)
gateway = route.get(3);
- String net = route.get(1);
+ String net = route.get(1);
try {
CIDRIP cidr = new CIDRIP(net, netmask);
if (gateway.equals("net_gateway"))
@@ -398,7 +398,7 @@ public class ConfigParser {
Vector<Vector<String>> tlsauthoptions = getAllOption("tls-auth", 1, 2);
if(tlsauthoptions!=null) {
for(Vector<String> tlsauth:tlsauthoptions) {
- if(tlsauth!=null)
+ if(tlsauth!=null)
{
if(!tlsauth.get(1).equals("[inline]")) {
np.mTLSAuthFilename=tlsauth.get(1);
@@ -458,36 +458,6 @@ public class ConfigParser {
throw new ConfigParseError("Invalid mode for --mode specified, need p2p");
}
- Vector<String> port = getOption("port", 1,1);
- if(port!=null){
- np.mServerPort = port.get(1);
- }
-
- Vector<String> rport = getOption("rport", 1,1);
- if(rport!=null){
- np.mServerPort = rport.get(1);
- }
-
- Vector<String> proto = getOption("proto", 1,1);
- if(proto!=null){
- np.mUseUdp=isUdpProto(proto.get(1));
- }
-
- // Parse remote config
- Vector<Vector<String>> remotes = getAllOption("remote",1,3);
-
- if(remotes!=null && remotes.size()>=1 ) {
- Vector<String> remote = remotes.get(0);
- switch (remote.size()) {
- case 4:
- np.mUseUdp=isUdpProto(remote.get(3));
- case 3:
- np.mServerPort = remote.get(2);
- case 2:
- np.mServerName = remote.get(1);
- }
- }
-
Vector<Vector<String>> dhcpoptions = getAllOption("dhcp-option", 2, 2);
@@ -581,18 +551,18 @@ public class ConfigParser {
if(verifyx509name!=null){
np.mRemoteCN = verifyx509name.get(1);
np.mCheckRemoteCN=true;
- if(verifyx509name.size()>2) {
+ if(verifyx509name.size()>2) {
if (verifyx509name.get(2).equals("name"))
np.mX509AuthType=VpnProfile.X509_VERIFY_TLSREMOTE_RDN;
else if (verifyx509name.get(2).equals("name-prefix"))
np.mX509AuthType=VpnProfile.X509_VERIFY_TLSREMOTE_RDN_PREFIX;
- else
+ else
throw new ConfigParseError("Unknown parameter to x509-verify-name: " + verifyx509name.get(2) );
} else {
np.mX509AuthType = VpnProfile.X509_VERIFY_TLSREMOTE_DN;
}
- }
+ }
Vector<String> verb = getOption("verb",1,1);
@@ -615,7 +585,7 @@ public class ConfigParser {
if(connectretrymax!=null)
np.mConnectRetryMax =connectretrymax.get(1);
- Vector<Vector<String>> remotetls = getAllOption("remote-cert-tls", 1, 1);
+ Vector<Vector<String>> remotetls = getAllOption("remote-cert-tls", 1, 1);
if(remotetls!=null)
if(remotetls.get(0).get(1).equals("server"))
np.mExpectTLSCert=true;
@@ -639,7 +609,49 @@ public class ConfigParser {
}
}
- // Parse OpenVPN Access Server extra
+ Pair<Connection, Connection[]> conns = parseConnectionOptions(null);
+ np.mConnections =conns.second;
+
+ Vector<Vector<String>> connectionBlocks = getAllOption("connection", 1, 1);
+
+ if (np.mConnections.length > 0 && connectionBlocks !=null ) {
+ throw new ConfigParseError("Using a <connection> block and --remote is not allowed.");
+ }
+
+ if (connectionBlocks!=null) {
+ np.mConnections = new Connection[connectionBlocks.size()];
+
+ int connIndex = 0;
+ for (Vector<String> conn : connectionBlocks) {
+ Pair<Connection, Connection[]> connectionBlockConnection =
+ parseConnection(conn.get(1), conns.first);
+
+ if (connectionBlockConnection.second.length != 1)
+ throw new ConfigParseError("A <connection> block must have exactly one remote");
+ np.mConnections[connIndex] = connectionBlockConnection.second[0];
+ connIndex++;
+ }
+ }
+ if(getOption("remote-random", 0, 0) != null)
+ np.mRemoteRandom=true;
+
+ Vector<String> protoforce = getOption("proto-force", 1, 1);
+ if(protoforce!=null) {
+ boolean disableUDP;
+ String protoToDisable = protoforce.get(1);
+ if (protoToDisable.equals("udp"))
+ disableUDP=true;
+ else if (protoToDisable.equals("tcp"))
+ disableUDP=false;
+ else
+ throw new ConfigParseError(String.format("Unknown protocol %s in proto-force", protoToDisable));
+
+ for (Connection conn:np.mConnections)
+ if(conn.mUseUdp==disableUDP)
+ conn.mEnabled=false;
+ }
+
+ // Parse OpenVPN Access Server extra
Vector<String> friendlyname = meta.get("FRIENDLY_NAME");
if(friendlyname !=null && friendlyname.size() > 1)
np.mName=friendlyname.get(1);
@@ -649,20 +661,95 @@ public class ConfigParser {
if(ocusername !=null && ocusername.size() > 1)
np.mUsername=ocusername.get(1);
- // Check the other options
- if(remotes !=null && remotes.size()>1 && extraRemotesAsCustom) {
- // first is already added
- remotes.remove(0);
- np.mCustomConfigOptions += getOptionStrings(remotes);
- np.mUseCustomConfig=true;
-
- }
- checkIgnoreAndInvalidOptions(np);
+ checkIgnoreAndInvalidOptions(np);
fixup(np);
return np;
}
+ private Pair<Connection, Connection[]> parseConnection(String connection, Connection defaultValues) throws IOException, ConfigParseError {
+ // Parse a connection Block as a new configuration file
+
+
+ ConfigParser connectionParser = new ConfigParser();
+ StringReader reader = new StringReader(connection.substring(VpnProfile.INLINE_TAG.length()));
+ connectionParser.parseConfig(reader);
+
+ Pair<Connection, Connection[]> conn = connectionParser.parseConnectionOptions(defaultValues);
+
+ return conn;
+ }
+
+ private Pair<Connection, Connection[]> parseConnectionOptions(Connection connDefault) throws ConfigParseError {
+ Connection conn;
+ if (connDefault!=null)
+ try {
+ conn = connDefault.clone();
+ } catch (CloneNotSupportedException e) {
+ e.printStackTrace();
+ return null;
+ }
+ else
+ conn = new Connection();
+
+ Vector<String> port = getOption("port", 1,1);
+ if(port!=null){
+ conn.mServerPort = port.get(1);
+ }
+
+ Vector<String> rport = getOption("rport", 1,1);
+ if(rport!=null){
+ conn.mServerPort = rport.get(1);
+ }
+
+ Vector<String> proto = getOption("proto", 1,1);
+ if(proto!=null){
+ conn.mUseUdp=isUdpProto(proto.get(1));
+ }
+
+
+ // Parse remote config
+ Vector<Vector<String>> remotes = getAllOption("remote",1,3);
+
+
+ // Assume that we need custom options if connectionDefault are set
+ if(connDefault!=null) {
+ for (Vector<Vector<String>> option : options.values()) {
+
+ conn.mCustomConfiguration += getOptionStrings(option);
+
+ }
+ if (!TextUtils.isEmpty(conn.mCustomConfiguration))
+ conn.mUseCustomConfig = true;
+ }
+ // Make remotes empty to simplify code
+ if (remotes==null)
+ remotes = new Vector<Vector<String>>();
+
+ Connection[] connections = new Connection[remotes.size()];
+
+
+ int i=0;
+ for (Vector<String> remote: remotes) {
+ try {
+ connections[i] = conn.clone();
+ } catch (CloneNotSupportedException e) {
+ e.printStackTrace();
+ }
+ switch (remote.size()) {
+ case 4:
+ connections[i].mUseUdp=isUdpProto(remote.get(3));
+ case 3:
+ connections[i].mServerPort = remote.get(2);
+ case 2:
+ connections[i].mServerName = remote.get(1);
+ }
+ i++;
+ }
+ return Pair.create(conn, connections);
+
+ }
+
private void checkRedirectParameters(VpnProfile np, Vector<Vector<String>> defgw) {
for (Vector<String> redirect: defgw)
for (int i=1;i<redirect.size();i++){
@@ -673,20 +760,16 @@ public class ConfigParser {
}
}
- public void useExtraRemotesAsCustom(boolean b) {
- this.extraRemotesAsCustom = b;
- }
-
private boolean isUdpProto(String proto) throws ConfigParseError {
boolean isudp;
if(proto.equals("udp") || proto.equals("udp6"))
isudp=true;
else if (proto.equals("tcp-client") ||
- proto.equals("tcp") ||
+ proto.equals("tcp") ||
proto.equals("tcp6") ||
proto.endsWith("tcp6-client"))
isudp =false;
- else
+ else
throw new ConfigParseError("Unsupported option to --proto " + proto);
return isudp;
}
diff --git a/main/src/main/java/de/blinkt/openvpn/core/Connection.java b/main/src/main/java/de/blinkt/openvpn/core/Connection.java
new file mode 100644
index 00000000..88cde137
--- /dev/null
+++ b/main/src/main/java/de/blinkt/openvpn/core/Connection.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2012-2014 Arne Schwabe
+ * Distributed under the GNU GPL v2. For full terms see the file doc/LICENSE.txt
+ */
+
+package de.blinkt.openvpn.core;
+
+import android.text.TextUtils;
+
+import java.io.Serializable;
+
+public class Connection implements Serializable, Cloneable {
+ public String mServerName = "openvpn.blinkt.de";
+ public String mServerPort = "1194";
+ public boolean mUseUdp = true;
+ public String mCustomConfiguration="";
+ public boolean mUseCustomConfig=false;
+ public boolean mEnabled=true;
+
+
+ public String getConnectionBlock() {
+ String cfg="";
+
+ // Server Address
+ cfg += "remote ";
+ cfg += mServerName;
+ cfg += " ";
+ cfg += mServerPort;
+ if (mUseUdp)
+ cfg += " udp\n";
+ else
+ cfg += " tcp-client\n";
+
+ if (!TextUtils.isEmpty(mCustomConfiguration) && mUseCustomConfig) {
+ cfg += mCustomConfiguration;
+ cfg += "\n";
+ }
+ return cfg;
+ }
+
+ @Override
+ public Connection clone() throws CloneNotSupportedException {
+ return (Connection) super.clone();
+ }
+}
diff --git a/main/src/main/java/de/blinkt/openvpn/fragments/ConnectionsAdapter.java b/main/src/main/java/de/blinkt/openvpn/fragments/ConnectionsAdapter.java
new file mode 100644
index 00000000..3d20db73
--- /dev/null
+++ b/main/src/main/java/de/blinkt/openvpn/fragments/ConnectionsAdapter.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright (c) 2012-2014 Arne Schwabe
+ * Distributed under the GNU GPL v2. For full terms see the file doc/LICENSE.txt
+ */
+
+package de.blinkt.openvpn.fragments;
+
+import android.content.Context;
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.CheckBox;
+import android.widget.CompoundButton;
+import android.widget.EditText;
+import android.widget.RadioGroup;
+import android.widget.Switch;
+import android.widget.TextView;
+
+import java.util.Arrays;
+
+import de.blinkt.openvpn.R;
+import de.blinkt.openvpn.VpnProfile;
+import de.blinkt.openvpn.core.Connection;
+
+/**
+ * Created by arne on 30.10.14.
+ */
+public class ConnectionsAdapter extends BaseAdapter {
+ private final Context mContext;
+ private final VpnProfile mProfile;
+ private final Settings_Connections mConnectionFragment;
+ private Connection[] mConnections;
+
+ public ConnectionsAdapter(Context c, Settings_Connections connections_fragments, VpnProfile vpnProfile)
+ {
+ mContext = c;
+ mConnections = vpnProfile.mConnections;
+ mProfile = vpnProfile;
+ mConnectionFragment = connections_fragments;
+ }
+
+ @Override
+ public int getCount() {
+ return mConnections.length;
+ }
+
+ @Override
+ public Object getItem(int position) {
+ return position;
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return 0;
+ }
+
+ // create a new ImageView for each item referenced by the Adapter
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ View card;
+ if (convertView==null) {
+ LayoutInflater li = LayoutInflater.from(mContext);
+ card = li.inflate(R.layout.server_card, parent, false);
+ } else {
+ card = convertView;
+ }
+ final Connection connection = mConnections[position];
+ ((TextView)card.findViewById(R.id.portnumber)).
+ setText(connection.mServerPort);
+
+ EditText serverNameView = (EditText) card.findViewById(R.id.servername);
+ EditText portNumberView = (EditText) card.findViewById(R.id.portnumber);
+
+ serverNameView.setText(connection.mServerName);
+ portNumberView.setText(connection.mServerPort);
+
+ Switch remoteSwitch = (Switch) card.findViewById (R.id.remoteSwitch);
+ remoteSwitch.setChecked(connection.mEnabled);
+ remoteSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
+ @Override
+ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+ connection.mEnabled = isChecked;
+ displayWarningifNoneEnabled();
+ }
+ });
+
+
+ serverNameView.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) {
+ connection.mServerName = s.toString();
+ }
+ });
+
+ portNumberView.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) {
+ connection.mServerPort = s.toString();
+ }
+ });
+
+ CheckBox customOptionCB = (CheckBox) card.findViewById(R.id.use_customoptions);
+ final EditText editText = (EditText) card.findViewById(R.id.customoptions);
+ RadioGroup protoGroup = (RadioGroup) card.findViewById(R.id.udptcpradiogroup);
+ protoGroup.check(connection.mUseUdp ? R.id.udp_proto : R.id.tcp_proto);
+ protoGroup.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
+ @Override
+ public void onCheckedChanged(RadioGroup group, int checkedId) {
+ if (checkedId == R.id.udp_proto)
+ connection.mUseUdp=true;
+ else if (checkedId == R.id.tcp_proto)
+ connection.mUseUdp=false;
+ }
+ });
+
+ final View customOptionsLayout = card.findViewById(R.id.custom_options_layout);
+
+ customOptionsLayout.setVisibility(connection.mUseCustomConfig ? View.VISIBLE : View.GONE);
+ editText.setText(connection.mCustomConfiguration);
+
+ customOptionCB.setChecked(connection.mUseCustomConfig);
+ customOptionCB.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
+ @Override
+ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked)
+ {
+ connection.mUseCustomConfig = isChecked;
+ customOptionsLayout.setVisibility(connection.mUseCustomConfig ? View.VISIBLE : View.GONE);
+ }
+ });
+ displayWarningifNoneEnabled();
+ return card;
+ }
+
+ private void displayWarningifNoneEnabled() {
+ int showWarning = View.VISIBLE;
+ for(Connection conn:mConnections) {
+ if(conn.mEnabled)
+ showWarning= View.GONE;
+ }
+ mConnectionFragment.setWarningVisible(showWarning);
+ }
+
+ public void addRemote() {
+ mConnections = Arrays.copyOf(mConnections, mConnections.length+1);
+ mConnections[mConnections.length-1] = new Connection();
+
+ notifyDataSetInvalidated();
+ }
+
+ public void saveProfile() {
+ mProfile.mConnections= mConnections;
+ }
+}
diff --git a/main/src/main/java/de/blinkt/openvpn/fragments/Settings_Basic.java b/main/src/main/java/de/blinkt/openvpn/fragments/Settings_Basic.java
index 52e8d784..1fda0a9c 100644
--- a/main/src/main/java/de/blinkt/openvpn/fragments/Settings_Basic.java
+++ b/main/src/main/java/de/blinkt/openvpn/fragments/Settings_Basic.java
@@ -30,7 +30,6 @@ import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.Spinner;
import android.widget.TextView;
-import android.widget.ToggleButton;
import java.security.cert.X509Certificate;
@@ -45,15 +44,12 @@ public class Settings_Basic extends Fragment implements View.OnClickListener, On
private static final int CHOOSE_FILE_OFFSET = 1000;
private static final int UPDATE_ALIAS = 20;
- private TextView mServerAddress;
- private TextView mServerPort;
private FileSelectLayout mClientCert;
private FileSelectLayout mCaCert;
private FileSelectLayout mClientKey;
private TextView mAliasName;
private TextView mAliasCertificate;
private CheckBox mUseLzo;
- private ToggleButton mTcpUdp;
private Spinner mType;
private FileSelectLayout mpkcs12;
private TextView mPKCS12Password;
@@ -129,14 +125,11 @@ public class Settings_Basic extends Fragment implements View.OnClickListener, On
mView = inflater.inflate(R.layout.basic_settings,container,false);
mProfileName = (EditText) mView.findViewById(R.id.profilename);
- mServerAddress = (TextView) mView.findViewById(R.id.address);
- mServerPort = (TextView) mView.findViewById(R.id.port);
mClientCert = (FileSelectLayout) mView.findViewById(R.id.certselect);
mClientKey = (FileSelectLayout) mView.findViewById(R.id.keyselect);
mCaCert = (FileSelectLayout) mView.findViewById(R.id.caselect);
mpkcs12 = (FileSelectLayout) mView.findViewById(R.id.pkcs12select);
mUseLzo = (CheckBox) mView.findViewById(R.id.lzo);
- mTcpUdp = (ToggleButton) mView.findViewById(id.tcpudp);
mType = (Spinner) mView.findViewById(R.id.type);
mPKCS12Password = (TextView) mView.findViewById(R.id.pkcs12password);
mAliasName = (TextView) mView.findViewById(R.id.aliasname);
@@ -262,9 +255,6 @@ public class Settings_Basic extends Fragment implements View.OnClickListener, On
mCaCert.setData(mProfile.mCaFilename, getActivity());
mUseLzo.setChecked(mProfile.mUseLzo);
- mServerPort.setText(mProfile.mServerPort);
- mServerAddress.setText(mProfile.mServerName);
- mTcpUdp.setChecked(mProfile.mUseUdp);
mType.setSelection(mProfile.mAuthenticationType);
mpkcs12.setData(mProfile.mPKCS12Filename, getActivity());
mPKCS12Password.setText(mProfile.mPKCS12Password);
@@ -284,10 +274,6 @@ public class Settings_Basic extends Fragment implements View.OnClickListener, On
mProfile.mClientKeyFilename = mClientKey.getData();
mProfile.mUseLzo = mUseLzo.isChecked();
- mProfile.mServerPort =mServerPort.getText().toString();
- mProfile.mServerName = mServerAddress.getText().toString();
- mProfile.mUseUdp = mTcpUdp.isChecked();
-
mProfile.mAuthenticationType = mType.getSelectedItemPosition();
mProfile.mPKCS12Filename = mpkcs12.getData();
mProfile.mPKCS12Password = mPKCS12Password.getText().toString();
diff --git a/main/src/main/java/de/blinkt/openvpn/fragments/Settings_Connections.java b/main/src/main/java/de/blinkt/openvpn/fragments/Settings_Connections.java
new file mode 100644
index 00000000..435bdb26
--- /dev/null
+++ b/main/src/main/java/de/blinkt/openvpn/fragments/Settings_Connections.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (c) 2012-2014 Arne Schwabe
+ * Distributed under the GNU GPL v2. For full terms see the file doc/LICENSE.txt
+ */
+
+package de.blinkt.openvpn.fragments;
+
+import android.app.Fragment;
+import android.os.Build;
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.CheckBox;
+import android.widget.GridView;
+import android.widget.ImageButton;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import org.w3c.dom.Text;
+
+import de.blinkt.openvpn.R;
+import de.blinkt.openvpn.VpnProfile;
+import de.blinkt.openvpn.core.ProfileManager;
+
+public class Settings_Connections extends Fragment implements View.OnClickListener {
+ private VpnProfile mProfile;
+ private ConnectionsAdapter mConnectionsAdapter;
+ private TextView mWarning;
+ private CheckBox mUseRandomRemote;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ String profileUuid = getArguments().getString(getActivity().getPackageName() + ".profileUUID");
+ mProfile= ProfileManager.get(getActivity(), profileUuid);
+ getActivity().setTitle(getString(R.string.edit_profile_title, mProfile.getName()));
+
+ setHasOptionsMenu(true);
+ }
+
+ @Override
+ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP)
+ inflater.inflate(R.menu.connections, menu);
+ super.onCreateOptionsMenu(menu, inflater);
+ }
+
+ @Nullable
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ View v = inflater.inflate(R.layout.connections, container, false);
+
+ GridView gridview = (GridView) v.findViewById(R.id.gridview);
+
+ gridview.setOnItemClickListener(new AdapterView.OnItemClickListener() {
+ public void onItemClick(AdapterView<?> parent, View v, int position, long id) {
+ Toast.makeText(getActivity(), "" + position, Toast.LENGTH_SHORT).show();
+ }
+ });
+
+ mConnectionsAdapter = new ConnectionsAdapter(getActivity(), this, mProfile);
+ gridview.setAdapter(mConnectionsAdapter);
+
+ ImageButton fab_button = (ImageButton) v.findViewById(R.id.add_new_remote);
+ if(fab_button!=null)
+ fab_button.setOnClickListener(this);
+
+ mUseRandomRemote = (CheckBox) v.findViewById(R.id.remote_random);
+ mUseRandomRemote.setChecked(mProfile.mRemoteRandom);
+
+ mWarning = (TextView) v.findViewById(R.id.noserver_active_warning);
+ return v;
+ }
+
+
+
+ @Override
+ public void onClick(View v) {
+ if (v.getId() == R.id.add_new_remote) {
+ mConnectionsAdapter.addRemote();
+ }
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ if (item.getItemId()==R.id.add_new_remote)
+ mConnectionsAdapter.addRemote();
+ return super.onOptionsItemSelected(item);
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ mConnectionsAdapter.saveProfile();
+ mProfile.mRemoteRandom = mUseRandomRemote.isChecked();
+ }
+
+ public void setWarningVisible(int showWarning) {
+ mWarning.setVisibility(showWarning);
+ }
+}
diff --git a/main/src/main/java/de/blinkt/openvpn/fragments/Utils.java b/main/src/main/java/de/blinkt/openvpn/fragments/Utils.java
index 2408956c..8676ccaf 100644
--- a/main/src/main/java/de/blinkt/openvpn/fragments/Utils.java
+++ b/main/src/main/java/de/blinkt/openvpn/fragments/Utils.java
@@ -109,7 +109,6 @@ public class Utils {
// DocumentsContract.EXTRA_SHOW_ADVANCED is hidden
i.putExtra("android.content.extra.SHOW_ADVANCED", true);
-
/* Samsung has decided to do something strange, on stock Android GET_CONTENT opens the document UI */
/* fist try with documentsui */
i.setPackage("com.android.documentsui");
diff --git a/main/src/main/java/de/blinkt/openvpn/fragments/VPNProfileList.java b/main/src/main/java/de/blinkt/openvpn/fragments/VPNProfileList.java
index e4911a41..d13d1ca6 100644
--- a/main/src/main/java/de/blinkt/openvpn/fragments/VPNProfileList.java
+++ b/main/src/main/java/de/blinkt/openvpn/fragments/VPNProfileList.java
@@ -135,12 +135,8 @@ public class VPNProfileList extends ListFragment {
TextView newvpntext = (TextView) v.findViewById(R.id.add_new_vpn_hint);
TextView importvpntext = (TextView) v.findViewById(R.id.import_vpn_hint);
-
-
newvpntext.setText(Html.fromHtml(getString(R.string.add_new_vpn_hint),new MiniImageGetter(),null));
importvpntext.setText(Html.fromHtml(getString(R.string.vpn_import_hint),new MiniImageGetter(),null));
-
-
return v;
diff --git a/main/src/main/res/anim/fab_anim.xml b/main/src/main/res/anim/fab_anim.xml
new file mode 100644
index 00000000..40d72b9a
--- /dev/null
+++ b/main/src/main/res/anim/fab_anim.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (c) 2012-2014 Arne Schwabe
+ ~ Distributed under the GNU GPL v2. For full terms see the file doc/LICENSE.txt
+ -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+<item android:state_pressed="true">
+ <objectAnimator
+ android:propertyName="translationZ"
+ android:duration="@android:integer/config_shortAnimTime"
+ android:valueFrom="@dimen/elevation_low"
+ android:valueTo="@dimen/elevation_high"
+ android:valueType="floatType"/>
+</item>
+<item>
+ <objectAnimator
+ android:propertyName="translationZ"
+ android:duration="@android:integer/config_shortAnimTime"
+ android:valueFrom="@dimen/elevation_high"
+ android:valueTo="@dimen/elevation_low"
+ android:valueType="floatType"/>
+</item>
+</selector>
diff --git a/main/src/main/res/drawable/oval_ripple.xml b/main/src/main/res/drawable/oval_ripple.xml
new file mode 100644
index 00000000..65f754d2
--- /dev/null
+++ b/main/src/main/res/drawable/oval_ripple.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (c) 2012-2014 Arne Schwabe
+ ~ Distributed under the GNU GPL v2. For full terms see the file doc/LICENSE.txt
+ -->
+
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+ android:color="?android:colorControlHighlight">
+ <item>
+ <shape android:shape="oval">
+ <solid android:color="?android:colorAccent" />
+ </shape>
+ </item>
+</ripple>
diff --git a/main/src/main/res/drawable/white_rect.xml b/main/src/main/res/drawable/white_rect.xml
new file mode 100644
index 00000000..bb5a9fba
--- /dev/null
+++ b/main/src/main/res/drawable/white_rect.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (c) 2012-2014 Arne Schwabe
+ ~ Distributed under the GNU GPL v2. For full terms see the file doc/LICENSE.txt
+ -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+ <solid android:color="#42000000" />
+ <corners android:radius="5dp" />
+
+</shape> \ No newline at end of file
diff --git a/main/src/main/res/layout-v21/connection_fab.xml b/main/src/main/res/layout-v21/connection_fab.xml
new file mode 100644
index 00000000..571a2a17
--- /dev/null
+++ b/main/src/main/res/layout-v21/connection_fab.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+
+<!--
+ ~ Copyright (c) 2012-2014 Arne Schwabe
+ ~ Distributed under the GNU GPL v2. For full terms see the file doc/LICENSE.txt
+ -->
+
+<ImageButton xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:id="@+id/add_new_remote"
+ android:layout_width="@dimen/round_button_diameter"
+ android:layout_height="@dimen/round_button_diameter"
+ android:layout_alignParentBottom="true"
+ android:layout_alignParentRight="true"
+ android:layout_marginBottom="16dp"
+ android:layout_marginRight="16dp"
+ android:background="@drawable/oval_ripple"
+ android:elevation="1dp"
+ android:src="@android:drawable/ic_input_add"
+ android:stateListAnimator="@anim/fab_anim"
+ android:tint="@android:color/white"
+ tools:showIn="@layout/connections" /> \ No newline at end of file
diff --git a/main/src/main/res/layout/basic_settings.xml b/main/src/main/res/layout/basic_settings.xml
index 0a27a3e5..f8546a1b 100644
--- a/main/src/main/res/layout/basic_settings.xml
+++ b/main/src/main/res/layout/basic_settings.xml
@@ -27,52 +27,7 @@
style="@style/item"
android:inputType="text" />
- <TextView
- style="@style/item"
- android:text="@string/address"
- android:textAppearance="?android:attr/textAppearanceSmall" />
-
- <EditText
- android:id="@+id/address"
- style="@style/item"
- android:inputType="textUri"
- android:text="@string/defaultserver" >
-
- <!-- <requestFocus /> -->
- </EditText>
-
- <TextView
- style="@style/item"
- android:text="@string/port"
- android:textAppearance="?android:attr/textAppearanceSmall" />
-
- <RelativeLayout
- android:id="@+id/relativeLayout1"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginLeft="8dip"
- android:layout_marginRight="8dip" >
- <EditText
- android:id="@+id/port"
- style="@style/item"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_alignParentLeft="true"
- android:layout_alignParentTop="true"
- android:inputType="number"
- android:text="@string/defaultport" />
-
- <ToggleButton
- android:id="@+id/tcpudp"
- style="@style/accountSetupButton"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_alignParentRight="true"
- android:layout_alignParentTop="true"
- android:textOff="TCP"
- android:textOn="UDP" />
- </RelativeLayout>
<CheckBox
android:id="@+id/lzo"
diff --git a/main/src/main/res/layout/connection_fab.xml b/main/src/main/res/layout/connection_fab.xml
new file mode 100644
index 00000000..0cf6ca95
--- /dev/null
+++ b/main/src/main/res/layout/connection_fab.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (c) 2012-2014 Arne Schwabe
+ ~ Distributed under the GNU GPL v2. For full terms see the file doc/LICENSE.txt
+ -->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical" android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+</FrameLayout> \ No newline at end of file
diff --git a/main/src/main/res/layout/connections.xml b/main/src/main/res/layout/connections.xml
new file mode 100644
index 00000000..30494bef
--- /dev/null
+++ b/main/src/main/res/layout/connections.xml
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (c) 2012-2014 Arne Schwabe
+ ~ Distributed under the GNU GPL v2. For full terms see the file doc/LICENSE.txt
+ -->
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:animateLayoutChanges="true"
+ android:orientation="vertical">
+
+
+
+ <TextView
+ android:id="@+id/noserver_active_warning"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_margin="10dp"
+ android:background="@drawable/white_rect"
+ android:drawableLeft="@drawable/ic_dialog_alert"
+ android:drawablePadding="10dp"
+ android:elevation="10dp"
+ android:gravity="center_vertical"
+ android:padding="@dimen/stdpadding"
+ android:text="You need to enable at least one server."
+ android:visibility="visible"
+ tools:visibility="visible" />
+
+ <CheckBox
+ android:text="Use connection entries in random order on connect"
+ android:id="@+id/remote_random"
+ android:layout_below="@id/noserver_active_warning"
+ android:padding="@dimen/stdpadding"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ <GridView
+ android:layout_below="@id/remote_random"
+ android:id="@+id/gridview"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:columnWidth="290dp"
+ android:gravity="center"
+ android:horizontalSpacing="0dp"
+ android:numColumns="auto_fit"
+ android:stretchMode="columnWidth"
+ android:verticalSpacing="10dp" />
+
+ <include layout="@layout/connection_fab" />
+
+</RelativeLayout> \ No newline at end of file
diff --git a/main/src/main/res/layout/server_card.xml b/main/src/main/res/layout/server_card.xml
new file mode 100644
index 00000000..d9051b29
--- /dev/null
+++ b/main/src/main/res/layout/server_card.xml
@@ -0,0 +1,195 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (c) 2012-2014 Arne Schwabe
+ ~ Distributed under the GNU GPL v2. For full terms see the file doc/LICENSE.txt
+ -->
+
+<FrameLayout xmlns:tools="http://schemas.android.com/tools"
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:card_view="http://schemas.android.com/apk/res-auto"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_margin="30dp"
+ >
+ <!-- A CardView that contains a TextView -->
+ <android.support.v7.widget.CardView
+ android:id="@+id/card_view"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ card_view:cardCornerRadius="10dp"
+ >
+
+
+ <RelativeLayout
+ android:animateLayoutChanges="true"
+ android:padding="5dp"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ xmlns:android="http://schemas.android.com/apk/res/android">
+
+
+ <TextView
+ style="@style/item"
+ android:id="@+id/port_label"
+ android:text="@string/port"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentRight="true"
+ android:layout_alignParentEnd="true"
+ android:textAppearance="?android:attr/textAppearanceSmall" />
+
+
+ <TextView
+ style="@style/item"
+ android:text="@string/address"
+ android:id="@+id/server_label"
+ android:textAppearance="?android:attr/textAppearanceSmall" />
+
+
+ <EditText
+ android:id="@+id/portnumber"
+ android:layout_alignParentRight="true"
+ android:layout_alignParentEnd="true"
+ android:layout_below="@id/port_label"
+ android:layout_width="wrap_content"
+
+ android:layout_height="wrap_content"
+ android:inputType="numberDecimal"
+ android:textAppearance="@android:style/TextAppearance.DeviceDefault.Medium"
+ android:text="1194" />
+
+
+ <EditText
+ android:id="@+id/servername"
+ android:singleLine="true"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentLeft="true"
+ android:layout_alignParentStart="true"
+ android:inputType="textUri"
+ android:layout_below="@id/server_label"
+ android:layout_toLeftOf="@id/portnumber"
+ android:layout_toStartOf="@id/portnumber"
+ android:textAppearance="@android:style/TextAppearance.DeviceDefault.Medium"
+ android:text="openvpn.blinkt.de" />
+
+ <TextView
+ android:paddingTop="10dp"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_below="@id/servername"
+ android:id="@+id/protocol"
+ android:text="Protocol" />
+
+ <RadioGroup
+ android:id="@+id/udptcpradiogroup"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_below="@id/protocol"
+ android:orientation="horizontal"
+ android:paddingLeft="20dp">
+
+ <RadioButton
+ android:id="@+id/udp_proto"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="UDP" />
+
+ <Space
+ android:layout_width="20dp"
+ android:layout_height="wrap_content" />
+
+ <RadioButton
+ android:id="@+id/tcp_proto"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="TCP" />
+ </RadioGroup>
+
+ <!-- <TextView
+ android:paddingTop="10dp"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_below="@id/udptcpradiogroup"
+ android:id="@+id/proxytext"
+ android:text="Proxy" />
+
+ <RadioGroup
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:id="@+id/proxygroup"
+ android:paddingLeft="20dp"
+ android:layout_below="@id/proxytext">
+
+ <RadioButton
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="None" />
+
+ <Space
+ android:layout_width="10dp"
+ android:layout_height="wrap_content" />
+
+ <RadioButton
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="HTTP" />
+
+ <Space
+ android:layout_width="10dp"
+ android:layout_height="wrap_content" />
+
+ <RadioButton
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Socks" />
+ </RadioGroup> -->
+
+
+ <CheckBox
+ android:paddingTop="10dp"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_below="@id/udptcpradiogroup"
+ android:id="@+id/use_customoptions"
+ android:text="Custom Options" />
+
+ <LinearLayout
+ android:paddingLeft="10dp"
+ android:paddingStart="10dp"
+ android:id="@+id/custom_options_layout"
+ android:layout_below="@id/use_customoptions"
+ android:layout_width="wrap_content"
+ android:orientation="vertical"
+ android:layout_height="wrap_content">
+ <TextView
+ android:id="@+id/warnung_custom"
+ android:text="Specify custom connection specific options. Use with care"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ <EditText
+ android:inputType="textMultiLine"
+ android:id="@+id/customoptions"
+ android:lines="5"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" />
+ </LinearLayout>
+
+ <Switch
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Enable"
+ android:id="@+id/remoteSwitch"
+ android:layout_gravity="right|bottom"
+ android:layout_below="@+id/portnumber"
+ android:layout_alignRight="@+id/portnumber"
+ android:layout_alignEnd="@+id/portnumber" />
+
+
+ </RelativeLayout>
+ </android.support.v7.widget.CardView>
+
+</FrameLayout> \ No newline at end of file
diff --git a/main/src/main/res/layout/server_layout b/main/src/main/res/layout/server_layout
new file mode 100644
index 00000000..c6d11fe6
--- /dev/null
+++ b/main/src/main/res/layout/server_layout
@@ -0,0 +1,172 @@
+<!--
+ ~ Copyright (c) 2012-2014 Arne Schwabe
+ ~ Distributed under the GNU GPL v2. For full terms see the file doc/LICENSE.txt
+ -->
+
+<RelativeLayout
+ android:padding="5dp"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ xmlns:android="http://schemas.android.com/apk/res/android">
+
+
+ <TextView
+ style="@style/item"
+ android:id="@+id/port_label"
+ android:text="@string/port"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentRight="true"
+ android:layout_alignParentEnd="true"
+ android:textAppearance="?android:attr/textAppearanceSmall" />
+
+
+ <TextView
+ style="@style/item"
+ android:text="@string/address"
+ android:id="@+id/server_label"
+ android:textAppearance="?android:attr/textAppearanceSmall" />
+
+
+ <EditText
+ android:id="@+id/portnumber"
+ android:layout_alignParentRight="true"
+ android:layout_alignParentEnd="true"
+ android:layout_below="@id/port_label"
+ android:layout_width="wrap_content"
+
+ android:layout_height="wrap_content"
+ android:inputType="numberDecimal"
+ android:textAppearance="@android:style/TextAppearance.DeviceDefault.Medium"
+ android:text="1194" />
+
+
+ <EditText
+ android:id="@+id/servername"
+ android:singleLine="true"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentLeft="true"
+ android:layout_alignParentStart="true"
+ android:inputType="textUri"
+ android:layout_below="@id/server_label"
+ android:layout_toLeftOf="@id/portnumber"
+ android:layout_toStartOf="@id/portnumber"
+ android:textAppearance="@android:style/TextAppearance.DeviceDefault.Medium"
+ android:text="openvpn.blinkt.de" />
+
+ <TextView
+ android:paddingTop="10dp"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_below="@id/servername"
+ android:id="@+id/protocol"
+ android:text="Protocol" />
+
+ <RadioGroup
+ android:id="@+id/udptcpradiogroup"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_below="@id/protocol"
+ android:orientation="horizontal"
+ android:paddingLeft="20dp">
+
+ <RadioButton
+ android:id="@+id/udp_proto"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="UDP" />
+
+ <Space
+ android:layout_width="20dp"
+ android:layout_height="wrap_content" />
+
+ <RadioButton
+ android:id="@+id/tcp_proto"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="TCP" />
+ </RadioGroup>
+
+<!-- <TextView
+ android:paddingTop="10dp"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_below="@id/udptcpradiogroup"
+ android:id="@+id/proxytext"
+ android:text="Proxy" />
+
+ <RadioGroup
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:id="@+id/proxygroup"
+ android:paddingLeft="20dp"
+ android:layout_below="@id/proxytext">
+
+ <RadioButton
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="None" />
+
+ <Space
+ android:layout_width="10dp"
+ android:layout_height="wrap_content" />
+
+ <RadioButton
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="HTTP" />
+
+ <Space
+ android:layout_width="10dp"
+ android:layout_height="wrap_content" />
+
+ <RadioButton
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Socks" />
+ </RadioGroup> -->
+
+
+ <CheckBox
+ android:paddingTop="10dp"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_below="@id/udptcpradiogroup"
+ android:id="@+id/use_customoptions"
+ android:text="Custom Options" />
+
+ <LinearLayout
+ android:paddingLeft="10dp"
+ android:paddingStart="10dp"
+ android:layout_below="@id/use_customoptions"
+ android:layout_width="wrap_content"
+ android:orientation="vertical"
+ android:layout_height="wrap_content">
+ <TextView
+ android:id="@+id/warnung_custom"
+ android:text="Specify custom connection specific options. Use with care"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ <EditText
+ android:inputType="textMultiLine"
+ android:id="@+id/customoptions"
+ android:lines="5"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" />
+ </LinearLayout>
+
+ <Switch
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Enable"
+ android:id="@+id/remoteSwitch"
+ android:layout_gravity="right|bottom"
+ android:layout_below="@+id/portnumber"
+ android:layout_alignRight="@+id/portnumber"
+ android:layout_alignEnd="@+id/portnumber" />
+
+
+</RelativeLayout> \ No newline at end of file
diff --git a/main/src/main/res/layout/vpn_profile_list.xml b/main/src/main/res/layout/vpn_profile_list.xml
index 18184e5d..79272f6c 100644
--- a/main/src/main/res/layout/vpn_profile_list.xml
+++ b/main/src/main/res/layout/vpn_profile_list.xml
@@ -1,71 +1,89 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
+<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright (c) 2012-2014 Arne Schwabe
~ Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
-->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical"
- android:paddingLeft="@dimen/stdpadding"
- android:paddingRight="@dimen/stdpadding" >
-
- <ListView
- android:descendantFocusability="afterDescendants"
- android:id="@android:id/list"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent" />
+ android:layout_height="match_parent">
<LinearLayout
- android:id="@android:id/empty"
+
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:gravity="center"
- android:orientation="vertical" >
+ android:orientation="vertical"
+ android:paddingLeft="@dimen/stdpadding"
+ android:paddingRight="@dimen/stdpadding">
- <TextView
+ <ListView
+ android:id="@android:id/list"
android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:text="@string/no_vpn_profiles_defined"
- android:textAppearance="?android:attr/textAppearanceLarge" />
+ android:layout_height="fill_parent"
+ android:descendantFocusability="afterDescendants" />
- <Space
+ <LinearLayout
+ android:id="@android:id/empty"
android:layout_width="match_parent"
- android:layout_height="12sp" />
+ android:layout_height="match_parent"
+ android:gravity="center"
+ android:orientation="vertical">
+ <TextView
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/no_vpn_profiles_defined"
+ android:textAppearance="?android:attr/textAppearanceLarge" />
- <TextView
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:text="@string/openvpn_is_no_free_vpn"
- android:autoLink="web" />
- <Space
- android:layout_width="match_parent"
- android:layout_height="12sp" />
+ <Space
+ android:layout_width="match_parent"
+ android:layout_height="12sp" />
- <TextView
- android:id="@+id/add_new_vpn_hint"
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- tools:ignore="SelectableText" />
+ <TextView
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:autoLink="web"
+ android:text="@string/openvpn_is_no_free_vpn" />
- <TextView
- android:id="@+id/import_vpn_hint"
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- tools:ignore="SelectableText" />
+ <Space
+ android:layout_width="match_parent"
+ android:layout_height="12sp" />
- <Space
- android:layout_width="match_parent"
- android:layout_height="12sp" />
- <TextView
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:text="@string/faq_hint" />
+ <TextView
+ android:id="@+id/add_new_vpn_hint"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ tools:ignore="SelectableText" />
+
+ <TextView
+ android:id="@+id/import_vpn_hint"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ tools:ignore="SelectableText" />
+
+ <Space
+ android:layout_width="match_parent"
+ android:layout_height="12sp" />
+
+ <TextView
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/faq_hint" />
+ </LinearLayout>
+
</LinearLayout>
-</LinearLayout> \ No newline at end of file
+<!-- <ImageButton
+ android:id="@+id/add_button"
+ android:layout_width="@dimen/round_button_diameter"
+ android:layout_height="@dimen/round_button_diameter"
+ android:layout_gravity="end|bottom"
+ android:tint="@android:color/white"
+ android:background="@drawable/oval_ripple"
+ android:elevation="@dimen/elevation_low"
+ android:visibility="gone"
+ android:layout_marginBottom="@dimen/add_button_margin"
+ android:layout_marginEnd="@dimen/add_button_margin"
+ android:src="@drawable/ic_archive_grey600_24dp" /> -->
+</FrameLayout>
diff --git a/main/src/main/res/menu/connections.xml b/main/src/main/res/menu/connections.xml
new file mode 100644
index 00000000..7446746f
--- /dev/null
+++ b/main/src/main/res/menu/connections.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (c) 2012-2014 Arne Schwabe
+ ~ Distributed under the GNU GPL v2. For full terms see the file doc/LICENSE.txt
+ -->
+
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+ <item
+ android:icon="@drawable/ic_menu_add"
+ android:title="Add new remote"
+ android:id="@+id/add_new_remote"
+ android:titleCondensed="@string/add"
+ android:showAsAction="ifRoom|withText"
+ />
+</menu> \ No newline at end of file
diff --git a/main/src/main/res/values/dimens.xml b/main/src/main/res/values/dimens.xml
index 9b43bd12..d46cfa98 100644
--- a/main/src/main/res/values/dimens.xml
+++ b/main/src/main/res/values/dimens.xml
@@ -9,4 +9,11 @@
<dimen name="stdpadding">8dp</dimen>
<bool name="logSildersAlwaysVisible">false</bool>
+ <dimen name="diameter">48dp</dimen>
+ <dimen name="elevation_low">1dp</dimen>
+ <dimen name="elevation_high">4dp</dimen>
+ <dimen name="add_button_margin">16dp</dimen>
+ <dimen name="round_button_diameter">56dp</dimen>
+
+
</resources> \ No newline at end of file
diff --git a/main/src/main/res/xml/vpn_headers.xml b/main/src/main/res/xml/vpn_headers.xml
index 65f5e825..576aea8b 100644
--- a/main/src/main/res/xml/vpn_headers.xml
+++ b/main/src/main/res/xml/vpn_headers.xml
@@ -6,11 +6,12 @@
<preference-headers xmlns:android="http://schemas.android.com/apk/res/android" >
-
<header
android:fragment="de.blinkt.openvpn.fragments.Settings_Behaviour"
android:title="@string/client_behaviour"
/>
+ <header android:fragment="de.blinkt.openvpn.fragments.Settings_Connections"
+ android:title="Server List" />
<header
android:id="@+id/basicsettingsid"
android:fragment="de.blinkt.openvpn.fragments.Settings_Basic"