diff --git a/app/build.gradle b/app/build.gradle
index 09858cd2ec80bd59fdb68af8ea89600c676b5741..e8f8b78fc0b5b2ab3c8a50ecef1ba12b9a1219db 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -1,7 +1,9 @@
+import java.util.concurrent.ExecutionException
 import java.util.regex.Matcher
 import java.util.regex.Pattern
 
 apply plugin: 'com.android.application'
+def appName = ''
 
 android {
   compileSdkVersion 27
@@ -42,7 +44,7 @@ android {
     }
   }
 
-  flavorDimensions "implementation"
+  flavorDimensions "mode","implementation"
   productFlavors {
     production {
       dimension "implementation"
@@ -50,6 +52,34 @@ android {
     insecure {
       dimension "implementation"
     }
+    normal {
+      dimension "mode"
+    }
+    //Configurations for custom branded app.
+    custom {
+      dimension "mode"
+      //Change the package name as needed
+      applicationId "org.sample.custom"
+      //Set app name here
+      appName = "Custom"
+      resValue "string", "app_name", appName
+      //Change the versionCode as needed
+      versionCode 1
+      //Change the versionName as needed
+      versionName "1.0"
+
+      //Build Config Fields for default donation details
+
+      //This is the donation URL and should be set to the relevant donation page.
+      buildConfigField 'String', 'donation_url', '"https://leap.se/en/about-us/donate"'
+      //The field to enable donations in the app.
+      buildConfigField 'boolean', 'enable_donation', 'true'
+      //The field to enable donation reminder popup in the app if enable_donation is set to 'false' this will be disabled.
+      buildConfigField 'boolean', 'enable_donation_reminder', 'true'
+      //The duration in days to trigger the donation reminder
+      buildConfigField 'int', 'donation_reminder_duration', '30'
+
+    }
   }
 
   buildTypes {
@@ -340,3 +370,41 @@ def getCurrentFlavorForBetaOrRelease() {
     return "";
   }
 }
+
+def getCurrentFlavor() {
+  Gradle gradle = getGradle()
+  String tskReqStr = gradle.getStartParameter().getTaskRequests().toString()
+
+  Pattern pattern;
+
+  if (tskReqStr.contains("assemble"))
+    pattern = Pattern.compile("assemble(\\w+)(Beta|Release|Debug)")
+  else
+    pattern = Pattern.compile("generate(\\w+)(Beta|Release|Debug)")
+
+  Matcher matcher = pattern.matcher(tskReqStr)
+
+  if (matcher.find())
+    return matcher.group(1).toLowerCase()
+  else {
+    return "";
+  }
+}
+
+task checkApplicationIdForCustomFlavor(type: Exec) {
+  def currFlavor = getCurrentFlavor()
+  if (currFlavor.contains("custom")) {
+    android.applicationVariants.all { variant ->
+      def mergedFlavor = variant.mergedFlavor
+      if (variant.flavorName.toString().equalsIgnoreCase(currFlavor) &&
+              mergedFlavor.getApplicationId().equalsIgnoreCase("org.sample.custom"))
+        throw new ExecutionException("ERROR: please change the applicationId(org.sample.custom) if you want to build a custom branded app!")
+    }
+  }
+}
+
+task checkAppNameForCustomFlavor(type: Exec) {
+  def currFlavor = getCurrentFlavor()
+  if (currFlavor.contains("custom") && appName.equalsIgnoreCase("custom"))
+    throw new ExecutionException("ERROR: please change the appName(Custom) if you want to build a custom branded app!")
+}
\ No newline at end of file
diff --git a/app/src/custom/res/drawable/ic_colorsquare.xml b/app/src/custom/res/drawable/ic_colorsquare.xml
new file mode 100644
index 0000000000000000000000000000000000000000..4b60e9dc903e9b398140d857d68bbb52e71a15bb
--- /dev/null
+++ b/app/src/custom/res/drawable/ic_colorsquare.xml
@@ -0,0 +1,52 @@
+<vector android:height="24dp" android:viewportHeight="100.0"
+    android:viewportWidth="100.0" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
+    <path android:pathData="M50,50m-82000,0a82000,82000 0,1 1,164000 0a82000,82000 0,1 1,-164000 0"/>
+    <path android:fillAlpha="1" android:fillColor="#e6ee9c"
+        android:pathData="M50,50 L30664.67,-73860.37A80000,80000 0,0 0,50 -79950Z"
+        android:strokeColor="#e6ee9c" android:strokeWidth="0"/>
+    <path android:fillAlpha="1" android:fillColor="#fff59d"
+        android:pathData="m50,50 l56568.54,-56568.54a80000,80000 0,0 0,-25953.87 -17341.82z"
+        android:strokeColor="#fff59d" android:strokeWidth="0"/>
+    <path android:fillAlpha="1" android:fillColor="#ffe082"
+        android:pathData="m50,50 l73910.37,-30614.67a80000,80000 0,0 0,-17341.82 -25953.87z"
+        android:strokeColor="#ffe082" android:strokeWidth="0"/>
+    <path android:fillAlpha="1" android:fillColor="#ffcc80"
+        android:pathData="M50,50L80050,50A80000,80000 0,0 0,73960.37 -30564.67Z"
+        android:strokeColor="#ffcc80" android:strokeWidth="0"/>
+    <path android:fillAlpha="1" android:fillColor="#ffab91"
+        android:pathData="M50,50 L73960.37,30664.67A80000,80000 0,0 0,80050 50Z"
+        android:strokeColor="#ffab91" android:strokeWidth="0"/>
+    <path android:fillAlpha="1" android:fillColor="#ef9a9a"
+        android:pathData="m50,50 l56568.54,56568.54a80000,80000 0,0 0,17341.82 -25953.87z"
+        android:strokeColor="#ef9a9a" android:strokeWidth="0"/>
+    <path android:fillAlpha="1" android:fillColor="#f48fb1"
+        android:pathData="m50,50 l30614.67,73910.37a80000,80000 0,0 0,25953.87 -17341.82z"
+        android:strokeColor="#f48fb1" android:strokeWidth="0"/>
+    <path android:fillAlpha="1" android:fillColor="#ce93d8"
+        android:pathData="M50,50L50,80050A80000,80000 0,0 0,30664.67 73960.37Z"
+        android:strokeColor="#ce93d8" android:strokeWidth="0"/>
+    <path android:fillAlpha="1" android:fillColor="#b39ddb"
+        android:pathData="M50,50 L-30564.67,73960.37A80000,80000 0,0 0,50 80050Z"
+        android:strokeColor="#b39ddb" android:strokeWidth="0"/>
+    <path android:fillAlpha="1" android:fillColor="#9fa8da"
+        android:pathData="m50,50 l-56568.54,56568.54a80000,80000 0,0 0,25953.87 17341.82z"
+        android:strokeColor="#9fa8da" android:strokeWidth="0"/>
+    <path android:fillAlpha="1" android:fillColor="#90caf9"
+        android:pathData="m50,50 l-73910.37,30614.67a80000,80000 0,0 0,17341.82 25953.87z"
+        android:strokeColor="#90caf9" android:strokeWidth="0"/>
+    <path android:fillAlpha="1" android:fillColor="#81d4fa"
+        android:pathData="m50,50l-80000,0a80000,80000 0,0 0,6089.64 30614.67z"
+        android:strokeColor="#81d4fa" android:strokeWidth="0"/>
+    <path android:fillAlpha="1" android:fillColor="#80deea"
+        android:pathData="M50,50 L-73860.37,-30564.67A80000,80000 0,0 0,-79950 50Z"
+        android:strokeColor="#80deea" android:strokeWidth="0"/>
+    <path android:fillAlpha="1" android:fillColor="#80cbc4"
+        android:pathData="m50,50 l-56568.54,-56568.54a80000,80000 0,0 0,-17341.82 25953.87z"
+        android:strokeColor="#80cbc4" android:strokeWidth="0"/>
+    <path android:fillAlpha="1" android:fillColor="#a5d6a7"
+        android:pathData="m50,50 l-30614.67,-73910.37a80000,80000 0,0 0,-25953.87 17341.82z"
+        android:strokeColor="#a5d6a7" android:strokeWidth="0"/>
+    <path android:fillAlpha="1" android:fillColor="#c5e1a5"
+        android:pathData="m50,50l0,-80000a80000,80000 0,0 0,-30614.67 6089.64z"
+        android:strokeColor="#c5e1a5" android:strokeWidth="0"/>
+</vector>
diff --git a/app/src/custom/res/drawable/ic_launcher_background.xml b/app/src/custom/res/drawable/ic_launcher_background.xml
new file mode 100644
index 0000000000000000000000000000000000000000..d5fccc538c179838bfdce779c26eebb4fa0b5ce9
--- /dev/null
+++ b/app/src/custom/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,170 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="108dp"
+    android:height="108dp"
+    android:viewportHeight="108"
+    android:viewportWidth="108">
+    <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/custom/res/drawable/ic_launcher_foreground.xml b/app/src/custom/res/drawable/ic_launcher_foreground.xml
new file mode 100644
index 0000000000000000000000000000000000000000..c7bd21dbd86990cde81fea8abd3bf904b4546749
--- /dev/null
+++ b/app/src/custom/res/drawable/ic_launcher_foreground.xml
@@ -0,0 +1,34 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:aapt="http://schemas.android.com/aapt"
+    android:width="108dp"
+    android:height="108dp"
+    android:viewportHeight="108"
+    android:viewportWidth="108">
+    <path
+        android:fillType="evenOdd"
+        android:pathData="M32,64C32,64 38.39,52.99 44.13,50.95C51.37,48.37 70.14,49.57 70.14,49.57L108.26,87.69L108,109.01L75.97,107.97L32,64Z"
+        android:strokeColor="#00000000"
+        android:strokeWidth="1">
+        <aapt:attr name="android:fillColor">
+            <gradient
+                android:endX="78.5885"
+                android:endY="90.9159"
+                android:startX="48.7653"
+                android:startY="61.0927"
+                android:type="linear">
+                <item
+                    android:color="#44000000"
+                    android:offset="0.0" />
+                <item
+                    android:color="#00000000"
+                    android:offset="1.0" />
+            </gradient>
+        </aapt:attr>
+    </path>
+    <path
+        android:fillColor="#FFFFFF"
+        android:fillType="nonZero"
+        android:pathData="M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z"
+        android:strokeColor="#00000000"
+        android:strokeWidth="1" />
+</vector>
diff --git a/app/src/custom/res/drawable/splash_page.xml b/app/src/custom/res/drawable/splash_page.xml
new file mode 100644
index 0000000000000000000000000000000000000000..8fe99847e28d3649774d3de923f187d2f9818d92
--- /dev/null
+++ b/app/src/custom/res/drawable/splash_page.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+ <layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <!--Rename ic_splash_background to background image name-->
+ <!--Change gravity to best fit background image to screen-->
+ <item android:drawable="@drawable/ic_splash_background"
+     android:gravity="fill_horizontal|fill_vertical" />
+
+ <!--If a foreground image is not need remove this <item> tag-->
+ <item>
+  <!--Rename mask to foreground image name-->
+  <bitmap
+      android:src="@drawable/mask"
+      android:gravity="center" />
+ </item>
+
+</layer-list>
\ No newline at end of file
diff --git a/app/src/custom/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/custom/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 0000000000000000000000000000000000000000..eca70cfe52eac1ba66ba280a68ca7be8fcf88a16
--- /dev/null
+++ b/app/src/custom/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="@drawable/ic_launcher_foreground" />
+</adaptive-icon>
\ No newline at end of file
diff --git a/app/src/custom/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/custom/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 0000000000000000000000000000000000000000..eca70cfe52eac1ba66ba280a68ca7be8fcf88a16
--- /dev/null
+++ b/app/src/custom/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="@drawable/ic_launcher_foreground" />
+</adaptive-icon>
\ No newline at end of file
diff --git a/app/src/custom/res/mipmap-hdpi/ic_launcher.png b/app/src/custom/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000000000000000000000000000000000000..a2f5908281d070150700378b64a84c7db1f97aa1
Binary files /dev/null and b/app/src/custom/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/app/src/custom/res/mipmap-hdpi/ic_launcher_round.png b/app/src/custom/res/mipmap-hdpi/ic_launcher_round.png
new file mode 100644
index 0000000000000000000000000000000000000000..1b523998081149a985cef0cdf89045b9ed29964a
Binary files /dev/null and b/app/src/custom/res/mipmap-hdpi/ic_launcher_round.png differ
diff --git a/app/src/custom/res/mipmap-mdpi/ic_launcher.png b/app/src/custom/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000000000000000000000000000000000000..ff10afd6e182edb2b1a63c8f984e9070d9f950ba
Binary files /dev/null and b/app/src/custom/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/app/src/custom/res/mipmap-mdpi/ic_launcher_round.png b/app/src/custom/res/mipmap-mdpi/ic_launcher_round.png
new file mode 100644
index 0000000000000000000000000000000000000000..115a4c768a20c9e13185c17043f4c4d12dd4632a
Binary files /dev/null and b/app/src/custom/res/mipmap-mdpi/ic_launcher_round.png differ
diff --git a/app/src/custom/res/mipmap-xhdpi/ic_launcher.png b/app/src/custom/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000000000000000000000000000000000000..dcd3cd8083358269d6ed7894726283bb9bcbbfea
Binary files /dev/null and b/app/src/custom/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/app/src/custom/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/custom/res/mipmap-xhdpi/ic_launcher_round.png
new file mode 100644
index 0000000000000000000000000000000000000000..459ca609d3ae0d3943ab44cdc27feef9256dc6d7
Binary files /dev/null and b/app/src/custom/res/mipmap-xhdpi/ic_launcher_round.png differ
diff --git a/app/src/custom/res/mipmap-xxhdpi/ic_launcher.png b/app/src/custom/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000000000000000000000000000000000000..8ca12fe024be86e868d14e91120a6902f8e88ac6
Binary files /dev/null and b/app/src/custom/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/app/src/custom/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/custom/res/mipmap-xxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000000000000000000000000000000000000..8e19b410a1b15ff180f3dacac19395fe3046cdec
Binary files /dev/null and b/app/src/custom/res/mipmap-xxhdpi/ic_launcher_round.png differ
diff --git a/app/src/custom/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/custom/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000000000000000000000000000000000000..b824ebdd48db917eea2e67a82260a100371f8a24
Binary files /dev/null and b/app/src/custom/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/app/src/custom/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/custom/res/mipmap-xxxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000000000000000000000000000000000000..4c19a13c239cb67b8a2134ddd5f325db1d2d5bee
Binary files /dev/null and b/app/src/custom/res/mipmap-xxxhdpi/ic_launcher_round.png differ
diff --git a/app/src/custom/res/values/custom-theme.xml b/app/src/custom/res/values/custom-theme.xml
new file mode 100644
index 0000000000000000000000000000000000000000..4421adb9de13c973c5ac4eafd007d4b9af73e696
--- /dev/null
+++ b/app/src/custom/res/values/custom-theme.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <!--Colors-->
+    <!--Color of the action bar-->
+    <color name="colorPrimary">#b39ddb</color>
+    <!--Color of the status bar-->
+    <color name="colorPrimaryDark">#ac97d2</color>
+    <!--Font color of the action bar title-->
+    <color name="colorActionBarTitleFont">#ffffff</color>
+    <!--Font color of the action bar subtitle-->
+    <color name="colorActionBarSubtitleFont">#000000</color>
+</resources>
diff --git a/app/src/custom/res/values/strings.xml b/app/src/custom/res/values/strings.xml
new file mode 100644
index 0000000000000000000000000000000000000000..b923b42ba657c0e72bf7913c02861a228c23c453
--- /dev/null
+++ b/app/src/custom/res/values/strings.xml
@@ -0,0 +1,4 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<resources>
+  <string name="donate_message">Please donate today if you value secure communication that is easy for both the end-user and the service provider.</string>
+</resources>
diff --git a/app/src/insecure/java/se/leap/bitmaskclient/ProviderApiManager.java b/app/src/insecure/java/se/leap/bitmaskclient/ProviderApiManager.java
index 61ae790b7ba4a0cd5b6814961660bfc56f08b571..e3fb549f83de7e1016eb607c68f02604e1b3aed2 100644
--- a/app/src/insecure/java/se/leap/bitmaskclient/ProviderApiManager.java
+++ b/app/src/insecure/java/se/leap/bitmaskclient/ProviderApiManager.java
@@ -46,6 +46,7 @@ import javax.net.ssl.X509TrustManager;
 
 import okhttp3.OkHttpClient;
 import se.leap.bitmaskclient.eip.EIP;
+import se.leap.bitmaskclient.utils.ConfigHelper;
 
 import static android.text.TextUtils.isEmpty;
 import static se.leap.bitmaskclient.Constants.BROADCAST_RESULT_KEY;
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 84fb83f83ca97c25cb9b7623f74fa47c51ed9c8b..b1131850137ee48955b8325bd1ffd5b6d034f7ca 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -86,7 +86,7 @@
 
         <activity
             android:name=".MainActivity"
-            android:label="@string/title_activity_main"
+            android:label="@string/app_name"
             android:launchMode="singleTop" />
 
         <activity
diff --git a/app/src/main/java/se/leap/bitmaskclient/Constants.java b/app/src/main/java/se/leap/bitmaskclient/Constants.java
index 7fa09441bda815dbc3889189cd81eaf6a6dec52a..2efc2c1fca2de295dd7a663e52cc23b7bc9d5cc9 100644
--- a/app/src/main/java/se/leap/bitmaskclient/Constants.java
+++ b/app/src/main/java/se/leap/bitmaskclient/Constants.java
@@ -100,6 +100,7 @@ public interface Constants {
     String DONATION_URL = TextUtils.isEmpty(BuildConfig.donation_url) ?
             BuildConfig.default_donation_url : BuildConfig.donation_url;
     String LAST_DONATION_REMINDER_DATE = "last_donation_reminder_date";
+    String FIRST_TIME_USER_DATE = "first_time_user_date";
 
 
 }
diff --git a/app/src/main/java/se/leap/bitmaskclient/EipFragment.java b/app/src/main/java/se/leap/bitmaskclient/EipFragment.java
index 535322e5b66307eec2bfc976fe4799204239544c..51f787b785540fa33bf6f330ecdda80703108a93 100644
--- a/app/src/main/java/se/leap/bitmaskclient/EipFragment.java
+++ b/app/src/main/java/se/leap/bitmaskclient/EipFragment.java
@@ -58,6 +58,7 @@ 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.utils.DateHelper;
 import se.leap.bitmaskclient.views.VpnStateImage;
 
 import static android.view.View.GONE;
@@ -65,6 +66,7 @@ import static android.view.View.VISIBLE;
 import static de.blinkt.openvpn.core.ConnectionStatus.LEVEL_NONETWORK;
 import static se.leap.bitmaskclient.Constants.DONATION_REMINDER_DURATION;
 import static se.leap.bitmaskclient.Constants.EIP_RESTART_ON_BOOT;
+import static se.leap.bitmaskclient.Constants.FIRST_TIME_USER_DATE;
 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;
@@ -530,7 +532,8 @@ public class EipFragment extends Fragment implements Observer {
                 }).setOnDismissListener(new DialogInterface.OnDismissListener() {
                     @Override
                     public void onDismiss(DialogInterface dialog) {
-                        saveLastDonationReminderDate();
+                        preferences.edit().putString(LAST_DONATION_REMINDER_DATE,
+                                DateHelper.getCurrentDateString()).apply();
                     }
                 }).show();
     }
