From a5a3dd31e53b772ccdddd6aaa6125968a894054c Mon Sep 17 00:00:00 2001 From: cyBerta Date: Mon, 15 Mar 2021 23:51:51 +0100 Subject: add an example menshen response, containing a host's load rate --- app/src/test/resources/riseup_geoip_v4.json | 35 +++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 app/src/test/resources/riseup_geoip_v4.json (limited to 'app/src') diff --git a/app/src/test/resources/riseup_geoip_v4.json b/app/src/test/resources/riseup_geoip_v4.json new file mode 100644 index 00000000..439d6ffb --- /dev/null +++ b/app/src/test/resources/riseup_geoip_v4.json @@ -0,0 +1,35 @@ +{ + "ip":"51.158.144.32", + "cc":"FR", + "city":"Paris", + "lat":48.8628, + "lon":2.3292, + "gateways":[ + "mouette.riseup.net", + "hoatzin.riseup.net", + "zarapito.riseup.net", + "redshank.riseup.net" + ], + "sortedGateways": [ + { + "host": "mouette.riseup.net", + "fullness": 0.3, + "overload": false + }, + { + "host": "hoatzin.riseup.net", + "fullness": 0.36, + "overload": false + }, + { + "host": "zarapito.riseup.net", + "fullness": 0.57, + "overload": false + }, + { + "host": "zarapito.riseup.net", + "fullness": 0.92, + "overload": true + } + ] +} \ No newline at end of file -- cgit v1.2.3 From c02cfd04253c5f5c839410d418789884b9bfb13a Mon Sep 17 00:00:00 2001 From: cyBerta Date: Tue, 16 Mar 2021 22:09:07 +0100 Subject: Adapt gateway selector to check for nearest gateway within a city. Also optionally parse sortedGateways json object from menshen backend reponse --- .../leap/bitmaskclient/base/models/Constants.java | 4 ++ .../bitmaskclient/base/utils/PreferenceHelper.java | 9 +++ .../java/se/leap/bitmaskclient/eip/Gateway.java | 15 ++++- .../se/leap/bitmaskclient/eip/GatewaysManager.java | 64 ++++++++++++++++++---- app/src/test/resources/riseup_geoip_v4.json | 35 ------------ app/src/test/resources/v4/riseup_geoip_v4.json | 35 ++++++++++++ 6 files changed, 115 insertions(+), 47 deletions(-) delete mode 100644 app/src/test/resources/riseup_geoip_v4.json create mode 100644 app/src/test/resources/v4/riseup_geoip_v4.json (limited to 'app/src') diff --git a/app/src/main/java/se/leap/bitmaskclient/base/models/Constants.java b/app/src/main/java/se/leap/bitmaskclient/base/models/Constants.java index a0d295bd..3edfbb3d 100644 --- a/app/src/main/java/se/leap/bitmaskclient/base/models/Constants.java +++ b/app/src/main/java/se/leap/bitmaskclient/base/models/Constants.java @@ -40,6 +40,7 @@ public interface Constants { String USE_IPv6_FIREWALL = "use_ipv6_firewall"; String RESTART_ON_UPDATE = "restart_on_update"; String LAST_UPDATE_CHECK = "last_update_check"; + String PREFERRED_CITY = "preferred_city"; ////////////////////////////////////////////// @@ -173,4 +174,7 @@ public interface Constants { String OPENVPN_CONFIGURATION = "openvpn_configuration"; String GATEWAYS = "gateways"; String HOST = "host"; + String SORTED_GATEWAYS = "sortedGateways"; + String FULLNESS = "fullness"; + String OVERLOAD = "overload"; } diff --git a/app/src/main/java/se/leap/bitmaskclient/base/utils/PreferenceHelper.java b/app/src/main/java/se/leap/bitmaskclient/base/utils/PreferenceHelper.java index d31c7a20..19b30b4d 100644 --- a/app/src/main/java/se/leap/bitmaskclient/base/utils/PreferenceHelper.java +++ b/app/src/main/java/se/leap/bitmaskclient/base/utils/PreferenceHelper.java @@ -24,6 +24,7 @@ import static se.leap.bitmaskclient.base.models.Constants.DEFAULT_SHARED_PREFS_B import static se.leap.bitmaskclient.base.models.Constants.EXCLUDED_APPS; import static se.leap.bitmaskclient.base.models.Constants.LAST_UPDATE_CHECK; import static se.leap.bitmaskclient.base.models.Constants.LAST_USED_PROFILE; +import static se.leap.bitmaskclient.base.models.Constants.PREFERRED_CITY; import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_CONFIGURED; import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_EIP_DEFINITION; import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_PRIVATE_KEY; @@ -203,6 +204,14 @@ public class PreferenceHelper { return getBoolean(context, ALWAYS_ON_SHOW_DIALOG, true); } + public static String getSelectedCity(Context context) { + return getString(context, PREFERRED_CITY, null); + } + + public static void setPreferredCity(Context context, String city) { + putString(context, PREFERRED_CITY, city); + } + public static JSONObject getEipDefinitionFromPreferences(SharedPreferences preferences) { JSONObject result = new JSONObject(); try { diff --git a/app/src/main/java/se/leap/bitmaskclient/eip/Gateway.java b/app/src/main/java/se/leap/bitmaskclient/eip/Gateway.java index 6b44856e..763b5449 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/Gateway.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/Gateway.java @@ -17,6 +17,7 @@ package se.leap.bitmaskclient.eip; import android.content.Context; + import androidx.annotation.NonNull; import com.google.gson.Gson; @@ -59,7 +60,9 @@ public class Gateway { private JSONObject generalConfiguration; private JSONObject secrets; private JSONObject gateway; + private JSONObject load; + // the location of a gateway is its name private String name; private int timezone; private int apiVersion; @@ -71,9 +74,15 @@ public class Gateway { */ public Gateway(JSONObject eipDefinition, JSONObject secrets, JSONObject gateway, Context context) throws ConfigParser.ConfigParseError, JSONException, IOException { + this(eipDefinition, secrets, gateway, null, context); + } + + public Gateway(JSONObject eipDefinition, JSONObject secrets, JSONObject gateway, JSONObject load, Context context) + throws ConfigParser.ConfigParseError, JSONException, IOException { this.gateway = gateway; this.secrets = secrets; + this.load = load; generalConfiguration = getGeneralConfiguration(eipDefinition); timezone = getTimezone(eipDefinition); @@ -82,6 +91,10 @@ public class Gateway { vpnProfiles = createVPNProfiles(context); } + public void updateLoad(JSONObject load) { + this.load = load; + } + private void addProfileInfos(Context context, HashMap profiles) { Set excludedAppsVpn = PreferenceHelper.getExcludedApps(context); for (VpnProfile profile : profiles.values()) { @@ -156,7 +169,7 @@ public class Gateway { return vpnProfiles.get(transportType); } - public boolean suppoortsTransport(Connection.TransportType transportType) { + public boolean supportsTransport(Connection.TransportType transportType) { return vpnProfiles.get(transportType) != null; } diff --git a/app/src/main/java/se/leap/bitmaskclient/eip/GatewaysManager.java b/app/src/main/java/se/leap/bitmaskclient/eip/GatewaysManager.java index a5d4c416..9a8ff0bd 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/GatewaysManager.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/GatewaysManager.java @@ -18,6 +18,8 @@ package se.leap.bitmaskclient.eip; import android.content.Context; +import androidx.annotation.Nullable; + import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; @@ -41,8 +43,11 @@ import se.leap.bitmaskclient.base.models.ProviderObservable; import static de.blinkt.openvpn.core.connection.Connection.TransportType.OBFS4; import static de.blinkt.openvpn.core.connection.Connection.TransportType.OPENVPN; import static se.leap.bitmaskclient.base.models.Constants.GATEWAYS; +import static se.leap.bitmaskclient.base.models.Constants.HOST; import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_PRIVATE_KEY; import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_VPN_CERTIFICATE; +import static se.leap.bitmaskclient.base.models.Constants.SORTED_GATEWAYS; +import static se.leap.bitmaskclient.base.utils.PreferenceHelper.getSelectedCity; import static se.leap.bitmaskclient.base.utils.PreferenceHelper.getUsePluggableTransports; /** @@ -68,23 +73,24 @@ public class GatewaysManager { */ public Gateway select(int nClosest) { Connection.TransportType transportType = getUsePluggableTransports(context) ? OBFS4 : OPENVPN; - + String selectedCity = getSelectedCity(context); if (presortedList.size() > 0) { - return getGatewayFromPresortedList(nClosest, transportType); + return getGatewayFromPresortedList(nClosest, transportType, selectedCity); } - return getGatewayFromTimezoneCalculation(nClosest, transportType); + return getGatewayFromTimezoneCalculation(nClosest, transportType, selectedCity); } - private Gateway getGatewayFromTimezoneCalculation(int nClosest, Connection.TransportType transportType) { + private Gateway getGatewayFromTimezoneCalculation(int nClosest, Connection.TransportType transportType, @Nullable String city) { List list = new ArrayList<>(gateways.values()); GatewaySelector gatewaySelector = new GatewaySelector(list); Gateway gateway; int found = 0; int i = 0; while ((gateway = gatewaySelector.select(i)) != null) { - if (gateway.suppoortsTransport(transportType)) { + if ((city == null && gateway.supportsTransport(transportType)) || + (gateway.getName().equals(city) && gateway.supportsTransport(transportType))) { if (found == nClosest) { return gateway; } @@ -95,10 +101,11 @@ public class GatewaysManager { return null; } - private Gateway getGatewayFromPresortedList(int nClosest, Connection.TransportType transportType) { + private Gateway getGatewayFromPresortedList(int nClosest, Connection.TransportType transportType, @Nullable String city) { int found = 0; for (Gateway gateway : presortedList) { - if (gateway.suppoortsTransport(transportType)) { + if ((city == null && gateway.supportsTransport(transportType)) || + (gateway.getName().equals(city) && gateway.supportsTransport(transportType))) { if (found == nClosest) { return gateway; } @@ -125,7 +132,7 @@ public class GatewaysManager { Connection.TransportType transportType = profile.mUsePluggableTransports ? OBFS4 : OPENVPN; int nClosest = 0; for (Gateway gateway : presortedList) { - if (gateway.suppoortsTransport(transportType)) { + if (gateway.supportsTransport(transportType)) { if (profile.equals(gateway.getProfile(transportType))) { return nClosest; } @@ -142,7 +149,7 @@ public class GatewaysManager { int nClosest = 0; int i = 0; while ((gateway = gatewaySelector.select(i)) != null) { - if (gateway.suppoortsTransport(transportType)) { + if (gateway.supportsTransport(transportType)) { if (profile.equals(gateway.getProfile(transportType))) { return nClosest; } @@ -206,7 +213,7 @@ public class GatewaysManager { } } - private void parseGatewaysFromGeoIpServiceJson(Provider provider) { + private void parseSimpleGatewayList(Provider provider) { try { JSONObject geoIpJson = provider.getGeoIpJson(); JSONArray gatewaylist = geoIpJson.getJSONArray(GATEWAYS); @@ -226,6 +233,36 @@ public class GatewaysManager { } } + private boolean hasSortedGatewaysWithLoad(@Nullable Provider provider) { + if (provider == null) { + return false; + } + JSONObject geoIpJson = provider.getGeoIpJson(); + return geoIpJson.has(SORTED_GATEWAYS); + } + + private void parseGatewaysWithLoad(Provider provider) { + try { + JSONObject geoIpJson = provider.getGeoIpJson(); + JSONArray gatewaylist = geoIpJson.getJSONArray(SORTED_GATEWAYS); + for (int i = 0; i < gatewaylist.length(); i++) { + try { + JSONObject load = gatewaylist.getJSONObject(i); + String hostName = load.getString(HOST); + if (gateways.containsKey(hostName)) { + Gateway gateway = gateways.get(hostName); + gateway.updateLoad(load); + presortedList.add(gateway); + } + } catch (JSONException e) { + e.printStackTrace(); + } + } + } catch (NullPointerException | JSONException npe) { + npe.printStackTrace(); + } + } + private JSONObject secretsConfigurationFromCurrentProvider() { JSONObject result = new JSONObject(); Provider provider = ProviderObservable.getInstance().getCurrentProvider(); @@ -247,6 +284,11 @@ public class GatewaysManager { private void configureFromCurrentProvider() { Provider provider = ProviderObservable.getInstance().getCurrentProvider(); parseDefaultGateways(provider); - parseGatewaysFromGeoIpServiceJson(provider); + if (hasSortedGatewaysWithLoad(provider)) { + parseGatewaysWithLoad(provider); + } else { + parseSimpleGatewayList(provider); + } + } } diff --git a/app/src/test/resources/riseup_geoip_v4.json b/app/src/test/resources/riseup_geoip_v4.json deleted file mode 100644 index 439d6ffb..00000000 --- a/app/src/test/resources/riseup_geoip_v4.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "ip":"51.158.144.32", - "cc":"FR", - "city":"Paris", - "lat":48.8628, - "lon":2.3292, - "gateways":[ - "mouette.riseup.net", - "hoatzin.riseup.net", - "zarapito.riseup.net", - "redshank.riseup.net" - ], - "sortedGateways": [ - { - "host": "mouette.riseup.net", - "fullness": 0.3, - "overload": false - }, - { - "host": "hoatzin.riseup.net", - "fullness": 0.36, - "overload": false - }, - { - "host": "zarapito.riseup.net", - "fullness": 0.57, - "overload": false - }, - { - "host": "zarapito.riseup.net", - "fullness": 0.92, - "overload": true - } - ] -} \ No newline at end of file diff --git a/app/src/test/resources/v4/riseup_geoip_v4.json b/app/src/test/resources/v4/riseup_geoip_v4.json new file mode 100644 index 00000000..439d6ffb --- /dev/null +++ b/app/src/test/resources/v4/riseup_geoip_v4.json @@ -0,0 +1,35 @@ +{ + "ip":"51.158.144.32", + "cc":"FR", + "city":"Paris", + "lat":48.8628, + "lon":2.3292, + "gateways":[ + "mouette.riseup.net", + "hoatzin.riseup.net", + "zarapito.riseup.net", + "redshank.riseup.net" + ], + "sortedGateways": [ + { + "host": "mouette.riseup.net", + "fullness": 0.3, + "overload": false + }, + { + "host": "hoatzin.riseup.net", + "fullness": 0.36, + "overload": false + }, + { + "host": "zarapito.riseup.net", + "fullness": 0.57, + "overload": false + }, + { + "host": "zarapito.riseup.net", + "fullness": 0.92, + "overload": true + } + ] +} \ No newline at end of file -- cgit v1.2.3 From 5c4960cdb37b42ac82d22288e6de946e230b4a7b Mon Sep 17 00:00:00 2001 From: cyBerta Date: Tue, 16 Mar 2021 22:29:57 +0100 Subject: rename preference helper method for getting the preferred city --- .../main/java/se/leap/bitmaskclient/base/utils/PreferenceHelper.java | 2 +- app/src/main/java/se/leap/bitmaskclient/eip/GatewaysManager.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'app/src') diff --git a/app/src/main/java/se/leap/bitmaskclient/base/utils/PreferenceHelper.java b/app/src/main/java/se/leap/bitmaskclient/base/utils/PreferenceHelper.java index 19b30b4d..a3d1314e 100644 --- a/app/src/main/java/se/leap/bitmaskclient/base/utils/PreferenceHelper.java +++ b/app/src/main/java/se/leap/bitmaskclient/base/utils/PreferenceHelper.java @@ -204,7 +204,7 @@ public class PreferenceHelper { return getBoolean(context, ALWAYS_ON_SHOW_DIALOG, true); } - public static String getSelectedCity(Context context) { + public static String getPreferredCity(Context context) { return getString(context, PREFERRED_CITY, null); } diff --git a/app/src/main/java/se/leap/bitmaskclient/eip/GatewaysManager.java b/app/src/main/java/se/leap/bitmaskclient/eip/GatewaysManager.java index 9a8ff0bd..b06e894e 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/GatewaysManager.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/GatewaysManager.java @@ -47,7 +47,7 @@ import static se.leap.bitmaskclient.base.models.Constants.HOST; import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_PRIVATE_KEY; import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_VPN_CERTIFICATE; import static se.leap.bitmaskclient.base.models.Constants.SORTED_GATEWAYS; -import static se.leap.bitmaskclient.base.utils.PreferenceHelper.getSelectedCity; +import static se.leap.bitmaskclient.base.utils.PreferenceHelper.getPreferredCity; import static se.leap.bitmaskclient.base.utils.PreferenceHelper.getUsePluggableTransports; /** @@ -73,7 +73,7 @@ public class GatewaysManager { */ public Gateway select(int nClosest) { Connection.TransportType transportType = getUsePluggableTransports(context) ? OBFS4 : OPENVPN; - String selectedCity = getSelectedCity(context); + String selectedCity = getPreferredCity(context); if (presortedList.size() > 0) { return getGatewayFromPresortedList(nClosest, transportType, selectedCity); } -- cgit v1.2.3 From 1d2e09736a0526c574fe4c541d2f83c1ef44ccc0 Mon Sep 17 00:00:00 2001 From: cyBerta Date: Sat, 20 Mar 2021 16:57:31 +0100 Subject: write tests for city based gateway selection --- .../bitmaskclient/eip/GatewaysManagerTest.java | 35 +++++ .../v4/riseup_eipservice_for_geoip_v4.json | 156 +++++++++++++++++++++ app/src/test/resources/v4/riseup_geoip_v4.json | 14 +- 3 files changed, 201 insertions(+), 4 deletions(-) create mode 100644 app/src/test/resources/v4/riseup_eipservice_for_geoip_v4.json (limited to 'app/src') diff --git a/app/src/test/java/se/leap/bitmaskclient/eip/GatewaysManagerTest.java b/app/src/test/java/se/leap/bitmaskclient/eip/GatewaysManagerTest.java index 6056f764..69c54db9 100644 --- a/app/src/test/java/se/leap/bitmaskclient/eip/GatewaysManagerTest.java +++ b/app/src/test/java/se/leap/bitmaskclient/eip/GatewaysManagerTest.java @@ -233,6 +233,41 @@ public class GatewaysManagerTest { } + @Test + public void testSelectN_selectFromCity_returnsGatewaysInPresortedOrder() { + Provider provider = getProvider(null, null, null, null, null, null, "v4/riseup_eipservice_for_geoip_v4.json", "v4/riseup_geoip_v4.json"); + + MockHelper.mockProviderObserver(provider); + //use openvpn, not pluggable transports + mockStatic(PreferenceHelper.class); + when(PreferenceHelper.getUsePluggableTransports(any(Context.class))).thenReturn(false); + when(PreferenceHelper.getPreferredCity(any(Context.class))).thenReturn("Paris"); + GatewaysManager gatewaysManager = new GatewaysManager(mockContext); + + assertEquals("mouette.riseup.net", gatewaysManager.select(0).getHost()); + assertEquals("hoatzin.riseup.net", gatewaysManager.select(1).getHost()); + assertEquals("zarapito.riseup.net", gatewaysManager.select(2).getHost()); + } + + @Test + public void testSelectN_selectFromCityWithTimezoneCalculation_returnsRandomizedGatewaysOfSelectedCity() { + Provider provider = getProvider(null, null, null, null, null, null, "v4/riseup_eipservice_for_geoip_v4.json", null); + + provider.setGeoIpJson(new JSONObject()); + MockHelper.mockProviderObserver(provider); + //use openvpn, not pluggable transports + mockStatic(PreferenceHelper.class); + when(PreferenceHelper.getUsePluggableTransports(any(Context.class))).thenReturn(false); + when(PreferenceHelper.getPreferredCity(any(Context.class))).thenReturn("Paris"); + GatewaysManager gatewaysManager = new GatewaysManager(mockContext); + + assertEquals("Paris", gatewaysManager.select(0).getName()); + assertEquals("Paris", gatewaysManager.select(1).getName()); + assertEquals("Paris", gatewaysManager.select(2).getName()); + assertEquals(null, gatewaysManager.select(3)); + } + + private String getJsonStringFor(String filename) throws IOException { return TestSetupHelper.getInputAsString(getClass().getClassLoader().getResourceAsStream(filename)); } diff --git a/app/src/test/resources/v4/riseup_eipservice_for_geoip_v4.json b/app/src/test/resources/v4/riseup_eipservice_for_geoip_v4.json new file mode 100644 index 00000000..b8fa8bbf --- /dev/null +++ b/app/src/test/resources/v4/riseup_eipservice_for_geoip_v4.json @@ -0,0 +1,156 @@ +{ + "gateways": [ + { + "capabilities": { + "adblock": false, + "filter_dns": false, + "limited": false, + "transport":[ + { + "type":"openvpn", + "protocols":[ + "tcp" + ], + "ports":[ + "443" + ] + } + ], + "user_ips": false + }, + "host": "zarapito.riseup.net", + "ip_address": "212.129.62.247", + "location": "paris" + }, + { + "capabilities": { + "adblock": false, + "filter_dns": false, + "limited": false, + "transport":[ + { + "type":"openvpn", + "protocols":[ + "tcp" + ], + "ports":[ + "443" + ] + } + ], + "user_ips": false + }, + "host": "hoatzin.riseup.net", + "ip_address": "212.83.143.67", + "location": "paris" + }, + { + "capabilities": { + "adblock": false, + "filter_dns": false, + "limited": false, + "transport":[ + { + "type":"openvpn", + "protocols":[ + "tcp" + ], + "ports":[ + "443" + ] + } + ], + "user_ips": false + }, + "host": "mouette.riseup.net", + "ip_address": "163.172.126.44", + "location": "paris" + }, + { + "capabilities": { + "adblock": false, + "filter_dns": false, + "limited": false, + "transport":[ + { + "type":"openvpn", + "protocols":[ + "tcp" + ], + "ports":[ + "443" + ] + } + ], + "user_ips": false + }, + "host": "redshank.riseup.net", + "ip_address": "212.83.165.160", + "location": "amsterdam" + }, + { + "capabilities": { + "adblock": false, + "filter_dns": false, + "limited": false, + "transport":[ + { + "type":"obfs4", + "protocols":[ + "tcp" + ], + "ports":[ + "23042" + ], + "options": { + "cert": "48F/JNm/LKYt7zkDmPHXcw5f3Jqgwg/3OBRrqW14Yj87ATZ4KyAZRV7np4RhCXGSJHgoCQ", + "iatMode": "0" + } + }, + { + "type":"openvpn", + "protocols":[ + "tcp" + ], + "ports":[ + "443" + ] + } + ], + "user_ips": false + }, + "host": "yal.riseup.net", + "ip_address": "199.58.83.10", + "location": "montreal" + } + ], + "locations": { + "amsterdam": { + "country_code": "NL", + "hemisphere": "N", + "name": "Amsterdam", + "timezone": "+2" + }, + "montreal": { + "country_code": "CA", + "hemisphere": "N", + "name": "Montreal", + "timezone": "-5" + }, + "paris": { + "country_code": "FR", + "hemisphere": "N", + "name": "Paris", + "timezone": "+2" + } + }, + "openvpn_configuration": { + "auth": "SHA1", + "cipher": "AES-128-CBC", + "keepalive": "10 30", + "tls-cipher": "DHE-RSA-AES128-SHA", + "tun-ipv6": true + }, + "serial": 3, + "version": 3 +} diff --git a/app/src/test/resources/v4/riseup_geoip_v4.json b/app/src/test/resources/v4/riseup_geoip_v4.json index 439d6ffb..c95a9e6d 100644 --- a/app/src/test/resources/v4/riseup_geoip_v4.json +++ b/app/src/test/resources/v4/riseup_geoip_v4.json @@ -7,8 +7,9 @@ "gateways":[ "mouette.riseup.net", "hoatzin.riseup.net", - "zarapito.riseup.net", - "redshank.riseup.net" + "yal.riseup.net", + "redshank.riseup.net", + "zarapito.riseup.net" ], "sortedGateways": [ { @@ -22,8 +23,13 @@ "overload": false }, { - "host": "zarapito.riseup.net", - "fullness": 0.57, + "host": "yal.riseup.net", + "fullness": 0.59, + "overload": false + }, + { + "host": "redshank.riseup.net", + "fullness": 0.8, "overload": false }, { -- cgit v1.2.3 From 95174abcb87af9d07465cda5c23cc35e1987b6d9 Mon Sep 17 00:00:00 2001 From: cyBerta Date: Sat, 20 Mar 2021 17:15:37 +0100 Subject: add test for city based gateway selection with geo-ip service v1 --- .../se/leap/bitmaskclient/eip/GatewaysManagerTest.java | 16 ++++++++++++++++ app/src/test/resources/v4/riseup_geoip_v1.json | 14 ++++++++++++++ 2 files changed, 30 insertions(+) create mode 100644 app/src/test/resources/v4/riseup_geoip_v1.json (limited to 'app/src') diff --git a/app/src/test/java/se/leap/bitmaskclient/eip/GatewaysManagerTest.java b/app/src/test/java/se/leap/bitmaskclient/eip/GatewaysManagerTest.java index 69c54db9..255d98e0 100644 --- a/app/src/test/java/se/leap/bitmaskclient/eip/GatewaysManagerTest.java +++ b/app/src/test/java/se/leap/bitmaskclient/eip/GatewaysManagerTest.java @@ -249,6 +249,22 @@ public class GatewaysManagerTest { assertEquals("zarapito.riseup.net", gatewaysManager.select(2).getHost()); } + @Test + public void testSelectN_selectFromCityWithGeoIpServiceV1_returnsGatewaysInPresortedOrder() { + Provider provider = getProvider(null, null, null, null, null, null, "v4/riseup_eipservice_for_geoip_v4.json", "v4/riseup_geoip_v1.json"); + + MockHelper.mockProviderObserver(provider); + //use openvpn, not pluggable transports + mockStatic(PreferenceHelper.class); + when(PreferenceHelper.getUsePluggableTransports(any(Context.class))).thenReturn(false); + when(PreferenceHelper.getPreferredCity(any(Context.class))).thenReturn("Paris"); + GatewaysManager gatewaysManager = new GatewaysManager(mockContext); + + assertEquals("mouette.riseup.net", gatewaysManager.select(0).getHost()); + assertEquals("hoatzin.riseup.net", gatewaysManager.select(1).getHost()); + assertEquals("zarapito.riseup.net", gatewaysManager.select(2).getHost()); + } + @Test public void testSelectN_selectFromCityWithTimezoneCalculation_returnsRandomizedGatewaysOfSelectedCity() { Provider provider = getProvider(null, null, null, null, null, null, "v4/riseup_eipservice_for_geoip_v4.json", null); diff --git a/app/src/test/resources/v4/riseup_geoip_v1.json b/app/src/test/resources/v4/riseup_geoip_v1.json new file mode 100644 index 00000000..4e3bda2a --- /dev/null +++ b/app/src/test/resources/v4/riseup_geoip_v1.json @@ -0,0 +1,14 @@ +{ + "ip":"51.158.144.32", + "cc":"FR", + "city":"Paris", + "lat":48.8628, + "lon":2.3292, + "gateways":[ + "mouette.riseup.net", + "hoatzin.riseup.net", + "yal.riseup.net", + "redshank.riseup.net", + "zarapito.riseup.net" + ] +} \ No newline at end of file -- cgit v1.2.3 From ff5188d7de1df36c5d71d309a08d290560e9d337 Mon Sep 17 00:00:00 2001 From: cyBerta Date: Mon, 22 Mar 2021 18:49:19 +0100 Subject: initial draft for gateway selection on Android --- .../base/fragments/GatewaySelectionFragment.java | 286 +++++++++++++++++++++ .../base/fragments/NavigationDrawerFragment.java | 24 ++ .../leap/bitmaskclient/base/models/Location.java | 42 +++ .../leap/bitmaskclient/base/models/Provider.java | 9 + .../bitmaskclient/base/views/IconSwitchEntry.java | 27 +- .../java/se/leap/bitmaskclient/eip/Gateway.java | 22 ++ .../se/leap/bitmaskclient/eip/GatewaysManager.java | 41 ++- .../ic_map_marker_star_black_36dp.png | Bin 0 -> 919 bytes .../ic_map_marker_star_black_36dp.png | Bin 0 -> 645 bytes .../ic_map_marker_star_black_36dp.png | Bin 0 -> 1182 bytes .../ic_map_marker_star_black_36dp.png | Bin 0 -> 1809 bytes .../ic_map_marker_star_black_36dp.png | Bin 0 -> 2284 bytes .../main/res/layout-xlarge/v_switch_list_item.xml | 93 +++---- app/src/main/res/layout/f_drawer_main.xml | 9 + app/src/main/res/layout/f_gateway_selection.xml | 99 +++++++ .../main/res/layout/v_select_text_list_item.xml | 63 +++++ app/src/main/res/layout/v_switch_list_item.xml | 97 +++---- app/src/main/res/values/strings.xml | 6 + 18 files changed, 726 insertions(+), 92 deletions(-) create mode 100644 app/src/main/java/se/leap/bitmaskclient/base/fragments/GatewaySelectionFragment.java create mode 100644 app/src/main/java/se/leap/bitmaskclient/base/models/Location.java create mode 100644 app/src/main/res/drawable-hdpi/ic_map_marker_star_black_36dp.png create mode 100644 app/src/main/res/drawable-mdpi/ic_map_marker_star_black_36dp.png create mode 100644 app/src/main/res/drawable-xhdpi/ic_map_marker_star_black_36dp.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_map_marker_star_black_36dp.png create mode 100644 app/src/main/res/drawable-xxxhdpi/ic_map_marker_star_black_36dp.png create mode 100644 app/src/main/res/layout/f_gateway_selection.xml create mode 100644 app/src/main/res/layout/v_select_text_list_item.xml (limited to 'app/src') diff --git a/app/src/main/java/se/leap/bitmaskclient/base/fragments/GatewaySelectionFragment.java b/app/src/main/java/se/leap/bitmaskclient/base/fragments/GatewaySelectionFragment.java new file mode 100644 index 00000000..b14a9e44 --- /dev/null +++ b/app/src/main/java/se/leap/bitmaskclient/base/fragments/GatewaySelectionFragment.java @@ -0,0 +1,286 @@ +/** + * Copyright (c) 2021 LEAP Encryption Access Project and contributors + * + * 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 3 of the License, or + * (at your option) any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package se.leap.bitmaskclient.base.fragments; + +import android.app.Activity; +import android.content.Intent; +import android.content.SharedPreferences; +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.widget.AppCompatButton; +import androidx.appcompat.widget.AppCompatImageView; +import androidx.appcompat.widget.AppCompatTextView; +import androidx.core.content.ContextCompat; +import androidx.core.graphics.drawable.DrawableCompat; +import androidx.fragment.app.Fragment; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import java.util.List; +import java.util.Observable; +import java.util.Observer; + +import se.leap.bitmaskclient.R; +import se.leap.bitmaskclient.base.MainActivity; +import se.leap.bitmaskclient.base.models.Location; +import se.leap.bitmaskclient.base.utils.PreferenceHelper; +import se.leap.bitmaskclient.base.views.IconSwitchEntry; +import se.leap.bitmaskclient.eip.EipCommand; +import se.leap.bitmaskclient.eip.EipStatus; +import se.leap.bitmaskclient.eip.GatewaysManager; + +import static android.content.Context.MODE_PRIVATE; +import static android.view.View.INVISIBLE; +import static android.view.View.VISIBLE; +import static se.leap.bitmaskclient.base.MainActivity.ACTION_SHOW_VPN_FRAGMENT; +import static se.leap.bitmaskclient.base.models.Constants.SHARED_PREFERENCES; +import static se.leap.bitmaskclient.base.models.Constants.USE_PLUGGABLE_TRANSPORTS; +import static se.leap.bitmaskclient.base.utils.PreferenceHelper.getPreferredCity; +import static se.leap.bitmaskclient.base.utils.PreferenceHelper.setPreferredCity; + +public class GatewaySelectionFragment extends Fragment implements SharedPreferences.OnSharedPreferenceChangeListener, Observer { + + private static final String TAG = GatewaySelectionFragment.class.getSimpleName(); + + + private RecyclerView recyclerView; + private LinearLayoutManager layoutManager; + private LocationListAdapter locationListAdapter; + private IconSwitchEntry autoSelectionSwitch; + private AppCompatButton vpnButton; + private GatewaysManager gatewaysManager; + private SharedPreferences preferences; + private EipStatus eipStatus; + + public GatewaySelectionFragment() { + // Required empty public constructor + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + gatewaysManager = new GatewaysManager(getContext()); + preferences = getContext().getSharedPreferences(SHARED_PREFERENCES, MODE_PRIVATE); + eipStatus = EipStatus.getInstance(); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + // Inflate the layout for this fragment + return inflater.inflate(R.layout.f_gateway_selection, container, false); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + initRecyclerView(); + initAutoSelectionSwitch(); + initVpnButton(); + eipStatus.addObserver(this); + preferences.registerOnSharedPreferenceChangeListener(this); + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + preferences.unregisterOnSharedPreferenceChangeListener(this); + eipStatus.deleteObserver(this); + } + + + + private void initRecyclerView() { + recyclerView = (RecyclerView) getActivity().findViewById(R.id.gatewaySelection_list); + recyclerView.setHasFixedSize(true); + layoutManager = new LinearLayoutManager(this.getContext()); + recyclerView.setLayoutManager(layoutManager); + locationListAdapter = new LocationListAdapter(gatewaysManager.getGatewayLocations()); + recyclerView.setAdapter(locationListAdapter); + recyclerView.setVisibility(getPreferredCity(getContext()) == null ? INVISIBLE : VISIBLE); + } + + private void initAutoSelectionSwitch() { + autoSelectionSwitch = getActivity().findViewById(R.id.automatic_gateway_switch); + autoSelectionSwitch.setSingleLine(false); + autoSelectionSwitch.setSubtitle(getString(R.string.gateway_selection_warning, getString(R.string.app_name))); + autoSelectionSwitch.setChecked(getPreferredCity(getContext()) == null); + autoSelectionSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> { + recyclerView.setVisibility(!isChecked ? VISIBLE : View.GONE); + Log.d(TAG, "autoselection enabled: " + isChecked); + if (isChecked) { + PreferenceHelper.setPreferredCity(getContext(), null); + locationListAdapter.resetSelection(); + } + }); + } + + private void initVpnButton() { + vpnButton = getActivity().findViewById(R.id.vpn_button); + setVpnButtonState(); + vpnButton.setOnClickListener(v -> { + EipCommand.startVPN(getContext(), false); + Intent intent = new Intent(getContext(), MainActivity.class); + intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP); + intent.setAction(ACTION_SHOW_VPN_FRAGMENT); + startActivity(intent); + }); + } + + private void setVpnButtonState() { + if (eipStatus.isDisconnected()) { + vpnButton.setText(R.string.vpn_button_turn_on); + } else { + vpnButton.setText(R.string.reconnect); + } + } + + @Override + public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { + if (USE_PLUGGABLE_TRANSPORTS.equals(key)) { + locationListAdapter.updateData(gatewaysManager.getGatewayLocations()); + } + } + + @Override + public void update(Observable o, Object arg) { + if (o instanceof EipStatus) { + eipStatus = (EipStatus) o; + Activity activity = getActivity(); + if (activity != null) { + activity.runOnUiThread(this::setVpnButtonState); + } + } + + } + + static class LocationListAdapter extends RecyclerView.Adapter { + private static final String TAG = LocationListAdapter.class.getSimpleName(); + private List values; + private Location selectedLocation = null; + + static class ViewHolder extends RecyclerView.ViewHolder { + public AppCompatTextView locationLabel; + public AppCompatTextView qualityLabel; + public AppCompatImageView checkedIcon; + public View layout; + + public ViewHolder(View v) { + super(v); + layout = v; + locationLabel = (AppCompatTextView) v.findViewById(R.id.location); + qualityLabel = (AppCompatTextView) v.findViewById(R.id.quality); + checkedIcon = (AppCompatImageView) v.findViewById(R.id.checked_icon); + } + } + + public void add(int position, Location item) { + values.add(position, item); + notifyItemInserted(position); + } + + public void remove(int position) { + values.remove(position); + notifyItemRemoved(position); + } + + public void resetSelection() { + if (selectedLocation != null) { + selectedLocation.selected = false; + notifyDataSetChanged(); + } + } + + public void updateData(List data) { + values = data; + notifyDataSetChanged(); + } + + public LocationListAdapter(List data) { + values = data; + } + + @NonNull + @Override + public LocationListAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, + int viewType) { + LayoutInflater inflater = LayoutInflater.from( + parent.getContext()); + View v = inflater.inflate(R.layout.v_select_text_list_item, parent, false); + return new ViewHolder(v); + } + + // Replace the contents of a view (invoked by the layout manager) + @Override + public void onBindViewHolder(ViewHolder holder, final int position) { + final Location location = values.get(position); + holder.locationLabel.setText(location.name); + holder.layout.setOnClickListener(v -> { + Log.d(TAG, "view at position clicked: " + position); + if (selectedLocation == null) { + selectedLocation = location; + selectedLocation.selected = true; + } else if (selectedLocation.equals(location.name)){ + selectedLocation.selected = !selectedLocation.selected; + } else { + selectedLocation.selected = false; + selectedLocation = location; + selectedLocation.selected = true; + } + setPreferredCity(holder.layout.getContext(), selectedLocation.selected ? selectedLocation.name : null); + holder.checkedIcon.setVisibility(selectedLocation.selected ? VISIBLE : INVISIBLE); + notifyDataSetChanged(); + }); + Drawable checkIcon = DrawableCompat.wrap(holder.layout.getContext().getResources().getDrawable(R.drawable.ic_check_bold)).mutate(); + DrawableCompat.setTint(checkIcon, ContextCompat.getColor(holder.layout.getContext(), R.color.colorSuccess)); + holder.checkedIcon.setImageDrawable(checkIcon); + holder.checkedIcon.setVisibility(location.selected ? VISIBLE : INVISIBLE); + holder.qualityLabel.setText(getQualityString(location.averageLoad)); + if (location.selected) { + selectedLocation = location; + } + } + + public String getQualityString(double quality) { + if (quality == 0) { + return ""; + } else if (quality < 0.25) { + return "BAD"; + } else if (quality < 0.6) { + return "AVERAGE"; + } else if (quality < 0.8){ + return "GOOD"; + } else { + return "EXCELLENT"; + } + } + + + @Override + public int getItemCount() { + return values.size(); + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/se/leap/bitmaskclient/base/fragments/NavigationDrawerFragment.java b/app/src/main/java/se/leap/bitmaskclient/base/fragments/NavigationDrawerFragment.java index 2ce0e597..cbfe8a71 100644 --- a/app/src/main/java/se/leap/bitmaskclient/base/fragments/NavigationDrawerFragment.java +++ b/app/src/main/java/se/leap/bitmaskclient/base/fragments/NavigationDrawerFragment.java @@ -70,6 +70,7 @@ import static android.view.View.VISIBLE; import static se.leap.bitmaskclient.base.BitmaskApp.getRefWatcher; import static se.leap.bitmaskclient.base.models.Constants.DONATION_URL; import static se.leap.bitmaskclient.base.models.Constants.ENABLE_DONATION; +import static se.leap.bitmaskclient.base.models.Constants.PREFERRED_CITY; import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_KEY; import static se.leap.bitmaskclient.base.models.Constants.REQUEST_CODE_SWITCH_PROVIDER; import static se.leap.bitmaskclient.base.models.Constants.SHARED_PREFERENCES; @@ -79,6 +80,7 @@ import static se.leap.bitmaskclient.R.string.about_fragment_title; import static se.leap.bitmaskclient.R.string.exclude_apps_fragment_title; import static se.leap.bitmaskclient.R.string.log_fragment_title; import static se.leap.bitmaskclient.base.utils.ConfigHelper.isDefaultBitmask; +import static se.leap.bitmaskclient.base.utils.PreferenceHelper.getPreferredCity; import static se.leap.bitmaskclient.base.utils.PreferenceHelper.getSaveBattery; import static se.leap.bitmaskclient.base.utils.PreferenceHelper.getShowAlwaysOnDialog; import static se.leap.bitmaskclient.base.utils.PreferenceHelper.getUsePluggableTransports; @@ -114,6 +116,7 @@ public class NavigationDrawerFragment extends Fragment implements SharedPreferen private IconSwitchEntry saveBattery; private IconTextEntry tethering; private IconSwitchEntry firewall; + private IconTextEntry manualGatewaySelection; private View experimentalFeatureFooter; private boolean userLearnedDrawer; @@ -255,6 +258,7 @@ public class NavigationDrawerFragment extends Fragment implements SharedPreferen initSaveBatteryEntry(); initAlwaysOnVpnEntry(); initExcludeAppsEntry(); + initManualGatewayEntry(); initShowExperimentalHint(); initTetheringEntry(); initFirewallEntry(); @@ -412,6 +416,23 @@ public class NavigationDrawerFragment extends Fragment implements SharedPreferen }); } + private void initManualGatewayEntry() { + manualGatewaySelection = drawerView.findViewById(R.id.manualGatewaySelection); + String preferredGateway = getPreferredCity(getContext()); + String subtitle = preferredGateway != null ? preferredGateway : getString(R.string.gateway_selection_best_location); + manualGatewaySelection.setSubtitle(subtitle); + boolean show = ProviderObservable.getInstance().getCurrentProvider().hasGatewaysInDifferentLocations(); + manualGatewaySelection.setVisibility(show ? VISIBLE : GONE); + + manualGatewaySelection.setOnClickListener(v -> { + FragmentManagerEnhanced fragmentManager = new FragmentManagerEnhanced(getActivity().getSupportFragmentManager()); + closeDrawer(); + Fragment fragment = new GatewaySelectionFragment(); + setActionBarTitle(R.string.gateway_selection_title); + fragmentManager.replace(R.id.main_container, fragment, MainActivity.TAG); + }); + } + private void initTetheringEntry() { tethering = drawerView.findViewById(R.id.tethering); boolean show = showExperimentalFeatures(getContext()); @@ -627,6 +648,7 @@ public class NavigationDrawerFragment extends Fragment implements SharedPreferen Provider currentProvider = ProviderObservable.getInstance().getCurrentProvider(); account.setText(currentProvider.getName()); initUseBridgesEntry(); + initManualGatewayEntry(); } private void updateExcludeAppsSubtitle(IconTextEntry excludeApps, int number) { @@ -651,6 +673,8 @@ public class NavigationDrawerFragment extends Fragment implements SharedPreferen initUseBridgesEntry(); } else if (key.equals(USE_IPv6_FIREWALL)) { initFirewallEntry(); + } else if (key.equals(PREFERRED_CITY)) { + initManualGatewayEntry(); } } diff --git a/app/src/main/java/se/leap/bitmaskclient/base/models/Location.java b/app/src/main/java/se/leap/bitmaskclient/base/models/Location.java new file mode 100644 index 00000000..ae7818ba --- /dev/null +++ b/app/src/main/java/se/leap/bitmaskclient/base/models/Location.java @@ -0,0 +1,42 @@ +package se.leap.bitmaskclient.base.models; + +import androidx.annotation.NonNull; + +public class Location { + @NonNull public String name; + public double averageLoad; + public int numberOfGateways; + public boolean selected; + + public Location(@NonNull String name, double averageLoad, int numberOfGateways, boolean selected) { + this.name = name; + this.averageLoad = averageLoad; + this.numberOfGateways = numberOfGateways; + this.selected = selected; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof Location)) return false; + + Location location = (Location) o; + + if (Double.compare(location.averageLoad, averageLoad) != 0) return false; + if (numberOfGateways != location.numberOfGateways) return false; + if (selected != location.selected) return false; + return name.equals(location.name); + } + + @Override + public int hashCode() { + int result; + long temp; + result = name.hashCode(); + temp = Double.doubleToLongBits(averageLoad); + result = 31 * result + (int) (temp ^ (temp >>> 32)); + result = 31 * result + numberOfGateways; + result = 31 * result + (selected ? 1 : 0); + return result; + } +} diff --git a/app/src/main/java/se/leap/bitmaskclient/base/models/Provider.java b/app/src/main/java/se/leap/bitmaskclient/base/models/Provider.java index 97f1019b..5d8b4e5d 100644 --- a/app/src/main/java/se/leap/bitmaskclient/base/models/Provider.java +++ b/app/src/main/java/se/leap/bitmaskclient/base/models/Provider.java @@ -32,6 +32,7 @@ import java.util.Locale; import static de.blinkt.openvpn.core.connection.Connection.TransportType.OBFS4; import static se.leap.bitmaskclient.base.models.Constants.CAPABILITIES; import static se.leap.bitmaskclient.base.models.Constants.GATEWAYS; +import static se.leap.bitmaskclient.base.models.Constants.LOCATIONS; import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_ALLOWED_REGISTERED; import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_ALLOW_ANONYMOUS; import static se.leap.bitmaskclient.base.models.Constants.TRANSPORT; @@ -333,6 +334,14 @@ public final class Provider implements Parcelable { && !getEipServiceJson().has(ERRORS); } + public boolean hasGatewaysInDifferentLocations() { + try { + return getEipServiceJson().getJSONObject(LOCATIONS).length() > 1; + } catch (NullPointerException | JSONException e) { + return false; + } + } + @Override public int describeContents() { return 0; diff --git a/app/src/main/java/se/leap/bitmaskclient/base/views/IconSwitchEntry.java b/app/src/main/java/se/leap/bitmaskclient/base/views/IconSwitchEntry.java index 1160986e..628b4c15 100644 --- a/app/src/main/java/se/leap/bitmaskclient/base/views/IconSwitchEntry.java +++ b/app/src/main/java/se/leap/bitmaskclient/base/views/IconSwitchEntry.java @@ -1,3 +1,19 @@ +/** + * Copyright (c) 2021 LEAP Encryption Access Project and contributors + * + * 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 3 of the License, or + * (at your option) any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ package se.leap.bitmaskclient.base.views; import android.annotation.TargetApi; @@ -51,7 +67,7 @@ public class IconSwitchEntry extends LinearLayout { LayoutInflater inflater = (LayoutInflater) context .getSystemService(Context.LAYOUT_INFLATER_SERVICE); View rootview = inflater.inflate(R.layout.v_switch_list_item, this, true); - textView = rootview.findViewById(android.R.id.text1); + textView = rootview.findViewById(R.id.title); subtitleView = rootview.findViewById(R.id.subtitle); iconView = rootview.findViewById(R.id.material_icon); switchView = rootview.findViewById(R.id.option_switch); @@ -88,6 +104,15 @@ public class IconSwitchEntry extends LinearLayout { textView.setText(id); } + public void setSubtitle(CharSequence text) { + subtitleView.setText(text); + } + + public void setSingleLine(boolean singleLine) { + textView.setSingleLine(singleLine); + subtitleView.setSingleLine(singleLine); + } + public void showSubtitle(boolean show) { subtitleView.setVisibility(show ? VISIBLE : GONE); } diff --git a/app/src/main/java/se/leap/bitmaskclient/eip/Gateway.java b/app/src/main/java/se/leap/bitmaskclient/eip/Gateway.java index 763b5449..78b33355 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/Gateway.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/Gateway.java @@ -35,12 +35,14 @@ import de.blinkt.openvpn.core.ConfigParser; import de.blinkt.openvpn.core.connection.Connection; import se.leap.bitmaskclient.base.utils.PreferenceHelper; +import static se.leap.bitmaskclient.base.models.Constants.FULLNESS; import static se.leap.bitmaskclient.base.models.Constants.HOST; import static se.leap.bitmaskclient.base.models.Constants.IP_ADDRESS; import static se.leap.bitmaskclient.base.models.Constants.LOCATION; import static se.leap.bitmaskclient.base.models.Constants.LOCATIONS; import static se.leap.bitmaskclient.base.models.Constants.NAME; import static se.leap.bitmaskclient.base.models.Constants.OPENVPN_CONFIGURATION; +import static se.leap.bitmaskclient.base.models.Constants.OVERLOAD; import static se.leap.bitmaskclient.base.models.Constants.TIMEZONE; import static se.leap.bitmaskclient.base.models.Constants.VERSION; @@ -146,6 +148,26 @@ public class Gateway { } } + public boolean hasLoadInfo() { + return load != null; + } + + public double getFullness() { + try { + return load.getDouble(FULLNESS); + } catch (JSONException | NullPointerException e) { + return 0; + } + } + + public boolean isOverloaded() { + try { + return load.getBoolean(OVERLOAD); + } catch (JSONException | NullPointerException e) { + return false; + } + } + /** * Create and attach the VpnProfile to our gateway object */ diff --git a/app/src/main/java/se/leap/bitmaskclient/eip/GatewaysManager.java b/app/src/main/java/se/leap/bitmaskclient/eip/GatewaysManager.java index b06e894e..5f6910bf 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/GatewaysManager.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/GatewaysManager.java @@ -30,6 +30,8 @@ import org.json.JSONObject; import java.io.IOException; import java.lang.reflect.Type; import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; @@ -37,8 +39,10 @@ import de.blinkt.openvpn.VpnProfile; import de.blinkt.openvpn.core.ConfigParser; import de.blinkt.openvpn.core.VpnStatus; import de.blinkt.openvpn.core.connection.Connection; +import se.leap.bitmaskclient.base.models.Location; import se.leap.bitmaskclient.base.models.Provider; import se.leap.bitmaskclient.base.models.ProviderObservable; +import se.leap.bitmaskclient.base.utils.PreferenceHelper; import static de.blinkt.openvpn.core.connection.Connection.TransportType.OBFS4; import static de.blinkt.openvpn.core.connection.Connection.TransportType.OPENVPN; @@ -72,15 +76,46 @@ public class GatewaysManager { * @return the n closest Gateway */ public Gateway select(int nClosest) { - Connection.TransportType transportType = getUsePluggableTransports(context) ? OBFS4 : OPENVPN; String selectedCity = getPreferredCity(context); + return select(nClosest, selectedCity); + } + + public Gateway select(int nClosest, String city) { + Connection.TransportType transportType = getUsePluggableTransports(context) ? OBFS4 : OPENVPN; if (presortedList.size() > 0) { - return getGatewayFromPresortedList(nClosest, transportType, selectedCity); + return getGatewayFromPresortedList(nClosest, transportType, city); } - return getGatewayFromTimezoneCalculation(nClosest, transportType, selectedCity); + return getGatewayFromTimezoneCalculation(nClosest, transportType, city); } + public List getGatewayLocations() { + String selectedCity = PreferenceHelper.getPreferredCity(context); + HashMap locationNames = new HashMap<>(); + ArrayList locations = new ArrayList<>(); + int n = 0; + Gateway gateway; + while ((gateway = select(n, null)) != null) { + if (!locationNames.containsKey(gateway.getName())) { + locationNames.put(gateway.getName(), locations.size()); + Location location = new Location( + gateway.getName(), + gateway.getFullness(), + 1, + gateway.getName().equals(selectedCity)); + locations.add(location); + } else { + int index = locationNames.get(gateway.getName()); + Location location = locations.get(index); + location.averageLoad = (location.numberOfGateways * location.averageLoad + gateway.getFullness()) / (location.numberOfGateways + 1); + location.numberOfGateways += 1; + locations.set(index, location); + } + n++; + } + + return locations; + } private Gateway getGatewayFromTimezoneCalculation(int nClosest, Connection.TransportType transportType, @Nullable String city) { List list = new ArrayList<>(gateways.values()); diff --git a/app/src/main/res/drawable-hdpi/ic_map_marker_star_black_36dp.png b/app/src/main/res/drawable-hdpi/ic_map_marker_star_black_36dp.png new file mode 100644 index 00000000..0d395564 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_map_marker_star_black_36dp.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_map_marker_star_black_36dp.png b/app/src/main/res/drawable-mdpi/ic_map_marker_star_black_36dp.png new file mode 100644 index 00000000..3065f57e Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_map_marker_star_black_36dp.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_map_marker_star_black_36dp.png b/app/src/main/res/drawable-xhdpi/ic_map_marker_star_black_36dp.png new file mode 100644 index 00000000..57016c8c Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_map_marker_star_black_36dp.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_map_marker_star_black_36dp.png b/app/src/main/res/drawable-xxhdpi/ic_map_marker_star_black_36dp.png new file mode 100644 index 00000000..3f0cdaba Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_map_marker_star_black_36dp.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_map_marker_star_black_36dp.png b/app/src/main/res/drawable-xxxhdpi/ic_map_marker_star_black_36dp.png new file mode 100644 index 00000000..a4afda4d Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_map_marker_star_black_36dp.png differ diff --git a/app/src/main/res/layout-xlarge/v_switch_list_item.xml b/app/src/main/res/layout-xlarge/v_switch_list_item.xml index 3fa2ac7d..02e52d6d 100644 --- a/app/src/main/res/layout-xlarge/v_switch_list_item.xml +++ b/app/src/main/res/layout-xlarge/v_switch_list_item.xml @@ -1,7 +1,7 @@ @@ -10,68 +10,75 @@ android:layout_width="?android:attr/listPreferredItemHeight" android:layout_height="?android:attr/listPreferredItemHeight" android:layout_gravity="center" + android:layout_alignTop="@+id/textContainer" + android:layout_alignBottom="@+id/textContainer" android:paddingStart="?android:attr/listPreferredItemPaddingStart" android:paddingLeft="?android:attr/listPreferredItemPaddingLeft" - tools:src="@drawable/ic_add_circle_outline_grey600_24dp" - /> + tools:src="@drawable/ic_add_circle_outline_grey600_24dp" /> - - - + android:layout_height="wrap_content"> + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/f_drawer_main.xml b/app/src/main/res/layout/f_drawer_main.xml index d2729998..15dd0425 100644 --- a/app/src/main/res/layout/f_drawer_main.xml +++ b/app/src/main/res/layout/f_drawer_main.xml @@ -98,6 +98,15 @@ android:visibility="gone" /> + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/v_select_text_list_item.xml b/app/src/main/res/layout/v_select_text_list_item.xml new file mode 100644 index 00000000..07187016 --- /dev/null +++ b/app/src/main/res/layout/v_select_text_list_item.xml @@ -0,0 +1,63 @@ + + + + + + + + + + + diff --git a/app/src/main/res/layout/v_switch_list_item.xml b/app/src/main/res/layout/v_switch_list_item.xml index b595e7ce..1686a99d 100644 --- a/app/src/main/res/layout/v_switch_list_item.xml +++ b/app/src/main/res/layout/v_switch_list_item.xml @@ -1,7 +1,7 @@ @@ -10,69 +10,76 @@ android:layout_width="?android:attr/listPreferredItemHeightSmall" android:layout_height="?android:attr/listPreferredItemHeightSmall" android:layout_gravity="center" + android:layout_alignTop="@+id/textContainer" + android:layout_alignBottom="@+id/textContainer" android:paddingStart="?android:attr/listPreferredItemPaddingStart" android:paddingLeft="?android:attr/listPreferredItemPaddingLeft" - tools:src="@drawable/ic_add_circle_outline_grey600_24dp" - /> + tools:src="@drawable/ic_add_circle_outline_grey600_24dp" /> - - - + android:layout_height="wrap_content"> + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 000effc7..e883b974 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -149,4 +149,10 @@ PGP verification error. Ignoring download. Update failed. No permissions to install app. + Select location + %s will find the best connection for you. + Location with best connection + Automatic + Your traffic is currently routed through: + -- cgit v1.2.3 From 080d176ff73dd4bc7e42a0233b012a967dbbf77b Mon Sep 17 00:00:00 2001 From: cyBerta Date: Fri, 26 Mar 2021 12:04:21 +0100 Subject: add tests for getGatewayLocations --- .../bitmaskclient/eip/GatewaysManagerTest.java | 108 +++++++++++++++++++++ .../v4/riseup_eipservice_for_geoip_v4.json | 13 +++ 2 files changed, 121 insertions(+) (limited to 'app/src') diff --git a/app/src/test/java/se/leap/bitmaskclient/eip/GatewaysManagerTest.java b/app/src/test/java/se/leap/bitmaskclient/eip/GatewaysManagerTest.java index 255d98e0..81fcd7d5 100644 --- a/app/src/test/java/se/leap/bitmaskclient/eip/GatewaysManagerTest.java +++ b/app/src/test/java/se/leap/bitmaskclient/eip/GatewaysManagerTest.java @@ -16,9 +16,11 @@ import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.PowerMockRunner; import java.io.IOException; +import java.util.List; import de.blinkt.openvpn.VpnProfile; import de.blinkt.openvpn.core.ConfigParser; +import se.leap.bitmaskclient.base.models.Location; import se.leap.bitmaskclient.base.models.Provider; import se.leap.bitmaskclient.base.models.ProviderObservable; import se.leap.bitmaskclient.testutils.MockHelper; @@ -283,6 +285,112 @@ public class GatewaysManagerTest { assertEquals(null, gatewaysManager.select(3)); } + @Test + public void testSelectN_selectNAndCity_returnsGatewaysInPresortedOrder() { + Provider provider = getProvider(null, null, null, null, null, null, "v4/riseup_eipservice_for_geoip_v4.json", "v4/riseup_geoip_v4.json"); + + MockHelper.mockProviderObserver(provider); + //use openvpn, not pluggable transports + mockStatic(PreferenceHelper.class); + when(PreferenceHelper.getUsePluggableTransports(any(Context.class))).thenReturn(false); + GatewaysManager gatewaysManager = new GatewaysManager(mockContext); + + assertEquals("mouette.riseup.net", gatewaysManager.select(0, "Paris").getHost()); + assertEquals("hoatzin.riseup.net", gatewaysManager.select(1, "Paris").getHost()); + assertEquals("zarapito.riseup.net", gatewaysManager.select(2, "Paris").getHost()); + } + + @Test + public void testSelectN_selectNAndCityWithGeoIpServiceV1_returnsGatewaysInPresortedOrder() { + Provider provider = getProvider(null, null, null, null, null, null, "v4/riseup_eipservice_for_geoip_v4.json", "v4/riseup_geoip_v1.json"); + + MockHelper.mockProviderObserver(provider); + //use openvpn, not pluggable transports + mockStatic(PreferenceHelper.class); + when(PreferenceHelper.getUsePluggableTransports(any(Context.class))).thenReturn(false); + GatewaysManager gatewaysManager = new GatewaysManager(mockContext); + + assertEquals("mouette.riseup.net", gatewaysManager.select(0, "Paris").getHost()); + assertEquals("hoatzin.riseup.net", gatewaysManager.select(1, "Paris").getHost()); + assertEquals("zarapito.riseup.net", gatewaysManager.select(2, "Paris").getHost()); + } + + @Test + public void testSelectN_selectNAndCityWithTimezoneCalculation_returnsRandomizedGatewaysOfSelectedCity() { + Provider provider = getProvider(null, null, null, null, null, null, "v4/riseup_eipservice_for_geoip_v4.json", null); + + provider.setGeoIpJson(new JSONObject()); + MockHelper.mockProviderObserver(provider); + //use openvpn, not pluggable transports + mockStatic(PreferenceHelper.class); + when(PreferenceHelper.getUsePluggableTransports(any(Context.class))).thenReturn(false); + GatewaysManager gatewaysManager = new GatewaysManager(mockContext); + + assertEquals("Paris", gatewaysManager.select(0, "Paris").getName()); + assertEquals("Paris", gatewaysManager.select(1, "Paris").getName()); + assertEquals("Paris", gatewaysManager.select(2, "Paris").getName()); + assertEquals(null, gatewaysManager.select(3, "Paris")); + } + + @Test + public void testSelectN_selectFromCityWithTimezoneCalculationCityNotExisting_returnsNull() { + Provider provider = getProvider(null, null, null, null, null, null, "v4/riseup_eipservice_for_geoip_v4.json", "v4/riseup_geoip_v4.json"); + + provider.setGeoIpJson(new JSONObject()); + MockHelper.mockProviderObserver(provider); + //use openvpn, not pluggable transports + mockStatic(PreferenceHelper.class); + when(PreferenceHelper.getUsePluggableTransports(any(Context.class))).thenReturn(false); + GatewaysManager gatewaysManager = new GatewaysManager(mockContext); + assertNull(gatewaysManager.select(0, "Stockholm")); + } + + @Test + public void testGetLocations_openvpn() { + Provider provider = getProvider(null, null, null, null, null, null, "v4/riseup_eipservice_for_geoip_v4.json", "v4/riseup_geoip_v4.json"); + + MockHelper.mockProviderObserver(provider); + mockStatic(PreferenceHelper.class); + when(PreferenceHelper.getUsePluggableTransports(any(Context.class))).thenReturn(false); + GatewaysManager gatewaysManager = new GatewaysManager(mockContext); + List locations = gatewaysManager.getGatewayLocations(); + + assertEquals(3, locations.size()); + for (Location location : locations) { + if ("Paris".equals(location.name)) { + assertEquals(3, location.numberOfGateways); + // manually calculate average load of paris gateways in "v4/riseup_geoip_v4.json" + double averageLoad = (0.3 + 0.36 + 0.92) / 3.0; + assertEquals(averageLoad, location.averageLoad); + } + } + } + + @Test + public void testGetLocations_obfs4() { + Provider provider = getProvider(null, null, null, null, null, null, "v4/riseup_eipservice_for_geoip_v4.json", "v4/riseup_geoip_v4.json"); + + MockHelper.mockProviderObserver(provider); + mockStatic(PreferenceHelper.class); + when(PreferenceHelper.getUsePluggableTransports(any(Context.class))).thenReturn(true); + GatewaysManager gatewaysManager = new GatewaysManager(mockContext); + List locations = gatewaysManager.getGatewayLocations(); + + assertEquals(2, locations.size()); + for (Location location : locations) { + if ("Montreal".equals(location.name)) { + assertEquals(1, location.numberOfGateways); + assertEquals(0.59, location.averageLoad); + } + if ("Paris".equals(location.name)) { + // checks that only gateways supporting obfs4 are taken into account + assertEquals(1, location.numberOfGateways); + assertEquals(0.36, location.averageLoad); + } + } + + } + private String getJsonStringFor(String filename) throws IOException { return TestSetupHelper.getInputAsString(getClass().getClassLoader().getResourceAsStream(filename)); diff --git a/app/src/test/resources/v4/riseup_eipservice_for_geoip_v4.json b/app/src/test/resources/v4/riseup_eipservice_for_geoip_v4.json index b8fa8bbf..76fbea52 100644 --- a/app/src/test/resources/v4/riseup_eipservice_for_geoip_v4.json +++ b/app/src/test/resources/v4/riseup_eipservice_for_geoip_v4.json @@ -28,6 +28,19 @@ "filter_dns": false, "limited": false, "transport":[ + { + "type":"obfs4", + "protocols":[ + "tcp" + ], + "ports":[ + "23042" + ], + "options": { + "cert": "48F/JNm/LKYt7zkDmPHXcw5f3Jqgwg/3OBRrqW14Yj87ATZ4KyAZRV7np4RhCXGSJHgoCQ", + "iatMode": "0" + } + }, { "type":"openvpn", "protocols":[ -- cgit v1.2.3 From 16add6d6595a49de9a7e68479c6a2bec382aec78 Mon Sep 17 00:00:00 2001 From: cyBerta Date: Fri, 26 Mar 2021 12:05:22 +0100 Subject: shorter debug log if GatewayManager parses Provider data that doesn't support menshen --- app/src/main/java/se/leap/bitmaskclient/eip/GatewaysManager.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'app/src') diff --git a/app/src/main/java/se/leap/bitmaskclient/eip/GatewaysManager.java b/app/src/main/java/se/leap/bitmaskclient/eip/GatewaysManager.java index 5f6910bf..77ecfc5f 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/GatewaysManager.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/GatewaysManager.java @@ -17,6 +17,7 @@ package se.leap.bitmaskclient.eip; import android.content.Context; +import android.util.Log; import androidx.annotation.Nullable; @@ -264,7 +265,7 @@ public class GatewaysManager { } } } catch (NullPointerException | JSONException npe) { - npe.printStackTrace(); + Log.d(TAG, "No valid geoip json found: " + npe.getLocalizedMessage()); } } -- cgit v1.2.3 From 93e66fa7bca76c4fd6c77159a9cb71c1687d4881 Mon Sep 17 00:00:00 2001 From: cyBerta Date: Sat, 27 Mar 2021 15:37:03 +0100 Subject: fix location name comparison, allow to unselect a location --- .../se/leap/bitmaskclient/base/fragments/GatewaySelectionFragment.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app/src') diff --git a/app/src/main/java/se/leap/bitmaskclient/base/fragments/GatewaySelectionFragment.java b/app/src/main/java/se/leap/bitmaskclient/base/fragments/GatewaySelectionFragment.java index b14a9e44..b7d5831c 100644 --- a/app/src/main/java/se/leap/bitmaskclient/base/fragments/GatewaySelectionFragment.java +++ b/app/src/main/java/se/leap/bitmaskclient/base/fragments/GatewaySelectionFragment.java @@ -241,7 +241,7 @@ public class GatewaySelectionFragment extends Fragment implements SharedPreferen if (selectedLocation == null) { selectedLocation = location; selectedLocation.selected = true; - } else if (selectedLocation.equals(location.name)){ + } else if (selectedLocation.name.equals(location.name)){ selectedLocation.selected = !selectedLocation.selected; } else { selectedLocation.selected = false; -- cgit v1.2.3 From 299dee25b134805114e8a9580716607bd338f30b Mon Sep 17 00:00:00 2001 From: cyBerta Date: Sat, 27 Mar 2021 15:44:57 +0100 Subject: disable 'turn on' button if automatic gateway selection is disabled but no location is manually selected --- .../bitmaskclient/base/fragments/GatewaySelectionFragment.java | 8 ++++++++ .../java/se/leap/bitmaskclient/base/views/IconSwitchEntry.java | 4 ++++ 2 files changed, 12 insertions(+) (limited to 'app/src') diff --git a/app/src/main/java/se/leap/bitmaskclient/base/fragments/GatewaySelectionFragment.java b/app/src/main/java/se/leap/bitmaskclient/base/fragments/GatewaySelectionFragment.java index b7d5831c..30a167fc 100644 --- a/app/src/main/java/se/leap/bitmaskclient/base/fragments/GatewaySelectionFragment.java +++ b/app/src/main/java/se/leap/bitmaskclient/base/fragments/GatewaySelectionFragment.java @@ -54,6 +54,7 @@ import static android.content.Context.MODE_PRIVATE; import static android.view.View.INVISIBLE; import static android.view.View.VISIBLE; import static se.leap.bitmaskclient.base.MainActivity.ACTION_SHOW_VPN_FRAGMENT; +import static se.leap.bitmaskclient.base.models.Constants.PREFERRED_CITY; import static se.leap.bitmaskclient.base.models.Constants.SHARED_PREFERENCES; import static se.leap.bitmaskclient.base.models.Constants.USE_PLUGGABLE_TRANSPORTS; import static se.leap.bitmaskclient.base.utils.PreferenceHelper.getPreferredCity; @@ -133,6 +134,7 @@ public class GatewaySelectionFragment extends Fragment implements SharedPreferen PreferenceHelper.setPreferredCity(getContext(), null); locationListAdapter.resetSelection(); } + setVpnButtonState(); }); } @@ -154,12 +156,18 @@ public class GatewaySelectionFragment extends Fragment implements SharedPreferen } else { vpnButton.setText(R.string.reconnect); } + vpnButton.setEnabled( + (locationListAdapter.selectedLocation != null && locationListAdapter.selectedLocation.selected) || + autoSelectionSwitch.isChecked()); } @Override public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { if (USE_PLUGGABLE_TRANSPORTS.equals(key)) { locationListAdapter.updateData(gatewaysManager.getGatewayLocations()); + setVpnButtonState(); + } else if (PREFERRED_CITY.equals(key)) { + setVpnButtonState(); } } diff --git a/app/src/main/java/se/leap/bitmaskclient/base/views/IconSwitchEntry.java b/app/src/main/java/se/leap/bitmaskclient/base/views/IconSwitchEntry.java index 628b4c15..b6d72ab6 100644 --- a/app/src/main/java/se/leap/bitmaskclient/base/views/IconSwitchEntry.java +++ b/app/src/main/java/se/leap/bitmaskclient/base/views/IconSwitchEntry.java @@ -131,6 +131,10 @@ public class IconSwitchEntry extends LinearLayout { switchView.setOnCheckedChangeListener(checkedChangeListener); } + public boolean isChecked() { + return switchView.isChecked(); + } + @Override public void setEnabled(boolean enabled) { super.setEnabled(enabled); -- cgit v1.2.3 From 90d91d7b1ac692cda7016a886fd57c49aaf124ac Mon Sep 17 00:00:00 2001 From: cyBerta Date: Sat, 27 Mar 2021 15:45:18 +0100 Subject: make LinearLayoutManager a local variable --- .../se/leap/bitmaskclient/base/fragments/GatewaySelectionFragment.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'app/src') diff --git a/app/src/main/java/se/leap/bitmaskclient/base/fragments/GatewaySelectionFragment.java b/app/src/main/java/se/leap/bitmaskclient/base/fragments/GatewaySelectionFragment.java index 30a167fc..84024962 100644 --- a/app/src/main/java/se/leap/bitmaskclient/base/fragments/GatewaySelectionFragment.java +++ b/app/src/main/java/se/leap/bitmaskclient/base/fragments/GatewaySelectionFragment.java @@ -66,7 +66,6 @@ public class GatewaySelectionFragment extends Fragment implements SharedPreferen private RecyclerView recyclerView; - private LinearLayoutManager layoutManager; private LocationListAdapter locationListAdapter; private IconSwitchEntry autoSelectionSwitch; private AppCompatButton vpnButton; @@ -115,7 +114,7 @@ public class GatewaySelectionFragment extends Fragment implements SharedPreferen private void initRecyclerView() { recyclerView = (RecyclerView) getActivity().findViewById(R.id.gatewaySelection_list); recyclerView.setHasFixedSize(true); - layoutManager = new LinearLayoutManager(this.getContext()); + LinearLayoutManager layoutManager = new LinearLayoutManager(this.getContext()); recyclerView.setLayoutManager(layoutManager); locationListAdapter = new LocationListAdapter(gatewaysManager.getGatewayLocations()); recyclerView.setAdapter(locationListAdapter); -- cgit v1.2.3 From d4c418faf893d28e3c543f19a276fada23e2fd6e Mon Sep 17 00:00:00 2001 From: cyberta Date: Wed, 28 Apr 2021 10:40:18 +0200 Subject: ask to disable manual gateway selection if current combination of transport and location fails --- .../base/fragments/MainActivityErrorDialog.java | 10 +++++++++- app/src/main/java/se/leap/bitmaskclient/eip/EIP.java | 13 +++++++++++-- app/src/main/res/values/untranslatable.xml | 4 ++++ 3 files changed, 24 insertions(+), 3 deletions(-) (limited to 'app/src') diff --git a/app/src/main/java/se/leap/bitmaskclient/base/fragments/MainActivityErrorDialog.java b/app/src/main/java/se/leap/bitmaskclient/base/fragments/MainActivityErrorDialog.java index f036b411..4034bd04 100644 --- a/app/src/main/java/se/leap/bitmaskclient/base/fragments/MainActivityErrorDialog.java +++ b/app/src/main/java/se/leap/bitmaskclient/base/fragments/MainActivityErrorDialog.java @@ -18,6 +18,7 @@ package se.leap.bitmaskclient.base.fragments; import android.app.Dialog; import android.content.Context; +import android.content.DialogInterface; import android.os.Bundle; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -32,6 +33,8 @@ import se.leap.bitmaskclient.eip.EipCommand; import se.leap.bitmaskclient.base.models.Provider; import se.leap.bitmaskclient.providersetup.ProviderAPICommand; +import static se.leap.bitmaskclient.base.utils.PreferenceHelper.getPreferredCity; +import static se.leap.bitmaskclient.base.utils.PreferenceHelper.setPreferredCity; import static se.leap.bitmaskclient.providersetup.ProviderAPI.UPDATE_INVALID_VPN_CERTIFICATE; import static se.leap.bitmaskclient.R.string.warning_option_try_ovpn; import static se.leap.bitmaskclient.R.string.warning_option_try_pt; @@ -120,7 +123,12 @@ public class MainActivityErrorDialog extends DialogFragment { break; case NO_MORE_GATEWAYS: builder.setNegativeButton(R.string.cancel, (dialog, id) -> {}); - if (provider.supportsPluggableTransports()) { + if (getPreferredCity(applicationContext) != null) { + builder.setPositiveButton(R.string.warning_option_try_best, (dialog, which) -> { + setPreferredCity(applicationContext, null); + EipCommand.startVPN(applicationContext, false); + }); + } else if (provider.supportsPluggableTransports()) { if (getUsePluggableTransports(applicationContext)) { builder.setPositiveButton(warning_option_try_ovpn, ((dialog, which) -> { usePluggableTransports(applicationContext, false); diff --git a/app/src/main/java/se/leap/bitmaskclient/eip/EIP.java b/app/src/main/java/se/leap/bitmaskclient/eip/EIP.java index 74226250..d632c09e 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/EIP.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/EIP.java @@ -84,6 +84,7 @@ import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_PROFILE; import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_VPN_CERTIFICATE; import static se.leap.bitmaskclient.base.models.Constants.SHARED_PREFERENCES; import static se.leap.bitmaskclient.base.utils.ConfigHelper.ensureNotOnMainThread; +import static se.leap.bitmaskclient.base.utils.PreferenceHelper.getPreferredCity; import static se.leap.bitmaskclient.base.utils.PreferenceHelper.getUsePluggableTransports; import static se.leap.bitmaskclient.eip.EIP.EIPErrors.ERROR_INVALID_PROFILE; import static se.leap.bitmaskclient.eip.EIP.EIPErrors.ERROR_INVALID_VPN_CERTIFICATE; @@ -317,7 +318,12 @@ public final class EIP extends JobIntentService implements Observer { Connection.TransportType transportType = getUsePluggableTransports(this) ? OBFS4 : OPENVPN; if (gateway == null || (profile = gateway.getProfile(transportType)) == null) { - setErrorResult(result, NO_MORE_GATEWAYS.toString(), getStringResourceForNoMoreGateways(), getString(R.string.app_name)); + String preferredLocation = getPreferredCity(getApplicationContext()); + if (preferredLocation != null) { + setErrorResult(result, NO_MORE_GATEWAYS.toString(), getStringResourceForNoMoreGateways(), getString(R.string.app_name), preferredLocation); + } else { + setErrorResult(result, NO_MORE_GATEWAYS.toString(), getStringResourceForNoMoreGateways(), getString(R.string.app_name)); + } return; } @@ -527,7 +533,10 @@ public final class EIP extends JobIntentService implements Observer { private @StringRes int getStringResourceForNoMoreGateways() { - if (ProviderObservable.getInstance().getCurrentProvider().supportsPluggableTransports()) { + boolean isManualGatewaySelection = PreferenceHelper.getLastConnectedVpnProfile(getApplicationContext()) != null; + if (isManualGatewaySelection) { + return R.string.warning_no_more_gateways_manual_gw_selection; + } else if (ProviderObservable.getInstance().getCurrentProvider().supportsPluggableTransports()) { if (PreferenceHelper.getUsePluggableTransports(getApplicationContext())) { return R.string.warning_no_more_gateways_use_ovpn; } else { diff --git a/app/src/main/res/values/untranslatable.xml b/app/src/main/res/values/untranslatable.xml index 9c6afadd..6f989cc8 100644 --- a/app/src/main/res/values/untranslatable.xml +++ b/app/src/main/res/values/untranslatable.xml @@ -44,5 +44,9 @@ CircleImageView Copyright 2014 - 2020 Henning Dodenhof. Licensed under the Apache License, Version 2.0 + + %1$s could not connect to %2$s. Do you want to try to connect automatically with best location? + Try best location + \ No newline at end of file -- cgit v1.2.3 From f1d4b39bdc30417247bc1c8dccc75476e9eb9317 Mon Sep 17 00:00:00 2001 From: cyberta Date: Sat, 15 May 2021 14:18:41 +0200 Subject: fix missing empty space in untranslatable.xml string resources --- app/src/main/res/values/untranslatable.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app/src') diff --git a/app/src/main/res/values/untranslatable.xml b/app/src/main/res/values/untranslatable.xml index 6f989cc8..a08857c1 100644 --- a/app/src/main/res/values/untranslatable.xml +++ b/app/src/main/res/values/untranslatable.xml @@ -46,7 +46,7 @@ %1$s could not connect to %2$s. Do you want to try to connect automatically with best location? - Try best location + Try best location \ No newline at end of file -- cgit v1.2.3 From 4853d6a3d3d8c1077feb61899f6b07b5f528607c Mon Sep 17 00:00:00 2001 From: cyberta Date: Tue, 18 May 2021 17:45:52 +0200 Subject: implement feature switch, so that gateway selection can be turned off on compile time, turned off for Bitmask for now --- app/src/main/java/se/leap/bitmaskclient/base/StartActivity.java | 6 ++++++ .../leap/bitmaskclient/base/fragments/NavigationDrawerFragment.java | 4 ++++ 2 files changed, 10 insertions(+) (limited to 'app/src') diff --git a/app/src/main/java/se/leap/bitmaskclient/base/StartActivity.java b/app/src/main/java/se/leap/bitmaskclient/base/StartActivity.java index cf64905a..9d689e5d 100644 --- a/app/src/main/java/se/leap/bitmaskclient/base/StartActivity.java +++ b/app/src/main/java/se/leap/bitmaskclient/base/StartActivity.java @@ -30,6 +30,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import de.blinkt.openvpn.core.VpnStatus; +import se.leap.bitmaskclient.BuildConfig; import se.leap.bitmaskclient.providersetup.ProviderListActivity; import se.leap.bitmaskclient.eip.EipCommand; import se.leap.bitmaskclient.base.models.FeatureVersionCode; @@ -157,6 +158,11 @@ public class StartActivity extends Activity{ } } + // always check if manual gateway selection feature switch has been disabled + if (!BuildConfig.allow_manual_gateway_selection && PreferenceHelper.getPreferredCity(this) != null) { + PreferenceHelper.setPreferredCity(this, null); + } + // ensure all upgrades have passed before storing new information storeAppVersion(); } diff --git a/app/src/main/java/se/leap/bitmaskclient/base/fragments/NavigationDrawerFragment.java b/app/src/main/java/se/leap/bitmaskclient/base/fragments/NavigationDrawerFragment.java index cbfe8a71..5cae1591 100644 --- a/app/src/main/java/se/leap/bitmaskclient/base/fragments/NavigationDrawerFragment.java +++ b/app/src/main/java/se/leap/bitmaskclient/base/fragments/NavigationDrawerFragment.java @@ -50,6 +50,7 @@ import java.util.Observer; import java.util.Set; import de.blinkt.openvpn.core.VpnStatus; +import se.leap.bitmaskclient.BuildConfig; import se.leap.bitmaskclient.base.FragmentManagerEnhanced; import se.leap.bitmaskclient.base.MainActivity; import se.leap.bitmaskclient.base.models.Provider; @@ -417,6 +418,9 @@ public class NavigationDrawerFragment extends Fragment implements SharedPreferen } private void initManualGatewayEntry() { + if (!BuildConfig.allow_manual_gateway_selection) { + return; + } manualGatewaySelection = drawerView.findViewById(R.id.manualGatewaySelection); String preferredGateway = getPreferredCity(getContext()); String subtitle = preferredGateway != null ? preferredGateway : getString(R.string.gateway_selection_best_location); -- cgit v1.2.3 From 4b8ea1252cddfd54278676a8b2f64eb937f92c2d Mon Sep 17 00:00:00 2001 From: cyBerta Date: Tue, 18 May 2021 17:53:00 +0200 Subject: adapt default to hide manual gateway selection entry in navigation drawer, if feature switch is enabled and the provider has gateways in different cities the entry is shown~ --- app/src/main/res/layout/f_drawer_main.xml | 1 + 1 file changed, 1 insertion(+) (limited to 'app/src') diff --git a/app/src/main/res/layout/f_drawer_main.xml b/app/src/main/res/layout/f_drawer_main.xml index 15dd0425..1f1df7f2 100644 --- a/app/src/main/res/layout/f_drawer_main.xml +++ b/app/src/main/res/layout/f_drawer_main.xml @@ -105,6 +105,7 @@ app:icon="@drawable/ic_map_marker_star_black_36dp" android:layout_height="wrap_content" android:layout_width="wrap_content" + android:visibility="gone" />