summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
Diffstat (limited to 'app')
-rw-r--r--app/assets/calyx.net.json2
-rw-r--r--app/assets/riseup.net.json2
-rw-r--r--app/assets/urls/calyx.net.url2
-rw-r--r--app/assets/urls/demo.bitmask.net.url2
-rw-r--r--app/assets/urls/riseup.net.url2
-rw-r--r--app/src/insecure/java/se/leap/bitmaskclient/ProviderApiManager.java9
-rw-r--r--app/src/main/AndroidManifest.xml8
-rw-r--r--app/src/main/ic_launcher-web.pngbin0 -> 39579 bytes
-rw-r--r--app/src/main/java/de/blinkt/openvpn/core/OpenVPNService.java6
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/ConfigHelper.java80
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/ConfigWizardBaseActivity.java20
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/Constants.java5
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/Dashboard.java0
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/DrawerSettingsAdapter.java219
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/EipFragment.java96
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/MainActivity.java28
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/Provider.java35
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/ProviderAPI.java1
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/ProviderApiManagerBase.java41
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/ProviderCredentialsBaseActivity.java30
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/ProviderListBaseActivity.java9
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/ProviderManager.java53
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/StartActivity.java9
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/drawer/NavigationDrawerFragment.java162
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/eip/EIP.java1
-rw-r--r--app/src/main/res/drawable-hdpi/icon.pngbin7965 -> 0 bytes
-rw-r--r--app/src/main/res/drawable-ldpi/icon.pngbin3118 -> 0 bytes
-rw-r--r--app/src/main/res/drawable-mdpi/icon.pngbin4518 -> 0 bytes
-rw-r--r--app/src/main/res/drawable-xhdpi/icon.pngbin11135 -> 0 bytes
-rw-r--r--app/src/main/res/drawable/ic_launcher_background.xml74
-rw-r--r--app/src/main/res/layout-xlarge/about.xml52
-rw-r--r--app/src/main/res/layout-xlarge/eip_service_fragment.xml35
-rw-r--r--app/src/main/res/layout/about.xml46
-rw-r--r--app/src/main/res/layout/drawer_main.xml48
-rw-r--r--app/src/main/res/layout/eip_service_fragment.xml37
-rw-r--r--app/src/main/res/layout/switch_list_item.xml15
-rw-r--r--app/src/main/res/menu/configuration_wizard_activity.xml15
-rw-r--r--app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml5
-rw-r--r--app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml5
-rw-r--r--app/src/main/res/mipmap-hdpi/ic_launcher.pngbin0 -> 4054 bytes
-rw-r--r--app/src/main/res/mipmap-hdpi/ic_launcher_foreground.pngbin0 -> 5812 bytes
-rw-r--r--app/src/main/res/mipmap-hdpi/ic_launcher_round.pngbin0 -> 6301 bytes
-rw-r--r--app/src/main/res/mipmap-mdpi/ic_launcher.pngbin0 -> 2738 bytes
-rw-r--r--app/src/main/res/mipmap-mdpi/ic_launcher_foreground.pngbin0 -> 3796 bytes
-rw-r--r--app/src/main/res/mipmap-mdpi/ic_launcher_round.pngbin0 -> 3812 bytes
-rw-r--r--app/src/main/res/mipmap-xhdpi/ic_launcher.pngbin0 -> 5607 bytes
-rw-r--r--app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.pngbin0 -> 8292 bytes
-rw-r--r--app/src/main/res/mipmap-xhdpi/ic_launcher_round.pngbin0 -> 8783 bytes
-rw-r--r--app/src/main/res/mipmap-xxhdpi/ic_launcher.pngbin0 -> 8486 bytes
-rw-r--r--app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.pngbin0 -> 13528 bytes
-rw-r--r--app/src/main/res/mipmap-xxhdpi/ic_launcher_round.pngbin0 -> 13542 bytes
-rw-r--r--app/src/main/res/mipmap-xxxhdpi/ic_launcher.pngbin0 -> 11566 bytes
-rw-r--r--app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.pngbin0 -> 20381 bytes
-rw-r--r--app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.pngbin0 -> 19074 bytes
-rw-r--r--app/src/main/res/values-ar/strings_ar.xml12
-rw-r--r--app/src/main/res/values-de/strings.xml13
-rw-r--r--app/src/main/res/values-es/strings.xml7
-rw-r--r--app/src/main/res/values-fa-rIR/strings.xml92
-rw-r--r--app/src/main/res/values-fr/strings.xml16
-rw-r--r--app/src/main/res/values-gl/strings.xml110
-rw-r--r--app/src/main/res/values-nl/strings.xml73
-rw-r--r--app/src/main/res/values-no/strings.xml5
-rw-r--r--app/src/main/res/values-uk/strings.xml1
-rw-r--r--app/src/main/res/values/strings.xml5
-rw-r--r--app/src/main/res/values/untranslatable.xml20
-rw-r--r--app/src/production/java/se/leap/bitmaskclient/ProviderApiManager.java10
-rw-r--r--app/src/production/java/se/leap/bitmaskclient/ProviderListActivity.java3
-rw-r--r--app/src/test/java/se/leap/bitmaskclient/ProviderTest.java30
-rw-r--r--app/src/test/java/se/leap/bitmaskclient/testutils/MockHelper.java15
-rw-r--r--app/src/test/resources/externalDir/leapcolombia.json3
-rw-r--r--app/src/test/resources/preconfigured/calyx.net.json37
-rw-r--r--app/src/test/resources/preconfigured/calyx.net.pem31
-rw-r--r--app/src/test/resources/preconfigured/demo.bitmask.net.json42
-rw-r--r--app/src/test/resources/preconfigured/demo.bitmask.net.pem32
-rw-r--r--app/src/test/resources/preconfigured/riseup.net.json37
-rw-r--r--app/src/test/resources/preconfigured/riseup.net.pem32
-rw-r--r--app/src/test/resources/preconfigured/urls/calyx.net.url3
-rw-r--r--app/src/test/resources/preconfigured/urls/demo.bitmask.net.url3
-rw-r--r--app/src/test/resources/preconfigured/urls/riseup.net.url3
79 files changed, 1501 insertions, 288 deletions
diff --git a/app/assets/calyx.net.json b/app/assets/calyx.net.json
index 30ab43c5..69e1c2a6 100644
--- a/app/assets/calyx.net.json
+++ b/app/assets/calyx.net.json
@@ -1,5 +1,5 @@
{
- "api_uri": "https://calyx.net:4430",
+ "api_uri": "https://api.calyx.net:4430",
"api_version": "1",
"ca_cert_fingerprint": "SHA256: 43683c9ba3862c5384a8c1885072fcac40b5d2d4dd67331443f13a3077fa2e69",
"ca_cert_uri": "https://calyx.net/ca.crt",
diff --git a/app/assets/riseup.net.json b/app/assets/riseup.net.json
index 9a5ec79e..82290ecd 100644
--- a/app/assets/riseup.net.json
+++ b/app/assets/riseup.net.json
@@ -16,7 +16,7 @@
"en": "Riseup Networks"
},
"service": {
- "allow_anonymous": false,
+ "allow_anonymous": true,
"allow_free": true,
"allow_limited_bandwidth": false,
"allow_paid": false,
diff --git a/app/assets/urls/calyx.net.url b/app/assets/urls/calyx.net.url
index 8de04fe9..807e9e18 100644
--- a/app/assets/urls/calyx.net.url
+++ b/app/assets/urls/calyx.net.url
@@ -1,3 +1,3 @@
{
- "main_url" : "https://calyx.net/"
+ "main_url" : "https://calyx.net"
}
diff --git a/app/assets/urls/demo.bitmask.net.url b/app/assets/urls/demo.bitmask.net.url
index 1a412055..0c4de648 100644
--- a/app/assets/urls/demo.bitmask.net.url
+++ b/app/assets/urls/demo.bitmask.net.url
@@ -1,3 +1,3 @@
{
- "main_url" : "https://demo.bitmask.net/"
+ "main_url" : "https://demo.bitmask.net"
}
diff --git a/app/assets/urls/riseup.net.url b/app/assets/urls/riseup.net.url
index 4548b433..42cdb979 100644
--- a/app/assets/urls/riseup.net.url
+++ b/app/assets/urls/riseup.net.url
@@ -1,3 +1,3 @@
{
- "main_url" : "https://riseup.net/"
+ "main_url" : "https://riseup.net"
}
diff --git a/app/src/insecure/java/se/leap/bitmaskclient/ProviderApiManager.java b/app/src/insecure/java/se/leap/bitmaskclient/ProviderApiManager.java
index 42bd576a..798b6433 100644
--- a/app/src/insecure/java/se/leap/bitmaskclient/ProviderApiManager.java
+++ b/app/src/insecure/java/se/leap/bitmaskclient/ProviderApiManager.java
@@ -140,7 +140,7 @@ public class ProviderApiManager extends ProviderApiManagerBase {
else
providerDotJsonString = downloadFromApiUrlWithProviderCA("/provider.json", caCert, providerDefinition, dangerOn);
- if (!isValidJson(providerDotJsonString)) {
+ if (ConfigHelper.checkErroneousDownload(providerDotJsonString) || !isValidJson(providerDotJsonString)) {
result.putString(ERRORS, resources.getString(malformed_url));
result.putBoolean(BROADCAST_RESULT_KEY, false);
return result;
@@ -206,9 +206,7 @@ public class ProviderApiManager extends ProviderApiManagerBase {
protected Bundle updateVpnCertificate(Provider provider) {
Bundle result = new Bundle();
try {
- JSONObject providerJson = provider.getDefinition();
- String providerMainUrl = providerJson.getString(Provider.API_URL);
- URL newCertStringUrl = new URL(providerMainUrl + "/" + providerJson.getString(Provider.API_VERSION) + "/" + PROVIDER_VPN_CERTIFICATE);
+ URL newCertStringUrl = new URL(provider.getApiUrlWithVersion() + "/" + PROVIDER_VPN_CERTIFICATE);
String certString = downloadWithProviderCA(provider.getCaCert(), newCertStringUrl.toString(), lastDangerOn);
if (ConfigHelper.checkErroneousDownload(certString)) {
@@ -223,8 +221,7 @@ public class ProviderApiManager extends ProviderApiManagerBase {
}
}
result = loadCertificate(provider, certString);
- } catch (IOException | JSONException e) {
- // TODO try to get Provider Json
+ } catch (IOException e) {
setErrorResult(result, downloading_vpn_certificate_failed, null);
e.printStackTrace();
}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index b11bd89f..6f6feebb 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -16,8 +16,8 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="se.leap.bitmaskclient"
- android:versionCode="133"
- android:versionName="0.9.8RC2" >
+ android:versionCode="134"
+ android:versionName="0.9.8" >
<uses-sdk
android:minSdkVersion="16"
@@ -32,9 +32,9 @@
<application
android:name=".BitmaskApp"
android:allowBackup="true"
- android:icon="@drawable/icon"
+ android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
- android:logo="@drawable/icon"
+ android:logo="@mipmap/ic_launcher"
android:theme="@style/BitmaskTheme">
<service
android:name=".eip.VoidVpnService"
diff --git a/app/src/main/ic_launcher-web.png b/app/src/main/ic_launcher-web.png
new file mode 100644
index 00000000..6cf4e79b
--- /dev/null
+++ b/app/src/main/ic_launcher-web.png
Binary files differ
diff --git a/app/src/main/java/de/blinkt/openvpn/core/OpenVPNService.java b/app/src/main/java/de/blinkt/openvpn/core/OpenVPNService.java
index 6c312c87..f701b7aa 100644
--- a/app/src/main/java/de/blinkt/openvpn/core/OpenVPNService.java
+++ b/app/src/main/java/de/blinkt/openvpn/core/OpenVPNService.java
@@ -913,7 +913,7 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac
// CONNECTED
// Does not work :(
notificationManager.buildOpenVpnNotification(
- mProfile.mName,
+ mProfile != null ? mProfile.mName : "",
VpnStatus.getLastCleanLogMessage(this),
VpnStatus.getLastCleanLogMessage(this),
level,
@@ -944,7 +944,7 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac
humanReadableByteCount(out, false, getResources()),
humanReadableByteCount(diffOut / OpenVPNManagement.mBytecountInterval, true, getResources()));
notificationManager.buildOpenVpnNotification(
- mProfile.mName,
+ mProfile != null ? mProfile.mName : "",
netstat,
null,
LEVEL_CONNECTED,
@@ -987,7 +987,7 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac
public void requestInputFromUser(int resid, String needed) {
VpnStatus.updateStateString("NEED", "need " + needed, resid, LEVEL_WAITING_FOR_USER_INPUT);
notificationManager.buildOpenVpnNotification(
- mProfile.mName,
+ mProfile != null ? mProfile.mName : "",
getString(resid),
getString(resid),
LEVEL_WAITING_FOR_USER_INPUT,
diff --git a/app/src/main/java/se/leap/bitmaskclient/ConfigHelper.java b/app/src/main/java/se/leap/bitmaskclient/ConfigHelper.java
index a52df460..f07a2a23 100644
--- a/app/src/main/java/se/leap/bitmaskclient/ConfigHelper.java
+++ b/app/src/main/java/se/leap/bitmaskclient/ConfigHelper.java
@@ -16,7 +16,9 @@
*/
package se.leap.bitmaskclient;
+import android.content.Context;
import android.content.SharedPreferences;
+import android.preference.PreferenceManager;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Log;
@@ -27,6 +29,8 @@ import org.spongycastle.util.encoders.Base64;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
@@ -47,11 +51,14 @@ import java.security.interfaces.RSAPrivateKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.ArrayList;
+import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
+import java.util.Set;
import static android.R.attr.name;
+import static se.leap.bitmaskclient.Constants.DEFAULT_SHARED_PREFS_BATTERY_SAVER;
import static se.leap.bitmaskclient.Constants.PREFERENCES_APP_VERSION;
import static se.leap.bitmaskclient.Constants.PROVIDER_CONFIGURED;
import static se.leap.bitmaskclient.Constants.PROVIDER_EIP_DEFINITION;
@@ -74,7 +81,7 @@ public class ConfigHelper {
public static boolean checkErroneousDownload(String downloadedString) {
try {
- if (downloadedString == null || downloadedString.isEmpty() || new JSONObject(downloadedString).has(ProviderAPI.ERRORS)) {
+ if (downloadedString == null || downloadedString.isEmpty() || new JSONObject(downloadedString).has(ProviderAPI.ERRORS) || new JSONObject(downloadedString).has(ProviderAPI.BACKEND_ERROR_KEY)) {
return true;
} else {
return false;
@@ -125,36 +132,14 @@ public class ConfigHelper {
return (X509Certificate) certificate;
}
+ public static String loadInputStreamAsString(java.io.InputStream is) {
+ java.util.Scanner s = new java.util.Scanner(is).useDelimiter("\\A");
+ return s.hasNext() ? s.next() : "";
+ }
- public static String loadInputStreamAsString(InputStream inputStream) {
- BufferedReader in = null;
- try {
- StringBuilder buf = new StringBuilder();
- in = new BufferedReader(new InputStreamReader(inputStream));
-
- String str;
- boolean isFirst = true;
- while ( (str = in.readLine()) != null ) {
- if (isFirst)
- isFirst = false;
- else
- buf.append('\n');
- buf.append(str);
- }
- return buf.toString();
- } catch (IOException e) {
- Log.e(TAG, "Error opening asset " + name);
- } finally {
- if (in != null) {
- try {
- in.close();
- } catch (IOException e) {
- Log.e(TAG, "Error closing asset " + name);
- }
- }
- }
-
- return null;
+ //allows us to mock FileInputStream
+ public static InputStream getInputStreamFrom(String filePath) throws FileNotFoundException {
+ return new FileInputStream(filePath);
}
protected static RSAPrivateKey parseRsaKeyFromString(String rsaKeyString) {
@@ -383,6 +368,7 @@ public class ConfigHelper {
clearDataOfLastProvider(preferences, false);
}
+ @Deprecated
public static void clearDataOfLastProvider(SharedPreferences preferences, boolean commit) {
Map<String, ?> allEntries = preferences.getAll();
List<String> lastProvidersKeys = new ArrayList<>();
@@ -410,18 +396,30 @@ public class ConfigHelper {
}
public static void deleteProviderDetailsFromPreferences(@NonNull SharedPreferences preferences, String providerDomain) {
- preferences.edit().
- remove(Provider.KEY + "." + providerDomain).
- remove(Provider.CA_CERT + "." + providerDomain).
- remove(Provider.CA_CERT_FINGERPRINT + "." + providerDomain).
- remove(Provider.MAIN_URL + "." + providerDomain).
- remove(Provider.KEY + "." + providerDomain).
- remove(Provider.CA_CERT + "." + providerDomain).
- remove(PROVIDER_EIP_DEFINITION + "." + providerDomain).
- remove(PROVIDER_PRIVATE_KEY + "." + providerDomain).
- remove(PROVIDER_VPN_CERTIFICATE + "." + providerDomain).
- apply();
+ preferences.edit().
+ remove(Provider.KEY + "." + providerDomain).
+ remove(Provider.CA_CERT + "." + providerDomain).
+ remove(Provider.CA_CERT_FINGERPRINT + "." + providerDomain).
+ remove(Provider.MAIN_URL + "." + providerDomain).
+ remove(Provider.KEY + "." + providerDomain).
+ remove(Provider.CA_CERT + "." + providerDomain).
+ remove(PROVIDER_EIP_DEFINITION + "." + providerDomain).
+ remove(PROVIDER_PRIVATE_KEY + "." + providerDomain).
+ remove(PROVIDER_VPN_CERTIFICATE + "." + providerDomain).
+ apply();
+ }
+
+ public static void saveBattery(Context context, boolean isEnabled) {
+ if (context == null) {
+ return;
+ }
+ SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
+ preferences.edit().putBoolean(DEFAULT_SHARED_PREFS_BATTERY_SAVER, isEnabled).apply();
}
+ public static boolean getSaveBattery(@NonNull Context context) {
+ SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
+ return preferences.getBoolean(DEFAULT_SHARED_PREFS_BATTERY_SAVER, false);
+ }
}
diff --git a/app/src/main/java/se/leap/bitmaskclient/ConfigWizardBaseActivity.java b/app/src/main/java/se/leap/bitmaskclient/ConfigWizardBaseActivity.java
index ea328216..f0e2de85 100644
--- a/app/src/main/java/se/leap/bitmaskclient/ConfigWizardBaseActivity.java
+++ b/app/src/main/java/se/leap/bitmaskclient/ConfigWizardBaseActivity.java
@@ -1,16 +1,19 @@
package se.leap.bitmaskclient;
import android.content.SharedPreferences;
+import android.graphics.PorterDuff;
+import android.os.Build;
import android.os.Bundle;
-import android.os.PersistableBundle;
import android.support.annotation.DrawableRes;
import android.support.annotation.Nullable;
import android.support.annotation.StringRes;
+import android.support.v4.content.ContextCompat;
import android.support.v7.widget.AppCompatImageView;
import android.support.v7.widget.AppCompatTextView;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
+import android.widget.ProgressBar;
import butterknife.InjectView;
@@ -38,6 +41,9 @@ public abstract class ConfigWizardBaseActivity extends ButterKnifeActivity {
@InjectView(R.id.loading_screen)
protected LinearLayout loadingScreen;
+ @InjectView(R.id.progressbar)
+ protected ProgressBar progressBar;
+
@InjectView(R.id.progressbar_description)
protected AppCompatTextView progressbarText;
@@ -59,6 +65,7 @@ public abstract class ConfigWizardBaseActivity extends ButterKnifeActivity {
super.setContentView(view);
if (provider != null)
setProviderHeaderText(provider.getName());
+ setProgressbarColorForPreLollipop();
}
@Override
@@ -66,6 +73,7 @@ public abstract class ConfigWizardBaseActivity extends ButterKnifeActivity {
super.setContentView(layoutResID);
if (provider != null)
setProviderHeaderText(provider.getName());
+ setProgressbarColorForPreLollipop();
}
@Override
@@ -73,8 +81,18 @@ public abstract class ConfigWizardBaseActivity extends ButterKnifeActivity {
super.setContentView(view, params);
if (provider != null)
setProviderHeaderText(provider.getName());
+ setProgressbarColorForPreLollipop();
}
+ private void setProgressbarColorForPreLollipop() {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
+ progressBar.getIndeterminateDrawable().setColorFilter(
+ ContextCompat.getColor(this, R.color.colorPrimary),
+ PorterDuff.Mode.SRC_IN);
+ }
+ }
+
+
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
diff --git a/app/src/main/java/se/leap/bitmaskclient/Constants.java b/app/src/main/java/se/leap/bitmaskclient/Constants.java
index 2b7a8113..58145015 100644
--- a/app/src/main/java/se/leap/bitmaskclient/Constants.java
+++ b/app/src/main/java/se/leap/bitmaskclient/Constants.java
@@ -81,4 +81,9 @@ public interface Constants {
String BROADCAST_RESULT_CODE = "BROADCAST.RESULT_CODE";
String BROADCAST_RESULT_KEY = "BROADCAST.RESULT_KEY";
+
+ //////////////////////////////////////////////
+ // ICS-OPENVPN CONSTANTS
+ /////////////////////////////////////////////
+ String DEFAULT_SHARED_PREFS_BATTERY_SAVER = "screenoff";
}
diff --git a/app/src/main/java/se/leap/bitmaskclient/Dashboard.java b/app/src/main/java/se/leap/bitmaskclient/Dashboard.java
deleted file mode 100644
index e69de29b..00000000
--- a/app/src/main/java/se/leap/bitmaskclient/Dashboard.java
+++ /dev/null
diff --git a/app/src/main/java/se/leap/bitmaskclient/DrawerSettingsAdapter.java b/app/src/main/java/se/leap/bitmaskclient/DrawerSettingsAdapter.java
new file mode 100644
index 00000000..867f3d48
--- /dev/null
+++ b/app/src/main/java/se/leap/bitmaskclient/DrawerSettingsAdapter.java
@@ -0,0 +1,219 @@
+/**
+ * Copyright (c) 2018 LEAP Encryption Access Project and contributers
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+package se.leap.bitmaskclient;
+
+import android.support.annotation.NonNull;
+import android.support.v7.widget.SwitchCompat;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.CompoundButton;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+
+/**
+ * Created by cyberta on 21.02.18.
+ */
+
+public class DrawerSettingsAdapter extends BaseAdapter {
+
+ //item types
+ public static final int NONE = -1;
+ public static final int SWITCH_PROVIDER = 0;
+ public static final int LOG = 1;
+ public static final int ABOUT = 2;
+ public static final int BATTERY_SAVER = 3;
+
+ //view types
+ public final static int VIEW_SIMPLE_TEXT = 0;
+ public final static int VIEW_SWITCH = 1;
+
+ public static class DrawerSettingsItem {
+ private String description = "";
+ private int viewType = VIEW_SIMPLE_TEXT;
+ private boolean isChecked = false;
+ private int itemType = NONE;
+ private CompoundButton.OnCheckedChangeListener callback;
+
+ private DrawerSettingsItem(String description, int viewType, boolean isChecked, int itemType, CompoundButton.OnCheckedChangeListener callback) {
+ this.description = description;
+ this.viewType = viewType;
+ this.isChecked = isChecked;
+ this.itemType = itemType;
+ this.callback = callback;
+ }
+
+ public static DrawerSettingsItem getSimpleTextInstance(String description, int itemType) {
+ return new DrawerSettingsItem(description, VIEW_SIMPLE_TEXT, false, itemType, null);
+ }
+
+ public static DrawerSettingsItem getSwitchInstance(String description, boolean isChecked, int itemType, CompoundButton.OnCheckedChangeListener callback) {
+ return new DrawerSettingsItem(description, VIEW_SWITCH, isChecked, itemType, callback);
+ }
+
+ public int getItemType() {
+ return itemType;
+ }
+
+ public void setChecked(boolean checked) {
+ isChecked = checked;
+ }
+
+ public boolean isChecked() {
+ return isChecked;
+ }
+ }
+
+ private ArrayList<DrawerSettingsItem> mData = new ArrayList<>();
+ private LayoutInflater mInflater;
+
+ public DrawerSettingsAdapter(LayoutInflater layoutInflater) {
+ mInflater = layoutInflater;
+ }
+
+ public void addItem(final DrawerSettingsItem item) {
+ mData.add(item);
+ notifyDataSetChanged();
+ }
+
+ @Override
+ public int getItemViewType(int position) {
+ DrawerSettingsItem item = mData.get(position);
+ return item.viewType;
+ }
+
+ @Override
+ public int getViewTypeCount() {
+ boolean hasSwitchItem = false;
+ for (DrawerSettingsItem item : mData) {
+ if (item.viewType == VIEW_SWITCH) {
+ hasSwitchItem = true;
+ break;
+ }
+ }
+ return hasSwitchItem ? 2 : 1;
+ }
+
+ @Override
+ public int getCount() {
+ return mData.size();
+ }
+
+ @Override
+ public DrawerSettingsItem getItem(int position) {
+ return mData.get(position);
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return position;
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+
+ DrawerSettingsItem drawerSettingsItem = mData.get(position);
+ ViewHolder holder = null;
+ int type = getItemViewType(position);
+ if (convertView == null) {
+ holder = new ViewHolder();
+ switch(type) {
+ case VIEW_SIMPLE_TEXT:
+ convertView = initTextViewBinding(holder);
+ holder.textView.setText(drawerSettingsItem.description);
+ break;
+ case VIEW_SWITCH:
+ convertView = initSwitchBinding(holder);
+ bindSwitch(drawerSettingsItem, holder);
+ break;
+ }
+ convertView.setTag(holder);
+ } else {
+ holder = (ViewHolder)convertView.getTag();
+ switch (type) {
+ case VIEW_SIMPLE_TEXT:
+ if (holder.isSwitchViewHolder()) {
+ holder.resetSwitchView();
+ convertView = initTextViewBinding(holder);
+ }
+ holder.textView.setText(drawerSettingsItem.description);
+ break;
+ case VIEW_SWITCH:
+ if (!holder.isSwitchViewHolder()) {
+ holder.resetTextView();
+ convertView = initSwitchBinding(holder);
+ }
+ bindSwitch(drawerSettingsItem, holder);
+ break;
+ }
+ convertView.setTag(holder);
+ }
+ return convertView;
+ }
+
+ private void bindSwitch(DrawerSettingsItem drawerSettingsItem, ViewHolder holder) {
+ holder.switchView.setChecked(drawerSettingsItem.isChecked);
+ holder.switchView.setText(drawerSettingsItem.description);
+ holder.switchView.setOnCheckedChangeListener(drawerSettingsItem.callback);
+ }
+
+ @NonNull
+ private View initSwitchBinding(ViewHolder holder) {
+ View convertView = mInflater.inflate(R.layout.switch_list_item, null);
+ holder.switchView = convertView.findViewById(android.R.id.text1);
+ return convertView;
+ }
+
+ @NonNull
+ private View initTextViewBinding(ViewHolder holder) {
+ View convertView = mInflater.inflate(R.layout.single_list_item, null);
+ holder.textView = convertView.findViewById(android.R.id.text1);
+ return convertView;
+ }
+
+ public DrawerSettingsItem getDrawerItem(int elementType) {
+ for (DrawerSettingsItem item : mData) {
+ if (item.itemType == elementType) {
+ return item;
+ }
+ }
+ return null;
+ }
+
+ static class ViewHolder {
+ TextView textView;
+ SwitchCompat switchView;
+
+ boolean isSwitchViewHolder() {
+ return switchView != null;
+ }
+
+ void resetSwitchView() {
+ switchView.setOnCheckedChangeListener(null);
+ switchView = null;
+ }
+
+ void resetTextView() {
+ textView = null;
+ }
+ }
+}
+
+
+
diff --git a/app/src/main/java/se/leap/bitmaskclient/EipFragment.java b/app/src/main/java/se/leap/bitmaskclient/EipFragment.java
index fb57aea8..34120859 100644
--- a/app/src/main/java/se/leap/bitmaskclient/EipFragment.java
+++ b/app/src/main/java/se/leap/bitmaskclient/EipFragment.java
@@ -1,5 +1,5 @@
/**
- * Copyright (c) 2013 LEAP Encryption Access Project and contributers
+ * Copyright (c) 2018 LEAP Encryption Access Project and contributers
*
* 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
@@ -29,8 +29,10 @@ import android.os.Bundle;
import android.os.IBinder;
import android.support.annotation.NonNull;
import android.support.v4.app.Fragment;
+import android.support.v4.content.LocalBroadcastManager;
import android.support.v7.app.AlertDialog;
import android.support.v7.widget.AppCompatImageView;
+import android.support.v7.widget.AppCompatTextView;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
@@ -44,30 +46,35 @@ import java.util.Observer;
import butterknife.ButterKnife;
import butterknife.InjectView;
import butterknife.OnClick;
+import de.blinkt.openvpn.VpnProfile;
import de.blinkt.openvpn.core.IOpenVPNServiceInternal;
import de.blinkt.openvpn.core.OpenVPNService;
+import de.blinkt.openvpn.core.ProfileManager;
import se.leap.bitmaskclient.eip.EipCommand;
import se.leap.bitmaskclient.eip.EipStatus;
-import se.leap.bitmaskclient.eip.VoidVpnService;
import se.leap.bitmaskclient.views.VpnStateImage;
import static android.view.View.GONE;
import static android.view.View.VISIBLE;
import static de.blinkt.openvpn.core.ConnectionStatus.LEVEL_NONETWORK;
+import static se.leap.bitmaskclient.Constants.BROADCAST_EIP_EVENT;
+import static se.leap.bitmaskclient.Constants.BROADCAST_RESULT_CODE;
+import static se.leap.bitmaskclient.Constants.BROADCAST_RESULT_KEY;
+import static se.leap.bitmaskclient.Constants.EIP_ACTION_STOP;
+import static se.leap.bitmaskclient.Constants.EIP_REQUEST;
import static se.leap.bitmaskclient.Constants.EIP_RESTART_ON_BOOT;
import static se.leap.bitmaskclient.Constants.PROVIDER_KEY;
import static se.leap.bitmaskclient.Constants.REQUEST_CODE_LOG_IN;
import static se.leap.bitmaskclient.Constants.REQUEST_CODE_SWITCH_PROVIDER;
import static se.leap.bitmaskclient.Constants.SHARED_PREFERENCES;
import static se.leap.bitmaskclient.ProviderAPI.DOWNLOAD_VPN_CERTIFICATE;
-import static se.leap.bitmaskclient.ProviderCredentialsBaseActivity.USER_MESSAGE;
+import static se.leap.bitmaskclient.ProviderAPI.USER_MESSAGE;
import static se.leap.bitmaskclient.R.string.vpn_certificate_user_message;
public class EipFragment extends Fragment implements Observer {
public final static String TAG = EipFragment.class.getSimpleName();
- public static final String START_EIP_ON_BOOT = "start on boot";
public static final String ASK_TO_CANCEL_VPN = "ask_to_cancel_vpn";
@@ -84,10 +91,10 @@ public class EipFragment extends Fragment implements Observer {
Button mainButton;
@InjectView(R.id.routed_text)
- TextView routedText;
+ AppCompatTextView routedText;
@InjectView(R.id.vpn_route)
- TextView vpnRoute;
+ AppCompatTextView vpnRoute;
private EipStatus eipStatus;
@@ -100,23 +107,7 @@ public class EipFragment extends Fragment implements Observer {
AlertDialog alertDialog;
private IOpenVPNServiceInternal mService;
- private ServiceConnection openVpnConnection = new ServiceConnection() {
-
-
-
- @Override
- public void onServiceConnected(ComponentName className,
- IBinder service) {
-
- mService = IOpenVPNServiceInternal.Stub.asInterface(service);
- }
-
- @Override
- public void onServiceDisconnected(ComponentName arg0) {
- mService = null;
- }
-
- };
+ private ServiceConnection openVpnConnection;
@Override
public void onAttach(Context context) {
@@ -141,6 +132,7 @@ public class EipFragment extends Fragment implements Observer {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+ openVpnConnection = new EipFragmentServiceConnection();
eipStatus = EipStatus.getInstance();
Activity activity = getActivity();
if (activity != null) {
@@ -171,8 +163,8 @@ public class EipFragment extends Fragment implements Observer {
super.onResume();
//FIXME: avoid race conditions while checking certificate an logging in at about the same time
//eipCommand(Constants.EIP_ACTION_CHECK_CERT_VALIDITY);
- handleNewState();
bindOpenVpnService();
+ handleNewState();
}
@Override
@@ -230,7 +222,7 @@ public class EipFragment extends Fragment implements Observer {
}
void handleIcon() {
- if (eipStatus.isConnected() || eipStatus.isConnecting())
+ if (isOpenVpnRunningWithoutNetwork() || eipStatus.isConnected() || eipStatus.isConnecting())
handleSwitchOff();
else
handleSwitchOn();
@@ -266,7 +258,7 @@ public class EipFragment extends Fragment implements Observer {
}
private void handleSwitchOff() {
- if (eipStatus.isConnecting()) {
+ if (isOpenVpnRunningWithoutNetwork() || eipStatus.isConnecting()) {
askPendingStartCancellation();
} else if (eipStatus.isConnected()) {
askToStopEIP();
@@ -290,7 +282,20 @@ public class EipFragment extends Fragment implements Observer {
protected void stopEipIfPossible() {
Context context = getContext();
if (context != null) {
- EipCommand.stopVPN(getContext());
+ if (isOpenVpnRunningWithoutNetwork()) {
+ // TODO move to EIP
+ // TODO see stopEIP function
+ Bundle resultData = new Bundle();
+ resultData.putString(EIP_REQUEST, EIP_ACTION_STOP);
+ Intent intentUpdate = new Intent(BROADCAST_EIP_EVENT);
+ intentUpdate.addCategory(Intent.CATEGORY_DEFAULT);
+ intentUpdate.putExtra(BROADCAST_RESULT_CODE, Activity.RESULT_OK);
+ intentUpdate.putExtra(BROADCAST_RESULT_KEY, resultData);
+ Log.d(TAG, "sending broadcast");
+ LocalBroadcastManager.getInstance(getActivity()).sendBroadcast(intentUpdate);
+ } else {
+ EipCommand.stopVPN(getContext());
+ }
} else {
Log.e(TAG, "context is null when trying to stop EIP");
}
@@ -386,14 +391,24 @@ public class EipFragment extends Fragment implements Observer {
routedText.setVisibility(GONE);
vpnRoute.setVisibility(GONE);
colorBackgroundALittle();
- } else if (eipStatus.isConnected() || isOpenVpnRunningWithoutNetwork()) {
+ } else if (eipStatus.isConnected() ) {
mainButton.setText(activity.getString(R.string.vpn_button_turn_off));
vpnStateImage.setStateIcon(R.drawable.vpn_connected);
vpnStateImage.stopProgress(true);
+ routedText.setText(R.string.vpn_securely_routed);
routedText.setVisibility(VISIBLE);
vpnRoute.setVisibility(VISIBLE);
- vpnRoute.setText(ConfigHelper.getProviderName(preferences));
+ setVpnRouteText();
colorBackground();
+ } else if(isOpenVpnRunningWithoutNetwork()){
+ mainButton.setText(activity.getString(R.string.vpn_button_turn_off));
+ vpnStateImage.setStateIcon(R.drawable.vpn_disconnected);
+ vpnStateImage.stopProgress(true);
+ routedText.setText(R.string.vpn_securely_routed_no_internet);
+ routedText.setVisibility(VISIBLE);
+ vpnRoute.setVisibility(VISIBLE);
+ setVpnRouteText();
+ colorBackgroundALittle();
} else {
mainButton.setText(activity.getString(R.string.vpn_button_turn_on));
vpnStateImage.setStateIcon(R.drawable.vpn_disconnected);
@@ -465,4 +480,27 @@ public class EipFragment extends Fragment implements Observer {
activity.startActivityForResult(intent, REQUEST_CODE_LOG_IN);
}
}
+
+ private void setVpnRouteText() {
+ String vpnRouteString = provider.getName();
+ VpnProfile vpnProfile = ProfileManager.getLastConnectedVpn();
+ if (vpnProfile != null && vpnProfile.mName != null) {
+ vpnRouteString += " (" + vpnProfile.mName + ")";
+ }
+ vpnRoute.setText(vpnRouteString);
+ }
+
+ private class EipFragmentServiceConnection implements ServiceConnection {
+ @Override
+ public void onServiceConnected(ComponentName className,
+ IBinder service) {
+ mService = IOpenVPNServiceInternal.Stub.asInterface(service);
+ handleNewState();
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName arg0) {
+ mService = null;
+ }
+ }
}
diff --git a/app/src/main/java/se/leap/bitmaskclient/MainActivity.java b/app/src/main/java/se/leap/bitmaskclient/MainActivity.java
index 6e778309..19294618 100644
--- a/app/src/main/java/se/leap/bitmaskclient/MainActivity.java
+++ b/app/src/main/java/se/leap/bitmaskclient/MainActivity.java
@@ -1,3 +1,19 @@
+/**
+ * Copyright (c) 2018 LEAP Encryption Access Project and contributers
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
package se.leap.bitmaskclient;
@@ -20,7 +36,6 @@ import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.util.Log;
-import org.jetbrains.annotations.NotNull;
import org.json.JSONException;
import org.json.JSONObject;
@@ -58,7 +73,7 @@ import static se.leap.bitmaskclient.ProviderAPI.CORRECTLY_DOWNLOADED_VPN_CERTIFI
import static se.leap.bitmaskclient.ProviderAPI.ERRORS;
import static se.leap.bitmaskclient.ProviderAPI.INCORRECTLY_DOWNLOADED_EIP_SERVICE;
import static se.leap.bitmaskclient.ProviderAPI.INCORRECTLY_DOWNLOADED_VPN_CERTIFICATE;
-import static se.leap.bitmaskclient.ProviderCredentialsBaseActivity.USER_MESSAGE;
+import static se.leap.bitmaskclient.ProviderAPI.USER_MESSAGE;
import static se.leap.bitmaskclient.R.string.downloading_vpn_certificate_failed;
import static se.leap.bitmaskclient.R.string.vpn_certificate_user_message;
@@ -67,12 +82,7 @@ public class MainActivity extends AppCompatActivity implements Observer {
public final static String TAG = MainActivity.class.getSimpleName();
- private static final String KEY_ACTIVITY_STATE = "key state of activity";
- private static final String DEFAULT_UI_STATE = "default state";
- private static final String SHOW_DIALOG_STATE = "show dialog";
- private static final String REASON_TO_FAIL = "reason to fail";
-
- private static Provider provider = new Provider();
+ private Provider provider = new Provider();
private SharedPreferences preferences;
private EipStatus eipStatus;
private NavigationDrawerFragment navigationDrawerFragment;
@@ -198,7 +208,7 @@ public class MainActivity extends AppCompatActivity implements Observer {
break;
}
}
-
+ //TODO: Why do we want this --v? legacy and redundant?
Fragment fragment = new EipFragment();
Bundle arguments = new Bundle();
arguments.putParcelable(PROVIDER_KEY, provider);
diff --git a/app/src/main/java/se/leap/bitmaskclient/Provider.java b/app/src/main/java/se/leap/bitmaskclient/Provider.java
index 7104143c..fd067bf9 100644
--- a/app/src/main/java/se/leap/bitmaskclient/Provider.java
+++ b/app/src/main/java/se/leap/bitmaskclient/Provider.java
@@ -90,9 +90,8 @@ public final class Provider implements Parcelable {
}
if (definition != null) {
try {
- this.definition = new JSONObject(definition);
- parseDefinition(this.definition);
- } catch (JSONException | NullPointerException e) {
+ define(new JSONObject(definition));
+ } catch (JSONException e) {
e.printStackTrace();
}
}
@@ -133,26 +132,8 @@ public final class Provider implements Parcelable {
}
public boolean define(JSONObject providerJson) {
- /*
- * fix against "api_uri": "https://calyx.net.malicious.url.net:4430",
- * This method aims to prevent attacks where the provider.json file got manipulated by a third party.
- * The main url should not change.
- */
-
- try {
- String providerApiUrl = providerJson.getString(Provider.API_URL);
- String providerDomain = providerJson.getString(Provider.DOMAIN);
- if (getMainUrlString().contains(providerDomain) && providerApiUrl.contains(providerDomain + ":")) {
- definition = providerJson;
- parseDefinition(definition);
- return true;
- } else {
- return false;
- }
- } catch (JSONException e) {
- e.printStackTrace();
- return false;
- }
+ definition = providerJson;
+ return parseDefinition(definition);
}
public JSONObject getDefinition() {
@@ -297,8 +278,6 @@ public final class Provider implements Parcelable {
try {
json.put(Provider.MAIN_URL, mainUrl);
//TODO: add other fields here?
- //this is used to save custom providers as json. I guess this doesn't work correctly
- //TODO 2: verify that
} catch (JSONException e) {
e.printStackTrace();
}
@@ -345,7 +324,7 @@ public final class Provider implements Parcelable {
}
}
- private void parseDefinition(JSONObject definition) {
+ private boolean parseDefinition(JSONObject definition) {
try {
String pin = definition.getString(CA_CERT_FINGERPRINT);
this.certificatePin = pin.split(":")[1].trim();
@@ -354,8 +333,9 @@ public final class Provider implements Parcelable {
this.allowAnonymous = definition.getJSONObject(Provider.SERVICE).getBoolean(PROVIDER_ALLOW_ANONYMOUS);
this.allowRegistered = definition.getJSONObject(Provider.SERVICE).getBoolean(PROVIDER_ALLOWED_REGISTERED);
this.apiVersion = getDefinition().getString(Provider.API_VERSION);
+ return true;
} catch (JSONException | ArrayIndexOutOfBoundsException | MalformedURLException e) {
- e.printStackTrace();
+ return false;
}
}
@@ -446,5 +426,4 @@ public final class Provider implements Parcelable {
allowRegistered = false;
allowAnonymous = false;
}
-
}
diff --git a/app/src/main/java/se/leap/bitmaskclient/ProviderAPI.java b/app/src/main/java/se/leap/bitmaskclient/ProviderAPI.java
index f5efde05..f1f474d7 100644
--- a/app/src/main/java/se/leap/bitmaskclient/ProviderAPI.java
+++ b/app/src/main/java/se/leap/bitmaskclient/ProviderAPI.java
@@ -51,6 +51,7 @@ public class ProviderAPI extends IntentService implements ProviderApiManagerBase
ERRORID = "errorId",
BACKEND_ERROR_KEY = "error",
BACKEND_ERROR_MESSAGE = "message",
+ USER_MESSAGE = "userMessage",
DOWNLOAD_SERVICE_JSON = "ProviderAPI.DOWNLOAD_SERVICE_JSON",
PROVIDER_SET_UP = "ProviderAPI.PROVIDER_SET_UP";
diff --git a/app/src/main/java/se/leap/bitmaskclient/ProviderApiManagerBase.java b/app/src/main/java/se/leap/bitmaskclient/ProviderApiManagerBase.java
index b93abaeb..2cde431e 100644
--- a/app/src/main/java/se/leap/bitmaskclient/ProviderApiManagerBase.java
+++ b/app/src/main/java/se/leap/bitmaskclient/ProviderApiManagerBase.java
@@ -46,6 +46,7 @@ import java.security.cert.X509Certificate;
import java.security.interfaces.RSAPrivateKey;
import java.util.ArrayList;
import java.util.List;
+import java.util.NoSuchElementException;
import javax.net.ssl.SSLHandshakeException;
@@ -63,19 +64,16 @@ import static se.leap.bitmaskclient.Constants.PROVIDER_PRIVATE_KEY;
import static se.leap.bitmaskclient.Constants.PROVIDER_VPN_CERTIFICATE;
import static se.leap.bitmaskclient.ProviderAPI.BACKEND_ERROR_KEY;
import static se.leap.bitmaskclient.ProviderAPI.BACKEND_ERROR_MESSAGE;
-import static se.leap.bitmaskclient.ProviderSetupFailedDialog.DOWNLOAD_ERRORS.ERROR_CERTIFICATE_PINNING;
-import static se.leap.bitmaskclient.ProviderSetupFailedDialog.DOWNLOAD_ERRORS.ERROR_CORRUPTED_PROVIDER_JSON;
-import static se.leap.bitmaskclient.ProviderSetupFailedDialog.DOWNLOAD_ERRORS.ERROR_INVALID_CERTIFICATE;
-import static se.leap.bitmaskclient.ProviderAPI.CORRECTLY_DOWNLOADED_VPN_CERTIFICATE;
import static se.leap.bitmaskclient.ProviderAPI.CORRECTLY_DOWNLOADED_EIP_SERVICE;
-import static se.leap.bitmaskclient.ProviderAPI.DOWNLOAD_VPN_CERTIFICATE;
+import static se.leap.bitmaskclient.ProviderAPI.CORRECTLY_DOWNLOADED_VPN_CERTIFICATE;
import static se.leap.bitmaskclient.ProviderAPI.DOWNLOAD_SERVICE_JSON;
+import static se.leap.bitmaskclient.ProviderAPI.DOWNLOAD_VPN_CERTIFICATE;
import static se.leap.bitmaskclient.ProviderAPI.ERRORID;
import static se.leap.bitmaskclient.ProviderAPI.ERRORS;
import static se.leap.bitmaskclient.ProviderAPI.FAILED_LOGIN;
import static se.leap.bitmaskclient.ProviderAPI.FAILED_SIGNUP;
-import static se.leap.bitmaskclient.ProviderAPI.INCORRECTLY_DOWNLOADED_VPN_CERTIFICATE;
import static se.leap.bitmaskclient.ProviderAPI.INCORRECTLY_DOWNLOADED_EIP_SERVICE;
+import static se.leap.bitmaskclient.ProviderAPI.INCORRECTLY_DOWNLOADED_VPN_CERTIFICATE;
import static se.leap.bitmaskclient.ProviderAPI.LOGOUT_FAILED;
import static se.leap.bitmaskclient.ProviderAPI.LOG_IN;
import static se.leap.bitmaskclient.ProviderAPI.LOG_OUT;
@@ -90,15 +88,18 @@ import static se.leap.bitmaskclient.ProviderAPI.SUCCESSFUL_LOGIN;
import static se.leap.bitmaskclient.ProviderAPI.SUCCESSFUL_LOGOUT;
import static se.leap.bitmaskclient.ProviderAPI.SUCCESSFUL_SIGNUP;
import static se.leap.bitmaskclient.ProviderAPI.UPDATE_PROVIDER_DETAILS;
+import static se.leap.bitmaskclient.ProviderAPI.USER_MESSAGE;
+import static se.leap.bitmaskclient.ProviderSetupFailedDialog.DOWNLOAD_ERRORS.ERROR_CERTIFICATE_PINNING;
+import static se.leap.bitmaskclient.ProviderSetupFailedDialog.DOWNLOAD_ERRORS.ERROR_CORRUPTED_PROVIDER_JSON;
+import static se.leap.bitmaskclient.ProviderSetupFailedDialog.DOWNLOAD_ERRORS.ERROR_INVALID_CERTIFICATE;
import static se.leap.bitmaskclient.R.string.certificate_error;
-import static se.leap.bitmaskclient.R.string.switch_provider_menu_option;
-import static se.leap.bitmaskclient.R.string.vpn_certificate_is_invalid;
import static se.leap.bitmaskclient.R.string.error_io_exception_user_message;
import static se.leap.bitmaskclient.R.string.error_json_exception_user_message;
import static se.leap.bitmaskclient.R.string.error_no_such_algorithm_exception_user_message;
import static se.leap.bitmaskclient.R.string.malformed_url;
import static se.leap.bitmaskclient.R.string.server_unreachable_message;
import static se.leap.bitmaskclient.R.string.service_is_down_error;
+import static se.leap.bitmaskclient.R.string.vpn_certificate_is_invalid;
import static se.leap.bitmaskclient.R.string.warning_corrupted_provider_cert;
import static se.leap.bitmaskclient.R.string.warning_corrupted_provider_details;
import static se.leap.bitmaskclient.R.string.warning_expired_provider_cert;
@@ -290,7 +291,7 @@ public abstract class ProviderApiManagerBase {
JSONObject stepResult = null;
OkHttpClient okHttpClient = clientGenerator.initSelfSignedCAHttpClient(provider.getCaCert(), stepResult);
if (okHttpClient == null) {
- return authFailedNotification(stepResult, username);
+ return backendErrorNotification(stepResult, username);
}
LeapSRPSession client = new LeapSRPSession(username, password);
@@ -302,7 +303,7 @@ public abstract class ProviderApiManagerBase {
Bundle result = new Bundle();
if (api_result.has(ERRORS) || api_result.has(BACKEND_ERROR_KEY))
- result = authFailedNotification(api_result, username);
+ result = backendErrorNotification(api_result, username);
else {
result.putString(CREDENTIALS_USERNAME, username);
result.putString(CREDENTIALS_PASSWORD, password);
@@ -349,7 +350,7 @@ public abstract class ProviderApiManagerBase {
OkHttpClient okHttpClient = clientGenerator.initSelfSignedCAHttpClient(provider.getCaCert(), stepResult);
if (okHttpClient == null) {
- return authFailedNotification(stepResult, username);
+ return backendErrorNotification(stepResult, username);
}
LeapSRPSession client = new LeapSRPSession(username, password);
@@ -367,15 +368,15 @@ public abstract class ProviderApiManagerBase {
if (client.verify(M2)) {
result.putBoolean(BROADCAST_RESULT_KEY, true);
} else {
- authFailedNotification(step_result, username);
+ backendErrorNotification(step_result, username);
}
} else {
result.putBoolean(BROADCAST_RESULT_KEY, false);
result.putString(CREDENTIALS_USERNAME, username);
- result.putString(resources.getString(R.string.user_message), resources.getString(R.string.error_srp_math_error_user_message));
+ result.putString(USER_MESSAGE, resources.getString(R.string.error_srp_math_error_user_message));
}
} catch (JSONException e) {
- result = authFailedNotification(step_result, username);
+ result = backendErrorNotification(step_result, username);
e.printStackTrace();
}
@@ -391,7 +392,7 @@ public abstract class ProviderApiManagerBase {
return true;
}
- private Bundle authFailedNotification(JSONObject result, String username) {
+ private Bundle backendErrorNotification(JSONObject result, String username) {
Bundle userNotificationBundle = new Bundle();
if (result.has(ERRORS)) {
Object baseErrorMessage = result.opt(ERRORS);
@@ -400,14 +401,14 @@ public abstract class ProviderApiManagerBase {
JSONObject errorMessage = result.getJSONObject(ERRORS);
String errorType = errorMessage.keys().next().toString();
String message = errorMessage.get(errorType).toString();
- userNotificationBundle.putString(resources.getString(R.string.user_message), message);
- } catch (JSONException e) {
+ userNotificationBundle.putString(USER_MESSAGE, message);
+ } catch (JSONException | NoSuchElementException | NullPointerException e) {
e.printStackTrace();
}
} else if (baseErrorMessage instanceof String) {
try {
String errorMessage = result.getString(ERRORS);
- userNotificationBundle.putString(resources.getString(R.string.user_message), errorMessage);
+ userNotificationBundle.putString(USER_MESSAGE, errorMessage);
} catch (JSONException e) {
e.printStackTrace();
}
@@ -418,7 +419,7 @@ public abstract class ProviderApiManagerBase {
if (result.has(BACKEND_ERROR_MESSAGE)) {
backendErrorMessage = resources.getString(R.string.error) + result.getString(BACKEND_ERROR_MESSAGE);
}
- userNotificationBundle.putString(resources.getString(R.string.user_message), backendErrorMessage);
+ userNotificationBundle.putString(USER_MESSAGE, backendErrorMessage);
} catch (JSONException e) {
e.printStackTrace();
}
@@ -431,7 +432,7 @@ public abstract class ProviderApiManagerBase {
return userNotificationBundle;
}
- void sendToReceiverOrBroadcast(ResultReceiver receiver, int resultCode, Bundle resultData, Provider provider) {
+ private void sendToReceiverOrBroadcast(ResultReceiver receiver, int resultCode, Bundle resultData, Provider provider) {
if (resultData == null || resultData == Bundle.EMPTY) {
resultData = new Bundle();
}
diff --git a/app/src/main/java/se/leap/bitmaskclient/ProviderCredentialsBaseActivity.java b/app/src/main/java/se/leap/bitmaskclient/ProviderCredentialsBaseActivity.java
index d41be512..15cd9617 100644
--- a/app/src/main/java/se/leap/bitmaskclient/ProviderCredentialsBaseActivity.java
+++ b/app/src/main/java/se/leap/bitmaskclient/ProviderCredentialsBaseActivity.java
@@ -30,6 +30,7 @@ import org.json.JSONException;
import butterknife.InjectView;
import butterknife.OnClick;
import se.leap.bitmaskclient.Constants.CREDENTIAL_ERRORS;
+import se.leap.bitmaskclient.eip.EipCommand;
import se.leap.bitmaskclient.userstatus.User;
import static android.view.View.GONE;
@@ -41,9 +42,12 @@ import static se.leap.bitmaskclient.Constants.BROADCAST_RESULT_KEY;
import static se.leap.bitmaskclient.Constants.CREDENTIALS_PASSWORD;
import static se.leap.bitmaskclient.Constants.CREDENTIALS_USERNAME;
import static se.leap.bitmaskclient.Constants.PROVIDER_KEY;
+import static se.leap.bitmaskclient.ProviderAPI.BACKEND_ERROR_KEY;
import static se.leap.bitmaskclient.ProviderAPI.DOWNLOAD_VPN_CERTIFICATE;
+import static se.leap.bitmaskclient.ProviderAPI.ERRORS;
import static se.leap.bitmaskclient.ProviderAPI.LOG_IN;
import static se.leap.bitmaskclient.ProviderAPI.SIGN_UP;
+import static se.leap.bitmaskclient.ProviderAPI.USER_MESSAGE;
/**
* Base Activity for activities concerning a provider interaction
@@ -59,7 +63,6 @@ public abstract class ProviderCredentialsBaseActivity extends ConfigWizardBaseAc
final private static String SHOWING_FORM = "SHOWING_FORM";
final private static String PERFORMING_ACTION = "PERFORMING_ACTION";
- final public static String USER_MESSAGE = "USER_MESSAGE";
final private static String USERNAME_ERROR = "USERNAME_ERROR";
final private static String PASSWORD_ERROR = "PASSWORD_ERROR";
final private static String PASSWORD_VERIFICATION_ERROR = "PASSWORD_VERIFICATION_ERROR";
@@ -183,7 +186,11 @@ public abstract class ProviderCredentialsBaseActivity extends ConfigWizardBaseAc
String username = usernameField.getText().toString();
String providerDomain = provider.getDomain();
if (username.endsWith(providerDomain)) {
- return username.split("@" + providerDomain)[0];
+ try {
+ return username.split("@" + providerDomain)[0];
+ } catch (ArrayIndexOutOfBoundsException e) {
+ return "";
+ }
}
return username;
}
@@ -237,9 +244,15 @@ public abstract class ProviderCredentialsBaseActivity extends ConfigWizardBaseAc
@Override
public void afterTextChanged(Editable s) {
if (getUsername().equalsIgnoreCase("")) {
+ s.clear();
usernameError.setError(getString(R.string.username_ask));
} else {
usernameError.setError(null);
+ String suffix = "@" + provider.getDomain();
+ if (!usernameField.getText().toString().endsWith(suffix)) {
+ s.append(suffix);
+ usernameField.setSelection(usernameField.getText().toString().indexOf('@'));
+ }
}
}
});
@@ -344,8 +357,8 @@ public abstract class ProviderCredentialsBaseActivity extends ConfigWizardBaseAc
if (arguments.containsKey(CREDENTIAL_ERRORS.USERNAME_MISSING.toString())) {
usernameError.setError(getString(R.string.username_ask));
}
- if (arguments.containsKey(getString(R.string.user_message))) {
- String userMessageString = arguments.getString(getString(R.string.user_message));
+ if (arguments.containsKey(USER_MESSAGE)) {
+ String userMessageString = arguments.getString(USER_MESSAGE);
try {
userMessageString = new JSONArray(userMessageString).getString(0);
} catch (JSONException e) {
@@ -395,6 +408,10 @@ public abstract class ProviderCredentialsBaseActivity extends ConfigWizardBaseAc
switch (resultCode) {
case ProviderAPI.SUCCESSFUL_SIGNUP:
+ String password = resultData.getString(CREDENTIALS_PASSWORD);
+ String username = resultData.getString(CREDENTIALS_USERNAME);
+ login(username, password);
+ break;
case ProviderAPI.SUCCESSFUL_LOGIN:
downloadVpnCertificate(handledProvider);
break;
@@ -403,12 +420,11 @@ public abstract class ProviderCredentialsBaseActivity extends ConfigWizardBaseAc
handleReceivedErrors((Bundle) intent.getParcelableExtra(BROADCAST_RESULT_KEY));
break;
+ case ProviderAPI.INCORRECTLY_DOWNLOADED_VPN_CERTIFICATE:
+ // error handling takes place in MainActivity
case ProviderAPI.CORRECTLY_DOWNLOADED_VPN_CERTIFICATE:
successfullyFinished(handledProvider);
break;
- case ProviderAPI.INCORRECTLY_DOWNLOADED_VPN_CERTIFICATE:
- // TODO activity.setResult(RESULT_CANCELED);
- break;
}
}
}
diff --git a/app/src/main/java/se/leap/bitmaskclient/ProviderListBaseActivity.java b/app/src/main/java/se/leap/bitmaskclient/ProviderListBaseActivity.java
index e961b0a2..75fffaf7 100644
--- a/app/src/main/java/se/leap/bitmaskclient/ProviderListBaseActivity.java
+++ b/app/src/main/java/se/leap/bitmaskclient/ProviderListBaseActivity.java
@@ -207,7 +207,8 @@ public abstract class ProviderListBaseActivity extends ConfigWizardBaseActivity
void handleProviderSetUp(Provider handledProvider) {
this.provider = handledProvider;
-
+ adapter.add(provider);
+ adapter.saveProviders();
if (provider.allowsAnonymous()) {
mConfigState.putExtra(SERVICES_RETRIEVED, true);
downloadVpnCertificate();
@@ -368,12 +369,6 @@ public abstract class ProviderListBaseActivity extends ConfigWizardBaseActivity
}
}
- @Override
- public boolean onCreateOptionsMenu(Menu menu) {
- getMenuInflater().inflate(R.menu.configuration_wizard_activity, menu);
- return true;
- }
-
public class ProviderAPIBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
diff --git a/app/src/main/java/se/leap/bitmaskclient/ProviderManager.java b/app/src/main/java/se/leap/bitmaskclient/ProviderManager.java
index ed41be67..97ba3b98 100644
--- a/app/src/main/java/se/leap/bitmaskclient/ProviderManager.java
+++ b/app/src/main/java/se/leap/bitmaskclient/ProviderManager.java
@@ -31,6 +31,8 @@ public class ProviderManager implements AdapteeCollection<Provider> {
private File externalFilesDir;
private Set<Provider> defaultProviders;
private Set<Provider> customProviders;
+ private Set<URL> defaultProviderURLs;
+ private Set<URL> customProviderURLs;
private static ProviderManager instance;
@@ -52,11 +54,20 @@ public class ProviderManager implements AdapteeCollection<Provider> {
private void addDefaultProviders(AssetManager assets_manager) {
try {
defaultProviders = providersFromAssets(URLS, assets_manager.list(URLS));
+ defaultProviderURLs = getProviderUrlSetFromProviderSet(defaultProviders);
} catch (IOException e) {
e.printStackTrace();
}
}
+ private Set<URL> getProviderUrlSetFromProviderSet(Set<Provider> providers) {
+ HashSet<URL> providerUrls = new HashSet<>();
+ for (Provider provider : providers) {
+ providerUrls.add(provider.getMainUrl().getUrl());
+ }
+ return providerUrls;
+ }
+
private Set<Provider> providersFromAssets(String directory, String[] relativeFilePaths) {
Set<Provider> providers = new HashSet<>();
@@ -89,13 +100,14 @@ public class ProviderManager implements AdapteeCollection<Provider> {
customProviders = externalFilesDir != null && externalFilesDir.isDirectory() ?
providersFromFiles(externalFilesDir.list()) :
new HashSet<Provider>();
+ customProviderURLs = getProviderUrlSetFromProviderSet(customProviders);
}
private Set<Provider> providersFromFiles(String[] files) {
Set<Provider> providers = new HashSet<>();
try {
for (String file : files) {
- String mainUrl = extractMainUrlFromInputStream(new FileInputStream(externalFilesDir.getAbsolutePath() + "/" + file));
+ String mainUrl = extractMainUrlFromInputStream(ConfigHelper.getInputStreamFrom(externalFilesDir.getAbsolutePath() + "/" + file));
providers.add(new Provider(new URL(mainUrl)));
}
} catch (MalformedURLException | FileNotFoundException e) {
@@ -132,6 +144,8 @@ public class ProviderManager implements AdapteeCollection<Provider> {
allProviders.addAll(defaultProviders);
if(customProviders != null)
allProviders.addAll(customProviders);
+ //add an option to add a custom provider
+ //TODO: refactor me?
allProviders.add(new Provider());
return allProviders;
}
@@ -153,32 +167,59 @@ public class ProviderManager implements AdapteeCollection<Provider> {
@Override
public boolean add(Provider element) {
- return !defaultProviders.contains(element) || customProviders.add(element);
+ return element != null &&
+ !defaultProviderURLs.contains(element.getMainUrl().getUrl()) &&
+ customProviders.add(element) &&
+ customProviderURLs.add(element.getMainUrl().getUrl());
}
@Override
public boolean remove(Object element) {
- return customProviders.remove(element);
+ return element instanceof Provider &&
+ customProviders.remove(element) &&
+ customProviderURLs.remove(((Provider) element).getMainUrl().getUrl());
}
@Override
public boolean addAll(Collection<? extends Provider> elements) {
- return customProviders.addAll(elements);
+ Iterator iterator = elements.iterator();
+ boolean addedAll = true;
+ while (iterator.hasNext()) {
+ Provider p = (Provider) iterator.next();
+ addedAll = customProviders.add(p) &&
+ customProviderURLs.add(p.getMainUrl().getUrl()) &&
+ addedAll;
+ }
+ return addedAll;
}
@Override
public boolean removeAll(Collection<?> elements) {
- if(!elements.getClass().equals(Provider.class))
+ Iterator iterator = elements.iterator();
+ boolean removedAll = true;
+ try {
+ while (iterator.hasNext()) {
+ Provider p = (Provider) iterator.next();
+ removedAll = ((defaultProviders.remove(p) && defaultProviderURLs.remove(p.getMainUrl().getUrl())) ||
+ (customProviders.remove(p) && customProviderURLs.remove(p.getMainUrl().getUrl()))) &&
+ removedAll;
+ }
+ } catch (ClassCastException e) {
return false;
- return defaultProviders.removeAll(elements) || customProviders.removeAll(elements);
+ }
+
+ return removedAll;
}
@Override
public void clear() {
defaultProviders.clear();
customProviders.clear();
+ customProviderURLs.clear();
+ defaultProviderURLs.clear();
}
+ //FIXME: removed custom providers should be deleted here as well
void saveCustomProvidersToFile() {
try {
for (Provider provider : customProviders) {
diff --git a/app/src/main/java/se/leap/bitmaskclient/StartActivity.java b/app/src/main/java/se/leap/bitmaskclient/StartActivity.java
index 39717bd8..6bbdeb4f 100644
--- a/app/src/main/java/se/leap/bitmaskclient/StartActivity.java
+++ b/app/src/main/java/se/leap/bitmaskclient/StartActivity.java
@@ -13,12 +13,10 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import de.blinkt.openvpn.core.VpnStatus;
-import se.leap.bitmaskclient.eip.EIP;
import se.leap.bitmaskclient.eip.EipCommand;
import se.leap.bitmaskclient.userstatus.User;
import static se.leap.bitmaskclient.Constants.APP_ACTION_CONFIGURE_ALWAYS_ON_PROFILE;
-import static se.leap.bitmaskclient.Constants.EIP_ACTION_START;
import static se.leap.bitmaskclient.Constants.EIP_RESTART_ON_BOOT;
import static se.leap.bitmaskclient.Constants.PREFERENCES_APP_VERSION;
import static se.leap.bitmaskclient.Constants.PROVIDER_EIP_DEFINITION;
@@ -32,7 +30,7 @@ import static se.leap.bitmaskclient.MainActivity.ACTION_SHOW_VPN_FRAGMENT;
* and acts and calls another activity accordingly.
*
*/
-public class StartActivity extends Activity {
+public class StartActivity extends Activity{
public static final String TAG = StartActivity.class.getSimpleName();
@Retention(RetentionPolicy.SOURCE)
@@ -184,12 +182,9 @@ public class StartActivity extends Activity {
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
- if (data == null) {
- return;
- }
if (requestCode == REQUEST_CODE_CONFIGURE_LEAP) {
- if (resultCode == RESULT_OK && data.hasExtra(Provider.KEY)) {
+ if (resultCode == RESULT_OK && data != null && data.hasExtra(Provider.KEY)) {
Provider provider = data.getParcelableExtra(Provider.KEY);
ConfigHelper.storeProviderInPreferences(preferences, provider);
EipCommand.startVPN(this, false);
diff --git a/app/src/main/java/se/leap/bitmaskclient/drawer/NavigationDrawerFragment.java b/app/src/main/java/se/leap/bitmaskclient/drawer/NavigationDrawerFragment.java
index 772140b0..6e39e9ed 100644
--- a/app/src/main/java/se/leap/bitmaskclient/drawer/NavigationDrawerFragment.java
+++ b/app/src/main/java/se/leap/bitmaskclient/drawer/NavigationDrawerFragment.java
@@ -1,6 +1,24 @@
+/**
+ * Copyright (c) 2018 LEAP Encryption Access Project and contributers
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
package se.leap.bitmaskclient.drawer;
+import android.app.Activity;
+import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.Configuration;
@@ -11,6 +29,7 @@ import android.support.v4.view.GravityCompat;
import android.support.v4.widget.DrawerLayout;
import android.support.v7.app.ActionBar;
import android.support.v7.app.ActionBarDrawerToggle;
+import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.util.Log;
@@ -22,22 +41,35 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
+import android.widget.CompoundButton;
import android.widget.ListView;
import android.widget.Toast;
import se.leap.bitmaskclient.ConfigHelper;
+import se.leap.bitmaskclient.DrawerSettingsAdapter;
+import se.leap.bitmaskclient.DrawerSettingsAdapter.DrawerSettingsItem;
+import se.leap.bitmaskclient.EipFragment;
import se.leap.bitmaskclient.Provider;
import se.leap.bitmaskclient.ProviderListActivity;
-import se.leap.bitmaskclient.EipFragment;
import se.leap.bitmaskclient.R;
import se.leap.bitmaskclient.fragments.AboutFragment;
import se.leap.bitmaskclient.fragments.LogFragment;
import static android.content.Context.MODE_PRIVATE;
import static se.leap.bitmaskclient.BitmaskApp.getRefWatcher;
+import static se.leap.bitmaskclient.ConfigHelper.getSaveBattery;
import static se.leap.bitmaskclient.Constants.PROVIDER_KEY;
import static se.leap.bitmaskclient.Constants.REQUEST_CODE_SWITCH_PROVIDER;
import static se.leap.bitmaskclient.Constants.SHARED_PREFERENCES;
+import static se.leap.bitmaskclient.DrawerSettingsAdapter.ABOUT;
+import static se.leap.bitmaskclient.DrawerSettingsAdapter.BATTERY_SAVER;
+import static se.leap.bitmaskclient.DrawerSettingsAdapter.DrawerSettingsItem.getSimpleTextInstance;
+import static se.leap.bitmaskclient.DrawerSettingsAdapter.DrawerSettingsItem.getSwitchInstance;
+import static se.leap.bitmaskclient.DrawerSettingsAdapter.LOG;
+import static se.leap.bitmaskclient.DrawerSettingsAdapter.SWITCH_PROVIDER;
+import static se.leap.bitmaskclient.R.string.about_fragment_title;
+import static se.leap.bitmaskclient.R.string.log_fragment_title;
+import static se.leap.bitmaskclient.R.string.switch_provider_menu_option;
/**
* Fragment used for managing interactions for and presentation of a navigation drawer.
@@ -63,6 +95,7 @@ public class NavigationDrawerFragment extends Fragment {
private ListView mDrawerAccountsListView;
private View mFragmentContainerView;
private ArrayAdapter<String> accountListAdapter;
+ private DrawerSettingsAdapter settingsListAdapter;
private boolean mFromSavedInstanceState;
private boolean mUserLearnedDrawer;
@@ -71,17 +104,23 @@ public class NavigationDrawerFragment extends Fragment {
private SharedPreferences preferences;
+ private final static String KEY_SHOW_ENABLE_EXPERIMENTAL_FEATURE = "KEY_SHOW_ENABLE_EXPERIMENTAL_FEATURE";
+ private boolean showEnableExperimentalFeature = false;
+ AlertDialog alertDialog;
+
public NavigationDrawerFragment() {
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
-
// Read in the flag indicating whether or not the user has demonstrated awareness of the
// drawer. See PREF_USER_LEARNED_DRAWER for details.
preferences = getContext().getSharedPreferences(SHARED_PREFERENCES, MODE_PRIVATE);
mUserLearnedDrawer = preferences.getBoolean(PREF_USER_LEARNED_DRAWER, false);
+ if (savedInstanceState != null) {
+ mFromSavedInstanceState = true;
+ }
}
@Override
@@ -95,6 +134,7 @@ public class NavigationDrawerFragment extends Fragment {
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
mDrawerView = inflater.inflate(R.layout.drawer_main, container, false);
+ restoreFromSavedInstance(savedInstanceState);
return mDrawerView;
}
@@ -119,16 +159,23 @@ public class NavigationDrawerFragment extends Fragment {
selectItem(parent, position);
}
});
+ settingsListAdapter = new DrawerSettingsAdapter(getLayoutInflater());
+ if (getContext() != null) {
+ settingsListAdapter.addItem(getSwitchInstance(getString(R.string.save_battery),
+ getSaveBattery(getContext()),
+ BATTERY_SAVER,
+ new CompoundButton.OnCheckedChangeListener() {
+ @Override
+ public void onCheckedChanged(CompoundButton buttonView, boolean newStateIsChecked) {
+ onSwitchItemSelected(BATTERY_SAVER, newStateIsChecked);
+ }
+ }));
+ }
+ settingsListAdapter.addItem(getSimpleTextInstance(getString(switch_provider_menu_option), SWITCH_PROVIDER));
+ settingsListAdapter.addItem(getSimpleTextInstance(getString(log_fragment_title), LOG));
+ settingsListAdapter.addItem(getSimpleTextInstance(getString(about_fragment_title), ABOUT));
- mDrawerSettingsListView.setAdapter(new ArrayAdapter<String>(
- actionBar.getThemedContext(),
- R.layout.single_list_item,
- android.R.id.text1,
- new String[]{
- getString(R.string.switch_provider_menu_option),
- getString(R.string.log_fragment_title),
- getString(R.string.about_fragment_title),
- }));
+ mDrawerSettingsListView.setAdapter(settingsListAdapter);
mDrawerAccountsListView = mDrawerView.findViewById(R.id.accountList);
mDrawerAccountsListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@@ -215,12 +262,63 @@ public class NavigationDrawerFragment extends Fragment {
if (mDrawerLayout != null) {
mDrawerLayout.closeDrawer(mFragmentContainerView);
}
- onNavigationDrawerItemSelected(list, position);
+ onTextItemSelected(list, position);
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
+ if (showEnableExperimentalFeature) {
+ outState.putBoolean(KEY_SHOW_ENABLE_EXPERIMENTAL_FEATURE, true);
+ }
+ }
+
+ private void restoreFromSavedInstance(Bundle savedInstanceState) {
+ if (savedInstanceState != null && savedInstanceState.containsKey(KEY_SHOW_ENABLE_EXPERIMENTAL_FEATURE)) {
+ showExperimentalFeatureAlert();
+ }
+ }
+
+ private void showExperimentalFeatureAlert() {
+ Activity activity = getActivity();
+ if (activity == null) {
+ return;
+ }
+
+ try {
+ AlertDialog.Builder alertBuilder = new AlertDialog.Builder(getActivity());
+ showEnableExperimentalFeature = true;
+ alertDialog = alertBuilder.setTitle(activity.getString(R.string.save_battery))
+ .setMessage(activity.getString(R.string.save_battery_message))
+ .setPositiveButton((android.R.string.yes), new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ DrawerSettingsItem item = settingsListAdapter.getDrawerItem(BATTERY_SAVER);
+ item.setChecked(true);
+ settingsListAdapter.notifyDataSetChanged();
+ ConfigHelper.saveBattery(getContext(), item.isChecked());
+ }
+ })
+ .setNegativeButton(activity.getString(android.R.string.no), new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ disableSwitch(BATTERY_SAVER);
+ }
+ }).setOnDismissListener(new DialogInterface.OnDismissListener() {
+ @Override
+ public void onDismiss(DialogInterface dialog) {
+ showEnableExperimentalFeature = false;
+ }
+ }).setOnCancelListener(new DialogInterface.OnCancelListener() {
+ @Override
+ public void onCancel(DialogInterface dialog) {
+ disableSwitch(BATTERY_SAVER);
+ }
+ }).show();
+ } catch (IllegalStateException e) {
+ e.printStackTrace();
+ }
+
}
@Override
@@ -272,7 +370,32 @@ public class NavigationDrawerFragment extends Fragment {
return ((AppCompatActivity) getActivity()).getSupportActionBar();
}
- public void onNavigationDrawerItemSelected(AdapterView<?> parent, int position) {
+ private void onSwitchItemSelected(int elementType, boolean newStateIsChecked) {
+ switch (elementType) {
+ case BATTERY_SAVER:
+ if (ConfigHelper.getSaveBattery(getContext()) == newStateIsChecked) {
+ //initial ui setup, ignore
+ return;
+ }
+ if (newStateIsChecked) {
+ showExperimentalFeatureAlert();
+ } else {
+ ConfigHelper.saveBattery(this.getContext(), false);
+ disableSwitch(BATTERY_SAVER);
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ private void disableSwitch(int elementType) {
+ DrawerSettingsItem item = settingsListAdapter.getDrawerItem(elementType);
+ item.setChecked(false);
+ settingsListAdapter.notifyDataSetChanged();
+ }
+
+ public void onTextItemSelected(AdapterView<?> parent, int position) {
// update the main content by replacing fragments
FragmentManager fragmentManager = getFragmentManager();
Fragment fragment = null;
@@ -286,16 +409,17 @@ public class NavigationDrawerFragment extends Fragment {
fragment.setArguments(arguments);
} else {
Log.d("Drawer", String.format("Selected position %d", position));
- switch (position) {
- case 0:
+ DrawerSettingsItem settingsItem = settingsListAdapter.getItem(position);
+ switch (settingsItem.getItemType()) {
+ case SWITCH_PROVIDER:
getActivity().startActivityForResult(new Intent(getActivity(), ProviderListActivity.class), REQUEST_CODE_SWITCH_PROVIDER);
break;
- case 1:
- mTitle = getString(R.string.log_fragment_title);
+ case LOG:
+ mTitle = getString(log_fragment_title);
fragment = new LogFragment();
break;
- case 2:
- mTitle = getString(R.string.about_fragment_title);
+ case ABOUT:
+ mTitle = getString(about_fragment_title);
fragment = new AboutFragment();
break;
default:
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 5cf180d3..665e0ebd 100644
--- a/app/src/main/java/se/leap/bitmaskclient/eip/EIP.java
+++ b/app/src/main/java/se/leap/bitmaskclient/eip/EIP.java
@@ -184,6 +184,7 @@ public final class EIP extends IntentService {
private void stopEIP() {
// TODO stop eip from here if possible...
+ // TODO then refactor EipFragment.handleSwitchOff
EipStatus eipStatus = EipStatus.getInstance();
int resultCode = RESULT_CANCELED;
if (eipStatus.isConnected() || eipStatus.isConnecting())
diff --git a/app/src/main/res/drawable-hdpi/icon.png b/app/src/main/res/drawable-hdpi/icon.png
deleted file mode 100644
index 02ede650..00000000
--- a/app/src/main/res/drawable-hdpi/icon.png
+++ /dev/null
Binary files differ
diff --git a/app/src/main/res/drawable-ldpi/icon.png b/app/src/main/res/drawable-ldpi/icon.png
deleted file mode 100644
index e312075d..00000000
--- a/app/src/main/res/drawable-ldpi/icon.png
+++ /dev/null
Binary files differ
diff --git a/app/src/main/res/drawable-mdpi/icon.png b/app/src/main/res/drawable-mdpi/icon.png
deleted file mode 100644
index 468314c6..00000000
--- a/app/src/main/res/drawable-mdpi/icon.png
+++ /dev/null
Binary files differ
diff --git a/app/src/main/res/drawable-xhdpi/icon.png b/app/src/main/res/drawable-xhdpi/icon.png
deleted file mode 100644
index ead03720..00000000
--- a/app/src/main/res/drawable-xhdpi/icon.png
+++ /dev/null
Binary files differ
diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml
new file mode 100644
index 00000000..01f0af0a
--- /dev/null
+++ b/app/src/main/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector
+ android:height="108dp"
+ android:width="108dp"
+ android:viewportHeight="108"
+ android:viewportWidth="108"
+ xmlns:android="http://schemas.android.com/apk/res/android">
+ <path android:fillColor="#26A69A"
+ android:pathData="M0,0h108v108h-108z"/>
+ <path android:fillColor="#00000000" android:pathData="M9,0L9,108"
+ android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+ <path android:fillColor="#00000000" android:pathData="M19,0L19,108"
+ android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+ <path android:fillColor="#00000000" android:pathData="M29,0L29,108"
+ android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+ <path android:fillColor="#00000000" android:pathData="M39,0L39,108"
+ android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+ <path android:fillColor="#00000000" android:pathData="M49,0L49,108"
+ android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+ <path android:fillColor="#00000000" android:pathData="M59,0L59,108"
+ android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+ <path android:fillColor="#00000000" android:pathData="M69,0L69,108"
+ android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+ <path android:fillColor="#00000000" android:pathData="M79,0L79,108"
+ android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+ <path android:fillColor="#00000000" android:pathData="M89,0L89,108"
+ android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+ <path android:fillColor="#00000000" android:pathData="M99,0L99,108"
+ android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+ <path android:fillColor="#00000000" android:pathData="M0,9L108,9"
+ android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+ <path android:fillColor="#00000000" android:pathData="M0,19L108,19"
+ android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+ <path android:fillColor="#00000000" android:pathData="M0,29L108,29"
+ android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+ <path android:fillColor="#00000000" android:pathData="M0,39L108,39"
+ android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+ <path android:fillColor="#00000000" android:pathData="M0,49L108,49"
+ android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+ <path android:fillColor="#00000000" android:pathData="M0,59L108,59"
+ android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+ <path android:fillColor="#00000000" android:pathData="M0,69L108,69"
+ android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+ <path android:fillColor="#00000000" android:pathData="M0,79L108,79"
+ android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+ <path android:fillColor="#00000000" android:pathData="M0,89L108,89"
+ android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+ <path android:fillColor="#00000000" android:pathData="M0,99L108,99"
+ android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+ <path android:fillColor="#00000000" android:pathData="M19,29L89,29"
+ android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+ <path android:fillColor="#00000000" android:pathData="M19,39L89,39"
+ android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+ <path android:fillColor="#00000000" android:pathData="M19,49L89,49"
+ android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+ <path android:fillColor="#00000000" android:pathData="M19,59L89,59"
+ android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+ <path android:fillColor="#00000000" android:pathData="M19,69L89,69"
+ android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+ <path android:fillColor="#00000000" android:pathData="M19,79L89,79"
+ android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+ <path android:fillColor="#00000000" android:pathData="M29,19L29,89"
+ android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+ <path android:fillColor="#00000000" android:pathData="M39,19L39,89"
+ android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+ <path android:fillColor="#00000000" android:pathData="M49,19L49,89"
+ android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+ <path android:fillColor="#00000000" android:pathData="M59,19L59,89"
+ android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+ <path android:fillColor="#00000000" android:pathData="M69,19L69,89"
+ android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+ <path android:fillColor="#00000000" android:pathData="M79,19L79,89"
+ android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+</vector>
diff --git a/app/src/main/res/layout-xlarge/about.xml b/app/src/main/res/layout-xlarge/about.xml
index ea2251cf..c7896917 100644
--- a/app/src/main/res/layout-xlarge/about.xml
+++ b/app/src/main/res/layout-xlarge/about.xml
@@ -152,6 +152,58 @@
android:layout_height="wrap_content"
android:textSize="18sp"
android:text="@string/copyright_okhttp" />
+
+ <Space
+ android:layout_width="match_parent"
+ android:layout_height="20sp" />
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/mbetTLS"
+ android:textSize="24sp"
+ android:textAppearance="?android:attr/textAppearanceMedium" />
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:textSize="18sp"
+ android:autoLink="web"
+ android:text="@string/copyright_mbetTLS" />
+
+ <Space
+ android:layout_width="match_parent"
+ android:layout_height="20sp" />
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/asio"
+ android:textSize="24sp"
+ android:textAppearance="?android:attr/textAppearanceMedium" />
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:textSize="18sp"
+ android:text="@string/copyright_asio" />
+
+ <Space
+ android:layout_width="match_parent"
+ android:layout_height="20sp" />
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/openvpn3"
+ android:textSize="24sp"
+ android:textAppearance="?android:attr/textAppearanceMedium" />
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:textSize="18sp"
+ android:text="@string/copyright_openvpn3" />
</LinearLayout>
</ScrollView>
diff --git a/app/src/main/res/layout-xlarge/eip_service_fragment.xml b/app/src/main/res/layout-xlarge/eip_service_fragment.xml
index b7af5797..2326ffe8 100644
--- a/app/src/main/res/layout-xlarge/eip_service_fragment.xml
+++ b/app/src/main/res/layout-xlarge/eip_service_fragment.xml
@@ -54,17 +54,17 @@
app:srcCompat="@drawable/ic_colorsquare" />
- <TextView
+ <android.support.v7.widget.AppCompatTextView
android:id="@+id/eipLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_marginEnd="@dimen/stdpadding"
- android:layout_marginStart="@dimen/stdpadding"
- android:layout_marginTop="@dimen/stdpadding"
- android:layout_marginLeft="@dimen/stdpadding"
- android:layout_marginRight="@dimen/stdpadding"
+ android:paddingLeft="@dimen/stdpadding"
+ android:paddingRight="@dimen/stdpadding"
+ android:paddingStart="@dimen/stdpadding"
+ android:paddingEnd="@dimen/stdpadding"
android:text="@string/eip_service_label"
android:textAppearance="?android:attr/textAppearanceMedium"
+ android:gravity="center"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
@@ -97,29 +97,30 @@
style="@style/BitmaskButtonBlack"
/>
- <TextView
+ <android.support.v7.widget.AppCompatTextView
android:id="@+id/routed_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_marginEnd="@dimen/stdpadding"
- android:layout_marginStart="@dimen/stdpadding"
- android:layout_marginTop="@dimen/stdpadding"
- android:layout_marginLeft="@dimen/stdpadding"
- android:layout_marginRight="@dimen/stdpadding"
+ android:paddingLeft="@dimen/stdpadding"
+ android:paddingRight="@dimen/stdpadding"
+ android:paddingStart="@dimen/stdpadding"
+ android:paddingEnd="@dimen/stdpadding"
android:text="@string/vpn_securely_routed"
+ android:gravity="center"
android:visibility="visible"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/eipLabel" />
- <TextView
+ <android.support.v7.widget.AppCompatTextView
android:id="@+id/vpn_route"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_marginEnd="@dimen/stdpadding"
- android:layout_marginStart="@dimen/stdpadding"
- android:layout_marginLeft="@dimen/stdpadding"
- android:layout_marginRight="@dimen/stdpadding"
+ android:paddingLeft="@dimen/stdpadding"
+ android:paddingRight="@dimen/stdpadding"
+ android:paddingStart="@dimen/stdpadding"
+ android:paddingEnd="@dimen/stdpadding"
+ android:gravity="center"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/routed_text" />
diff --git a/app/src/main/res/layout/about.xml b/app/src/main/res/layout/about.xml
index abc12566..aba51141 100644
--- a/app/src/main/res/layout/about.xml
+++ b/app/src/main/res/layout/about.xml
@@ -150,6 +150,52 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/copyright_okhttp" />
+
+ <Space
+ android:layout_width="match_parent"
+ android:layout_height="20sp" />
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/mbetTLS"
+ android:textAppearance="?android:attr/textAppearanceMedium" />
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:autoLink="web"
+ android:text="@string/copyright_mbetTLS" />
+
+ <Space
+ android:layout_width="match_parent"
+ android:layout_height="20sp" />
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/asio"
+ android:textAppearance="?android:attr/textAppearanceMedium" />
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/copyright_asio" />
+
+ <Space
+ android:layout_width="match_parent"
+ android:layout_height="20sp" />
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/openvpn3"
+ android:textAppearance="?android:attr/textAppearanceMedium" />
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/copyright_openvpn3" />
</LinearLayout>
</ScrollView>
diff --git a/app/src/main/res/layout/drawer_main.xml b/app/src/main/res/layout/drawer_main.xml
index 26267bbb..54614f3f 100644
--- a/app/src/main/res/layout/drawer_main.xml
+++ b/app/src/main/res/layout/drawer_main.xml
@@ -1,13 +1,13 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- xmlns:app="http://schemas.android.com/apk/res-auto"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical"
- android:background="@color/colorBackground"
- tools:context="se.leap.bitmaskclient.drawer.NavigationDrawerFragment"
- android:clickable="true"
- android:focusable="true">
+ xmlns:tools="http://schemas.android.com/tools"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ android:background="@color/colorBackground"
+ tools:context="se.leap.bitmaskclient.drawer.NavigationDrawerFragment"
+ android:clickable="true"
+ android:focusable="true">
<FrameLayout
android:layout_width="match_parent"
@@ -28,7 +28,6 @@
android:layout_height="wrap_content"
app:srcCompat="@drawable/mask" />
</FrameLayout>
-
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
@@ -37,14 +36,33 @@
<ListView
android:id="@+id/accountList"
android:layout_width="match_parent"
- android:layout_height="wrap_content" />
+ android:layout_height="wrap_content"
+ android:isScrollContainer="false"
+ />
- <ListView
- android:id="@+id/settingsList"
+ <View
+ android:id="@+id/divider"
+ android:layout_below="@id/accountList"
+ android:layout_width="match_parent"
+ android:layout_height="1px"
+ android:background="@android:color/darker_gray"
+ />
+
+ <FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_gravity="bottom"
- android:layout_alignParentBottom="true" />
+ android:layout_alignTop="@id/divider"
+ android:layout_alignParentBottom="true"
+ >
+ <ListView
+ android:id="@+id/settingsList"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="bottom"
+ />
+ </FrameLayout>
+
+
</RelativeLayout>
diff --git a/app/src/main/res/layout/eip_service_fragment.xml b/app/src/main/res/layout/eip_service_fragment.xml
index 814ec310..e220bf16 100644
--- a/app/src/main/res/layout/eip_service_fragment.xml
+++ b/app/src/main/res/layout/eip_service_fragment.xml
@@ -52,17 +52,18 @@
app:srcCompat="@drawable/ic_colorsquare" />
- <TextView
+ <android.support.v7.widget.AppCompatTextView
android:id="@+id/eipLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_marginEnd="@dimen/stdpadding"
- android:layout_marginStart="@dimen/stdpadding"
- android:layout_marginTop="@dimen/stdpadding"
- android:layout_marginLeft="@dimen/stdpadding"
- android:layout_marginRight="@dimen/stdpadding"
+ android:paddingLeft="@dimen/stdpadding"
+ android:paddingRight="@dimen/stdpadding"
+ android:paddingStart="@dimen/stdpadding"
+ android:paddingEnd="@dimen/stdpadding"
+ android:paddingTop="@dimen/stdpadding"
android:text="@string/eip_service_label"
android:textAppearance="?android:attr/textAppearanceMedium"
+ android:gravity="center"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
@@ -97,29 +98,31 @@
style="@style/BitmaskButtonBlack"
/>
- <TextView
+ <android.support.v7.widget.AppCompatTextView
android:id="@+id/routed_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_marginEnd="@dimen/stdpadding"
- android:layout_marginStart="@dimen/stdpadding"
- android:layout_marginTop="@dimen/stdpadding"
- android:layout_marginLeft="@dimen/stdpadding"
- android:layout_marginRight="@dimen/stdpadding"
+ android:paddingLeft="@dimen/stdpadding"
+ android:paddingRight="@dimen/stdpadding"
+ android:paddingStart="@dimen/stdpadding"
+ android:paddingEnd="@dimen/stdpadding"
+ android:paddingTop="@dimen/stdpadding"
android:text="@string/vpn_securely_routed"
+ android:gravity="center"
android:visibility="visible"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/eipLabel" />
- <TextView
+ <android.support.v7.widget.AppCompatTextView
android:id="@+id/vpn_route"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_marginEnd="@dimen/stdpadding"
- android:layout_marginStart="@dimen/stdpadding"
- android:layout_marginLeft="@dimen/stdpadding"
- android:layout_marginRight="@dimen/stdpadding"
+ android:paddingLeft="@dimen/stdpadding"
+ android:paddingRight="@dimen/stdpadding"
+ android:paddingStart="@dimen/stdpadding"
+ android:paddingEnd="@dimen/stdpadding"
+ android:gravity="center"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/routed_text" />
diff --git a/app/src/main/res/layout/switch_list_item.xml b/app/src/main/res/layout/switch_list_item.xml
new file mode 100644
index 00000000..bdb9a74c
--- /dev/null
+++ b/app/src/main/res/layout/switch_list_item.xml
@@ -0,0 +1,15 @@
+<android.support.v7.widget.SwitchCompat xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:id="@android:id/text1"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:textAppearance="?android:attr/textAppearanceListItemSmall"
+ android:gravity="center_vertical"
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+ android:paddingLeft="?android:attr/listPreferredItemPaddingLeft"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+ android:paddingRight="?android:attr/listPreferredItemPaddingRight"
+ android:background="?android:attr/activatedBackgroundIndicator"
+ android:minHeight="?android:attr/listPreferredItemHeightSmall"
+ android:checked="false"
+ tools:text="someOption" /> \ No newline at end of file
diff --git a/app/src/main/res/menu/configuration_wizard_activity.xml b/app/src/main/res/menu/configuration_wizard_activity.xml
deleted file mode 100644
index 21591547..00000000
--- a/app/src/main/res/menu/configuration_wizard_activity.xml
+++ /dev/null
@@ -1,15 +0,0 @@
-<menu xmlns:android="http://schemas.android.com/apk/res/android" >
-
- <item
- android:id="@+id/about_leap"
- android:orderInCategory="110"
- android:title="@string/about"/>
-
- <item
- android:id="@+id/new_provider"
- android:orderInCategory="210"
- android:title="@string/add_provider"
- android:showAsAction="ifRoom|withText"
- android:icon="@drawable/ic_menu_add"
- />
-</menu> \ No newline at end of file
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 00000000..c4a603d4
--- /dev/null
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+ <background android:drawable="@drawable/ic_launcher_background"/>
+ <foreground android:drawable="@mipmap/ic_launcher_foreground"/>
+</adaptive-icon> \ No newline at end of file
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 00000000..c4a603d4
--- /dev/null
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+ <background android:drawable="@drawable/ic_launcher_background"/>
+ <foreground android:drawable="@mipmap/ic_launcher_foreground"/>
+</adaptive-icon> \ No newline at end of file
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 00000000..2d33905e
--- /dev/null
+++ b/app/src/main/res/mipmap-hdpi/ic_launcher.png
Binary files differ
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png
new file mode 100644
index 00000000..4b5ebe2f
--- /dev/null
+++ b/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png
Binary files differ
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
new file mode 100644
index 00000000..226eb6cd
--- /dev/null
+++ b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
Binary files differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 00000000..93c5dda3
--- /dev/null
+++ b/app/src/main/res/mipmap-mdpi/ic_launcher.png
Binary files differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png
new file mode 100644
index 00000000..8adc11b3
--- /dev/null
+++ b/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png
Binary files differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
new file mode 100644
index 00000000..8b289993
--- /dev/null
+++ b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
Binary files differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 00000000..f1ffdafa
--- /dev/null
+++ b/app/src/main/res/mipmap-xhdpi/ic_launcher.png
Binary files differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png
new file mode 100644
index 00000000..bdaf6943
--- /dev/null
+++ b/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png
Binary files differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
new file mode 100644
index 00000000..bf47a793
--- /dev/null
+++ b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
Binary files differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 00000000..8c507b7b
--- /dev/null
+++ b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png
new file mode 100644
index 00000000..7197b58a
--- /dev/null
+++ b/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png
Binary files differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
new file mode 100644
index 00000000..68139e11
--- /dev/null
+++ b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
Binary files differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 00000000..c0eafbc4
--- /dev/null
+++ b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Binary files differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png
new file mode 100644
index 00000000..cc095966
--- /dev/null
+++ b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png
Binary files differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
new file mode 100644
index 00000000..89d2ec18
--- /dev/null
+++ b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
Binary files differ
diff --git a/app/src/main/res/values-ar/strings_ar.xml b/app/src/main/res/values-ar/strings_ar.xml
new file mode 100644
index 00000000..d432873f
--- /dev/null
+++ b/app/src/main/res/values-ar/strings_ar.xml
@@ -0,0 +1,12 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<resources>
+ <string name="retry">حاول مجددا</string>
+ <string name="save">حفظ</string>
+ <string name="new_provider_uri">اسم النطاق</string>
+ <string name="username_hint">إسم المستخدم</string>
+ <string name="password_hint">كلمة المرور</string>
+ <string name="login_button">تسجيل الدخول</string>
+ <string name="logout_button">تسجيل الخروج</string>
+ <string name="setup_error_configure_button">تكوين</string>
+ <string name="setup_error_close_button">الخروج</string>
+</resources>
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
index f42ddb03..38e98e4a 100644
--- a/app/src/main/res/values-de/strings.xml
+++ b/app/src/main/res/values-de/strings.xml
@@ -23,7 +23,7 @@
<string name="valid_url_entered">Gültige URL</string>
<string name="not_valid_url_entered">Ungültige URL</string>
<string name="provider_details_title">Provider Details</string>
- <string name="use_anonymously_button">Anonym benutzen.</string>
+ <string name="use_anonymously_button">Anonym benutzen</string>
<string name="username_hint">Benutzer*innenname</string>
<string name="username_ask">Bitte geben Sie Ihr Benutzer*innenname ein</string>
<string name="password_ask">Bitte Passwort eingeben</string>
@@ -41,10 +41,10 @@
<string name="error_no_such_algorithm_exception_user_message">Verschlüsselungsalgorithmus nicht gefunden. Bitte aktualisiere deine Android-Version!</string>
<string name="signup_or_login_button">Registrieren / Anmelden</string>
<string name="login_button">Anmelden</string>
- <string name="login_to_profile">Profil anmelden.</string>
+ <string name="login_to_profile">Anmelden</string>
<string name="logout_button">Abmelden</string>
<string name="signup_button">Registrieren</string>
- <string name="create_profile">Profil erstellen.</string>
+ <string name="create_profile">Profil erstellen</string>
<string name="setup_provider">Provider einrichten.</string>
<string name="setup_error_title">Konfigurationsfehler</string>
<string name="setup_error_configure_button">Konfigurieren</string>
@@ -99,7 +99,14 @@
<string name="void_vpn_title">Internetverkehr wird blockiert</string>
<string name="update_provider_details">Providerdetails werden aktualisiert</string>
<string name="update_certificate">Zertifikat aktualisieren</string>
+ <string name="warning_eip_json_corrupted">Die Aktualisierung der Providerkonfiguration ist fehlgeschlagen.</string>
+ <string name="eip_json_corrupted_user_message">Die Aktualisierung der Providerkonfiguration ist fehlgeschlagen. Bitte einloggen, um es erneut zu versuchen.</string>
<string name="warning_corrupted_provider_details">Die gespeicherten Providerdetails sind fehlerhaft. Du kannst entweder Bitmask aktualisieren (Empfehlung) oder die Providerdetails mithilfe eines kommerziellen CA Zertifikates aktualisieren. </string>
<string name="warning_corrupted_provider_cert">Das gespeicherte Providerzertifikat ist ungültig. Du kannst entweder Bitmask aktualisieren (Empfehlung) oder das Providerzertifikat mithilfe eines kommerziellen CA Zertifikates aktualisieren. </string>
<string name="warning_expired_provider_cert">Das gespeicherte Providerzertifikat ist abgelaufen. Du kannst entweder Bitmask aktualisieren (Empfehlung) oder das Providerzertifikat mithilfe eines kommerziellen CA Zertifikates aktualisieren. </string>
+ <string name="downloading_vpn_certificate_failed">Das Herunterladen des VPN Zertifikates ist fehlgeschlagen. Versuche es erneut oder wähle einen anderen Provider aus.</string>
+ <string name="vpn_certificate_is_invalid">Das VPN Zertifikat ist ungültig. Versuche ein neues herunterzuladen.</string>
+ <string name="vpn_certificate_user_message">Das VPN Zertifikat ist ungültig. Bitte melde dich an, um ein neues herunterzuladen.</string>
+ <string name="save_battery">Batteriesparmodus</string>
+ <string name="save_battery_message">Hintergrunddaten werden nicht übertragen während das Telefon inaktiv ist. Diese Funktion ist noch experimentell.</string>
</resources>
diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml
index 710b1b8d..abb263bc 100644
--- a/app/src/main/res/values-es/strings.xml
+++ b/app/src/main/res/values-es/strings.xml
@@ -99,7 +99,14 @@
<string name="void_vpn_title">Bloqueando el tráfico</string>
<string name="update_provider_details">Actualizar detalles del proveedor</string>
<string name="update_certificate">Actualizar certificado</string>
+ <string name="warning_eip_json_corrupted">No se pudo actualizar la configuración del proveedor.</string>
+ <string name="eip_json_corrupted_user_message">No se pudo actualizar la configuración del proveedor. Por favor, incie sesión e inténtelo de nuevo.</string>
<string name="warning_corrupted_provider_details">Los detalles del proveedor almacenados están corrompidos. Puede actualizar Bitmask (recomendado), o bien actualizar los detalles del proveedor usando un certificado de una autoridad de certificación/CA comercial.</string>
<string name="warning_corrupted_provider_cert">El certificado del proveedor almacenado no es válido. Puede actualizar Bitmask (recomendado), o bien actualizar el certificado de proveedor usando una autoridad de certificación/CA comercial.</string>
<string name="warning_expired_provider_cert">El certificado del proveedor almacenado ha caducado. Puede actualizar Bitmask (recomendado) o actualizar el certificado de proveedor usando una autoridad de certificación/CA comercial.</string>
+ <string name="downloading_vpn_certificate_failed">No se pudo descargar el certificado de la VPN. Inténtelo de nuevo o escoja otro proveedor.</string>
+ <string name="vpn_certificate_is_invalid">El certificado de la VPN no es válido. Trate de descargar uno nuevo.</string>
+ <string name="vpn_certificate_user_message">El certificado de la VPN no es válido. Por favor, inicie sesión para descargar uno nuevo.</string>
+ <string name="save_battery">Ahorrar batería</string>
+ <string name="save_battery_message">Las conexiones de datos en segundo plano hibernarán cuando su teléfono esté inactivo. Esta característica todavía es experimental.</string>
</resources>
diff --git a/app/src/main/res/values-fa-rIR/strings.xml b/app/src/main/res/values-fa-rIR/strings.xml
index 551bdd16..99819cfb 100644
--- a/app/src/main/res/values-fa-rIR/strings.xml
+++ b/app/src/main/res/values-fa-rIR/strings.xml
@@ -1,38 +1,110 @@
<?xml version='1.0' encoding='UTF-8'?>
<resources>
<string name="retry">تلاش دوباره</string>
+ <string name="repository_url_text">دسترسی به منبع کد در https://0xacab.org/leap/bitmask_android </string>
+ <string name="leap_tracker">نظرات، پیشنهادات و گزارش اشکالات در https://0xacab.org/leap/bitmask_android/issues</string>
+ <string name="translation_project_text">از همکاری شما برای ترجمه استقبال می‌کنیم. پروژه Transifex ما در اینجا ببینید: https://www.transifex.com/projects/p/bitmask-android/</string>
<string name="switch_provider_menu_option">تعویض ارائه دهنده</string>
<string name="info">اطلاعات</string>
- <string name="show_connection_details">نمایش اطلاعات تماس</string>
+ <string name="show_connection_details">نمایش جزئیات ارتباط</string>
<string name="routes_info">مسیرها: %s</string>
+ <string name="routes_info6">مسیرهای IPv6: %s</string>
<string name="error_empty_username">نام کاربری نباید خالی باشد.</string>
+ <string name="cert_from_keystore">گرفتن تصدیق \'%s\' از کی‌استور</string>
<string name="provider_label">ارائه‌دهنده:</string>
- <string name="provider_label_none">ارائه‌دهنده‌ای تنظیم نشده</string>
- <string name="status_unknown">وضعیت نامشخص.</string>
+ <string name="provider_label_none">ارائه‌دهنده‌ای تنظیم نشده است</string>
+ <string name="status_unknown">وضعیت نامشخص</string>
+ <string name="eip_service_label">دسترسی کدگزاری شده به اینترنت با وی‌پی‌ان</string>
+ <string name="configuration_wizard_title">انتخاب سرویس ارائه‌دهنده</string>
+ <string name="add_provider">اضافه کردن ارائه‌دهنده جدید</string>
<string name="introduce_new_provider">ارائه‌دهنده‌ی سروریس جدید وارد کنید</string>
<string name="save">ذخیره</string>
<string name="new_provider_uri">نام دامنه</string>
- <string name="use_anonymously_button">استفاده از ناشناسی</string>
+ <string name="valid_url_entered">URL معتبر است</string>
+ <string name="not_valid_url_entered">URL صحیح نیست</string>
+ <string name="provider_details_title">اطلاعات ارائه‌دهنده</string>
+ <string name="use_anonymously_button">استفاده به صورت ناشناس</string>
<string name="username_hint">نام کاربری</string>
<string name="username_ask">لطفا شناسه‌ی خود را وارد کنید</string>
- <string name="password_hint">کلمه عبور</string>
+ <string name="password_ask">لطفا رمز عبور خود را وارد کنید</string>
+ <string name="password_hint">رمز عبور</string>
+ <string name="password_match">رمزهای عبور یکسان است</string>
+ <string name="password_mismatch">رمزهای عبور یکسان نیست</string>
<string name="user_message">پیغام کاربر</string>
- <string name="error_bad_user_password_user_message">شناسه یا رمز نادرست</string>
- <string name="error_client_http_user_message">تلاش دوباره: خطای HTTP کاربر</string>
- <string name="error_io_exception_user_message">تلاش دوباره: خطای I/O</string>
- <string name="error_json_exception_user_message">تلاش دوباره: پاسخ نامناسب از سرور</string>
+ <string name="about_fragment_title">درباره‌</string>
+ <string name="error_srp_math_error_user_message">دوباره تلاش کنید: اشکال در سرور</string>
+ <string name="error_bad_user_password_user_message">نام کاربری یا رمز نادرست</string>
+ <string name="error_not_valid_password_user_message">باید حداقل ۸ حرف باشد.</string>
+ <string name="error_client_http_user_message">دوباره تلاش کنید: خطای HTTP کاربر</string>
+ <string name="error_io_exception_user_message">دوباره تلاش کنید: خطای I/O</string>
+ <string name="error_json_exception_user_message">دوباره تلاش کنید: پاسخ نامناسب از سرور</string>
+ <string name="error_no_such_algorithm_exception_user_message">الگوریتم کدگذاری‌شده یافت نشد. لطفا آندروید را به‌روز رسانی کنید!</string>
+ <string name="signup_or_login_button">ثبت نام/ ورود</string>
<string name="login_button">ورود</string>
- <string name="logout_button">برون رفت</string>
+ <string name="login_to_profile">ورود به صفحه شخصی</string>
+ <string name="logout_button">خروج</string>
+ <string name="signup_button">ثبت‌نام</string>
+ <string name="create_profile">درست‌کردن صفحه شخصی</string>
+ <string name="setup_provider">تنظیم ارائه‌دهنده</string>
<string name="setup_error_title">خطای تنظیمات</string>
<string name="setup_error_configure_button">تنظیم</string>
<string name="setup_error_close_button">خروج</string>
+ <string name="setup_error_text">خطای تنظیم بیتمسک با ارائه‌دهنده انتخابی شما. n\n\ تنظیم را دوباره انجام دهید یا از برنامه خارج شوید و تنظیم ارائه‌دهنده را با بازکردن مجدد انجام دهید.</string>
+ <string name="server_unreachable_message">سرور قابل دسترس نیست، لطفا دوباره تلاش کنید.</string>
+ <string name="error.security.pinnedcertificate">اخطار امنیتی، برنامه app را به‌روز رسانی کنید یا ارائه‌دهنده دیگری انتخاب کنید.</string>
<string name="malformed_url">به نظر نمی‌رسد که یک ارائه‌دهنده‌ی بیتمسک باشد.</string>
<string name="certificate_error">این یک ارائه‌دهنده‌ی قابل اعتماد بیتمسک نیست.</string>
+ <string name="service_is_down_error">سرور وی‌پی‌ان در دسترس نیست.</string>
<string name="configuring_provider">در حال تنظیم کردن ارائه‌دهنده</string>
<string name="incorrectly_downloaded_certificate_message">گواهی ناشناسی شما دریافت نشد</string>
+ <string name="downloading_certificate_message">دریافت گواهی وی‌پی‌ان</string>
+ <string name="updating_certificate_message">به‌روز رسانی گواهی وی‌پی‌ان</string>
+ <string name="login.riseup.warning">کاربران Riseup نیاز به ساختن حساب کاربری جداگانه‌ای برای استفاده از وی‌پی‌ان دارند</string>
+ <string name="succesful_authentication_message">احراز هویت</string>
<string name="authentication_failed_message">احراز هویت ناموفق بود.</string>
+ <string name="registration_failed_message">ثبت‌ ناموفق</string>
<string name="eip_status_start_pending">آماده‌سازی اتصال</string>
<string name="eip_cancel_connect_title">انصراف از اتصال؟</string>
<string name="eip_cancel_connect_text">تلاشی برای برقراری ارتباط در جریان است. آیا می‌خواهید آن را قطع کنید؟</string>
+ <string name="eip.warning.browser_inconsistency">خاموش کردن اتصال با وی‌پی‌ان؟ وقتی وی‌پی‌ان خاموش است، اطلاعات شما ممکن است توسط ارائه‌دهنده اینترنت و یا شبکه محلی قابل دسترس باشد.</string>
+ <string name="eip_state_not_connected">کار نمی‌کند! اتصال ناامن!</string>
<string name="eip_state_connected">اتصال امن.</string>
+ <string name="provider_problem">به نظر می‌رسد مشکلی با ارائه‌دهنده وجود دارد.</string>
+ <string name="try_another_provider">لطفا از ارائه‌دهنده دیگری استفاده کنید و یا با ارائه‌دهنده تماس بگیرید.</string>
+ <string name="default_username">ناشناس</string>
+ <string name="logged_in_user_status">وارد شده است.</string>
+ <string name="logged_out_user_status">خارج شده است</string>
+ <string name="didnt_log_out_user_status">خارج نشده‌است. بعدا دوباره امتحان کنید، ممکن است مشکلی در شبکه یا با ارائه‌دهنده باشد. اگر قابل حل نیست، اطلاعات بیتمسک را از روی تنظیمات اندروید پاک کنید. </string>
+ <string name="not_logged_in_user_status">وارد نشده است.</string>
+ <string name="logging_in_user_status">در حال ورود.</string>
+ <string name="logging_in">وارد شدن</string>
+ <string name="signing_up">ثبت‌نام کردن</string>
+ <string name="logging_out_user_status">در حال خروج.</string>
+ <string name="signingup_message">ثبت شده است.</string>
+ <string name="vpn.button.turn.on">روشن کردن</string>
+ <string name="vpn.button.turn.off">خاموش کردن</string>
+ <string name="vpn_button_turn_off_blocking">توقف مسدود کردن</string>
+ <string name="vpn_securely_routed">ترافیک شما به صورت امن از اینجا گذشته است:</string>
+ <string name="bitmask_log">لاگ بیتمسک</string>
+ <string name="title_activity_main">بیتمسک</string>
+ <string name="log_fragment_title">لاگ</string>
+ <string name="vpn_fragment_title">وی‌پی‌ان</string>
+ <string name="navigation_drawer_open">بازکردن نوار ابزار</string>
+ <string name="navigation_drawer_close">بستن نوار ابزار</string>
+ <string name="action_example">فعالیت نمونه</string>
+ <string name="action_settings">تنظیمات</string>
+ <string name="void_vpn_establish">بیتمسک تمام ترافیک خروجی اینترنت را مسدود می‌کند.</string>
+ <string name="void_vpn_error_establish">ناموفق در راه‌اندازی وی‌پی‌ان مسدودکننده</string>
+ <string name="void_vpn_stopped">مسدودکردن تمام ترافیک خروجی اینترنت متوقف شد.</string>
+ <string name="void_vpn_title">مسدود کردن ترافیک</string>
+ <string name="update_provider_details">به‌روز رسانی اطلاعات ارائه‌دهنده</string>
+ <string name="update_certificate">به‌روز رسانی گواهی </string>
+ <string name="warning_eip_json_corrupted">ناموفق در به‌روز رسانی تنظیمات ارا‌ئه‌دهنده</string>
+ <string name="eip_json_corrupted_user_message">ناموفق در به‌روز رسانی تنظیمات ارا‌ئه‌دهنده. برای تلاش مجدد، لطفا دوباره وارد شوید.</string>
+ <string name="warning_corrupted_provider_details">گواهی ارائه‌دهنده ذخیره‌شده خراب است. یا بیتمسک را به‌روز رسانی کنید (گزینه پیشنهادی)، و یا اطلاعات ارائه‌دهنده را با استفاده از گواهی تجاری CA به‌روز رسانی کنید.</string>
+ <string name="warning_corrupted_provider_cert">گواهی ارائه‌دهنده ذخیره‌شده نامعتبر است. یا بیتمسک را به‌روز رسانی کنید (گزینه پیشنهادی)، و یا گواهی ارائه‌دهنده را با استفاده از گواهی تجاری CA به‌روز رسانی کنید.</string>
+ <string name="warning_expired_provider_cert"> گواهی ارائه‌دهنده ذخیره‌شده تاریخ‌گذشته است. یا بیتمسک را به‌روز رسانی کنید (گزینه پیشنهادی)، و یا گواهی ارائه‌دهنده را با استفاده از گواهی تجاری CA به‌روز رسانی کنید.</string>
+ <string name="downloading_vpn_certificate_failed">ناموفق در دریافت گواهی وی‌پی‌ان. لطفا دوباره تلاش کنید و یا ارائه‌دهنده دیگری را انتخاب کنید.</string>
+ <string name="vpn_certificate_is_invalid">گواهی وی‌پی‌ان نامعتبر است. تلاش کنید که گواهی جدیدی دریافت کنید.</string>
+ <string name="vpn_certificate_user_message">گواهی وی‌پی‌ان نامعتبر است. لطفا برای دریافت گواهی جدید وارد شوید. </string>
</resources>
diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml
index e1133837..3a668286 100644
--- a/app/src/main/res/values-fr/strings.xml
+++ b/app/src/main/res/values-fr/strings.xml
@@ -16,15 +16,20 @@
<string name="status_unknown">État inconnu.</string>
<string name="eip_service_label">Accès à Internet par RPV chiffré</string>
<string name="configuration_wizard_title">Choisir un fournisseur de services</string>
+ <string name="add_provider">Ajouter un nouveau fournisseur</string>
<string name="introduce_new_provider">Ajouter un nouveau fournisseur de services</string>
<string name="save">Enregistrer</string>
<string name="new_provider_uri">Nom de domaine</string>
<string name="valid_url_entered">Cette URL est valide</string>
<string name="not_valid_url_entered">URL malformée</string>
+ <string name="provider_details_title">Renseignements sur le fournisseur</string>
<string name="use_anonymously_button">Utiliser anonymement</string>
<string name="username_hint">nom d’utilisateur</string>
<string name="username_ask">Veuillez saisir votre nom d’utilisateur</string>
+ <string name="password_ask">Veuillez saisir votre mot de passe</string>
<string name="password_hint">mot de passe</string>
+ <string name="password_match">Les mots de passe correspondent</string>
+ <string name="password_mismatch">Les mots de passe ne correspondent pas</string>
<string name="user_message">Message utilisateur</string>
<string name="about_fragment_title">À propos</string>
<string name="error_srp_math_error_user_message">Ressayer : erreur mathématique du serveur.</string>
@@ -82,6 +87,10 @@
<string name="vpn_securely_routed">Votre trafic est acheminé en toute sécurité par :</string>
<string name="bitmask_log">Journal Bitmask</string>
<string name="title_activity_main">Bitmask</string>
+ <string name="log_fragment_title">Journal</string>
+ <string name="vpn_fragment_title">RPV</string>
+ <string name="navigation_drawer_open">Ouvrir le tiroir de navigation</string>
+ <string name="navigation_drawer_close">Fermer le tiroir de navigation</string>
<string name="action_example">Exemple d’action</string>
<string name="action_settings">Paramètres</string>
<string name="void_vpn_establish">Bitmask bloque tout le trafic Internet sortant.</string>
@@ -90,7 +99,14 @@
<string name="void_vpn_title">Blocage du trafic</string>
<string name="update_provider_details">Mettre les renseignements de fournisseur à jour</string>
<string name="update_certificate">Mettre le certificat à jour</string>
+ <string name="warning_eip_json_corrupted">Échec de mise à jour de la configuration du fournisseur.</string>
+ <string name="eip_json_corrupted_user_message">Échec de mise à jour de la configuration du fournisseur. Veuillez vous connecter pour ressayer.</string>
<string name="warning_corrupted_provider_details">Les renseignements de fournisseur enregistrés sont corrompus. Vous pouvez soit mettre Bitmask à jour (recommandé), soit mettre les renseignements de fournisseur à jour en utilisant un certificat CA commercial.</string>
<string name="warning_corrupted_provider_cert">Le certificat de fournisseur enregistré est invalide. Vous pouvez soit mettre Bitmask à jour (recommandé), soit mettre le certificat de fournisseur à jour en utilisant un certificat CA commercial.</string>
<string name="warning_expired_provider_cert">Le certificat de fournisseur enregistré est expiré. Vous pouvez soit mettre Bitmask à jour (recommandé), soit mettre le certificat de fournisseur à jour en utilisant un certificat CA commercial.</string>
+ <string name="downloading_vpn_certificate_failed">Échec de téléchargement du certificat du RPV. Ressayez ou choisissez un autre fournisseur.</string>
+ <string name="vpn_certificate_is_invalid">Le certificat du RPV est invalide. Essayez d’en télécharger un nouveau.</string>
+ <string name="vpn_certificate_user_message">Le certificat du RPV est invalide. Veuillez vous connecter pour en télécharger un nouveau.</string>
+ <string name="save_battery">Économiser la batterie</string>
+ <string name="save_battery_message">Les communications de données d’arrière-plan seront mises en veille quand votre téléphone sera inactif. Cette fonction est encore expérimentale.</string>
</resources>
diff --git a/app/src/main/res/values-gl/strings.xml b/app/src/main/res/values-gl/strings.xml
new file mode 100644
index 00000000..35c77f4f
--- /dev/null
+++ b/app/src/main/res/values-gl/strings.xml
@@ -0,0 +1,110 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<resources>
+ <string name="retry">Intentar de novo</string>
+ <string name="repository_url_text">Código fonte dispoñible en https://0xacab.org/leap/bitmask_android</string>
+ <string name="leap_tracker">Seguimento de incidencias en https://0xacab.org/leap/bitmask_android/issues</string>
+ <string name="translation_project_text">Agradécese a tradución. Aquí está o noso proxecto en Transifex https://www.transifex.com/projects/p/bitmask-android/</string>
+ <string name="switch_provider_menu_option">Cambiar provedor</string>
+ <string name="info">info</string>
+ <string name="show_connection_details">Mostrar detalles da conexión</string>
+ <string name="routes_info">Rutas: %s</string>
+ <string name="routes_info6">Rutas IPv6: %s</string>
+ <string name="error_empty_username">A caixa nome de usuaria non pode estar baldeira.</string>
+ <string name="cert_from_keystore">Obteña o certificado \'%s\' do almacén de chaves</string>
+ <string name="provider_label">Provedor:</string>
+ <string name="provider_label_none">Non hai provedor configurado</string>
+ <string name="status_unknown">Estado descoñecido.</string>
+ <string name="eip_service_label">Acceso a internet con VPN cifrado</string>
+ <string name="configuration_wizard_title">Escolla un provedor de acceso</string>
+ <string name="add_provider">Engadir novo provedor</string>
+ <string name="introduce_new_provider">Engadir novo provedor de servizo</string>
+ <string name="save">Gardar</string>
+ <string name="new_provider_uri">Nome de dominio</string>
+ <string name="valid_url_entered">O URL é válido</string>
+ <string name="not_valid_url_entered">URL mal formado</string>
+ <string name="provider_details_title">Detalles do provedor</string>
+ <string name="use_anonymously_button">Utilizar de xeito anónimo</string>
+ <string name="username_hint">nome de usuaria</string>
+ <string name="username_ask">Por favor introduza o seu nome de usuaria</string>
+ <string name="password_ask">Por favor introduza o seu contrasinal</string>
+ <string name="password_hint">contrasinal</string>
+ <string name="password_match">Os contrasinais concordan</string>
+ <string name="password_mismatch">Os contrasinais non concordan</string>
+ <string name="user_message">Mensaxe de usuaria</string>
+ <string name="about_fragment_title">Sobre</string>
+ <string name="error_srp_math_error_user_message">Inténtar de novo: Fallo no servidor.</string>
+ <string name="error_bad_user_password_user_message">Nome de usuaria ou contrasinal incorrectos.</string>
+ <string name="error_not_valid_password_user_message">Cando menos debe ter 8 caracteres.</string>
+ <string name="error_client_http_user_message">Intentar de novo: Fallo no cliente HTTP</string>
+ <string name="error_io_exception_user_message">Intentar de novo: fallo I/O</string>
+ <string name="error_json_exception_user_message">Intentar de novo: resposta incorrecta desde o servidor</string>
+ <string name="error_no_such_algorithm_exception_user_message">Non se atopou algoritmo de cifrado. Por favor actualice Android!</string>
+ <string name="signup_or_login_button">Conexión/Rexistro</string>
+ <string name="login_button">Conectar</string>
+ <string name="login_to_profile">Conectar perfil</string>
+ <string name="logout_button">Desconectar</string>
+ <string name="signup_button">Rexistro</string>
+ <string name="create_profile">Crear perfil</string>
+ <string name="setup_provider">Establecer provedor</string>
+ <string name="setup_error_title">Fallo na configuración</string>
+ <string name="setup_error_configure_button">Configurar</string>
+ <string name="setup_error_close_button">Saír</string>
+ <string name="setup_error_text">Algo fallou ao configurar Bitmask co provedor escollido.\n\nPode escoller reconfigurar ou saír e configurar un provedor no seguinte inicio.</string>
+ <string name="server_unreachable_message">Non se puido conectar ao servidor, por favor inténteo de novo.</string>
+ <string name="error.security.pinnedcertificate">Fallo de seguridade, anove o aplicativo ou escolla outro provedor</string>
+ <string name="malformed_url">Non semella ser un provedor Bitmask.</string>
+ <string name="certificate_error">Este non é un provedor de confianza de Bitmask</string>
+ <string name="service_is_down_error">O servizo está caído.</string>
+ <string name="configuring_provider">Configurando provedor</string>
+ <string name="incorrectly_downloaded_certificate_message">Non se descargou o seu certificado de anonimato</string>
+ <string name="downloading_certificate_message">Descargando certificado VPN</string>
+ <string name="updating_certificate_message">Anovando certificado VPN</string>
+ <string name="login.riseup.warning">As usuarias de Riseup precisarán crear unha conta separada para utilizar o servizo VPN</string>
+ <string name="succesful_authentication_message">Autenticada.</string>
+ <string name="authentication_failed_message">Fallo na autenticación.</string>
+ <string name="registration_failed_message">Fallo no rexistro</string>
+ <string name="eip_status_start_pending">Iniciando conexión</string>
+ <string name="eip_cancel_connect_title">Cancelar a conexión?</string>
+ <string name="eip_cancel_connect_text">Hai un intento de conexión en proceso. Desexa cancelalo?</string>
+ <string name="eip.warning.browser_inconsistency">Apagar a conexión VPN? Si está desconectada a VPN podería suministrar información personal ao seu provedor de acceso a internet ou rede local.</string>
+ <string name="eip_state_not_connected">Non está a conectada! Conexión insegura!</string>
+ <string name="eip_state_connected">Conexión segura.</string>
+ <string name="provider_problem">Semella que hai un problema co provedor.</string>
+ <string name="try_another_provider">Por favor, inténteo con un novo provedor ou contacte co seu.</string>
+ <string name="default_username">Anónimo</string>
+ <string name="logged_in_user_status">está conectada.</string>
+ <string name="logged_out_user_status">desconectada.</string>
+ <string name="didnt_log_out_user_status">non se desconectou. Inténteo máis tarde, podería haber un problema na rede ou co provedor. Si persiste o problema, borre os datos de Bitmask nos axustes de Android.</string>
+ <string name="not_logged_in_user_status">non se ten conectado.</string>
+ <string name="logging_in_user_status">está a conectar.</string>
+ <string name="logging_in">Conectando</string>
+ <string name="signing_up">Rexistrando</string>
+ <string name="logging_out_user_status">está a desconectar.</string>
+ <string name="signingup_message">está sendo rexistrada.</string>
+ <string name="vpn.button.turn.on">Acender</string>
+ <string name="vpn.button.turn.off">Apagar</string>
+ <string name="vpn_button_turn_off_blocking">Deixar de bloquear</string>
+ <string name="vpn_securely_routed">O seu tráfico está securizado a través de:</string>
+ <string name="bitmask_log">Rexistro Bitmask</string>
+ <string name="title_activity_main">Bitmask</string>
+ <string name="log_fragment_title">Rexistro</string>
+ <string name="vpn_fragment_title">VPN</string>
+ <string name="navigation_drawer_open">Abrir cadro de navegación</string>
+ <string name="navigation_drawer_close">Pechar cadro de navegación</string>
+ <string name="action_example">Acción de exemplo</string>
+ <string name="action_settings">Axustes</string>
+ <string name="void_vpn_establish">Bitmask bloquea todo o tráfico saínte a internet.</string>
+ <string name="void_vpn_error_establish">Fallo ao establecer bloqueo VPN.</string>
+ <string name="void_vpn_stopped">Deixou de bloquear todo o tráfico saínte.</string>
+ <string name="void_vpn_title">Bloqueando tráfico</string>
+ <string name="update_provider_details">Actualizar detalles do provedor</string>
+ <string name="update_certificate">Anovar certificado</string>
+ <string name="warning_eip_json_corrupted">Fallo ao anovar a configuración do provedor.</string>
+ <string name="eip_json_corrupted_user_message">Fallo ao anovar a configuración do provedor. Por favor conéctese para intentalo de novo.</string>
+ <string name="warning_corrupted_provider_details">Os datos do provedor gardados son defectuosos. Pode actualizar Bitmask (recomendado) ou ben actualizar os detalles do provedor utilizando un certificado CA de confianza.</string>
+ <string name="warning_corrupted_provider_cert">O certificado de provedor gardado non é válido. Pode actualizar Bitmask (recomendado) ou actualizar o certificado do provedor utilizando un certificado CA comercial.</string>
+ <string name="warning_expired_provider_cert">O certificado do provedor gardado caducou. Pode actualizar Bitmask (recomendado) ou pode actualizar o certificado do provedor utilizando un certificado CA comercial.</string>
+ <string name="downloading_vpn_certificate_failed">Fallou a descarga do certificado do VPN. Inténteo de novo ou escolla outro provedor.</string>
+ <string name="vpn_certificate_is_invalid">O certificado do VPN non é válido. Intente descargar un novo.</string>
+ <string name="vpn_certificate_user_message">O certificado VPN non é válido. Por favor conéctese para descargar un novo.</string>
+</resources>
diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml
index 0d8b85a6..b17ac66d 100644
--- a/app/src/main/res/values-nl/strings.xml
+++ b/app/src/main/res/values-nl/strings.xml
@@ -3,39 +3,112 @@
<string name="retry">Opnieuw proberen</string>
<string name="repository_url_text">Broncode is beschikbaar op https://0xacab.org/leap/bitmask_android</string>
<string name="leap_tracker">Issue tracker https://0xacab.org/leap/bitmask_android/issues</string>
+ <string name="translation_project_text">Vertalingen zijn welkom en worden gewaardeerd. Kijk voor meer info op onze Transifex pagina: https://www.transifex.com/projects/p/bitmask-android/</string>
<string name="switch_provider_menu_option">Provider wisselen</string>
<string name="info">info</string>
<string name="show_connection_details">Details van de verbinding weergeven</string>
<string name="routes_info">Routes: %s</string>
+ <string name="routes_info6">IPv6 routes: %s</string>
<string name="error_empty_username">De gebruikersnaam moet niet leeg zijn.</string>
+ <string name="cert_from_keystore">Kreeg certificaat \'%s\' van de keystore</string>
<string name="provider_label">Provider:</string>
<string name="provider_label_none">Geen provider ingesteld</string>
<string name="status_unknown">Status onbekend</string>
+ <string name="eip_service_label">VPN Versleutelde Internet Toegang</string>
+ <string name="configuration_wizard_title">Service provider selecteren</string>
+ <string name="add_provider">Nieuwe provider toevoegen</string>
<string name="introduce_new_provider">Nieuwe serviceprovider toevoegen</string>
<string name="save">Opslaan</string>
<string name="new_provider_uri">Domeinnaam</string>
+ <string name="valid_url_entered">De URL is correct</string>
+ <string name="not_valid_url_entered">Incorrecte URL</string>
+ <string name="provider_details_title">Provider details</string>
<string name="use_anonymously_button">Anoniem gebruiken</string>
<string name="username_hint">gebruikersnaam</string>
<string name="username_ask">Geef je gebruikersnaam in</string>
+ <string name="password_ask">Voer je wachtwoord in</string>
<string name="password_hint">wachtwoord</string>
+ <string name="password_match">Wachtwoorden komen overeen</string>
+ <string name="password_mismatch">Wachtwoorden komen niet overeen</string>
<string name="user_message">Bericht</string>
+ <string name="about_fragment_title">Over</string>
+ <string name="error_srp_math_error_user_message">Probeer opnieuw: serverrekenfout.</string>
<string name="error_bad_user_password_user_message">Gebruikersnaam of wachtwoord verkeerd.</string>
+ <string name="error_not_valid_password_user_message">Wachtwoord moet minstens 8 tekens bevatten.</string>
<string name="error_client_http_user_message">Probeer opnieuw: Client HTTP fout</string>
<string name="error_io_exception_user_message">Probeer opnieuw: I/O fout</string>
<string name="error_json_exception_user_message">Probeer opnieuw: incorrect antwoord van de server</string>
+ <string name="error_no_such_algorithm_exception_user_message">Coderingsalgoritme niet gevonden. Gelieve Android te upgraden!</string>
+ <string name="signup_or_login_button">Inschrijven/Inloggen</string>
<string name="login_button">Aanmelden</string>
+ <string name="login_to_profile">Login voor profiel</string>
<string name="logout_button">Afmelden</string>
+ <string name="signup_button">Registreer</string>
+ <string name="create_profile">Creeer profiel</string>
+ <string name="setup_provider">Setup provider</string>
<string name="setup_error_title">Configuratiefout</string>
<string name="setup_error_configure_button">Configureren</string>
<string name="setup_error_close_button">Aflsluiten</string>
+ <string name="setup_error_text">Er is een probleem opgetreden om Bitmask te configureren met jou provider.
+
+Je kan kiezen om te herconfigureren of af te sluiten en bij de volgende start een provider in te stellen.</string>
+ <string name="server_unreachable_message">Server is onbereikbaar, probeer later opnieuw.</string>
+ <string name="error.security.pinnedcertificate">Beveiligingsprobleem, upgrade de app of kies een andere provider.</string>
<string name="malformed_url">Dit lijkt geen Bitmask provider te zijn.</string>
<string name="certificate_error">Dit is geen vertrouwde Bitmask provider.</string>
+ <string name="service_is_down_error">Service is onbereikbaar.</string>
<string name="configuring_provider">Provider wordt ingesteld</string>
<string name="incorrectly_downloaded_certificate_message">Je anonieme certificaat is niet gedownload</string>
+ <string name="downloading_certificate_message">Downloading VPN certificaat</string>
+ <string name="updating_certificate_message">Updaten VPN certificaat</string>
+ <string name="login.riseup.warning">Riseup gebruikers moeten een apart account aanmaken om de VPN service te gebruiken</string>
+ <string name="succesful_authentication_message">Geauthenticeerd.</string>
<string name="authentication_failed_message">Authenticatie mislukt.</string>
+ <string name="registration_failed_message">Registratie mislukt.</string>
<string name="eip_status_start_pending">Verbinding wordt geïnitialiseerd</string>
<string name="eip_cancel_connect_title">Verbinding annuleren?</string>
<string name="eip_cancel_connect_text">We proberen een verbinding tot stand te brengen. Wil je dit annuleren?</string>
+ <string name="eip.warning.browser_inconsistency">VPN verbinding uitzetten? Wanneer de VPN uitstaat, kan je persoonlijke data lekken naar je internet provider of je lokale netwerk.</string>
+ <string name="eip_state_not_connected">Uitgeschakeld! Onbeveiligde verbinding!</string>
<string name="eip_state_connected">Verbinding beveiligd.</string>
+ <string name="provider_problem">Het lijkt er op dat er een probleem is met de provider.</string>
+ <string name="try_another_provider">Gelieve een andere provider te proberen of contacteer je huidige.</string>
+ <string name="default_username">Anoniem</string>
+ <string name="logged_in_user_status">is ingelogd.</string>
+ <string name="logged_out_user_status">is uitgelogd.</string>
+ <string name="didnt_log_out_user_status">niet uitgelogd. Probeer het later, er kan een probleem zijn met het netwerk of de provider. Als het probleem zich blijft voordoen, verwijder dan de Bitmask data uit de Android settings.</string>
+ <string name="not_logged_in_user_status">heeft zich niet ingelogd.</string>
+ <string name="logging_in_user_status">wordt aangemeld.</string>
+ <string name="logging_in">Aanmelden</string>
+ <string name="signing_up">Sign up</string>
+ <string name="logging_out_user_status">wordt uitgelogd.</string>
+ <string name="signingup_message">wordt geregistreerd.</string>
+ <string name="vpn.button.turn.on">Aanzetten</string>
+ <string name="vpn.button.turn.off">Uitschakelen</string>
+ <string name="vpn_button_turn_off_blocking">Stop blokkeren</string>
+ <string name="vpn_securely_routed">Je verkeer wordt veilig geroute door:</string>
+ <string name="bitmask_log">Bitmask Log</string>
+ <string name="title_activity_main">Bitmask</string>
+ <string name="log_fragment_title">Log</string>
+ <string name="vpn_fragment_title">VPN</string>
+ <string name="navigation_drawer_open">Open navigatiemenu</string>
+ <string name="navigation_drawer_close">Sluit navigatiemenu</string>
+ <string name="action_example">Voorbeeld</string>
+ <string name="action_settings">Instellingen</string>
+ <string name="void_vpn_establish">Bitmaks blokkert alle uitgaande internetverkeer.</string>
+ <string name="void_vpn_error_establish">Niet gelukt om een blokkerende VPN op te zetten.</string>
+ <string name="void_vpn_stopped">Gestopt met blokkkeren van alle uitgaande internetverkeer.</string>
+ <string name="void_vpn_title">Blokkeert verkeer</string>
+ <string name="update_provider_details">Update provider details</string>
<string name="update_certificate">Update certificaat</string>
+ <string name="warning_eip_json_corrupted">Updaten providerconfiguratie mislukt</string>
+ <string name="eip_json_corrupted_user_message">Updaten providerconfiguratie mislukt. Login of probeer opnieuw.</string>
+ <string name="warning_corrupted_provider_details">Opgeslagen providerdetails zijn corrupt. Je kan Bitmask updaten (aanbevolen) of de provider details updaten met een commercieel CA-certificaat.</string>
+ <string name="warning_corrupted_provider_cert">Opgeslagen providercertificaat is ongeldig. Je kan Bitmask updaten (aanbevolen) of het provider certificaat updaten met een commercieel CA-certificaat.</string>
+ <string name="warning_expired_provider_cert">Opgeslagen providercertificaat is verlopen. Je kan Bitmask updaten (aanbevolen) of het provider certificaat updaten met een commercieel CA-certificaat.</string>
+ <string name="downloading_vpn_certificate_failed">Downloaden van het VPN certificaat mislukt. Probeer opnieuw of kies een andere provider.</string>
+ <string name="vpn_certificate_is_invalid">VPN certificaat is ongeldig. Probeer een nieuw te downloaden.</string>
+ <string name="vpn_certificate_user_message">Het VPN certificaat in ongeldig. Gelieve opnieuw in te loggen om een nieuw te downloaden.</string>
+ <string name="save_battery">Batterij besparen</string>
+ <string name="save_battery_message">Achtergrond dataverbindingen worden in slaapstand gezet wanneer uw telefoon inactief is. Deze functionaliteit is nog experimenteel.</string>
</resources>
diff --git a/app/src/main/res/values-no/strings.xml b/app/src/main/res/values-no/strings.xml
index 42a03df0..1e825b9d 100644
--- a/app/src/main/res/values-no/strings.xml
+++ b/app/src/main/res/values-no/strings.xml
@@ -99,7 +99,12 @@
<string name="void_vpn_title">Blokkerer trafikk</string>
<string name="update_provider_details">Oppdater tilbyderdetaljer</string>
<string name="update_certificate">Oppdater sertifikat</string>
+ <string name="warning_eip_json_corrupted">Oppdatering av tilbyderoppsett mislyktes</string>
+ <string name="eip_json_corrupted_user_message">Oppdatering av tilbyderoppsett mislyktes. Logg inn og prøv igjen.</string>
<string name="warning_corrupted_provider_details">De lagrede tilbyderdetaljene er skadet. Du kan enten oppdatere Bitmask (anbefalt) eller oppdatere tilbyderdetaljene til å bruke et kommersielt CA-sertifikat.</string>
<string name="warning_corrupted_provider_cert">Lagret tilbydersertifikat er ugyldig. Du kan enten oppdatere Bitmask (anbefalt) eller oppdatere tilbydersertifikatet til å bruke et kommersielt CA-sertifikat.</string>
<string name="warning_expired_provider_cert">Det lagrede tilbydersertifikatet er utløpt. Du kan enten oppdatere Bitmask (anbefalt) eller oppdatere tilbydersertifikatet til å bruke et kommersielt CA-sertifikat.</string>
+ <string name="downloading_vpn_certificate_failed">Nedlasting av VPN-sertifikat mislyktes. Prøv igjen eller velg en annen tilbyder.</string>
+ <string name="vpn_certificate_is_invalid">VPN-sertifikatet er ugyldig. Prøv å laste ned et nytt et.</string>
+ <string name="vpn_certificate_user_message">VPN-sertifikatet er ugyldig. Logg inn for å laste ned et nytt et.</string>
</resources>
diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml
index b810af61..70efcd9d 100644
--- a/app/src/main/res/values-uk/strings.xml
+++ b/app/src/main/res/values-uk/strings.xml
@@ -9,6 +9,7 @@
<string name="new_provider_uri">Назва домена</string>
<string name="username_hint">ім\'я користувача</string>
<string name="password_hint">пароль</string>
+ <string name="about_fragment_title">Про</string>
<string name="login_button">Вхід у систему</string>
<string name="setup_error_configure_button">Налаштування</string>
<string name="setup_error_close_button">Вихід</string>
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index fa8fcd6c..71b6d437 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -85,6 +85,7 @@
<string name="vpn.button.turn.off">Turn off</string>
<string name="vpn_button_turn_off_blocking">Stop blocking</string>
<string name="vpn_securely_routed">Your traffic is securely routed through:</string>
+ <string name="vpn_securely_routed_no_internet">No internet connection. Your traffic will be securely routed through:</string>
<string name="bitmask_log">Bitmask Log</string>
<string name="title_activity_main">Bitmask</string>
<string name="log_fragment_title">Log</string>
@@ -106,5 +107,7 @@
<string name="warning_expired_provider_cert">Stored provider certificate is expired. You can either update Bitmask (recommended) or update the provider certificate using a commercial CA certificate.</string>
<string name="downloading_vpn_certificate_failed">Downloading the VPN certificate failed. Try again or choose another provider.</string>
<string name="vpn_certificate_is_invalid">VPN certificate is invalid. Try to download a new one.</string>
- <string name="vpn_certificate_user_message">The VPN certificate is invalid. Please log in to do download a new one.</string>
+ <string name="vpn_certificate_user_message">The VPN certificate is invalid. Please log in to download a new one.</string>
+ <string name="save_battery">Save battery</string>
+ <string name="save_battery_message">Background data connections will hibernate when your phone is inactive. This feature is still experimental.</string>
</resources>
diff --git a/app/src/main/res/values/untranslatable.xml b/app/src/main/res/values/untranslatable.xml
index 59605609..2e91366a 100644
--- a/app/src/main/res/values/untranslatable.xml
+++ b/app/src/main/res/values/untranslatable.xml
@@ -1,8 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="notifcation_title_bitmask">Bitmask - %s</string>
-
- <string name="copyright_leapgui" translatable="false">Copyright 2012-2017\nLEAP Encryption Access Project &lt;info@leap.se></string>
+ <string name="copyright_leapgui" translatable="false">Copyright 2012-2018\nLEAP Encryption Access Project &lt;info@leap.se></string>
<string name="opevpn_copyright" translatable="false">Copyright © 2002–2010 OpenVPN Technologies, Inc. &lt;sales@openvpn.net>\n
"OpenVPN" is a trademark of OpenVPN Technologies, Inc.</string>
<string name="lzo_copyright" translatable="false">Copyright © 1996 – 2011 Markus Franz Xaver Johannes Oberhumer</string>
@@ -15,20 +14,21 @@
<string name="openvpn" translatable="false">OpenVPN</string>
<string name="lzo" translatable="false">LZO</string>
<string name="openssl" translatable="false">OpenSSL</string>
+ <string name="mbetTLS" translatable="false">Mbed TLS</string>
+ <string name="copyright_mbetTLS" translatable="false">Unless specifically indicated otherwise in a file, files are licensed under the Apache 2.0 license, as can be found in: apache-2.0.txt. \n
+ https://github.com/schwabe/polarssl/blob/icsopenvpn_270/apache-2.0.txt
+ </string>
+ <string name="asio">Asio</string>
+ <string name="copyright_asio">Boost Software License - Version 1.0 - August 17th, 2003</string>
+ <string name="openvpn3">OpenVPN 3</string>
+ <string name="copyright_openvpn3">GNU AFFERO GENERAL PUBLIC LICENSE\n
+ Version 3, 19 November 2007</string>
<string name="unknown_state" translatable="false">Unknown state</string>
-
<string name="copyright_blinktgui" translatable="false">Copyright 2012–2017 Arne Schwabe &lt;arne@rfc2549.org></string>
-
<string name="defaultserver" translatable="false">openvpn.uni-paderborn.de</string>
<string name="defaultport" translatable="false">1194</string>
<string name="copyright_file_dialog" translatable="false">File Dialog based on work by Alexander Ponomarev</string>
-
-
-
<string name="file_dialog" translatable="false">File Dialog</string>
-
-
-
<string name="permission_description">Allows another app to control OpenVPN</string>
<string name="bouncy_castle" translatable="false">Bouncy Castle Crypto APIs</string>
<string name="copyright_bouncycastle" translatable="false">Copyright © 2000–2012 The Legion Of The Bouncy Castle (http://www.bouncycastle.org)</string>
diff --git a/app/src/production/java/se/leap/bitmaskclient/ProviderApiManager.java b/app/src/production/java/se/leap/bitmaskclient/ProviderApiManager.java
index e5ee6c49..885d69db 100644
--- a/app/src/production/java/se/leap/bitmaskclient/ProviderApiManager.java
+++ b/app/src/production/java/se/leap/bitmaskclient/ProviderApiManager.java
@@ -34,6 +34,7 @@ import se.leap.bitmaskclient.eip.EIP;
import static android.text.TextUtils.isEmpty;
import static se.leap.bitmaskclient.Constants.BROADCAST_RESULT_KEY;
+import static se.leap.bitmaskclient.Constants.PROVIDER_KEY;
import static se.leap.bitmaskclient.Constants.PROVIDER_VPN_CERTIFICATE;
import static se.leap.bitmaskclient.ProviderAPI.ERRORS;
import static se.leap.bitmaskclient.ProviderSetupFailedDialog.DOWNLOAD_ERRORS.ERROR_CERTIFICATE_PINNING;
@@ -84,6 +85,7 @@ public class ProviderApiManager extends ProviderApiManagerBase {
//provider details invalid
if (currentDownload.containsKey(ERRORS)) {
+ currentDownload.putParcelable(PROVIDER_KEY, provider);
return currentDownload;
}
@@ -121,7 +123,7 @@ public class ProviderApiManager extends ProviderApiManagerBase {
providerDotJsonString = downloadFromApiUrlWithProviderCA("/provider.json", caCert, providerDefinition);
}
- if (!isValidJson(providerDotJsonString)) {
+ if (ConfigHelper.checkErroneousDownload(providerDotJsonString) || !isValidJson(providerDotJsonString)) {
setErrorResult(result, malformed_url, null);
return result;
}
@@ -177,9 +179,7 @@ public class ProviderApiManager extends ProviderApiManagerBase {
protected Bundle updateVpnCertificate(Provider provider) {
Bundle result = new Bundle();
try {
- JSONObject providerJson = provider.getDefinition();
- String providerMainUrl = providerJson.getString(Provider.API_URL);
- URL newCertStringUrl = new URL(providerMainUrl + "/" + providerJson.getString(Provider.API_VERSION) + "/" + PROVIDER_VPN_CERTIFICATE);
+ URL newCertStringUrl = new URL(provider.getApiUrlWithVersion() + "/" + PROVIDER_VPN_CERTIFICATE);
String certString = downloadWithProviderCA(provider.getCaCert(), newCertStringUrl.toString());
if (ConfigHelper.checkErroneousDownload(certString)) {
@@ -194,7 +194,7 @@ public class ProviderApiManager extends ProviderApiManagerBase {
}
}
return loadCertificate(provider, certString);
- } catch (IOException | JSONException e) {
+ } catch (IOException e) {
// TODO try to get Provider Json
setErrorResult(result, downloading_vpn_certificate_failed, null);
e.printStackTrace();
diff --git a/app/src/production/java/se/leap/bitmaskclient/ProviderListActivity.java b/app/src/production/java/se/leap/bitmaskclient/ProviderListActivity.java
index b6e67331..cf1d1aa6 100644
--- a/app/src/production/java/se/leap/bitmaskclient/ProviderListActivity.java
+++ b/app/src/production/java/se/leap/bitmaskclient/ProviderListActivity.java
@@ -39,11 +39,10 @@ public class ProviderListActivity extends ProviderListBaseActivity {
setUpProvider();
}
+ @Override
public void showAndSelectProvider(String provider_main_url) {
try {
provider = new Provider(new URL((provider_main_url)));
- adapter.add(provider);
- adapter.saveProviders();
autoSelectProvider(provider);
} catch (MalformedURLException e) {
e.printStackTrace();
diff --git a/app/src/test/java/se/leap/bitmaskclient/ProviderTest.java b/app/src/test/java/se/leap/bitmaskclient/ProviderTest.java
index 794c3087..495d5b3f 100644
--- a/app/src/test/java/se/leap/bitmaskclient/ProviderTest.java
+++ b/app/src/test/java/se/leap/bitmaskclient/ProviderTest.java
@@ -1,9 +1,15 @@
package se.leap.bitmaskclient;
+import org.json.JSONException;
import org.junit.Test;
+import java.io.IOException;
+import java.util.HashSet;
+import java.util.Set;
+
import se.leap.bitmaskclient.testutils.TestSetupHelper;
+import static junit.framework.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
/**
@@ -18,4 +24,28 @@ public class ProviderTest {
assertTrue("Providers should be same:", p1.equals(p2));
}
+ @Test
+ public void testEquals_sameFields_returnsFalse() throws Exception {
+ Provider p1 = TestSetupHelper.getConfiguredProvider();
+ Provider p2 = TestSetupHelper.getConfiguredProvider();
+ p2.setMainUrl("http://somethingsdiffer.org");
+ assertFalse("Providers should be same:", p1.equals(p2));
+ }
+
+ // see ProviderManagerTest testing add(...)
+ @Test
+ public void testEqualsThroughSetContains_differentFields_returnsFalse() throws Exception {
+ Provider p1 = TestSetupHelper.getConfiguredProvider();
+ Provider p2 = TestSetupHelper.getConfiguredProvider();
+ p2.setMainUrl("http://somethingsdiffer.org");
+ Provider p3 = new Provider("https://anotherprovider.net");
+
+ Set<Provider> defaultProviders = new HashSet<>();
+ defaultProviders.add(p1);
+ defaultProviders.add(p2);
+
+ assertTrue(defaultProviders.contains(p1));
+ assertTrue(defaultProviders.contains(p2));
+ assertFalse(defaultProviders.contains(p3));
+ }
}
diff --git a/app/src/test/java/se/leap/bitmaskclient/testutils/MockHelper.java b/app/src/test/java/se/leap/bitmaskclient/testutils/MockHelper.java
index c2362c7b..d85b050f 100644
--- a/app/src/test/java/se/leap/bitmaskclient/testutils/MockHelper.java
+++ b/app/src/test/java/se/leap/bitmaskclient/testutils/MockHelper.java
@@ -14,6 +14,7 @@ import org.json.JSONObject;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
+import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.security.NoSuchAlgorithmException;
@@ -29,7 +30,6 @@ import java.util.Set;
import okhttp3.OkHttpClient;
import se.leap.bitmaskclient.ConfigHelper;
-import se.leap.bitmaskclient.Constants;
import se.leap.bitmaskclient.OkHttpClientGenerator;
import se.leap.bitmaskclient.Provider;
import se.leap.bitmaskclient.R;
@@ -343,6 +343,18 @@ public class MockHelper {
return resultReceiver;
}
+ public static void mockConfigHelperForFileInputStream() throws FileNotFoundException {
+ mockStatic(ConfigHelper.class);
+ when(ConfigHelper.loadInputStreamAsString(any(InputStream.class))).thenCallRealMethod();
+ when(ConfigHelper.getInputStreamFrom(anyString())).thenAnswer(new Answer<InputStream>() {
+ @Override
+ public InputStream answer(InvocationOnMock invocation) throws Throwable {
+ String filename = (String) invocation.getArguments()[0];
+ return getClass().getClassLoader().getResourceAsStream(filename);
+ }
+ });
+ }
+
public static void mockConfigHelper(String mockedFingerprint, final Provider providerFromPrefs) throws CertificateEncodingException, NoSuchAlgorithmException {
// FIXME use MockSharedPreferences instead of provider
mockStatic(ConfigHelper.class);
@@ -366,6 +378,7 @@ public class MockHelper {
when(ConfigHelper.getFingerprintFromCertificate(any(X509Certificate.class), anyString())).thenReturn(mockedFingerprint);
when(ConfigHelper.checkErroneousDownload(anyString())).thenCallRealMethod();
when(ConfigHelper.parseX509CertificateFromString(anyString())).thenCallRealMethod();
+ when(ConfigHelper.loadInputStreamAsString(any(InputStream.class))).thenCallRealMethod();
}
public static void mockFingerprintForCertificate(String mockedFingerprint) throws CertificateEncodingException, NoSuchAlgorithmException {
mockStatic(ConfigHelper.class);
diff --git a/app/src/test/resources/externalDir/leapcolombia.json b/app/src/test/resources/externalDir/leapcolombia.json
new file mode 100644
index 00000000..6820988c
--- /dev/null
+++ b/app/src/test/resources/externalDir/leapcolombia.json
@@ -0,0 +1,3 @@
+{
+ "main_url" : "https://leapcolombia.org"
+} \ No newline at end of file
diff --git a/app/src/test/resources/preconfigured/calyx.net.json b/app/src/test/resources/preconfigured/calyx.net.json
new file mode 100644
index 00000000..30ab43c5
--- /dev/null
+++ b/app/src/test/resources/preconfigured/calyx.net.json
@@ -0,0 +1,37 @@
+{
+ "api_uri": "https://calyx.net:4430",
+ "api_version": "1",
+ "ca_cert_fingerprint": "SHA256: 43683c9ba3862c5384a8c1885072fcac40b5d2d4dd67331443f13a3077fa2e69",
+ "ca_cert_uri": "https://calyx.net/ca.crt",
+ "default_language": "en",
+ "description": {
+ "en": "Calyx Institute privacy focused ISP testbed"
+ },
+ "domain": "calyx.net",
+ "enrollment_policy": "open",
+ "languages": [
+ "en"
+ ],
+ "name": {
+ "en": "calyx"
+ },
+ "service": {
+ "allow_anonymous": false,
+ "allow_free": true,
+ "allow_limited_bandwidth": false,
+ "allow_paid": false,
+ "allow_registration": true,
+ "allow_unlimited_bandwidth": true,
+ "bandwidth_limit": 102400,
+ "default_service_level": 1,
+ "levels": {
+ "1": {
+ "description": "Please donate.",
+ "name": "free"
+ }
+ }
+ },
+ "services": [
+ "openvpn"
+ ]
+} \ No newline at end of file
diff --git a/app/src/test/resources/preconfigured/calyx.net.pem b/app/src/test/resources/preconfigured/calyx.net.pem
new file mode 100644
index 00000000..cedb2e38
--- /dev/null
+++ b/app/src/test/resources/preconfigured/calyx.net.pem
@@ -0,0 +1,31 @@
+-----BEGIN CERTIFICATE-----
+MIIFYzCCA0ugAwIBAgIBATANBgkqhkiG9w0BAQ0FADBEMQ4wDAYDVQQKDAVjYWx5
+eDEaMBgGA1UECwwRaHR0cHM6Ly9jYWx5eC5uZXQxFjAUBgNVBAMMDWNhbHl4IFJv
+b3QgQ0EwHhcNMTMwNzAyMDAwMDAwWhcNMjMwNzAyMDAwMDAwWjBEMQ4wDAYDVQQK
+DAVjYWx5eDEaMBgGA1UECwwRaHR0cHM6Ly9jYWx5eC5uZXQxFjAUBgNVBAMMDWNh
+bHl4IFJvb3QgQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDupdnx
+Bgat537XOqrZOulE/RvjoXB1S07sy9/MMtksXFoQuWJZRCSTp1Jaqg3H/e9o1nct
+LQO91+izfJe07TUyajFl7CfllYgMeyKTYcT85dFwNX4pcIHZr8UpmO0MpGBoR4W1
+8cPa3vxAG0CsyUmrASJVyhRouk4qazRosM5RwBxTdMzCK7L3SwqPQoxlY9YmRJlD
+XYZlK5VMJd0dj9XxhMeFs5n43R0bsDENryrExSbuxoNfnUoQg3wffKk+Z0gW7YgW
+ivPsbObqOgXUuBEU0xr9xMNBpU33ffLIsccrHq1EKp8zGfCOcww6v7+zEadUkVLo
+6j/rRhYYgRw9lijZG1rMuV/mTGnUqbjHsdoz5mzkFFWeTSqo44lvhveUyCcwRNmi
+2sjS77l0fCTzfreufffFoOEcRVMRfsnJdu/xPeARoXILEx8nQ421mSn6spOZlDQr
+Tt0T0BAWt+VNc+m0IGSW3SwS7r5MUyQ/M5GrbQBGi5W2SzPriKZ79YTOwPVmXKLZ
+vJoEuKRDkEPJLBAhcD5oSQljOm/Wp/hjmRH4HnI1y4XMshWlDsyRDB1Au5yrsfwN
+noFVSskEcbXlZfNgml4lktLBqz+qwsw+voq6Ak7ROKbc0ii5s8+iNMbAtIK7GcFF
+kuKKIyRmmGlDim/SDhlNdWo7Ah4Akde7zfWufwIDAQABo2AwXjAdBgNVHQ4EFgQU
+AY8+K4ZupAQ+L9ttFJG3vaLBq5gwDgYDVR0PAQH/BAQDAgIEMAwGA1UdEwQFMAMB
+Af8wHwYDVR0jBBgwFoAUAY8+K4ZupAQ+L9ttFJG3vaLBq5gwDQYJKoZIhvcNAQEN
+BQADggIBAOpXi5o3g/2o2rPa53iG7Zgcy8RpePGgZk6xknGYWeLamEqSh+XWQZ2w
+2kQP54bf8HfPj3ugJBWsVtYAs/ltJwzeBfYDrwEJd1N8tw2IRuGlQOWiTAVVLBj4
+Zs+dikSuMoA399f/7BlUIEpVLUiV/emTtbkjFnDeKEV9zql6ypR0BtR8Knf8ALvL
+YfMsWLvTe4rXeypzxIaE2pn8ttcXLYAX0ml2MofTi5xcDhMn1vznKIvs82xhncQx
+I1MJMWqPHNHgJUJpA+y1IFh5LPbpag9PKQ0yQ9sM+/dyGumF2jElsMw71flh/Txr
+2dEv8+FNV1pPK26XJZBK24rNWFs30eAFfH9EQCwVla174I4PDoWqsIR7vtQMObDt
+Bq34R3TjjJJIt2sCSlYLooWwiK7Q+d/SgYqA+MSDmmwhzm86ToK6cwbCsvuw1AxR
+X6VIs4U8wOotgljzX/CSpKqlxcqZjhnAuelZ1+KiN8RHKPj7AzSLYOv/YwTjLTIq
+EOxquoNR58uDa5pBG22a7xWbSaKosn/mEl8SrUr6klzzc8Vh09IMoxrw74uLdAg2
+1jnrhm7qg91Ttb0aXiqbV+Kg/qQzojdewnnoBFnv4jaQ3y8zDCfMhsBtWlWz4Knb
+Zqga1WyRm3Gj1j6IV0oOincYMrw5YA7bgXpwop/Lo/mmliMA14ps
+-----END CERTIFICATE----- \ No newline at end of file
diff --git a/app/src/test/resources/preconfigured/demo.bitmask.net.json b/app/src/test/resources/preconfigured/demo.bitmask.net.json
new file mode 100644
index 00000000..e7fe6099
--- /dev/null
+++ b/app/src/test/resources/preconfigured/demo.bitmask.net.json
@@ -0,0 +1,42 @@
+{
+ "api_uri": "https://api.demo.bitmask.net:4430",
+ "api_version": "1",
+ "ca_cert_fingerprint": "SHA256: 0f17c033115f6b76ff67871872303ff65034efe7dd1b910062ca323eb4da5c7e",
+ "ca_cert_uri": "https://demo.bitmask.net/ca.crt",
+ "default_language": "en",
+ "description": {
+ "el": "demo.bitmask.net allows you to test the Bitmask application. User accounts may be periodically deleted.",
+ "en": "demo.bitmask.net allows you to test the Bitmask application. User accounts may be periodically deleted.",
+ "es": "demo.bitmask.net allows you to test the Bitmask application. User accounts may be periodically deleted."
+ },
+ "domain": "demo.bitmask.net",
+ "enrollment_policy": "open",
+ "languages": [
+ "de",
+ "en",
+ "es",
+ "pt"
+ ],
+ "name": {
+ "en": "Bitmask"
+ },
+ "service": {
+ "allow_anonymous": true,
+ "allow_free": true,
+ "allow_limited_bandwidth": false,
+ "allow_paid": false,
+ "allow_registration": true,
+ "allow_unlimited_bandwidth": true,
+ "bandwidth_limit": 102400,
+ "default_service_level": 1,
+ "levels": {
+ "1": {
+ "description": "Please donate.",
+ "name": "free"
+ }
+ }
+ },
+ "services": [
+ "openvpn"
+ ]
+} \ No newline at end of file
diff --git a/app/src/test/resources/preconfigured/demo.bitmask.net.pem b/app/src/test/resources/preconfigured/demo.bitmask.net.pem
new file mode 100644
index 00000000..9a422161
--- /dev/null
+++ b/app/src/test/resources/preconfigured/demo.bitmask.net.pem
@@ -0,0 +1,32 @@
+-----BEGIN CERTIFICATE-----
+MIIFbzCCA1egAwIBAgIBATANBgkqhkiG9w0BAQ0FADBKMRgwFgYDVQQDDA9CaXRt
+YXNrIFJvb3QgQ0ExEDAOBgNVBAoMB0JpdG1hc2sxHDAaBgNVBAsME2h0dHBzOi8v
+Yml0bWFzay5uZXQwHhcNMTIxMTA2MDAwMDAwWhcNMjIxMTA2MDAwMDAwWjBKMRgw
+FgYDVQQDDA9CaXRtYXNrIFJvb3QgQ0ExEDAOBgNVBAoMB0JpdG1hc2sxHDAaBgNV
+BAsME2h0dHBzOi8vYml0bWFzay5uZXQwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAw
+ggIKAoICAQC1eV4YvayaU+maJbWrD4OHo3d7S1BtDlcvkIRS1Fw3iYDjsyDkZxai
+dHp4EUasfNQ+EVtXUvtk6170EmLco6Elg8SJBQ27trE6nielPRPCfX3fQzETRfvB
+7tNvGw4Jn2YKiYoMD79kkjgyZjkJ2r/bEHUSevmR09BRp86syHZerdNGpXYhcQ84
+CA1+V+603GFIHnrP+uQDdssW93rgDNYu+exT+Wj6STfnUkugyjmPRPjL7wh0tzy+
+znCeLl4xiV3g9sjPnc7r2EQKd5uaTe3j71sDPF92KRk0SSUndREz+B1+Dbe/RGk4
+MEqGFuOzrtsgEhPIX0hplhb0Tgz/rtug+yTT7oJjBa3u20AAOQ38/M99EfdeJvc4
+lPFF1XBBLh6X9UKF72an2NuANiX6XPySnJgZ7nZ09RiYZqVwu/qt3DfvLfhboq+0
+bQvLUPXrVDr70onv5UDjpmEA/cLmaIqqrduuTkFZOym65/PfAPvpGnt7crQj/Ibl
+DEDYZQmP7AS+6zBjoOzNjUGE5r40zWAR1RSi7zliXTu+yfsjXUIhUAWmYR6J3KxB
+lfsiHBQ+8dn9kC3YrUexWoOqBiqJOAJzZh5Y1tqgzfh+2nmHSB2dsQRs7rDRRlyy
+YMbkpzL9ZsOUO2eTP1mmar6YjCN+rggYjRrX71K2SpBG6b1zZxOG+wIDAQABo2Aw
+XjAdBgNVHQ4EFgQUuYGDLL2sswnYpHHvProt1JU+D48wDgYDVR0PAQH/BAQDAgIE
+MAwGA1UdEwQFMAMBAf8wHwYDVR0jBBgwFoAUuYGDLL2sswnYpHHvProt1JU+D48w
+DQYJKoZIhvcNAQENBQADggIBADeG67vaFcbITGpi51264kHPYPEWaXUa5XYbtmBl
+cXYyB6hY5hv/YNuVGJ1gWsDmdeXEyj0j2icGQjYdHRfwhrbEri+h1EZOm1cSBDuY
+k/P5+ctHyOXx8IE79DBsZ6IL61UKIaKhqZBfLGYcWu17DVV6+LT+AKtHhOrv3TSj
+RnAcKnCbKqXLhUPXpK0eTjPYS2zQGQGIhIy9sQXVXJJJsGrPgMxna1Xw2JikBOCG
+htD/JKwt6xBmNwktH0GI/LVtVgSp82Clbn9C4eZN9E5YbVYjLkIEDhpByeC71QhX
+EIQ0ZR56bFuJA/CwValBqV/G9gscTPQqd+iETp8yrFpAVHOW+YzSFbxjTEkBte1J
+aF0vmbqdMAWLk+LEFPQRptZh0B88igtx6tV5oVd+p5IVRM49poLhuPNJGPvMj99l
+mlZ4+AeRUnbOOeAEuvpLJbel4rhwFzmUiGoeTVoPZyMevWcVFq6BMkS+jRR2w0jK
+G6b0v5XDHlcFYPOgUrtsOBFJVwbutLvxdk6q37kIFnWCd8L3kmES5q4wjyFK47Co
+Ja8zlx64jmMZPg/t3wWqkZgXZ14qnbyG5/lGsj5CwVtfDljrhN0oCWK1FZaUmW3d
+69db12/g4f6phldhxiWuGC/W6fCW5kre7nmhshcltqAJJuU47iX+DarBFiIj816e
+yV8e
+-----END CERTIFICATE----- \ No newline at end of file
diff --git a/app/src/test/resources/preconfigured/riseup.net.json b/app/src/test/resources/preconfigured/riseup.net.json
new file mode 100644
index 00000000..9a5ec79e
--- /dev/null
+++ b/app/src/test/resources/preconfigured/riseup.net.json
@@ -0,0 +1,37 @@
+{
+ "api_uri": "https://api.black.riseup.net:443",
+ "api_version": "1",
+ "ca_cert_fingerprint": "SHA256: a5244308a1374709a9afce95e3ae47c1b44bc2398c0a70ccbf8b3a8a97f29494",
+ "ca_cert_uri": "https://black.riseup.net/ca.crt",
+ "default_language": "en",
+ "description": {
+ "en": "Riseup is a non-profit collective in Seattle that provides online communication tools for people and groups working toward liberatory social change."
+ },
+ "domain": "riseup.net",
+ "enrollment_policy": "open",
+ "languages": [
+ "en"
+ ],
+ "name": {
+ "en": "Riseup Networks"
+ },
+ "service": {
+ "allow_anonymous": false,
+ "allow_free": true,
+ "allow_limited_bandwidth": false,
+ "allow_paid": false,
+ "allow_registration": true,
+ "allow_unlimited_bandwidth": true,
+ "bandwidth_limit": 102400,
+ "default_service_level": 1,
+ "levels": {
+ "1": {
+ "description": "Please donate.",
+ "name": "free"
+ }
+ }
+ },
+ "services": [
+ "openvpn"
+ ]
+} \ No newline at end of file
diff --git a/app/src/test/resources/preconfigured/riseup.net.pem b/app/src/test/resources/preconfigured/riseup.net.pem
new file mode 100644
index 00000000..c890aff4
--- /dev/null
+++ b/app/src/test/resources/preconfigured/riseup.net.pem
@@ -0,0 +1,32 @@
+-----BEGIN CERTIFICATE-----
+MIIFjTCCA3WgAwIBAgIBATANBgkqhkiG9w0BAQ0FADBZMRgwFgYDVQQKDA9SaXNl
+dXAgTmV0d29ya3MxGzAZBgNVBAsMEmh0dHBzOi8vcmlzZXVwLm5ldDEgMB4GA1UE
+AwwXUmlzZXVwIE5ldHdvcmtzIFJvb3QgQ0EwHhcNMTQwNDI4MDAwMDAwWhcNMjQw
+NDI4MDAwMDAwWjBZMRgwFgYDVQQKDA9SaXNldXAgTmV0d29ya3MxGzAZBgNVBAsM
+Emh0dHBzOi8vcmlzZXVwLm5ldDEgMB4GA1UEAwwXUmlzZXVwIE5ldHdvcmtzIFJv
+b3QgQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC76J4ciMJ8Sg0m
+TP7DF2DT9zNe0Csk4myoMFC57rfJeqsAlJCv1XMzBmXrw8wq/9z7XHv6n/0sWU7a
+7cF2hLR33ktjwODlx7vorU39/lXLndo492ZBhXQtG1INMShyv+nlmzO6GT7ESfNE
+LliFitEzwIegpMqxCIHXFuobGSCWF4N0qLHkq/SYUMoOJ96O3hmPSl1kFDRMtWXY
+iw1SEKjUvpyDJpVs3NGxeLCaA7bAWhDY5s5Yb2fA1o8ICAqhowurowJpW7n5ZuLK
+5VNTlNy6nZpkjt1QycYvNycffyPOFm/Q/RKDlvnorJIrihPkyniV3YY5cGgP+Qkx
+HUOT0uLA6LHtzfiyaOqkXwc4b0ZcQD5Vbf6Prd20Ppt6ei0zazkUPwxld3hgyw58
+m/4UIjG3PInWTNf293GngK2Bnz8Qx9e/6TueMSAn/3JBLem56E0WtmbLVjvko+LF
+PM5xA+m0BmuSJtrD1MUCXMhqYTtiOvgLBlUm5zkNxALzG+cXB28k6XikXt6MRG7q
+hzIPG38zwkooM55yy5i1YfcIi5NjMH6A+t4IJxxwb67MSb6UFOwg5kFokdONZcwj
+shczHdG9gLKSBIvrKa03Nd3W2dF9hMbRu//STcQxOailDBQCnXXfAATj9pYzdY4k
+ha8VCAREGAKTDAex9oXf1yRuktES4QIDAQABo2AwXjAdBgNVHQ4EFgQUC4tdmLVu
+f9hwfK4AGliaet5KkcgwDgYDVR0PAQH/BAQDAgIEMAwGA1UdEwQFMAMBAf8wHwYD
+VR0jBBgwFoAUC4tdmLVuf9hwfK4AGliaet5KkcgwDQYJKoZIhvcNAQENBQADggIB
+AGzL+GRnYu99zFoy0bXJKOGCF5XUXP/3gIXPRDqQf5g7Cu/jYMID9dB3No4Zmf7v
+qHjiSXiS8jx1j/6/Luk6PpFbT7QYm4QLs1f4BlfZOti2KE8r7KRDPIecUsUXW6P/
+3GJAVYH/+7OjA39za9AieM7+H5BELGccGrM5wfl7JeEz8in+V2ZWDzHQO4hMkiTQ
+4ZckuaL201F68YpiItBNnJ9N5nHr1MRiGyApHmLXY/wvlrOpclh95qn+lG6/2jk7
+3AmihLOKYMlPwPakJg4PYczm3icFLgTpjV5sq2md9bRyAg3oPGfAuWHmKj2Ikqch
+Td5CHKGxEEWbGUWEMP0s1A/JHWiCbDigc4Cfxhy56CWG4q0tYtnc2GMw8OAUO6Wf
+Xu5pYKNkzKSEtT/MrNJt44tTZWbKV/Pi/N2Fx36my7TgTUj7g3xcE9eF4JV2H/sg
+tsK3pwE0FEqGnT4qMFbixQmc8bGyuakr23wjMvfO7eZUxBuWYR2SkcP26sozF9PF
+tGhbZHQVGZUTVPyvwahMUEhbPGVerOW0IYpxkm0x/eaWdTc4vPpf/rIlgbAjarnJ
+UN9SaWRlWKSdP4haujnzCoJbM7dU9bjvlGZNyXEekgeT0W2qFeGGp+yyUWw8tNsp
+0BuC1b7uW/bBn/xKm319wXVDvBgZgcktMolak39V7DVO
+-----END CERTIFICATE----- \ No newline at end of file
diff --git a/app/src/test/resources/preconfigured/urls/calyx.net.url b/app/src/test/resources/preconfigured/urls/calyx.net.url
new file mode 100644
index 00000000..807e9e18
--- /dev/null
+++ b/app/src/test/resources/preconfigured/urls/calyx.net.url
@@ -0,0 +1,3 @@
+{
+ "main_url" : "https://calyx.net"
+}
diff --git a/app/src/test/resources/preconfigured/urls/demo.bitmask.net.url b/app/src/test/resources/preconfigured/urls/demo.bitmask.net.url
new file mode 100644
index 00000000..0c4de648
--- /dev/null
+++ b/app/src/test/resources/preconfigured/urls/demo.bitmask.net.url
@@ -0,0 +1,3 @@
+{
+ "main_url" : "https://demo.bitmask.net"
+}
diff --git a/app/src/test/resources/preconfigured/urls/riseup.net.url b/app/src/test/resources/preconfigured/urls/riseup.net.url
new file mode 100644
index 00000000..42cdb979
--- /dev/null
+++ b/app/src/test/resources/preconfigured/urls/riseup.net.url
@@ -0,0 +1,3 @@
+{
+ "main_url" : "https://riseup.net"
+}