@@ -545,29 +548,32 @@ public class EipFragment extends Fragment implements Observer {
             return false;
         }
 
-        String lastDonationReminderDate = preferences.getString(LAST_DONATION_REMINDER_DATE, null);
-        if (lastDonationReminderDate == null) {
-            return true;
+        String firstTimeUserDate = preferences.getString(FIRST_TIME_USER_DATE, null);
+        if (firstTimeUserDate == null) {
+            preferences.edit().putString(FIRST_TIME_USER_DATE, DateHelper.getCurrentDateString()).apply();
+            return false;
         }
 
-        SimpleDateFormat sdf = new SimpleDateFormat(DATE_PATTERN, Locale.US);
-        Date lastDate;
         try {
-            lastDate = sdf.parse(lastDonationReminderDate);
+            long diffDays;
+
+            diffDays = DateHelper.getDateDiffToCurrentDateInDays(firstTimeUserDate);
+            if (diffDays < 1) {
+                return false;
+            }
+
+            String lastDonationReminderDate = preferences.getString(LAST_DONATION_REMINDER_DATE, null);
+            if (lastDonationReminderDate == null) {
+                return true;
+            }
+            diffDays = DateHelper.getDateDiffToCurrentDateInDays(lastDonationReminderDate);
+            return diffDays >= DONATION_REMINDER_DURATION;
+
         } catch (ParseException e) {
             e.printStackTrace();
             Log.e(TAG, e.getMessage());
             return false;
         }
-
-        Date currentDate = new Date();
-        long diffDays = (currentDate.getTime() - lastDate.getTime()) / ONE_DAY;
-        return diffDays >= DONATION_REMINDER_DURATION;
     }
 
-    private void saveLastDonationReminderDate() {
-        SimpleDateFormat sdf = new SimpleDateFormat(DATE_PATTERN, Locale.US);
-        Date lastDate = new Date();
-        preferences.edit().putString(LAST_DONATION_REMINDER_DATE, sdf.format(lastDate)).apply();
-    }
 }
diff --git a/app/src/main/java/se/leap/bitmaskclient/LeapSRPSession.java b/app/src/main/java/se/leap/bitmaskclient/LeapSRPSession.java
index 3a1fd6e0b4dd25778bf7cc8061d455921686af64..d1f1ed2188989eba46c2cdb79dee54d67b3c7ea6 100644
--- a/app/src/main/java/se/leap/bitmaskclient/LeapSRPSession.java
+++ b/app/src/main/java/se/leap/bitmaskclient/LeapSRPSession.java
@@ -17,12 +17,16 @@
 package se.leap.bitmaskclient;
 
 
-import org.jboss.security.srp.*;
+import org.jboss.security.srp.SRPParameters;
 
