0
0
mirror of https://github.com/schwabe/ics-openvpn.git synced 2024-09-20 03:52:27 +02:00

Implement support for setting proxies

This commit is contained in:
Arne Schwabe 2018-03-05 00:20:46 +01:00
parent c5b13bee94
commit 18ccd98389
7 changed files with 488 additions and 214 deletions

View File

@ -61,7 +61,7 @@ public class VpnProfile implements Serializable, Cloneable {
private static final long serialVersionUID = 7085688938959334563L;
public static final int MAXLOGLEVEL = 4;
public static final int CURRENT_PROFILE_VERSION = 6;
public static final int CURRENT_PROFILE_VERSION = 7;
public static final int DEFAULT_MSSFIX_SIZE = 1280;
public static String DEFAULT_DNS1 = "8.8.8.8";
public static String DEFAULT_DNS2 = "8.8.4.4";
@ -244,6 +244,7 @@ public class VpnProfile implements Serializable, Cloneable {
}
if (mAllowedAppsVpn == null)
mAllowedAppsVpn = new HashSet<>();
if (mConnections == null)
mConnections = new Connection[0];
@ -251,7 +252,11 @@ public class VpnProfile implements Serializable, Cloneable {
if (TextUtils.isEmpty(mProfileCreator))
mUserEditable = true;
}
if (mProfileVersion < 7) {
for (Connection c: mConnections)
if (c.mProxyType == null)
c.mProxyType = Connection.ProxyType.NONE;
}
mProfileVersion = CURRENT_PROFILE_VERSION;

View File

@ -19,6 +19,16 @@ public class Connection implements Serializable, Cloneable {
public boolean mEnabled = true;
public int mConnectTimeout = 0;
public static final int CONNECTION_DEFAULT_TIMEOUT = 120;
public ProxyType mProxyType = ProxyType.NONE;
public String mProxyName = "proxy.example.com";
public String mProxyPort = "8080";
public enum ProxyType {
NONE,
HTTP,
SOCKS5,
ORBOT
}
private static final long serialVersionUID = 92031902903829089L;

View File

@ -15,7 +15,6 @@ import android.app.UiModeManager;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.content.pm.ShortcutManager;
import android.content.res.Configuration;
@ -47,7 +46,6 @@ import java.util.Collection;
import java.util.Locale;
import java.util.Vector;
import de.blinkt.openvpn.BuildConfig;
import de.blinkt.openvpn.LaunchVPN;
import de.blinkt.openvpn.R;
import de.blinkt.openvpn.VpnProfile;
@ -58,8 +56,6 @@ import de.blinkt.openvpn.core.VpnStatus.ByteCountListener;
import de.blinkt.openvpn.core.VpnStatus.StateListener;
import static de.blinkt.openvpn.core.ConnectionStatus.LEVEL_CONNECTED;
import static de.blinkt.openvpn.core.ConnectionStatus.LEVEL_CONNECTING_NO_SERVER_REPLY_YET;
import static de.blinkt.openvpn.core.ConnectionStatus.LEVEL_START;
import static de.blinkt.openvpn.core.ConnectionStatus.LEVEL_WAITING_FOR_USER_INPUT;
import static de.blinkt.openvpn.core.NetworkSpace.ipAddress;
@ -68,17 +64,22 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac
public static final String START_SERVICE_STICKY = "de.blinkt.openvpn.START_SERVICE_STICKY";
public static final String ALWAYS_SHOW_NOTIFICATION = "de.blinkt.openvpn.NOTIFICATION_ALWAYS_VISIBLE";
public static final String DISCONNECT_VPN = "de.blinkt.openvpn.DISCONNECT_VPN";
private static final String PAUSE_VPN = "de.blinkt.openvpn.PAUSE_VPN";
private static final String RESUME_VPN = "de.blinkt.openvpn.RESUME_VPN";
public static final String NOTIFICATION_CHANNEL_BG_ID = "openvpn_bg";
public static final String NOTIFICATION_CHANNEL_NEWSTATUS_ID = "openvpn_newstat";
public static final String VPNSERVICE_TUN = "vpnservice-tun";
private String lastChannel;
public final static String ORBOT_PACKAGE_NAME = "org.torproject.android";
private static final String PAUSE_VPN = "de.blinkt.openvpn.PAUSE_VPN";
private static final String RESUME_VPN = "de.blinkt.openvpn.RESUME_VPN";
private static final int PRIORITY_MIN = -2;
private static final int PRIORITY_DEFAULT = 0;
private static final int PRIORITY_MAX = 2;
private static boolean mNotificationAlwaysVisible = false;
private static Class mNotificationActivityClass;
private final Vector<String> mDnslist = new Vector<>();
private final NetworkSpace mRoutes = new NetworkSpace();
private final NetworkSpace mRoutesv6 = new NetworkSpace();
private final Object mProcessLock = new Object();
private String lastChannel;
private Thread mProcessThread = null;
private VpnProfile mProfile;
private String mDomain = null;
@ -90,19 +91,6 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac
private boolean mStarting = false;
private long mConnecttime;
private OpenVPNManagement mManagement;
private String mLastTunCfg;
private String mRemoteGW;
private final Object mProcessLock = new Object();
private Handler guiHandler;
private Toast mlastToast;
private Runnable mOpenVPNThread;
private static Class mNotificationActivityClass;
private static final int PRIORITY_MIN = -2;
private static final int PRIORITY_DEFAULT = 0;
private static final int PRIORITY_MAX = 2;
private final IBinder mBinder = new IOpenVPNServiceInternal.Stub() {
@Override
@ -127,12 +115,11 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac
};
@Override
public void addAllowedExternalApp(String packagename) throws RemoteException {
ExternalAppDatabase extapps = new ExternalAppDatabase(OpenVPNService.this);
extapps.addApp(packagename);
}
private String mLastTunCfg;
private String mRemoteGW;
private Handler guiHandler;
private Toast mlastToast;
private Runnable mOpenVPNThread;
// From: http://stackoverflow.com/questions/3758606/how-to-convert-byte-size-into-human-readable-format-in-java
public static String humanReadableByteCount(long bytes, boolean speed, Resources res) {
@ -172,6 +159,21 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac
}
/**
* Sets the activity which should be opened when tapped on the permanent notification tile.
*
* @param activityClass The activity class to open
*/
public static void setNotificationActivityClass(Class<? extends Activity> activityClass) {
mNotificationActivityClass = activityClass;
}
@Override
public void addAllowedExternalApp(String packagename) throws RemoteException {
ExternalAppDatabase extapps = new ExternalAppDatabase(OpenVPNService.this);
extapps.addApp(packagename);
}
@Override
public IBinder onBind(Intent intent) {
String action = intent.getAction();
@ -211,7 +213,6 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac
}
}
private void showNotification(final String msg, String tickerText, @NonNull String channel, long when, ConnectionStatus status) {
NotificationManager mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
int icon = getIconByConnectionStatus(status);
@ -255,8 +256,8 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac
//noinspection NewApi
nbuilder.setChannelId(channel);
if (mProfile != null)
//noinspection NewApi
nbuilder.setShortcutId(mProfile.getUUIDString());
//noinspection NewApi
nbuilder.setShortcutId(mProfile.getUUIDString());
}
@ -370,15 +371,6 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac
}
/**
* Sets the activity which should be opened when tapped on the permanent notification tile.
*
* @param activityClass The activity class to open
*/
public static void setNotificationActivityClass(Class<? extends Activity> activityClass) {
mNotificationActivityClass = activityClass;
}
PendingIntent getUserInputIntent(String needed) {
Intent intent = new Intent(getApplicationContext(), LaunchVPN.class);
intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
@ -589,8 +581,7 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac
}
Runnable processThread;
if (useOpenVPN3)
{
if (useOpenVPN3) {
OpenVPNManagement mOpenVPN3 = instantiateOpenVPN3Core();
processThread = (Runnable) mOpenVPN3;
mManagement = mOpenVPN3;
@ -905,14 +896,36 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private void setAllowedVpnPackages(Builder builder) {
boolean profileUsesOrBot = false;
for (Connection c : mProfile.mConnections) {
if (c.mProxyType == Connection.ProxyType.ORBOT)
profileUsesOrBot = true;
}
if (profileUsesOrBot)
VpnStatus.logDebug("VPN Profile uses at least one server entry with Orbot. Setting up VPN so that OrBot is not redirected over VPN.");
boolean atLeastOneAllowedApp = false;
if (mProfile.mAllowedAppsVpnAreDisallowed && profileUsesOrBot) {
try {
builder.addDisallowedApplication(ORBOT_PACKAGE_NAME);
} catch (PackageManager.NameNotFoundException e) {
VpnStatus.logDebug("Orbot not installed?");
}
}
for (String pkg : mProfile.mAllowedAppsVpn) {
try {
if (mProfile.mAllowedAppsVpnAreDisallowed) {
builder.addDisallowedApplication(pkg);
} else {
builder.addAllowedApplication(pkg);
atLeastOneAllowedApp = true;
if (!(profileUsesOrBot && pkg.equals(ORBOT_PACKAGE_NAME))) {
builder.addAllowedApplication(pkg);
atLeastOneAllowedApp = true;
}
}
} catch (PackageManager.NameNotFoundException e) {
mProfile.mAllowedAppsVpn.remove(pkg);
@ -1051,7 +1064,7 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac
if (mLocalIP.len <= 31 && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
CIDRIP interfaceRoute = new CIDRIP(mLocalIP.mIp, mLocalIP.len);
interfaceRoute.normalise();
addRoute(interfaceRoute ,true);
addRoute(interfaceRoute, true);
}

View File

@ -6,6 +6,7 @@
package de.blinkt.openvpn.core;
import android.content.Context;
import android.content.Intent;
import android.net.LocalServerSocket;
import android.net.LocalSocket;
import android.net.LocalSocketAddress;
@ -375,22 +376,94 @@ public class OpenVpnManagementThread implements Runnable, OpenVPNManagement {
private void processProxyCMD(String argument) {
String[] args = argument.split(",", 3);
SocketAddress proxyaddr = ProxyDetection.detectProxy(mProfile);
Connection.ProxyType proxyType = Connection.ProxyType.NONE;
if (args.length >= 2) {
String proto = args[1];
if (proto.equals("UDP")) {
proxyaddr = null;
int connectionEntryNumber = Integer.parseInt(args[0]) - 1;
String proxyport = null;
String proxyname = null;
if (mProfile.mConnections.length > connectionEntryNumber) {
Connection connection = mProfile.mConnections[connectionEntryNumber];
proxyType = connection.mProxyType;
proxyname = connection.mProxyName;
proxyport = connection.mProxyPort;
} else {
VpnStatus.logError(String.format(Locale.ENGLISH, "OpenVPN is asking for a proxy of an unknonwn connection entry (%d)", connectionEntryNumber));
}
// atuo detection of proxy
if (proxyType == Connection.ProxyType.NONE) {
SocketAddress proxyaddr = ProxyDetection.detectProxy(mProfile);
if (proxyaddr instanceof InetSocketAddress) {
InetSocketAddress isa = (InetSocketAddress) proxyaddr;
proxyType = Connection.ProxyType.HTTP;
proxyname = isa.getHostName();
proxyport = String.valueOf(isa.getPort());
}
}
if (proxyaddr instanceof InetSocketAddress) {
InetSocketAddress isa = (InetSocketAddress) proxyaddr;
VpnStatus.logInfo(R.string.using_proxy, isa.getHostName(), isa.getPort());
if (args.length >= 2 && proxyType == Connection.ProxyType.HTTP) {
String proto = args[1];
if (proto.equals("UDP")) {
proxyname = null;
VpnStatus.logInfo("Not using an HTTP proxy since the connection uses UDP");
}
}
String proxycmd = String.format(Locale.ENGLISH, "proxy HTTP %s %d\n", isa.getHostName(), isa.getPort());
if (proxyType == Connection.ProxyType.ORBOT) {
// schwabe: TODO WIP and does not really work
/* OrbotHelper orbotHelper = OrbotHelper.get(mOpenVPNService);
orbotHelper.addStatusCallback(new StatusCallback() {
@Override
public void onEnabled(Intent statusIntent) {
VpnStatus.logDebug("Orbot onEnabled:" + statusIntent.toString());
}
@Override
public void onStarting() {
VpnStatus.logDebug("Orbot onStarting");
}
@Override
public void onStopping() {
VpnStatus.logDebug("Orbot onStopping");
}
@Override
public void onDisabled() {
VpnStatus.logDebug("Orbot onDisabled");
}
@Override
public void onStatusTimeout() {
VpnStatus.logDebug("Orbot onStatusTimeout");
}
@Override
public void onNotYetInstalled() {
VpnStatus.logDebug("Orbot notyetinstalled");
}
});
orbotHelper.init();
if(!OrbotHelper.requestStartTor(mOpenVPNService))
VpnStatus.logError("Request starting Orbot failed.");
*/
proxyname = "127.0.0.1";
proxyport = "8118";
proxyType = Connection.ProxyType.HTTP;
}
if (proxyType != Connection.ProxyType.NONE && proxyname != null) {
VpnStatus.logInfo(R.string.using_proxy, proxyname, proxyport);
String proxycmd = String.format(Locale.ENGLISH, "proxy %s %s %s\n",
proxyType == Connection.ProxyType.HTTP ? "HTTP" : "SOCKS",
proxyname, proxyport);
managmentCommand(proxycmd);
} else {
managmentCommand("proxy NONE\n");
@ -547,8 +620,9 @@ public class OpenVpnManagementThread implements Runnable, OpenVPNManagement {
try {
// Ignore Auth token message, already managed by openvpn itself
if (argument.startsWith("Auth-Token:"))
if (argument.startsWith("Auth-Token:")) {
return;
}
int p1 = argument.indexOf('\'');
int p2 = argument.indexOf('\'', p1 + 1);

View File

@ -7,7 +7,6 @@ package de.blinkt.openvpn.fragments;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.support.v7.widget.RecyclerView;
import android.text.Editable;
import android.text.TextWatcher;
@ -15,7 +14,6 @@ import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.RadioGroup;
@ -29,14 +27,13 @@ import de.blinkt.openvpn.VpnProfile;
import de.blinkt.openvpn.core.Connection;
public class ConnectionsAdapter extends RecyclerView.Adapter<ConnectionsAdapter.ConnectionsHolder> {
private static final int TYPE_NORMAL = 0;
private static final int TYPE_FOOTER = TYPE_NORMAL + 1;
private final Context mContext;
private final VpnProfile mProfile;
private final Settings_Connections mConnectionFragment;
private Connection[] mConnections;
private static final int TYPE_NORMAL = 0;
private static final int TYPE_FOOTER = TYPE_NORMAL + 1;
ConnectionsAdapter(Context c, Settings_Connections connections_fragments, VpnProfile vpnProfile) {
mContext = c;
mConnections = vpnProfile.mConnections;
@ -44,6 +41,134 @@ public class ConnectionsAdapter extends RecyclerView.Adapter<ConnectionsAdapter.
mConnectionFragment = connections_fragments;
}
@Override
public ConnectionsAdapter.ConnectionsHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
LayoutInflater li = LayoutInflater.from(mContext);
View card;
if (viewType == TYPE_NORMAL) {
card = li.inflate(R.layout.server_card, viewGroup, false);
} else { // TYPE_FOOTER
card = li.inflate(R.layout.server_footer, viewGroup, false);
}
return new ConnectionsHolder(card, this, viewType);
}
@Override
public void onBindViewHolder(final ConnectionsAdapter.ConnectionsHolder cH, int position) {
if (position == mConnections.length) {
// Footer
return;
}
final Connection connection = mConnections[position];
cH.mConnection = null;
cH.mPortNumberView.setText(connection.mServerPort);
cH.mServerNameView.setText(connection.mServerName);
cH.mPortNumberView.setText(connection.mServerPort);
cH.mRemoteSwitch.setChecked(connection.mEnabled);
cH.mProxyNameView.setText(connection.mProxyName);
cH.mProxyPortNumberView.setText(connection.mProxyPort);
cH.mConnectText.setText(String.valueOf(connection.getTimeout()));
cH.mConnectSlider.setProgress(connection.getTimeout());
cH.mProtoGroup.check(connection.mUseUdp ? R.id.udp_proto : R.id.tcp_proto);
switch (connection.mProxyType) {
case NONE:
cH.mProxyGroup.check(R.id.proxy_none);
break;
case HTTP:
cH.mProxyGroup.check(R.id.proxy_http);
break;
case SOCKS5:
cH.mProxyGroup.check(R.id.proxy_http);
break;
case ORBOT:
cH.mProxyGroup.check(R.id.proxy_orbot);
break;
}
cH.mCustomOptionsLayout.setVisibility(connection.mUseCustomConfig ? View.VISIBLE : View.GONE);
cH.mCustomOptionText.setText(connection.mCustomConfiguration);
cH.mCustomOptionCB.setChecked(connection.mUseCustomConfig);
cH.mConnection = connection;
setVisibilityProxyServer(cH, connection);
}
private void setVisibilityProxyServer(ConnectionsHolder cH, Connection connection) {
int visible = (connection.mProxyType == Connection.ProxyType.HTTP || connection.mProxyType == Connection.ProxyType.SOCKS5) ? View.VISIBLE : View.GONE;
cH.mProxyNameView.setVisibility(visible);
cH.mProxyPortNumberView.setVisibility(visible);
cH.mProxyNameLabel.setVisibility(visible);
}
private void removeRemote(int idx) {
Connection[] mConnections2 = Arrays.copyOf(mConnections, mConnections.length - 1);
for (int i = idx + 1; i < mConnections.length; i++) {
mConnections2[i - 1] = mConnections[i];
}
mConnections = mConnections2;
}
@Override
public int getItemCount() {
return mConnections.length + 1; //for footer
}
@Override
public int getItemViewType(int position) {
if (position == mConnections.length)
return TYPE_FOOTER;
else
return TYPE_NORMAL;
}
void addRemote() {
mConnections = Arrays.copyOf(mConnections, mConnections.length + 1);
mConnections[mConnections.length - 1] = new Connection();
notifyItemInserted(mConnections.length - 1);
displayWarningIfNoneEnabled();
}
void displayWarningIfNoneEnabled() {
int showWarning = View.VISIBLE;
for (Connection conn : mConnections) {
if (conn.mEnabled)
showWarning = View.GONE;
}
mConnectionFragment.setWarningVisible(showWarning);
}
void saveProfile() {
mProfile.mConnections = mConnections;
}
static abstract class OnTextChangedWatcher implements 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) {
}
}
class ConnectionsHolder extends RecyclerView.ViewHolder {
private final EditText mServerNameView;
private final EditText mPortNumberView;
@ -56,21 +181,31 @@ public class ConnectionsAdapter extends RecyclerView.Adapter<ConnectionsAdapter.
private final EditText mConnectText;
private final SeekBar mConnectSlider;
private final ConnectionsAdapter mConnectionsAdapter;
private final RadioGroup mProxyGroup;
private final EditText mProxyNameView;
private final EditText mProxyPortNumberView;
private final View mProxyNameLabel;
private Connection mConnection; // Set to null on update
ConnectionsHolder(View card, ConnectionsAdapter connectionsAdapter, int viewType) {
super(card);
mServerNameView = (EditText) card.findViewById(R.id.servername);
mPortNumberView = (EditText) card.findViewById(R.id.portnumber);
mRemoteSwitch = (Switch) card.findViewById(R.id.remoteSwitch);
mCustomOptionCB = (CheckBox) card.findViewById(R.id.use_customoptions);
mCustomOptionText = (EditText) card.findViewById(R.id.customoptions);
mProtoGroup = (RadioGroup) card.findViewById(R.id.udptcpradiogroup);
mServerNameView = card.findViewById(R.id.servername);
mPortNumberView = card.findViewById(R.id.portnumber);
mRemoteSwitch = card.findViewById(R.id.remoteSwitch);
mCustomOptionCB = card.findViewById(R.id.use_customoptions);
mCustomOptionText = card.findViewById(R.id.customoptions);
mProtoGroup = card.findViewById(R.id.udptcpradiogroup);
mCustomOptionsLayout = card.findViewById(R.id.custom_options_layout);
mDeleteButton = (ImageButton) card.findViewById(R.id.remove_connection);
mConnectSlider = (SeekBar) card.findViewById(R.id.connect_silder);
mConnectText = (EditText) card.findViewById(R.id.connect_timeout);
mDeleteButton = card.findViewById(R.id.remove_connection);
mConnectSlider = card.findViewById(R.id.connect_silder);
mConnectText = card.findViewById(R.id.connect_timeout);
mProxyGroup = card.findViewById(R.id.proxyradiogroup);
mProxyNameView = card.findViewById(R.id.proxyname);
mProxyPortNumberView = card.findViewById(R.id.proxyport);
mProxyNameLabel = card.findViewById(R.id.proxyserver_label);
mConnectionsAdapter = connectionsAdapter;
@ -80,25 +215,39 @@ public class ConnectionsAdapter extends RecyclerView.Adapter<ConnectionsAdapter.
void addListeners() {
mRemoteSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if (mConnection != null) {
mConnection.mEnabled = isChecked;
mConnectionsAdapter.displayWarningIfNoneEnabled();
}
mRemoteSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> {
if (mConnection != null) {
mConnection.mEnabled = isChecked;
mConnectionsAdapter.displayWarningIfNoneEnabled();
}
});
mProtoGroup.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(RadioGroup group, int checkedId) {
if (mConnection != null) {
if (checkedId == R.id.udp_proto)
mConnection.mUseUdp = true;
else if (checkedId == R.id.tcp_proto)
mConnection.mUseUdp = false;
mProtoGroup.setOnCheckedChangeListener((group, checkedId) -> {
if (mConnection != null) {
if (checkedId == R.id.udp_proto)
mConnection.mUseUdp = true;
else if (checkedId == R.id.tcp_proto)
mConnection.mUseUdp = false;
}
});
mProxyGroup.setOnCheckedChangeListener((group, checkedId) -> {
if (mConnection != null) {
switch (checkedId) {
case R.id.proxy_none:
mConnection.mProxyType = Connection.ProxyType.NONE;
break;
case R.id.proxy_http:
mConnection.mProxyType = Connection.ProxyType.HTTP;
break;
case R.id.proxy_socks:
mConnection.mProxyType = Connection.ProxyType.SOCKS5;
break;
case R.id.proxy_orbot:
mConnection.mProxyType = Connection.ProxyType.ORBOT;
break;
}
setVisibilityProxyServer(this, mConnection);
}
});
@ -110,13 +259,10 @@ public class ConnectionsAdapter extends RecyclerView.Adapter<ConnectionsAdapter.
}
});
mCustomOptionCB.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if (mConnection != null) {
mConnection.mUseCustomConfig = isChecked;
mCustomOptionsLayout.setVisibility(mConnection.mUseCustomConfig ? View.VISIBLE : View.GONE);
}
mCustomOptionCB.setOnCheckedChangeListener((buttonView, isChecked) -> {
if (mConnection != null) {
mConnection.mUseCustomConfig = isChecked;
mCustomOptionsLayout.setVisibility(mConnection.mUseCustomConfig ? View.VISIBLE : View.GONE);
}
});
@ -140,6 +286,25 @@ public class ConnectionsAdapter extends RecyclerView.Adapter<ConnectionsAdapter.
}
});
mProxyNameView.addTextChangedListener(new OnTextChangedWatcher() {
@Override
public void afterTextChanged(Editable s) {
if (mConnection != null) {
mConnection.mProxyName = s.toString();
}
}
});
mProxyPortNumberView.addTextChangedListener(new OnTextChangedWatcher() {
@Override
public void afterTextChanged(Editable s) {
if (mConnection != null) {
mConnection.mProxyPort = s.toString();
}
}
});
mCustomOptionText.addTextChangedListener(new OnTextChangedWatcher() {
@Override
public void afterTextChanged(Editable s) {
@ -183,130 +348,19 @@ public class ConnectionsAdapter extends RecyclerView.Adapter<ConnectionsAdapter.
});
mDeleteButton.setOnClickListener(
new View.OnClickListener() {
@Override
public void onClick(View v) {
AlertDialog.Builder ab = new AlertDialog.Builder(mContext);
ab.setTitle(R.string.query_delete_remote);
ab.setPositiveButton(R.string.keep, null);
ab.setNegativeButton(R.string.delete, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
removeRemote(getAdapterPosition());
notifyItemRemoved(getAdapterPosition());
}
});
ab.create().show();
}
v -> {
AlertDialog.Builder ab = new AlertDialog.Builder(mContext);
ab.setTitle(R.string.query_delete_remote);
ab.setPositiveButton(R.string.keep, null);
ab.setNegativeButton(R.string.delete, (dialog, which) -> {
removeRemote(getAdapterPosition());
notifyItemRemoved(getAdapterPosition());
});
ab.create().show();
}
);
}
}
@Override
public ConnectionsAdapter.ConnectionsHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
LayoutInflater li = LayoutInflater.from(mContext);
View card;
if (viewType == TYPE_NORMAL) {
card = li.inflate(R.layout.server_card, viewGroup, false);
} else { // TYPE_FOOTER
card = li.inflate(R.layout.server_footer, viewGroup, false);
}
return new ConnectionsHolder(card, this, viewType);
}
static abstract class OnTextChangedWatcher implements 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 onBindViewHolder(final ConnectionsAdapter.ConnectionsHolder cH, int position) {
if (position == mConnections.length) {
// Footer
return;
}
final Connection connection = mConnections[position];
cH.mConnection = null;
cH.mPortNumberView.setText(connection.mServerPort);
cH.mServerNameView.setText(connection.mServerName);
cH.mPortNumberView.setText(connection.mServerPort);
cH.mRemoteSwitch.setChecked(connection.mEnabled);
cH.mConnectText.setText(String.valueOf(connection.getTimeout()));
cH.mConnectSlider.setProgress(connection.getTimeout());
cH.mProtoGroup.check(connection.mUseUdp ? R.id.udp_proto : R.id.tcp_proto);
cH.mCustomOptionsLayout.setVisibility(connection.mUseCustomConfig ? View.VISIBLE : View.GONE);
cH.mCustomOptionText.setText(connection.mCustomConfiguration);
cH.mCustomOptionCB.setChecked(connection.mUseCustomConfig);
cH.mConnection = connection;
}
private void removeRemote(int idx) {
Connection[] mConnections2 = Arrays.copyOf(mConnections, mConnections.length - 1);
for (int i = idx + 1; i < mConnections.length; i++) {
mConnections2[i - 1] = mConnections[i];
}
mConnections = mConnections2;
}
@Override
public int getItemCount() {
return mConnections.length + 1; //for footer
}
@Override
public int getItemViewType(int position) {
if (position == mConnections.length)
return TYPE_FOOTER;
else
return TYPE_NORMAL;
}
void addRemote() {
mConnections = Arrays.copyOf(mConnections, mConnections.length + 1);
mConnections[mConnections.length - 1] = new Connection();
notifyItemInserted(mConnections.length - 1);
displayWarningIfNoneEnabled();
}
void displayWarningIfNoneEnabled() {
int showWarning = View.VISIBLE;
for (Connection conn : mConnections) {
if (conn.mEnabled)
showWarning = View.GONE;
}
mConnectionFragment.setWarningVisible(showWarning);
}
void saveProfile() {
mProfile.mConnections = mConnections;
}
}

View File

@ -154,11 +154,126 @@
android:text="Socks" />
</RadioGroup> -->
<TextView
android:id="@+id/proxy_label"
android:layout_below="@id/udptcpradiogroup"
android:text="@string/proxy"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
<RadioGroup
android:id="@+id/proxyradiogroup"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/proxy_label"
android:orientation="horizontal"
android:paddingStart="20dp"
android:paddingEnd="20dp"
android:paddingRight="20dp"
android:paddingLeft="20dp">
<RadioButton
android:id="@+id/proxy_none"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/Use_no_proxy" />
<Space
android:layout_width="10dp"
android:layout_height="wrap_content" />
<RadioButton
android:id="@+id/proxy_http"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="HTTP"
tools:ignore="HardcodedText" />
<Space
android:layout_width="10dp"
android:layout_height="wrap_content" />
<RadioButton
android:id="@+id/proxy_socks"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Socksv5"
tools:ignore="HardcodedText" />
<Space
android:layout_width="10dp"
android:layout_height="wrap_content" />
<RadioButton
android:visibility="invisible"
android:id="@+id/proxy_orbot"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/tor_orbot"
/>
</RadioGroup>
<TextView
android:id="@+id/proxyport_label"
style="@style/item"
android:layout_below="@id/proxyradiogroup"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:text="@string/port"
android:textAppearance="?android:attr/textAppearanceSmall" />
<TextView
android:layout_below="@id/proxyradiogroup"
android:layout_toLeftOf="@id/proxyport_label"
android:layout_toStartOf="@id/proxyport_label"
android:id="@+id/proxyserver_label"
android:paddingStart="20dp"
android:paddingLeft="20dp"
style="@style/item"
android:text="@string/address"
android:textAppearance="?android:attr/textAppearanceSmall"
tools:ignore="RtlSymmetry" />
<EditText
android:id="@+id/proxyport"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_below="@id/proxyport_label"
android:inputType="numberDecimal"
android:text="8080"
android:textAppearance="@android:style/TextAppearance.DeviceDefault.Medium"
tools:ignore="HardcodedText" />
<EditText
android:id="@+id/proxyname"
android:layout_marginLeft="20dp"
android:layout_marginStart="20dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_below="@id/proxyserver_label"
android:layout_toLeftOf="@id/proxyport"
android:layout_toStartOf="@id/proxyport"
android:inputType="textUri"
android:singleLine="true"
tools:text="proxy.blinkt.de"
android:textAppearance="@android:style/TextAppearance.DeviceDefault.Medium"
tools:ignore="HardcodedText" />
<TextView
android:id="@+id/connect_timeout_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/udptcpradiogroup"
android:layout_below="@id/proxyname"
android:paddingTop="10dp"
android:text="@string/connect_timeout" />

View File

@ -199,7 +199,7 @@
<string name="setting_loadtun">Load tun module</string>
<string name="importpkcs12fromconfig">Import PKCS12 from configuration into Android Keystore</string>
<string name="getproxy_error">Error getting proxy settings: %s</string>
<string name="using_proxy">Using proxy %1$s %2$d</string>
<string name="using_proxy">Using proxy %1$s %2$s</string>
<string name="use_system_proxy">Use system proxy</string>
<string name="use_system_proxy_summary">Use the system wide configuration for HTTP/HTTPS proxies to connect.</string>
<string name="onbootrestartsummary">OpenVPN will connect the specified VPN if it was active on system boot. Please read the connection warning FAQ before using this option on Android &lt; 5.0.</string>
@ -465,5 +465,8 @@
<string name="all_app_prompt">An external app tries to control %s. The app requesting access cannot be determined. Allowing this app grants ALL apps access.</string>
<string name="openvpn3_nostatickeys">The OpenVPN 3 C++ implementation does not support static keys. Please change to OpenVPN 2.x under general settings.</string>
<string name="openvpn3_pkcs12">Using PKCS12 files directly with OpenVPN 3 C++ implementation is not supported. Please import the pkcs12 files into the Android keystore or change to OpenVPN 2.x under general settings.</string>
<string name="proxy">Proxy</string>
<string name="Use_no_proxy">None</string>
<string name="tor_orbot">Tor (Orbot)</string>
</resources>