-import java.io.*;
-import java.math.*;
-import java.security.*;
-import java.util.*;
+import java.io.UnsupportedEncodingException;
+import java.math.BigInteger;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.util.Arrays;
+
+import se.leap.bitmaskclient.utils.ConfigHelper;
 
 /**
  * Implements all SRP algorithm logic.
diff --git a/app/src/main/java/se/leap/bitmaskclient/MainActivity.java b/app/src/main/java/se/leap/bitmaskclient/MainActivity.java
index 868d2876f3a8b8fbe77bdcff24b3b21bec71e3e8..c44e8a3e70378d6f8fe49349f9af706a0b7bcc65 100644
--- a/app/src/main/java/se/leap/bitmaskclient/MainActivity.java
+++ b/app/src/main/java/se/leap/bitmaskclient/MainActivity.java
@@ -38,6 +38,7 @@ import org.json.JSONObject;
 import se.leap.bitmaskclient.drawer.NavigationDrawerFragment;
 import se.leap.bitmaskclient.eip.EipCommand;
 import se.leap.bitmaskclient.fragments.LogFragment;
+import se.leap.bitmaskclient.utils.ConfigHelper;
 
 import static android.content.Intent.CATEGORY_DEFAULT;
 import static se.leap.bitmaskclient.Constants.BROADCAST_EIP_EVENT;
@@ -61,6 +62,8 @@ import static se.leap.bitmaskclient.ProviderAPI.INCORRECTLY_UPDATED_INVALID_VPN_
 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;
+import static se.leap.bitmaskclient.utils.PreferenceHelper.getSavedProviderFromSharedPreferences;
+import static se.leap.bitmaskclient.utils.PreferenceHelper.storeProviderInPreferences;
 
 
 public class MainActivity extends AppCompatActivity {
@@ -92,7 +95,7 @@ public class MainActivity extends AppCompatActivity {
                 getSupportFragmentManager().findFragmentById(R.id.navigation_drawer);
 
         preferences = getSharedPreferences(SHARED_PREFERENCES, MODE_PRIVATE);
-        provider = ConfigHelper.getSavedProviderFromSharedPreferences(preferences);
+        provider = getSavedProviderFromSharedPreferences(preferences);
 
         // Set up the drawer.
         navigationDrawerFragment.setUp(
@@ -179,7 +182,7 @@ public class MainActivity extends AppCompatActivity {
                 return;
             }
 
-            ConfigHelper.storeProviderInPreferences(preferences, provider);
+            storeProviderInPreferences(preferences, provider);
             navigationDrawerFragment.refresh();
 
             switch (requestCode) {
@@ -296,7 +299,7 @@ public class MainActivity extends AppCompatActivity {
 
             case CORRECTLY_UPDATED_INVALID_VPN_CERTIFICATE:
                 provider = resultData.getParcelable(PROVIDER_KEY);
-                ConfigHelper.storeProviderInPreferences(preferences, provider);
+                storeProviderInPreferences(preferences, provider);
                 EipCommand.startVPN(this, true);
                 break;
             case INCORRECTLY_UPDATED_INVALID_VPN_CERTIFICATE:
diff --git a/app/src/main/java/se/leap/bitmaskclient/Provider.java b/app/src/main/java/se/leap/bitmaskclient/Provider.java
index fd067bf9b63079c88ba836cb285947afc0304d6c..5e5a3a6258982e79620cc3e551ddfcc93be4d8da 100644
--- a/app/src/main/java/se/leap/bitmaskclient/Provider.java
+++ b/app/src/main/java/se/leap/bitmaskclient/Provider.java
@@ -140,7 +140,7 @@ public final class Provider implements Parcelable {
         return definition;
     }
 
-    String getDefinitionString() {
+    public String getDefinitionString() {
         return getDefinition().toString();
     }
 
@@ -148,7 +148,7 @@ public final class Provider implements Parcelable {
         return mainUrl.getDomain();
     }
 
-    String getMainUrlString() {
+    public String getMainUrlString() {
         return getMainUrl().toString();
     }
 
diff --git a/app/src/main/java/se/leap/bitmaskclient/ProviderApiManagerBase.java b/app/src/main/java/se/leap/bitmaskclient/ProviderApiManagerBase.java
index 753172e68e09362b3fbc0960e7e119711c721161..8f3acf1de3f029984e8dcb5c31e6fbe41c855d1a 100644
--- a/app/src/main/java/se/leap/bitmaskclient/ProviderApiManagerBase.java
+++ b/app/src/main/java/se/leap/bitmaskclient/ProviderApiManagerBase.java
@@ -52,8 +52,9 @@ import javax.net.ssl.SSLHandshakeException;
 
 import okhttp3.OkHttpClient;
 import se.leap.bitmaskclient.Constants.CREDENTIAL_ERRORS;
+import se.leap.bitmaskclient.utils.ConfigHelper;
 
-import static se.leap.bitmaskclient.ConfigHelper.getFingerprintFromCertificate;
+import static se.leap.bitmaskclient.utils.ConfigHelper.getFingerprintFromCertificate;
 import static se.leap.bitmaskclient.Constants.BROADCAST_PROVIDER_API_EVENT;
 import static se.leap.bitmaskclient.Constants.BROADCAST_RESULT_CODE;
 import static se.leap.bitmaskclient.Constants.BROADCAST_RESULT_KEY;
@@ -106,6 +107,9 @@ 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;
+import static se.leap.bitmaskclient.utils.ConfigHelper.parseRsaKeyFromString;
+import static se.leap.bitmaskclient.utils.PreferenceHelper.deleteProviderDetailsFromPreferences;
+import static se.leap.bitmaskclient.utils.PreferenceHelper.getFromPersistedProvider;
 
 /**
  * Implements the logic of the http api calls. The methods of this class needs to be called from
@@ -229,7 +233,7 @@ public abstract class ProviderApiManagerBase {
 
     void resetProviderDetails(Provider provider) {
         provider.reset();
-        ConfigHelper.deleteProviderDetailsFromPreferences(preferences, provider.getDomain());
+        deleteProviderDetailsFromPreferences(preferences, provider.getDomain());
     }
 
     String formatErrorMessage(final int toastStringId) {
@@ -765,16 +769,16 @@ public abstract class ProviderApiManagerBase {
     }
 
     protected String getPersistedPrivateKey(String providerDomain) {
-        return ConfigHelper.getFromPersistedProvider(PROVIDER_PRIVATE_KEY, providerDomain, preferences);
+        return getFromPersistedProvider(PROVIDER_PRIVATE_KEY, providerDomain, preferences);
     }
 
     protected String getPersistedVPNCertificate(String providerDomain) {
-        return ConfigHelper.getFromPersistedProvider(PROVIDER_VPN_CERTIFICATE, providerDomain, preferences);
+        return getFromPersistedProvider(PROVIDER_VPN_CERTIFICATE, providerDomain, preferences);
     }
 
     protected JSONObject getPersistedProviderDefinition(String providerDomain) {
         try {
-            return new JSONObject(ConfigHelper.getFromPersistedProvider(Provider.KEY, providerDomain, preferences));
+            return new JSONObject(getFromPersistedProvider(Provider.KEY, providerDomain, preferences));
         } catch (JSONException e) {
             e.printStackTrace();
             return new JSONObject();
@@ -860,7 +864,7 @@ public abstract class ProviderApiManagerBase {
                 }
             }
 
-            RSAPrivateKey key = ConfigHelper.parseRsaKeyFromString(keyString);
+            RSAPrivateKey key = parseRsaKeyFromString(keyString);
             keyString = Base64.encodeToString(key.getEncoded(), Base64.DEFAULT);
             provider.setPrivateKey( "-----BEGIN RSA PRIVATE KEY-----\n" + keyString + "-----END RSA PRIVATE KEY-----");
 
diff --git a/app/src/main/java/se/leap/bitmaskclient/ProviderManager.java b/app/src/main/java/se/leap/bitmaskclient/ProviderManager.java
index 97ba3b9852a3ca9a41edc57bf795e1e5a14307cf..a36c2dec203ac7bccec29dd941050b403d3d1f24 100644
--- a/app/src/main/java/se/leap/bitmaskclient/ProviderManager.java
+++ b/app/src/main/java/se/leap/bitmaskclient/ProviderManager.java
@@ -1,6 +1,7 @@
 package se.leap.bitmaskclient;
 
 import android.content.res.AssetManager;
+import android.support.annotation.VisibleForTesting;
 
 import com.pedrogomez.renderers.AdapteeCollection;
 
@@ -8,9 +9,7 @@ import org.json.JSONException;
 import org.json.JSONObject;
 
 import java.io.File;
-import java.io.FileInputStream;
 import java.io.FileNotFoundException;
-import java.io.FileWriter;
 import java.io.IOException;
 import java.io.InputStream;
 import java.net.MalformedURLException;
@@ -22,6 +21,11 @@ import java.util.Iterator;
 import java.util.List;
 import java.util.Set;
 
+import static se.leap.bitmaskclient.utils.FileHelper.createFile;
+import static se.leap.bitmaskclient.utils.FileHelper.persistFile;
+import static se.leap.bitmaskclient.utils.InputStreamHelper.getInputStreamFrom;
+import static se.leap.bitmaskclient.utils.InputStreamHelper.loadInputStreamAsString;
+
 /**
  * Created by parmegv on 4/12/14.
  */
@@ -37,6 +41,8 @@ public class ProviderManager implements AdapteeCollection<Provider> {
     private static ProviderManager instance;
 
     final private static String URLS = "urls";
+    final private static String EXT_JSON = ".json";
+    final private static String EXT_PEM = ".pem";
 
     public static ProviderManager getInstance(AssetManager assetsManager, File externalFilesDir) {
         if (instance == null)
@@ -45,6 +51,11 @@ public class ProviderManager implements AdapteeCollection<Provider> {
         return instance;
     }
 
+    @VisibleForTesting
+    static void reset() {
+        instance = null;
+    }
+
     private ProviderManager(AssetManager assetManager, File externalFilesDir) {
         this.assetsManager = assetManager;
         addDefaultProviders(assetManager);
@@ -79,8 +90,8 @@ public class ProviderManager implements AdapteeCollection<Provider> {
                     String provider = file.substring(0, file.length() - ".url".length());
                     InputStream provider_file = assetsManager.open(directory + "/" + file);
                     mainUrl = extractMainUrlFromInputStream(provider_file);
-                    certificate = ConfigHelper.loadInputStreamAsString(assetsManager.open(provider + ".pem"));
-                    providerDefinition = ConfigHelper.loadInputStreamAsString(assetsManager.open(provider + ".json"));
+                    certificate = loadInputStreamAsString(assetsManager.open(provider + EXT_PEM));
+                    providerDefinition = loadInputStreamAsString(assetsManager.open(provider + EXT_JSON));
                 } catch (IOException e) {
                     e.printStackTrace();
                 }
@@ -107,7 +118,7 @@ public class ProviderManager implements AdapteeCollection<Provider> {
         Set<Provider> providers = new HashSet<>();
         try {
             for (String file : files) {
-                String mainUrl = extractMainUrlFromInputStream(ConfigHelper.getInputStreamFrom(externalFilesDir.getAbsolutePath() + "/" + file));
+                String mainUrl = extractMainUrlFromInputStream(getInputStreamFrom(externalFilesDir.getAbsolutePath() + "/" + file));
                 providers.add(new Provider(new URL(mainUrl)));
             }
         } catch (MalformedURLException | FileNotFoundException e) {
@@ -219,19 +230,33 @@ public class ProviderManager implements AdapteeCollection<Provider> {
         defaultProviderURLs.clear();
     }
 
-    //FIXME: removed custom providers should be deleted here as well
     void saveCustomProvidersToFile() {
         try {
+            deleteLegacyCustomProviders();
+
             for (Provider provider : customProviders) {
-                File providerFile = new File(externalFilesDir, provider.getName() + ".json");
+                File providerFile = createFile(externalFilesDir, provider.getName() + EXT_JSON);
                 if (!providerFile.exists()) {
-                    FileWriter writer = new FileWriter(providerFile);
-                    writer.write(provider.toJson().toString());
-                    writer.close();
+                    persistFile(providerFile, provider.toJson().toString());
                 }
             }
-        } catch (IOException e) {
+        } catch (IOException | SecurityException e) {
             e.printStackTrace();
         }
     }
+
+    /**
+     * Deletes persisted custom providers from from internal storage that are not in customProviders list anymore
+     */
+    private void deleteLegacyCustomProviders() throws IOException, SecurityException {
+        Set<Provider> persistedCustomProviders = externalFilesDir != null && externalFilesDir.isDirectory() ?
+                providersFromFiles(externalFilesDir.list()) : new HashSet<Provider>();
+            persistedCustomProviders.removeAll(customProviders);
+        for (Provider providerToDelete : persistedCustomProviders) {
+            File providerFile = createFile(externalFilesDir, providerToDelete.getName() + EXT_JSON);
+            if (providerFile.exists()) {
+                providerFile.delete();
+            }
+        }
+    }
 }
diff --git a/app/src/main/java/se/leap/bitmaskclient/StartActivity.java b/app/src/main/java/se/leap/bitmaskclient/StartActivity.java
index 6bbdeb4f554cfc25b1db7419d58ef903a4502ff2..33c13b904f654ef0d9d2f2ea77eb78236dd6bcef 100644
--- a/app/src/main/java/se/leap/bitmaskclient/StartActivity.java
+++ b/app/src/main/java/se/leap/bitmaskclient/StartActivity.java
@@ -15,6 +15,7 @@ import java.lang.annotation.RetentionPolicy;
 import de.blinkt.openvpn.core.VpnStatus;
 import se.leap.bitmaskclient.eip.EipCommand;
 import se.leap.bitmaskclient.userstatus.User;
+import se.leap.bitmaskclient.utils.ConfigHelper;
 
 import static se.leap.bitmaskclient.Constants.APP_ACTION_CONFIGURE_ALWAYS_ON_PROFILE;
 import static se.leap.bitmaskclient.Constants.EIP_RESTART_ON_BOOT;
@@ -24,6 +25,9 @@ import static se.leap.bitmaskclient.Constants.PROVIDER_KEY;
 import static se.leap.bitmaskclient.Constants.REQUEST_CODE_CONFIGURE_LEAP;
 import static se.leap.bitmaskclient.Constants.SHARED_PREFERENCES;
 import static se.leap.bitmaskclient.MainActivity.ACTION_SHOW_VPN_FRAGMENT;
+import static se.leap.bitmaskclient.utils.PreferenceHelper.getSavedProviderFromSharedPreferences;
+import static se.leap.bitmaskclient.utils.PreferenceHelper.providerInSharedPreferences;
+import static se.leap.bitmaskclient.utils.PreferenceHelper.storeProviderInPreferences;
 
 /**
  * Activity shown at startup. Evaluates if App is started for the first time or has been upgraded
@@ -154,9 +158,9 @@ public class StartActivity extends Activity{
     }
 
     private void prepareEIP() {
-        boolean provider_exists = ConfigHelper.providerInSharedPreferences(preferences);
+        boolean provider_exists = providerInSharedPreferences(preferences);
         if (provider_exists) {
-            Provider provider = ConfigHelper.getSavedProviderFromSharedPreferences(preferences);
+            Provider provider = getSavedProviderFromSharedPreferences(preferences);
             if(!provider.isConfigured()) {
                 configureLeapProvider();
             } else {
@@ -186,7 +190,7 @@ public class StartActivity extends Activity{
         if (requestCode == REQUEST_CODE_CONFIGURE_LEAP) {
             if (resultCode == RESULT_OK && data != null && data.hasExtra(Provider.KEY)) {
                 Provider provider = data.getParcelableExtra(Provider.KEY);
-                ConfigHelper.storeProviderInPreferences(preferences, provider);
+                storeProviderInPreferences(preferences, provider);
                 EipCommand.startVPN(this, false);
                 showMainActivity();
             } else if (resultCode == RESULT_CANCELED) {
diff --git a/app/src/main/java/se/leap/bitmaskclient/TLSCompatSocketFactory.java b/app/src/main/java/se/leap/bitmaskclient/TLSCompatSocketFactory.java
index 76d38447c0aadb5297fa1ce78c36703f4aebe02a..cca75bdf713858a4336d194aba0c03931ea03e8a 100644
--- a/app/src/main/java/se/leap/bitmaskclient/TLSCompatSocketFactory.java
+++ b/app/src/main/java/se/leap/bitmaskclient/TLSCompatSocketFactory.java
@@ -22,6 +22,7 @@ import javax.net.ssl.TrustManagerFactory;
 import javax.net.ssl.X509TrustManager;
 
 import okhttp3.OkHttpClient;
+import se.leap.bitmaskclient.utils.ConfigHelper;
 
 /**
  * Created by cyberta on 24.10.17.
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 be4bdf993cff52762ad29a8ef9ed80f6e3b73e62..6e9879dd44e957b147a6f42309209dc52d684d80 100644
--- a/app/src/main/java/se/leap/bitmaskclient/drawer/NavigationDrawerFragment.java
+++ b/app/src/main/java/se/leap/bitmaskclient/drawer/NavigationDrawerFragment.java
@@ -50,7 +50,7 @@ import android.widget.ArrayAdapter;
 import android.widget.CompoundButton;
 import android.widget.ListView;
 
-import se.leap.bitmaskclient.ConfigHelper;
+import se.leap.bitmaskclient.utils.ConfigHelper;
 import se.leap.bitmaskclient.DrawerSettingsAdapter;
 import se.leap.bitmaskclient.DrawerSettingsAdapter.DrawerSettingsItem;
 import se.leap.bitmaskclient.EipFragment;
@@ -64,8 +64,6 @@ 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.ConfigHelper.getShowAlwaysOnDialog;
 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;
@@ -82,6 +80,11 @@ 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;
+import static se.leap.bitmaskclient.utils.PreferenceHelper.getProviderName;
+import static se.leap.bitmaskclient.utils.PreferenceHelper.getSaveBattery;
+import static se.leap.bitmaskclient.utils.PreferenceHelper.getSavedProviderFromSharedPreferences;
+import static se.leap.bitmaskclient.utils.PreferenceHelper.getShowAlwaysOnDialog;
+import static se.leap.bitmaskclient.utils.PreferenceHelper.saveBattery;
 import static se.leap.bitmaskclient.R.string.donate_title;
 
 /**
@@ -383,7 +386,7 @@ public class NavigationDrawerFragment extends Fragment {
                             DrawerSettingsItem item = settingsListAdapter.getDrawerItem(BATTERY_SAVER);
                             item.setChecked(true);
                             settingsListAdapter.notifyDataSetChanged();
-                            ConfigHelper.saveBattery(getContext(), item.isChecked());
+                            saveBattery(getContext(), item.isChecked());
                         }
                     })
                     .setNegativeButton(activity.getString(android.R.string.no), new DialogInterface.OnClickListener() {
@@ -467,14 +470,14 @@ public class NavigationDrawerFragment extends Fragment {
     private void onSwitchItemSelected(int elementType, boolean newStateIsChecked) {
         switch (elementType) {
             case BATTERY_SAVER:
-                if (ConfigHelper.getSaveBattery(getContext()) == newStateIsChecked) {
+                if (getSaveBattery(getContext()) == newStateIsChecked) {
                     //initial ui setup, ignore
                     return;
                 }
                 if (newStateIsChecked) {
                     showExperimentalFeatureAlert();
                 } else {
-                    ConfigHelper.saveBattery(this.getContext(), false);
+                    saveBattery(this.getContext(), false);
                     disableSwitch(BATTERY_SAVER);
                 }
                 break;
@@ -500,7 +503,7 @@ public class NavigationDrawerFragment extends Fragment {
             fragment = new EipFragment();
             fragmentTag = EipFragment.TAG;
             Bundle arguments = new Bundle();
-            Provider currentProvider = ConfigHelper.getSavedProviderFromSharedPreferences(preferences);
+            Provider currentProvider = getSavedProviderFromSharedPreferences(preferences);
             arguments.putParcelable(PROVIDER_KEY, currentProvider);
             fragment.setArguments(arguments);
         } else {
@@ -562,7 +565,7 @@ public class NavigationDrawerFragment extends Fragment {
 
     private void refreshAccountListAdapter() {
         accountListAdapter.clear();
-        String providerName = ConfigHelper.getProviderName(preferences);
+        String providerName = getProviderName(preferences);
         if (providerName == null) {
             //TODO: ADD A header to the ListView containing a useful message.
             //TODO 2: disable switchProvider
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 159bc9a722bc9cba74c9f7b346cdf57517c80fdd..971d973f4aea14b725aa6e4c918b7e1fc74ac1b9 100644
--- a/app/src/main/java/se/leap/bitmaskclient/eip/EIP.java
+++ b/app/src/main/java/se/leap/bitmaskclient/eip/EIP.java
@@ -53,7 +53,6 @@ import se.leap.bitmaskclient.OnBootReceiver;
 import static android.app.Activity.RESULT_CANCELED;
 import static android.app.Activity.RESULT_OK;
 import static android.content.Intent.CATEGORY_DEFAULT;
-import static se.leap.bitmaskclient.ConfigHelper.ensureNotOnMainThread;
 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;
@@ -71,6 +70,7 @@ import static se.leap.bitmaskclient.Constants.PROVIDER_VPN_CERTIFICATE;
 import static se.leap.bitmaskclient.Constants.SHARED_PREFERENCES;
 import static se.leap.bitmaskclient.MainActivityErrorDialog.DOWNLOAD_ERRORS.ERROR_INVALID_VPN_CERTIFICATE;
 import static se.leap.bitmaskclient.R.string.vpn_certificate_is_invalid;
+import static se.leap.bitmaskclient.utils.ConfigHelper.ensureNotOnMainThread;
 
 /**
  * EIP is the abstract base class for interacting with and managing the Encrypted
diff --git a/app/src/main/java/se/leap/bitmaskclient/eip/GatewaysManager.java b/app/src/main/java/se/leap/bitmaskclient/eip/GatewaysManager.java
index a04ede087fb9837127cda6fbcb86ca125da19c88..5b4db5af3be3d7ba2ba5b65c2519561372c315da 100644
--- a/app/src/main/java/se/leap/bitmaskclient/eip/GatewaysManager.java
+++ b/app/src/main/java/se/leap/bitmaskclient/eip/GatewaysManager.java
@@ -35,8 +35,8 @@ import java.util.List;
 import de.blinkt.openvpn.VpnProfile;
 import de.blinkt.openvpn.core.Connection;
 import de.blinkt.openvpn.core.ProfileManager;
-import se.leap.bitmaskclient.ConfigHelper;
 import se.leap.bitmaskclient.Provider;
+import se.leap.bitmaskclient.utils.PreferenceHelper;
 
 import static se.leap.bitmaskclient.Constants.PROVIDER_PRIVATE_KEY;
 import static se.leap.bitmaskclient.Constants.PROVIDER_VPN_CERTIFICATE;
@@ -216,7 +216,7 @@ public class GatewaysManager {
         // to add all gateways from prefs without duplicates, but this should be refactored.
         clearGatewaysAndProfiles();
         fromEipServiceJson(
-                ConfigHelper.getEipDefinitionFromPreferences(preferences)
+                PreferenceHelper.getEipDefinitionFromPreferences(preferences)
         );
     }
 }
diff --git a/app/src/main/java/se/leap/bitmaskclient/eip/VpnCertificateValidator.java b/app/src/main/java/se/leap/bitmaskclient/eip/VpnCertificateValidator.java
index 03dd9d05253e29ad8de44bc7a1ddc36a17e767a7..839047297cdc6cf0e59b68dfe719199e443978ef 100644
--- a/app/src/main/java/se/leap/bitmaskclient/eip/VpnCertificateValidator.java
+++ b/app/src/main/java/se/leap/bitmaskclient/eip/VpnCertificateValidator.java
@@ -22,7 +22,7 @@ import java.security.cert.X509Certificate;
 import java.util.Calendar;
 import java.util.Date;
 
-import se.leap.bitmaskclient.ConfigHelper;
+import se.leap.bitmaskclient.utils.ConfigHelper;
 
 public class VpnCertificateValidator {
     public final static String TAG = VpnCertificateValidator.class.getSimpleName();
diff --git a/app/src/main/java/se/leap/bitmaskclient/fragments/AlwaysOnDialog.java b/app/src/main/java/se/leap/bitmaskclient/fragments/AlwaysOnDialog.java
index 3558f37842dc69c765ecd07119aea524630fc44b..e3d004f55287453f34c152a3e19f1b5eb85b4746 100644
--- a/app/src/main/java/se/leap/bitmaskclient/fragments/AlwaysOnDialog.java
+++ b/app/src/main/java/se/leap/bitmaskclient/fragments/AlwaysOnDialog.java
@@ -17,7 +17,8 @@ import butterknife.InjectView;
 import se.leap.bitmaskclient.R;
 import se.leap.bitmaskclient.views.IconTextView;
 
-import static se.leap.bitmaskclient.ConfigHelper.saveShowAlwaysOnDialog;
+import static se.leap.bitmaskclient.utils.PreferenceHelper.saveShowAlwaysOnDialog;
+
 
 /**
  * Created by cyberta on 25.02.18.
diff --git a/app/src/main/java/se/leap/bitmaskclient/utils/ConfigHelper.java b/app/src/main/java/se/leap/bitmaskclient/utils/ConfigHelper.java
new file mode 100644
index 0000000000000000000000000000000000000000..5bb637b79c4ffbb8df0f99c7324170a1b42efae2
--- /dev/null
+++ b/app/src/main/java/se/leap/bitmaskclient/utils/ConfigHelper.java
@@ -0,0 +1,170 @@
+/**
+ * Copyright (c) 2013 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.utils;
+
+import android.content.Context;
+import android.os.Looper;
+import android.support.annotation.NonNull;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.spongycastle.util.encoders.Base64;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.math.BigInteger;
+import java.security.KeyFactory;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.security.interfaces.RSAPrivateKey;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.PKCS8EncodedKeySpec;
+
+import se.leap.bitmaskclient.ProviderAPI;
+
+/**
+ * Stores constants, and implements auxiliary methods used across all Bitmask Android classes.
+ *
+ * @author parmegv
+ * @author MeanderingCode
+ */
+public class ConfigHelper {
+    final public static String NG_1024 =
+            "eeaf0ab9adb38dd69c33f80afa8fc5e86072618775ff3c0b9ea2314c9c256576d674df7496ea81d3383b4813d692c6e0e0d5d8e250b98be48e495c1d6089dad15dc7d7b46154d6b6ce8ef4ad69b15d4982559b297bcf1885c529f566660e57ec68edbc3c05726cc02fd4cbf4976eaa9afd5138fe8376435b9fc61d2fc0eb06e3";
+    final public static BigInteger G = new BigInteger("2");
+
+    public static boolean checkErroneousDownload(String downloadedString) {
+        try {
+            if (downloadedString == null || downloadedString.isEmpty() || new JSONObject(downloadedString).has(ProviderAPI.ERRORS) || new JSONObject(downloadedString).has(ProviderAPI.BACKEND_ERROR_KEY)) {
+                return true;
+            } else {
+                return false;
+            }
+        } catch (NullPointerException | JSONException e) {
+            return false;
+        }
+    }
+
+    /**
+     * Treat the input as the MSB representation of a number,
+     * and lop off leading zero elements.  For efficiency, the
+     * input is simply returned if no leading zeroes are found.
+     *
+     * @param in array to be trimmed
+     */
+    public static byte[] trim(byte[] in) {
+        if (in.length == 0 || in[0] != 0)
+            return in;
+
+        int len = in.length;
+        int i = 1;
+        while (in[i] == 0 && i < len)
+            ++i;
+        byte[] ret = new byte[len - i];
+        System.arraycopy(in, i, ret, 0, len - i);
+        return ret;
+    }
+
+    public static X509Certificate parseX509CertificateFromString(String certificateString) {
+        java.security.cert.Certificate certificate = null;
+        CertificateFactory cf;
+        try {
+            cf = CertificateFactory.getInstance("X.509");
+
+            certificateString = certificateString.replaceFirst("-----BEGIN CERTIFICATE-----", "").replaceFirst("-----END CERTIFICATE-----", "").trim();
+            byte[] cert_bytes = Base64.decode(certificateString);
+            InputStream caInput = new ByteArrayInputStream(cert_bytes);
+            try {
+                certificate = cf.generateCertificate(caInput);
+                System.out.println("ca=" + ((X509Certificate) certificate).getSubjectDN());
+            } finally {
+                caInput.close();
+            }
+        } catch (NullPointerException | CertificateException | IOException | IllegalArgumentException e) {
+            return null;
+        }
+        return (X509Certificate) certificate;
+    }
+
+    public static RSAPrivateKey parseRsaKeyFromString(String rsaKeyString) {
+        RSAPrivateKey key;
+        try {
+            KeyFactory kf = KeyFactory.getInstance("RSA", "BC");
+            rsaKeyString = rsaKeyString.replaceFirst("-----BEGIN RSA PRIVATE KEY-----", "").replaceFirst("-----END RSA PRIVATE KEY-----", "");
+            PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(Base64.decode(rsaKeyString));
+            key = (RSAPrivateKey) kf.generatePrivate(keySpec);
+        } catch (InvalidKeySpecException e) {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+            return null;
+        } catch (NoSuchAlgorithmException e) {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+            return null;
+        } catch (NoSuchProviderException e) {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+            return null;
+        } catch (NullPointerException e) {
+            e.printStackTrace();
+            return null;
+        }
+
+        return key;
+    }
+
+    private static String byteArrayToHex(byte[] input) {
+        int readBytes = input.length;
+        StringBuffer hexData = new StringBuffer();
+        int onebyte;
+        for (int i = 0; i < readBytes; i++) {
+            onebyte = ((0x000000ff & input[i]) | 0xffffff00);
+            hexData.append(Integer.toHexString(onebyte).substring(6));
+        }
+        return hexData.toString();
+    }
+
+    /**
+     * Calculates the hexadecimal representation of a sha256/sha1 fingerprint of a certificate
+     *
+     * @param certificate
+     * @param encoding
+     * @return
+     * @throws NoSuchAlgorithmException
+     * @throws CertificateEncodingException
+     */
+    @NonNull
+    public static String getFingerprintFromCertificate(X509Certificate certificate, String encoding) throws NoSuchAlgorithmException, CertificateEncodingException /*, UnsupportedEncodingException*/ {
+        byte[] byteArray = MessageDigest.getInstance(encoding).digest(certificate.getEncoded());
+        return byteArrayToHex(byteArray);
+    }
+
+    public static void ensureNotOnMainThread(@NonNull Context context) throws IllegalStateException{
+        Looper looper = Looper.myLooper();
+        if (looper != null && looper == context.getMainLooper()) {
+            throw new IllegalStateException(
+                    "calling this from your main thread can lead to deadlock");
+        }
+    }
+
+}
diff --git a/app/src/main/java/se/leap/bitmaskclient/utils/DateHelper.java b/app/src/main/java/se/leap/bitmaskclient/utils/DateHelper.java
new file mode 100644
index 0000000000000000000000000000000000000000..523c8c4c4e131448311416d4b6f6f309e75d3cb5
--- /dev/null
+++ b/app/src/main/java/se/leap/bitmaskclient/utils/DateHelper.java
@@ -0,0 +1,29 @@
+package se.leap.bitmaskclient.utils;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+
+/**
+ * Contains helper methods related to date manipulation.
+ *
+ * @author Janak
+ */
+public class DateHelper {
+    private static final String DATE_PATTERN = "dd/MM/yyyy";
+    private static final int ONE_DAY = 86400000; //1000*60*60*24
+
+    public static long getDateDiffToCurrentDateInDays(String startDate) throws ParseException {
+        SimpleDateFormat sdf = new SimpleDateFormat(DATE_PATTERN, Locale.US);
+        Date lastDate = sdf.parse(startDate);
+        Date currentDate = new Date();
+        return (currentDate.getTime() - lastDate.getTime()) / ONE_DAY;
+    }
+
+    public static String getCurrentDateString() {
+        SimpleDateFormat sdf = new SimpleDateFormat(DATE_PATTERN, Locale.US);
+        Date lastDate = new Date();
+        return sdf.format(lastDate);
+    }
+}
diff --git a/app/src/main/java/se/leap/bitmaskclient/utils/FileHelper.java b/app/src/main/java/se/leap/bitmaskclient/utils/FileHelper.java
new file mode 100644
index 0000000000000000000000000000000000000000..1c3e1ebb3b7f37bc27ff575ab6c2760551349a3d
--- /dev/null
+++ b/app/src/main/java/se/leap/bitmaskclient/utils/FileHelper.java
@@ -0,0 +1,22 @@
+package se.leap.bitmaskclient.utils;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+
+/**
+ * Created by cyberta on 18.03.18.
+ */
+
+public class FileHelper {
+    public static File createFile(File dir, String fileName) {
+        return new File(dir, fileName);
+    }
+
+    public static void persistFile(File file, String content) throws IOException {
+        FileWriter writer = new FileWriter(file);
+        writer.write(content);
+        writer.close();
+    }
+
+}
diff --git a/app/src/main/java/se/leap/bitmaskclient/utils/InputStreamHelper.java b/app/src/main/java/se/leap/bitmaskclient/utils/InputStreamHelper.java
new file mode 100644
index 0000000000000000000000000000000000000000..879966151e4922b2613a6c9358f7c38d8a60f692
--- /dev/null
+++ b/app/src/main/java/se/leap/bitmaskclient/utils/InputStreamHelper.java
@@ -0,0 +1,21 @@
+package se.leap.bitmaskclient.utils;
+
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.InputStream;
+
+/**
+ * Created by cyberta on 18.03.18.
+ */
+
+public class InputStreamHelper {
+    //allows us to mock FileInputStream
+    public static InputStream getInputStreamFrom(String filePath) throws FileNotFoundException {
+        return new FileInputStream(filePath);
+    }
+
+    public static String loadInputStreamAsString(InputStream is) {
+        java.util.Scanner s = new java.util.Scanner(is).useDelimiter("\\A");
+        return s.hasNext() ? s.next() : "";
+    }
+}
diff --git a/app/src/main/java/se/leap/bitmaskclient/utils/KeyStoreHelper.java b/app/src/main/java/se/leap/bitmaskclient/utils/KeyStoreHelper.java
new file mode 100644
index 0000000000000000000000000000000000000000..48d4cbad0de29366d8b91a6c805a51e2c7f3fb30
--- /dev/null
+++ b/app/src/main/java/se/leap/bitmaskclient/utils/KeyStoreHelper.java
@@ -0,0 +1,78 @@
+package se.leap.bitmaskclient.utils;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+
+/**
+ * Created by cyberta on 18.03.18.
+ */
+
+public class KeyStoreHelper {
+    private static KeyStore trustedKeystore;
+
+    /**
+     * Adds a new X509 certificate given its input stream and its provider name
+     *
+     * @param provider    used to store the certificate in the keystore
+     * @param inputStream from which X509 certificate must be generated.
+     */
+    public static void addTrustedCertificate(String provider, InputStream inputStream) {
+        CertificateFactory cf;
+        try {
+            cf = CertificateFactory.getInstance("X.509");
+            X509Certificate cert =
+                    (X509Certificate) cf.generateCertificate(inputStream);
+            trustedKeystore.setCertificateEntry(provider, cert);
+        } catch (CertificateException e) {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+        } catch (KeyStoreException e) {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     * Adds a new X509 certificate given in its string from and using its provider name
+     *
+     * @param provider    used to store the certificate in the keystore
+     * @param certificate
+     */
+    public static void addTrustedCertificate(String provider, String certificate) {
+
+        try {
+            X509Certificate cert = ConfigHelper.parseX509CertificateFromString(certificate);
+            if (trustedKeystore == null) {
+                trustedKeystore = KeyStore.getInstance("BKS");
+                trustedKeystore.load(null);
+            }
+            trustedKeystore.setCertificateEntry(provider, cert);
+        } catch (KeyStoreException e) {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+        } catch (NoSuchAlgorithmException e) {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+        } catch (CertificateException e) {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+        } catch (IOException e) {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     * @return class wide keystore
+     */
+    public static KeyStore getKeystore() {
+        return trustedKeystore;
+    }
+
+}
diff --git a/app/src/main/java/se/leap/bitmaskclient/ConfigHelper.java b/app/src/main/java/se/leap/bitmaskclient/utils/PreferenceHelper.java
similarity index 52%
rename from app/src/main/java/se/leap/bitmaskclient/ConfigHelper.java
rename to app/src/main/java/se/leap/bitmaskclient/utils/PreferenceHelper.java
index bfc772612c34240e8d8bc8fb791f6352b40f2d91..12015dfb2feec455578109fdc1dff5e98fbc2b32 100644
--- a/app/src/main/java/se/leap/bitmaskclient/ConfigHelper.java
+++ b/app/src/main/java/se/leap/bitmaskclient/utils/PreferenceHelper.java
@@ -1,20 +1,4 @@
-/**
- * Copyright (c) 2013 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;
+package se.leap.bitmaskclient.utils;
 
 import android.content.Context;
 import android.content.SharedPreferences;
@@ -25,7 +9,6 @@ import android.support.annotation.Nullable;
 
 import org.json.JSONException;
 import org.json.JSONObject;
-import org.spongycastle.util.encoders.Base64;
 
 import java.io.ByteArrayInputStream;
 import java.io.FileInputStream;
@@ -35,24 +18,13 @@ import java.io.InputStream;
 import java.math.BigInteger;
 import java.net.MalformedURLException;
 import java.net.URL;
-import java.security.KeyFactory;
-import java.security.KeyStore;
-import java.security.KeyStoreException;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.security.NoSuchProviderException;
-import java.security.cert.CertificateEncodingException;
-import java.security.cert.CertificateException;
-import java.security.cert.CertificateFactory;
-import java.security.cert.X509Certificate;
-import java.security.interfaces.RSAPrivateKey;
-import java.security.spec.InvalidKeySpecException;
-import java.security.spec.PKCS8EncodedKeySpec;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 
+import se.leap.bitmaskclient.Provider;
+
 import static se.leap.bitmaskclient.Constants.ALWAYS_ON_SHOW_DIALOG;
 import static se.leap.bitmaskclient.Constants.DEFAULT_SHARED_PREFS_BATTERY_SAVER;
 import static se.leap.bitmaskclient.Constants.PREFERENCES_APP_VERSION;
@@ -63,195 +35,10 @@ import static se.leap.bitmaskclient.Constants.PROVIDER_VPN_CERTIFICATE;
 import static se.leap.bitmaskclient.Constants.SHARED_PREFERENCES;
 
 /**
- * Stores constants, and implements auxiliary methods used across all Bitmask Android classes.
- *
- * @author parmegv
- * @author MeanderingCode
+ * Created by cyberta on 18.03.18.
  */
-public class ConfigHelper {
-    private static final String TAG = ConfigHelper.class.getName();
-    private static KeyStore keystore_trusted;
-
-    final public static String NG_1024 =
-            "eeaf0ab9adb38dd69c33f80afa8fc5e86072618775ff3c0b9ea2314c9c256576d674df7496ea81d3383b4813d692c6e0e0d5d8e250b98be48e495c1d6089dad15dc7d7b46154d6b6ce8ef4ad69b15d4982559b297bcf1885c529f566660e57ec68edbc3c05726cc02fd4cbf4976eaa9afd5138fe8376435b9fc61d2fc0eb06e3";
-    final public static BigInteger G = new BigInteger("2");
-
-    public static boolean checkErroneousDownload(String downloadedString) {
-        try {
-            if (downloadedString == null || downloadedString.isEmpty() || new JSONObject(downloadedString).has(ProviderAPI.ERRORS) || new JSONObject(downloadedString).has(ProviderAPI.BACKEND_ERROR_KEY)) {
-                return true;
-            } else {
-                return false;
-            }
-        } catch (NullPointerException | JSONException e) {
-            return false;
-        }
-    }
-
-    /**
-     * Treat the input as the MSB representation of a number,
-     * and lop off leading zero elements.  For efficiency, the
-     * input is simply returned if no leading zeroes are found.
-     *
-     * @param in array to be trimmed
-     */
-    public static byte[] trim(byte[] in) {
-        if (in.length == 0 || in[0] != 0)
-            return in;
-
-        int len = in.length;
-        int i = 1;
-        while (in[i] == 0 && i < len)
-            ++i;
-        byte[] ret = new byte[len - i];
-        System.arraycopy(in, i, ret, 0, len - i);
-        return ret;
-    }
-
-    public static X509Certificate parseX509CertificateFromString(String certificateString) {
-        java.security.cert.Certificate certificate = null;
-        CertificateFactory cf;
-        try {
-            cf = CertificateFactory.getInstance("X.509");
-
-            certificateString = certificateString.replaceFirst("-----BEGIN CERTIFICATE-----", "").replaceFirst("-----END CERTIFICATE-----", "").trim();
-            byte[] cert_bytes = Base64.decode(certificateString);
-            InputStream caInput = new ByteArrayInputStream(cert_bytes);
-            try {
-                certificate = cf.generateCertificate(caInput);
-                System.out.println("ca=" + ((X509Certificate) certificate).getSubjectDN());
-            } finally {
-                caInput.close();
-            }
-        } catch (NullPointerException | CertificateException | IOException | IllegalArgumentException e) {
-            return null;
-        }
-        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() : "";
-    }
-
-    //allows us to mock FileInputStream
-    public static InputStream getInputStreamFrom(String filePath) throws FileNotFoundException {
-        return new FileInputStream(filePath);
-    }
-
-    protected static RSAPrivateKey parseRsaKeyFromString(String rsaKeyString) {
-        RSAPrivateKey key;
-        try {
-            KeyFactory kf = KeyFactory.getInstance("RSA", "BC");
-            rsaKeyString = rsaKeyString.replaceFirst("-----BEGIN RSA PRIVATE KEY-----", "").replaceFirst("-----END RSA PRIVATE KEY-----", "");
-            PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(Base64.decode(rsaKeyString));
-            key = (RSAPrivateKey) kf.generatePrivate(keySpec);
-        } catch (InvalidKeySpecException e) {
-            // TODO Auto-generated catch block
-            e.printStackTrace();
-            return null;
-        } catch (NoSuchAlgorithmException e) {
-            // TODO Auto-generated catch block
-            e.printStackTrace();
-            return null;
-        } catch (NoSuchProviderException e) {
-            // TODO Auto-generated catch block
-            e.printStackTrace();
-            return null;
-        } catch (NullPointerException e) {
-            e.printStackTrace();
-            return null;
-        }
-
-        return key;
-    }
-
-    private static String byteArrayToHex(byte[] input) {
-        int readBytes = input.length;
-        StringBuffer hexData = new StringBuffer();
-        int onebyte;
-        for (int i = 0; i < readBytes; i++) {
-            onebyte = ((0x000000ff & input[i]) | 0xffffff00);
-            hexData.append(Integer.toHexString(onebyte).substring(6));
-        }
-        return hexData.toString();
-    }
-
-    /**
-     * Calculates the hexadecimal representation of a sha256/sha1 fingerprint of a certificate
-     *
-     * @param certificate
-     * @param encoding
-     * @return
-     * @throws NoSuchAlgorithmException
-     * @throws CertificateEncodingException
-     */
-    @NonNull
-    public static String getFingerprintFromCertificate(X509Certificate certificate, String encoding) throws NoSuchAlgorithmException, CertificateEncodingException /*, UnsupportedEncodingException*/ {
-        byte[] byteArray = MessageDigest.getInstance(encoding).digest(certificate.getEncoded());
-        return byteArrayToHex(byteArray);
-    }
-    
-    /**
-     * Adds a new X509 certificate given its input stream and its provider name
-     *
-     * @param provider    used to store the certificate in the keystore
-     * @param inputStream from which X509 certificate must be generated.
-     */
-    public static void addTrustedCertificate(String provider, InputStream inputStream) {
-        CertificateFactory cf;
-        try {
-            cf = CertificateFactory.getInstance("X.509");
-            X509Certificate cert =
-                    (X509Certificate) cf.generateCertificate(inputStream);
-            keystore_trusted.setCertificateEntry(provider, cert);
-        } catch (CertificateException e) {
-            // TODO Auto-generated catch block
-            e.printStackTrace();
-        } catch (KeyStoreException e) {
-            // TODO Auto-generated catch block
-            e.printStackTrace();
-        }
-    }
-
-    /**
-     * Adds a new X509 certificate given in its string from and using its provider name
-     *
-     * @param provider    used to store the certificate in the keystore
-     * @param certificate
-     */
-    public static void addTrustedCertificate(String provider, String certificate) {
-
-        try {
-            X509Certificate cert = ConfigHelper.parseX509CertificateFromString(certificate);
-            if (keystore_trusted == null) {
-                keystore_trusted = KeyStore.getInstance("BKS");
-                keystore_trusted.load(null);
-            }
-            keystore_trusted.setCertificateEntry(provider, cert);
-        } catch (KeyStoreException e) {
-            // TODO Auto-generated catch block
-            e.printStackTrace();
-        } catch (NoSuchAlgorithmException e) {
-            // TODO Auto-generated catch block
-            e.printStackTrace();
-        } catch (CertificateException e) {
-            // TODO Auto-generated catch block
-            e.printStackTrace();
-        } catch (IOException e) {
-            // TODO Auto-generated catch block
-            e.printStackTrace();
-        }
-    }
-
-    /**
-     * @return class wide keystore
-     */
-    public static KeyStore getKeystore() {
-        return keystore_trusted;
-    }
-
 
+public class PreferenceHelper {
     public static boolean providerInSharedPreferences(@NonNull SharedPreferences preferences) {
         return preferences.getBoolean(PROVIDER_CONFIGURED, false);
     }
@@ -452,11 +239,4 @@ public class ConfigHelper {
         return result;
     }
 
-    public static void ensureNotOnMainThread(@NonNull Context context) throws IllegalStateException{
-        Looper looper = Looper.myLooper();
-        if (looper != null && looper == context.getMainLooper()) {
-            throw new IllegalStateException(
-                    "calling this from your main thread can lead to deadlock");
-        }
-    }
 }
diff --git a/app/src/main/res/layout/a_main.xml b/app/src/main/res/layout/a_main.xml
index bed05d18b70becb805e1d813cf7cc82aee92526d..21fdaa66c13b469a226caadce80f745f8aff334a 100644
--- a/app/src/main/res/layout/a_main.xml
+++ b/app/src/main/res/layout/a_main.xml
@@ -20,7 +20,8 @@
             android:minHeight="?attr/actionBarSize"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
-            app:titleTextColor="@android:color/white"
+            app:titleTextColor="@color/colorActionBarTitleFont"
+            app:subtitleTextColor="@color/colorActionBarSubtitleFont"
             android:background="?attr/colorPrimary">
         </android.support.v7.widget.Toolbar>
 
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
index 53ead0090d578087e84818adb56fae57a8da0174..40ab06c585fafc06499b580a0d858976bd9706cb 100644
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -27,4 +27,7 @@
 
     <color name="white">#ffffff</color>
 
+    <color name="colorActionBarTitleFont">@color/white</color>
+    <color name="colorActionBarSubtitleFont">@color/black800</color>
+
 </resources>
diff --git a/app/src/production/java/se/leap/bitmaskclient/ProviderApiManager.java b/app/src/production/java/se/leap/bitmaskclient/ProviderApiManager.java
index 885d69db6313a79e41781d7bafcdd893d23c661d..b1afa6d3c65c44a93b4474f8814c33be1d1e37b5 100644
--- a/app/src/production/java/se/leap/bitmaskclient/ProviderApiManager.java
+++ b/app/src/production/java/se/leap/bitmaskclient/ProviderApiManager.java
@@ -31,6 +31,7 @@ import java.util.List;
 
 import okhttp3.OkHttpClient;
 import se.leap.bitmaskclient.eip.EIP;
+import se.leap.bitmaskclient.utils.ConfigHelper;
 
 import static android.text.TextUtils.isEmpty;
 import static se.leap.bitmaskclient.Constants.BROADCAST_RESULT_KEY;
diff --git a/app/src/test/java/se/leap/bitmaskclient/ConfigHelperTest.java b/app/src/test/java/se/leap/bitmaskclient/PreferenceHelperTest.java
similarity index 78%
rename from app/src/test/java/se/leap/bitmaskclient/ConfigHelperTest.java
rename to app/src/test/java/se/leap/bitmaskclient/PreferenceHelperTest.java
index 2c7848bcf65eea19ca22ae20823c50823cfa7df0..d49fa08c91bb2363666e1219441a24351f4f4268 100644
--- a/app/src/test/java/se/leap/bitmaskclient/ConfigHelperTest.java
+++ b/app/src/test/java/se/leap/bitmaskclient/PreferenceHelperTest.java
@@ -6,6 +6,8 @@ import org.junit.Before;
 import org.junit.Test;
 
 import se.leap.bitmaskclient.testutils.MockSharedPreferences;
+import se.leap.bitmaskclient.utils.ConfigHelper;
+import se.leap.bitmaskclient.utils.PreferenceHelper;
 
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
@@ -14,11 +16,13 @@ import static se.leap.bitmaskclient.Constants.PROVIDER_EIP_DEFINITION;
 import static se.leap.bitmaskclient.Constants.PROVIDER_PRIVATE_KEY;
 import static se.leap.bitmaskclient.Constants.PROVIDER_VPN_CERTIFICATE;
 import static se.leap.bitmaskclient.testutils.TestSetupHelper.getInputAsString;
+import static se.leap.bitmaskclient.utils.PreferenceHelper.getSavedProviderFromSharedPreferences;
+import static se.leap.bitmaskclient.utils.PreferenceHelper.providerInSharedPreferences;
 
 /**
  * Created by cyberta on 17.01.18.
  */
-public class ConfigHelperTest {
+public class PreferenceHelperTest {
 
     private SharedPreferences mockPreferences;
 
@@ -30,18 +34,18 @@ public class ConfigHelperTest {
 
     @Test
     public void providerInSharedPreferences_notInPreferences_returnsFalse() throws Exception {
-        assertFalse(ConfigHelper.providerInSharedPreferences(mockPreferences));
+        assertFalse(providerInSharedPreferences(mockPreferences));
     }
 
     @Test
     public void providerInSharedPreferences_inPreferences_returnsTrue() throws Exception {
         mockPreferences.edit().putBoolean(PROVIDER_CONFIGURED, true).apply();
-        assertTrue(ConfigHelper.providerInSharedPreferences(mockPreferences));
+        assertTrue(providerInSharedPreferences(mockPreferences));
     }
 
     @Test
     public void getSavedProviderFromSharedPreferences_notInPreferences_returnsDefaultProvider() throws Exception {
-        Provider provider = ConfigHelper.getSavedProviderFromSharedPreferences(mockPreferences);
+        Provider provider = getSavedProviderFromSharedPreferences(mockPreferences);
         assertFalse(provider.isConfigured());
     }
 
@@ -55,7 +59,7 @@ public class ConfigHelperTest {
                 .putString(PROVIDER_VPN_CERTIFICATE, getInputAsString(getClass().getClassLoader().getResourceAsStream("riseup.net.vpn_cert.pem")))
                 .putString(PROVIDER_PRIVATE_KEY, getInputAsString(getClass().getClassLoader().getResourceAsStream("private_rsa_key.pem")))
                 .apply();
-        Provider provider = ConfigHelper.getSavedProviderFromSharedPreferences(mockPreferences);
+        Provider provider = getSavedProviderFromSharedPreferences(mockPreferences);
         assertTrue(provider.isConfigured());
     }
 
diff --git a/app/src/test/java/se/leap/bitmaskclient/ProviderManagerTest.java b/app/src/test/java/se/leap/bitmaskclient/ProviderManagerTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..1914f989c1004489e6b096e92bfde1e0b3711843
--- /dev/null
+++ b/app/src/test/java/se/leap/bitmaskclient/ProviderManagerTest.java
@@ -0,0 +1,189 @@
+package se.leap.bitmaskclient;
+
+import android.content.res.AssetManager;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+import org.powermock.core.classloader.annotations.PrepareForTest;
+import org.powermock.modules.junit4.PowerMockRunner;
+
+import java.io.File;
+import java.io.InputStream;
+import java.util.ArrayList;
+
+import se.leap.bitmaskclient.utils.ConfigHelper;
+import se.leap.bitmaskclient.utils.FileHelper;
+import se.leap.bitmaskclient.utils.InputStreamHelper;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.powermock.api.mockito.PowerMockito.verifyStatic;
+import static se.leap.bitmaskclient.testutils.MockHelper.mockFileHelper;
+import static se.leap.bitmaskclient.testutils.MockHelper.mockInputStreamHelper;
+
+/**
+ * Created by cyberta on 20.02.18.
+ */
+@RunWith(PowerMockRunner.class)
+@PrepareForTest({ConfigHelper.class, FileHelper.class, InputStreamHelper.class})
+public class ProviderManagerTest {
+
+    @Mock
+    private AssetManager assetManager;
+    @Mock
+    private File file;
+    private ProviderManager providerManager;
+
+    @Before
+    public void setup() throws Exception {
+        //mock assetManager methods
+        //--------------------------
+        when(assetManager.open(anyString())).thenAnswer(new Answer<InputStream>() {
+            @Override
+            public InputStream answer(InvocationOnMock invocation) throws Throwable {
+                String filename = "preconfigured/" + invocation.getArguments()[0];
+                return getClass().getClassLoader().getResourceAsStream(filename);
+            }
+        });
+        when(assetManager.list(anyString())).thenAnswer(new Answer<String[]>() {
+            @Override
+            public String[] answer(InvocationOnMock invocation) throws Throwable {
+                String path = (String) invocation.getArguments()[0];
+                if ("urls".equals(path)) {
+                    String[] preconfiguredUrls = new String[3];
+                    preconfiguredUrls[0] = "calyx.net.url";
+                    preconfiguredUrls[1] = "demo.bitmask.net.url";
+                    preconfiguredUrls[2] = "riseup.net.url";
+                    return preconfiguredUrls;
+                } else
+                    throw new IllegalArgumentException("You need to implement the expected path manually!");
+            }
+        });
+
+        //mock File methods
+        //------------------
+        when(file.isDirectory()).thenReturn(true);
+
+        ArrayList<String> mockedCustomProviderList = new ArrayList<>();
+        mockedCustomProviderList.add("leapcolombia.json");
+        String[] mockedCustomProviderArray = new String[mockedCustomProviderList.size()];
+        mockedCustomProviderArray = mockedCustomProviderList.toArray(mockedCustomProviderArray);
+        when(file.list()).thenReturn(mockedCustomProviderArray);
+
+        when(file.getAbsolutePath()).thenReturn("externalDir");
+        when(file.getPath()).thenReturn("externalDir");
+        mockFileHelper(file);
+
+        // mock inputStream
+        //-----------------------------------
+        mockInputStreamHelper();
+
+    }
+
+    @After
+    public void tearDown() {
+        ProviderManager.reset();
+    }
+
+    @Test
+    public void testSize_has5ProvidersWithCurrentTestSetup() {
+        providerManager = ProviderManager.getInstance(assetManager, file);
+        assertEquals("3 preconfigured, 1 custom provider, 1 dummy provider", 5, providerManager.size());
+    }
+
+    @Test
+    public void testAdd_newCustomProviderThatIsNotPartOfDefaultNorCustomList_returnTrue() throws Exception {
+        providerManager = ProviderManager.getInstance(assetManager, file);
+        Provider customProvider = new Provider("https://anewprovider.org");
+        assertTrue("custom provider added: ", providerManager.add(customProvider));
+        assertEquals("3 preconfigured, 2 custom providers, 1 dummy provider", 6, providerManager.providers().size());
+    }
+
+    @Test
+    public void testAdd_newCustomProviderThatIsNotPartOfDefaultButOfCustomList_returnFalse() throws Exception {
+        providerManager = ProviderManager.getInstance(assetManager, file);
+        Provider customProvider = new Provider("https://leapcolombia.org");
+        assertFalse("custom provider added: ", providerManager.add(customProvider));
+        assertEquals("3 preconfigured, 1 custom provider, 1 dummy provider", 5, providerManager.providers().size());
+    }
+
+    @Test
+    public void testAdd_newCustomProviderThatIsPartOfDefaultButNotOfCustomList_returnFalse() throws Exception {
+        providerManager = ProviderManager.getInstance(assetManager, file);
+        Provider customProvider = new Provider("https://demo.bitmask.net");
+        assertFalse("custom provider added: ", providerManager.add(customProvider));
+        assertEquals("3 preconfigured, 1 custom provider, 1 dummy provider", 5, providerManager.providers().size());
+    }
+
+    @Test
+    public void testRemove_ProviderIsPartOfDefaultButNotCustomList_returnsFalse() throws Exception {
+        providerManager = ProviderManager.getInstance(assetManager, file);
+        Provider customProvider = new Provider("https://demo.bitmask.net");
+        assertFalse("custom provider not removed: ", providerManager.remove(customProvider));
+        assertEquals("3 preconfigured, 1 custom provider, 1 dummy provider", 5, providerManager.providers().size());
+    }
+
+    @Test
+    public void testRemove_ProviderIsNotPartOfDefaultButOfCustomList_returnsTrue() throws Exception {
+        providerManager = ProviderManager.getInstance(assetManager, file);
+        Provider customProvider = new Provider("https://leapcolombia.org");
+        assertTrue("custom provider not removed: ", providerManager.remove(customProvider));
+        assertEquals("3 preconfigured, 0 custom providers, 1 dummy provider", 4, providerManager.providers().size());
+    }
+
+    @Test
+    public void testRemove_ProviderIsNotPartOfDefaultNorOfCustomList_returnsFalse() throws Exception {
+        providerManager = ProviderManager.getInstance(assetManager, file);
+        Provider customProvider = new Provider("https://anotherprovider.org");
+        assertFalse("custom provider not removed: ", providerManager.remove(customProvider));
+        assertEquals("3 preconfigured, 1 custom providers, 1 dummy provider", 5, providerManager.providers().size());
+    }
+
+    @Test
+    public void testClear_ProvidersListHasOnlyDummyProvider() throws Exception {
+        providerManager = ProviderManager.getInstance(assetManager, file);
+        providerManager.clear();
+        assertEquals("1 providers", 1, providerManager.providers().size());
+        assertEquals("provider is dummy element", "https://example.net", providerManager.get(0).getMainUrlString());
+    }
+
+    @Test
+    public void testSaveCustomProvidersToFile_CustomProviderDeleted_deletesFromDir() throws Exception {
+        when(file.exists()).thenReturn(true);
+        providerManager = ProviderManager.getInstance(assetManager, file);
+        //leapcolombia is mocked custom provider from setup
+        Provider customProvider = new Provider("https://leapcolombia.org");
+        providerManager.remove(customProvider);
+        providerManager.saveCustomProvidersToFile();
+        verify(file, times(1)).delete();
+    }
+
+
+    @Test
+    public void testSaveCustomProvidersToFile_newCustomProviders_persistNew() throws Exception {
+        when(file.list()).thenReturn(new String[0]);
+        when(file.exists()).thenReturn(false);
+        providerManager = ProviderManager.getInstance(assetManager, file);
+        Provider customProvider = new Provider("https://anotherprovider.org");
+        Provider secondCustomProvider = new Provider("https://yetanotherprovider.org");
+        providerManager.add(customProvider);
+        providerManager.add(secondCustomProvider);
+        providerManager.saveCustomProvidersToFile();
+
+        verifyStatic(FileHelper.class, times(2));
+        FileHelper.persistFile(any(File.class), anyString());
+    }
+
+
+}
\ No newline at end of file
diff --git a/app/src/test/java/se/leap/bitmaskclient/ProviderTest.java b/app/src/test/java/se/leap/bitmaskclient/ProviderTest.java
index 495d5b3f6162ca65d549a4f29efc669197363d50..a141edec0c77efa2a0d1ed6a6a6cbe532fe1ec61 100644
--- a/app/src/test/java/se/leap/bitmaskclient/ProviderTest.java
+++ b/app/src/test/java/se/leap/bitmaskclient/ProviderTest.java
@@ -1,9 +1,7 @@
 package se.leap.bitmaskclient;
 
-import org.json.JSONException;
 import org.junit.Test;
 
-import java.io.IOException;
 import java.util.HashSet;
 import java.util.Set;
 
diff --git a/app/src/test/java/se/leap/bitmaskclient/eip/ProviderApiManagerTest.java b/app/src/test/java/se/leap/bitmaskclient/eip/ProviderApiManagerTest.java
index 4842d17014b5da894b2d1044fac336a3b2c8558b..7283968bada676da250177526145348ffe673a75 100644
--- a/app/src/test/java/se/leap/bitmaskclient/eip/ProviderApiManagerTest.java
+++ b/app/src/test/java/se/leap/bitmaskclient/eip/ProviderApiManagerTest.java
@@ -38,13 +38,15 @@ import java.io.IOException;
 import java.security.NoSuchAlgorithmException;
 import java.security.cert.CertificateEncodingException;
 
-import se.leap.bitmaskclient.ConfigHelper;
+import se.leap.bitmaskclient.testutils.MockHelper;
+import se.leap.bitmaskclient.utils.ConfigHelper;
 import se.leap.bitmaskclient.Provider;
 import se.leap.bitmaskclient.ProviderAPI;
 import se.leap.bitmaskclient.ProviderApiConnector;
 import se.leap.bitmaskclient.ProviderApiManager;
 import se.leap.bitmaskclient.ProviderApiManagerBase;
 import se.leap.bitmaskclient.testutils.MockSharedPreferences;
+import se.leap.bitmaskclient.utils.PreferenceHelper;
 
 import static se.leap.bitmaskclient.Constants.BROADCAST_RESULT_KEY;
 import static se.leap.bitmaskclient.Constants.PROVIDER_KEY;
@@ -58,6 +60,7 @@ import static se.leap.bitmaskclient.testutils.MockHelper.mockClientGenerator;
 import static se.leap.bitmaskclient.testutils.MockHelper.mockConfigHelper;
 import static se.leap.bitmaskclient.testutils.MockHelper.mockFingerprintForCertificate;
 import static se.leap.bitmaskclient.testutils.MockHelper.mockIntent;
+import static se.leap.bitmaskclient.testutils.MockHelper.mockPreferenceHelper;
 import static se.leap.bitmaskclient.testutils.MockHelper.mockProviderApiConnector;
 import static se.leap.bitmaskclient.testutils.MockHelper.mockResources;
 import static se.leap.bitmaskclient.testutils.MockHelper.mockResultReceiver;
@@ -72,7 +75,7 @@ import static se.leap.bitmaskclient.testutils.TestSetupHelper.getProvider;
  */
 
 @RunWith(PowerMockRunner.class)
-@PrepareForTest({ProviderApiManager.class, TextUtils.class, ConfigHelper.class, ProviderApiConnector.class})
+@PrepareForTest({ProviderApiManager.class, TextUtils.class, ConfigHelper.class, ProviderApiConnector.class, PreferenceHelper.class})
 public class ProviderApiManagerTest {
 
     private SharedPreferences mockPreferences;
@@ -172,8 +175,8 @@ public class ProviderApiManagerTest {
     @Test
     public void test_handleIntentSetupProvider_happyPath_storedProviderAndCAFromPreviousSetup() throws IOException, CertificateEncodingException, NoSuchAlgorithmException, JSONException {
         Provider provider = new Provider("https://riseup.net");
-        mockConfigHelper("a5244308a1374709a9afce95e3ae47c1b44bc2398c0a70ccbf8b3a8a97f29494", getConfiguredProvider());
-
+        mockPreferenceHelper(getConfiguredProvider());
+        mockConfigHelper("a5244308a1374709a9afce95e3ae47c1b44bc2398c0a70ccbf8b3a8a97f29494");
         mockProviderApiConnector(NO_ERROR);
         mockPreferences.edit().putString(Provider.KEY + ".riseup.net", getInputAsString(getClass().getClassLoader().getResourceAsStream("riseup.net.json"))).apply();
         mockPreferences.edit().putString(Provider.CA_CERT + ".riseup.net", getInputAsString(getClass().getClassLoader().getResourceAsStream("riseup.net.pem"))).apply();
@@ -238,7 +241,8 @@ public class ProviderApiManagerTest {
     @Test
     public void test_handleIntentSetupProvider_storedProviderAndCAFromPreviousSetup_failedPinning() throws IOException, CertificateEncodingException, NoSuchAlgorithmException, JSONException {
         Provider provider = new Provider("https://riseup.net");
-        mockConfigHelper("a5244308a1374709a9afce95e3ae47c1b44bc2398c0a70ccbf8b3a8a97f29495", getConfiguredProvider());
+        mockPreferenceHelper(getConfiguredProvider());
+        mockConfigHelper("a5244308a1374709a9afce95e3ae47c1b44bc2398c0a70ccbf8b3a8a97f29495");
 
         mockProviderApiConnector(NO_ERROR);
         mockPreferences.edit().putString(Provider.KEY + ".riseup.net", getInputAsString(getClass().getClassLoader().getResourceAsStream("riseup.net.json"))).apply();
@@ -308,8 +312,8 @@ public class ProviderApiManagerTest {
     @Test
     public void test_handleIntentSetupProvider_preseededProviderAndCA_ValidCertificateButUpdatedCertificateOnServerSide() throws IOException, CertificateEncodingException, NoSuchAlgorithmException, JSONException {
         Provider provider = getConfiguredProvider();
-
-        mockConfigHelper("a5244308a1374709a9afce95e3ae47c1b44bc2398c0a70ccbf8b3a8a97f29494", getConfiguredProvider());
+        mockPreferenceHelper(provider);
+        mockConfigHelper("a5244308a1374709a9afce95e3ae47c1b44bc2398c0a70ccbf8b3a8a97f29494");
         mockProviderApiConnector(ERROR_CASE_UPDATED_CERTIFICATE);
 
         providerApiManager = new ProviderApiManager(mockPreferences, mockResources, mockClientGenerator(), new TestProviderApiServiceCallback());
@@ -331,8 +335,8 @@ public class ProviderApiManagerTest {
     @Test
     public void test_handleIntentSetupProvider_storedProviderAndCAFromPreviousSetup_ValidCertificateButUpdatedCertificateOnServerSide() throws IOException, CertificateEncodingException, NoSuchAlgorithmException, JSONException {
         Provider provider = new Provider("https://riseup.net");
-
-        mockConfigHelper("a5244308a1374709a9afce95e3ae47c1b44bc2398c0a70ccbf8b3a8a97f29494", getConfiguredProvider());
+        mockPreferenceHelper(getConfiguredProvider());
+        mockConfigHelper("a5244308a1374709a9afce95e3ae47c1b44bc2398c0a70ccbf8b3a8a97f29494");
         mockProviderApiConnector(ERROR_CASE_UPDATED_CERTIFICATE);
         mockPreferences.edit().putString(Provider.KEY + ".riseup.net", getInputAsString(getClass().getClassLoader().getResourceAsStream("riseup.net.json"))).apply();
         mockPreferences.edit().putString(Provider.CA_CERT + ".riseup.net", getInputAsString(getClass().getClassLoader().getResourceAsStream("riseup.net.pem"))).apply();
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 d85b050f1b510f6bb6af9d5511a72451f18f434b..d68296a85587d11eaf96a26d2d14256e2dd2da1b 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.File;
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;
@@ -29,12 +30,15 @@ import java.util.Map;
 import java.util.Set;
 
 import okhttp3.OkHttpClient;
-import se.leap.bitmaskclient.ConfigHelper;
 import se.leap.bitmaskclient.OkHttpClientGenerator;
 import se.leap.bitmaskclient.Provider;
 import se.leap.bitmaskclient.R;
 import se.leap.bitmaskclient.testutils.BackendMockResponses.BackendMockProvider;
 import se.leap.bitmaskclient.testutils.matchers.BundleMatcher;
+import se.leap.bitmaskclient.utils.ConfigHelper;
+import se.leap.bitmaskclient.utils.FileHelper;
+import se.leap.bitmaskclient.utils.InputStreamHelper;
+import se.leap.bitmaskclient.utils.PreferenceHelper;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertThat;
@@ -49,6 +53,8 @@ import static org.mockito.Mockito.when;
 import static org.powermock.api.mockito.PowerMockito.mockStatic;
 import static se.leap.bitmaskclient.Constants.PROVIDER_PRIVATE_KEY;
 import static se.leap.bitmaskclient.Constants.PROVIDER_VPN_CERTIFICATE;
+import static se.leap.bitmaskclient.utils.FileHelper.createFile;
+import static se.leap.bitmaskclient.utils.PreferenceHelper.getFromPersistedProvider;
 
 /**
  * Created by cyberta on 29.01.18.
@@ -343,22 +349,36 @@ 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>() {
+    public static void mockInputStreamHelper() throws FileNotFoundException {
+        mockStatic(InputStreamHelper.class);
+        when(InputStreamHelper.loadInputStreamAsString(any(InputStream.class))).thenCallRealMethod();
+        when(InputStreamHelper.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 mockFileHelper(final File mockedFile) throws FileNotFoundException {
+        mockStatic(FileHelper.class);
+        when(createFile(any(File.class), anyString())).thenReturn(mockedFile);
     }
 
-    public static void mockConfigHelper(String mockedFingerprint, final Provider providerFromPrefs) throws CertificateEncodingException, NoSuchAlgorithmException {
+    public static void mockConfigHelper(String mockedFingerprint) throws CertificateEncodingException, NoSuchAlgorithmException {
         // FIXME use MockSharedPreferences instead of provider
         mockStatic(ConfigHelper.class);
-        when(ConfigHelper.getFromPersistedProvider(anyString(), anyString(), any(SharedPreferences.class))).thenAnswer(new Answer<String>() {
+        when(ConfigHelper.getFingerprintFromCertificate(any(X509Certificate.class), anyString())).thenReturn(mockedFingerprint);
+        when(ConfigHelper.checkErroneousDownload(anyString())).thenCallRealMethod();
+        when(ConfigHelper.parseX509CertificateFromString(anyString())).thenCallRealMethod();
+    }
+
+    public static void mockPreferenceHelper(final Provider providerFromPrefs) {
+        // FIXME use MockSharedPreferences instead of provider
+        mockStatic(PreferenceHelper.class);
+        when(getFromPersistedProvider(anyString(), anyString(), any(SharedPreferences.class))).thenAnswer(new Answer<String>() {
             @Override
             public String answer(InvocationOnMock invocation) throws Throwable {
                 String key = (String) invocation.getArguments()[0];
@@ -375,11 +395,8 @@ public class MockHelper {
                 return null;
             }
         });
-        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);
         when(ConfigHelper.getFingerprintFromCertificate(any(X509Certificate.class), anyString())).thenReturn(mockedFingerprint);