diff --git a/.gitignore b/.gitignore
index fb10e3e29f3e7f6eee35f44f2921a94121f901a7..58b37fffbf9f193e2c283d6827ae8ac2c79ac58d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,6 +2,7 @@ obj
 bin
 gen
 libs
+vendor
 .settings
 openvpn/.git
 openvpn/autom4te.cache
@@ -106,3 +107,6 @@ lib-bitmask-core-x86_64/bitmaskcore_x86_64-sources.jar
 lib-bitmask-core-x86_64/bitmaskcore_x86_64.aar
 lib-bitmask-core/bitmaskcore-sources.jar
 lib-bitmask-core/bitmaskcore.aar
+
+
+fastlane/report.xml
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 286e6d45cd364d2a42172a9ebd410b22b6370540..7c9e8a54c65d2396ed9f860759e64f4a3dfffaaf 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -14,6 +14,7 @@ variables:
   image: 0xacab.org:4567/leap/docker/debian:bullseye_amd64
   tags:
     - docker-in-docker
+    - runner02-sea
   services:
     - docker:dind
 
@@ -51,6 +52,15 @@ docker_image:emulator:
   allow_failure: true
   <<: *build_docker_image
 
+docker_image:fastlane:
+  stage: docker_image_other
+  variables:
+    DOCKER_IMAGE: android-fastlane
+#    DEFAULT_IMAGE: "registry.0xacab.org/leap/bitmask_android/android-emulator"
+  when: manual
+  <<: *build_docker_image
+  allow_failure: true
+
 unit_test:
   image: "0xacab.org:4567/leap/bitmask_android/android-ndk:latest"
   stage: test
@@ -63,6 +73,38 @@ unit_test:
     when: on_failure
     expire_in: 3 days
 
+screenshot:
+  image: "0xacab.org:4567/leap/bitmask_android/android-fastlane:latest"
+  stage: test
+  when: always
+#  needs: ["docker_image:fastlane"]
+  tags:
+    - birch
+  variables:
+    LC_ALL: "en_US.UTF-8"
+    LANG: "en_US.UTF-8"
+    ANDROID_EMULATOR_USE_SYSTEM_LIBS: 1
+    DEBIAN_FRONTEN: "noninteractive"
+# try to re-use the build artifcats from before, lib building is slow
+#  dependencies:
+#    - build
+  script:
+    - ./scripts/prepareForScreenshots.sh
+    - ./scripts/installFastlane.sh
+    - ./gradlew testCustomProductionFatReleaseUnitTest testNormalProductionFatReleaseUnitTest
+    - ./scripts/startEmulators.sh
+    - ./scripts/fastlane.sh
+  artifacts:
+    paths:
+      - app
+      - app/build/screenshots
+      - ./source/custom/fastlane/metadata/
+      - build/
+      - src/normal/
+    when: always
+    expire_in: 3 days
+  allow_failure: true
+
 #ui_test:
 #  image: "0xacab.org:4567/leap/bitmask_android/android-emulator:latest"
 #  stage: test
@@ -106,7 +148,7 @@ build:
   script:
     - ./scripts/cleanProject.sh
     - ./scripts/build_deps.sh >> build_deps.log 2>&1
-    - ./gradlew clean assembleNormalProductionFatDebug --stacktrace >> build.log 2>&1
+    - ./gradlew clean assembleNormalProductionFatDebug -debug >> build.log 2>&1
   artifacts:
     paths:
     - app/build/outputs/
diff --git a/Gemfile b/Gemfile
new file mode 100644
index 0000000000000000000000000000000000000000..7a118b49be750543e59f7b9c55123e11322b00c6
--- /dev/null
+++ b/Gemfile
@@ -0,0 +1,3 @@
+source "https://rubygems.org"
+
+gem "fastlane"
diff --git a/Gemfile.lock b/Gemfile.lock
new file mode 100644
index 0000000000000000000000000000000000000000..0fdb16b0f3b2b97cde02147386984272fbf6484c
--- /dev/null
+++ b/Gemfile.lock
@@ -0,0 +1,218 @@
+GEM
+  remote: https://rubygems.org/
+  specs:
+    CFPropertyList (3.0.5)
+      rexml
+    addressable (2.8.1)
+      public_suffix (>= 2.0.2, < 6.0)
+    artifactory (3.0.15)
+    atomos (0.1.3)
+    aws-eventstream (1.2.0)
+    aws-partitions (1.686.0)
+    aws-sdk-core (3.168.4)
+      aws-eventstream (~> 1, >= 1.0.2)
+      aws-partitions (~> 1, >= 1.651.0)
+      aws-sigv4 (~> 1.5)
+      jmespath (~> 1, >= 1.6.1)
+    aws-sdk-kms (1.61.0)
+      aws-sdk-core (~> 3, >= 3.165.0)
+      aws-sigv4 (~> 1.1)
+    aws-sdk-s3 (1.117.2)
+      aws-sdk-core (~> 3, >= 3.165.0)
+      aws-sdk-kms (~> 1)
+      aws-sigv4 (~> 1.4)
+    aws-sigv4 (1.5.2)
+      aws-eventstream (~> 1, >= 1.0.2)
+    babosa (1.0.4)
+    claide (1.1.0)
+    colored (1.2)
+    colored2 (3.1.2)
+    commander (4.6.0)
+      highline (~> 2.0.0)
+    declarative (0.0.20)
+    digest-crc (0.6.4)
+      rake (>= 12.0.0, < 14.0.0)
+    domain_name (0.5.20190701)
+      unf (>= 0.0.5, < 1.0.0)
+    dotenv (2.8.1)
+    emoji_regex (3.2.3)
+    excon (0.95.0)
+    faraday (1.10.2)
+      faraday-em_http (~> 1.0)
+      faraday-em_synchrony (~> 1.0)
+      faraday-excon (~> 1.1)
+      faraday-httpclient (~> 1.0)
+      faraday-multipart (~> 1.0)
+      faraday-net_http (~> 1.0)
+      faraday-net_http_persistent (~> 1.0)
+      faraday-patron (~> 1.0)
+      faraday-rack (~> 1.0)
+      faraday-retry (~> 1.0)
+      ruby2_keywords (>= 0.0.4)
+    faraday-cookie_jar (0.0.7)
+      faraday (>= 0.8.0)
+      http-cookie (~> 1.0.0)
+    faraday-em_http (1.0.0)
+    faraday-em_synchrony (1.0.0)
+    faraday-excon (1.1.0)
+    faraday-httpclient (1.0.1)
+    faraday-multipart (1.0.4)
+      multipart-post (~> 2)
+    faraday-net_http (1.0.1)
+    faraday-net_http_persistent (1.2.0)
+    faraday-patron (1.0.0)
+    faraday-rack (1.0.0)
+    faraday-retry (1.0.3)
+    faraday_middleware (1.2.0)
+      faraday (~> 1.0)
+    fastimage (2.2.6)
+    fastlane (2.211.0)
+      CFPropertyList (>= 2.3, < 4.0.0)
+      addressable (>= 2.8, < 3.0.0)
+      artifactory (~> 3.0)
+      aws-sdk-s3 (~> 1.0)
+      babosa (>= 1.0.3, < 2.0.0)
+      bundler (>= 1.12.0, < 3.0.0)
+      colored
+      commander (~> 4.6)
+      dotenv (>= 2.1.1, < 3.0.0)
+      emoji_regex (>= 0.1, < 4.0)
+      excon (>= 0.71.0, < 1.0.0)
+      faraday (~> 1.0)
+      faraday-cookie_jar (~> 0.0.6)
+      faraday_middleware (~> 1.0)
+      fastimage (>= 2.1.0, < 3.0.0)
+      gh_inspector (>= 1.1.2, < 2.0.0)
+      google-apis-androidpublisher_v3 (~> 0.3)
+      google-apis-playcustomapp_v1 (~> 0.1)
+      google-cloud-storage (~> 1.31)
+      highline (~> 2.0)
+      json (< 3.0.0)
+      jwt (>= 2.1.0, < 3)
+      mini_magick (>= 4.9.4, < 5.0.0)
+      multipart-post (~> 2.0.0)
+      naturally (~> 2.2)
+      optparse (~> 0.1.1)
+      plist (>= 3.1.0, < 4.0.0)
+      rubyzip (>= 2.0.0, < 3.0.0)
+      security (= 0.1.3)
+      simctl (~> 1.6.3)
+      terminal-notifier (>= 2.0.0, < 3.0.0)
+      terminal-table (>= 1.4.5, < 2.0.0)
+      tty-screen (>= 0.6.3, < 1.0.0)
+      tty-spinner (>= 0.8.0, < 1.0.0)
+      word_wrap (~> 1.0.0)
+      xcodeproj (>= 1.13.0, < 2.0.0)
+      xcpretty (~> 0.3.0)
+      xcpretty-travis-formatter (>= 0.0.3)
+    gh_inspector (1.1.3)
+    google-apis-androidpublisher_v3 (0.32.0)
+      google-apis-core (>= 0.9.1, < 2.a)
+    google-apis-core (0.9.2)
+      addressable (~> 2.5, >= 2.5.1)
+      googleauth (>= 0.16.2, < 2.a)
+      httpclient (>= 2.8.1, < 3.a)
+      mini_mime (~> 1.0)
+      representable (~> 3.0)
+      retriable (>= 2.0, < 4.a)
+      rexml
+      webrick
+    google-apis-iamcredentials_v1 (0.16.0)
+      google-apis-core (>= 0.9.1, < 2.a)
+    google-apis-playcustomapp_v1 (0.12.0)
+      google-apis-core (>= 0.9.1, < 2.a)
+    google-apis-storage_v1 (0.19.0)
+      google-apis-core (>= 0.9.0, < 2.a)
+    google-cloud-core (1.6.0)
+      google-cloud-env (~> 1.0)
+      google-cloud-errors (~> 1.0)
+    google-cloud-env (1.6.0)
+      faraday (>= 0.17.3, < 3.0)
+    google-cloud-errors (1.3.0)
+    google-cloud-storage (1.44.0)
+      addressable (~> 2.8)
+      digest-crc (~> 0.4)
+      google-apis-iamcredentials_v1 (~> 0.1)
+      google-apis-storage_v1 (~> 0.19.0)
+      google-cloud-core (~> 1.6)
+      googleauth (>= 0.16.2, < 2.a)
+      mini_mime (~> 1.0)
+    googleauth (1.3.0)
+      faraday (>= 0.17.3, < 3.a)
+      jwt (>= 1.4, < 3.0)
+      memoist (~> 0.16)
+      multi_json (~> 1.11)
+      os (>= 0.9, < 2.0)
+      signet (>= 0.16, < 2.a)
+    highline (2.0.3)
+    http-cookie (1.0.5)
+      domain_name (~> 0.5)
+    httpclient (2.8.3)
+    jmespath (1.6.2)
+    json (2.6.3)
+    jwt (2.6.0)
+    memoist (0.16.2)
+    mini_magick (4.12.0)
+    mini_mime (1.1.2)
+    multi_json (1.15.0)
+    multipart-post (2.0.0)
+    nanaimo (0.3.0)
+    naturally (2.2.1)
+    optparse (0.1.1)
+    os (1.1.4)
+    plist (3.6.0)
+    public_suffix (5.0.1)
+    rake (13.0.6)
+    representable (3.2.0)
+      declarative (< 0.1.0)
+      trailblazer-option (>= 0.1.1, < 0.2.0)
+      uber (< 0.2.0)
+    retriable (3.1.2)
+    rexml (3.2.5)
+    rouge (2.0.7)
+    ruby2_keywords (0.0.5)
+    rubyzip (2.3.2)
+    security (0.1.3)
+    signet (0.17.0)
+      addressable (~> 2.8)
+      faraday (>= 0.17.5, < 3.a)
+      jwt (>= 1.5, < 3.0)
+      multi_json (~> 1.10)
+    simctl (1.6.8)
+      CFPropertyList
+      naturally
+    terminal-notifier (2.0.0)
+    terminal-table (1.8.0)
+      unicode-display_width (~> 1.1, >= 1.1.1)
+    trailblazer-option (0.1.2)
+    tty-cursor (0.7.1)
+    tty-screen (0.8.1)
+    tty-spinner (0.9.3)
+      tty-cursor (~> 0.7)
+    uber (0.1.0)
+    unf (0.1.4)
+      unf_ext
+    unf_ext (0.0.8.2)
+    unicode-display_width (1.8.0)
+    webrick (1.7.0)
+    word_wrap (1.0.0)
+    xcodeproj (1.22.0)
+      CFPropertyList (>= 2.3.3, < 4.0)
+      atomos (~> 0.1.3)
+      claide (>= 1.0.2, < 2.0)
+      colored2 (~> 3.1)
+      nanaimo (~> 0.3.0)
+      rexml (~> 3.2.4)
+    xcpretty (0.3.0)
+      rouge (~> 2.0.7)
+    xcpretty-travis-formatter (1.0.1)
+      xcpretty (~> 0.2, >= 0.0.7)
+
+PLATFORMS
+  x86_64-darwin-21
+
+DEPENDENCIES
+  fastlane
+
+BUNDLED WITH
+   2.3.26
diff --git a/app/build.gradle b/app/build.gradle
index 072105283fcda6df540897eac30a7814d7f32cf7..5e5de9d05bffc990c464b53f987970bae02bbed4 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -276,12 +276,13 @@ android {
       applicationIdSuffix ".beta"
       appSuffix = " Beta"
       buildConfigField "Boolean", "DEBUG_MODE", "true"
+      testCoverageEnabled = false
 
       // tor-android doesn't know this build-type, fallback to release in that case
       matchingFallbacks = ['release']
     }
     debug {
-      testCoverageEnabled = true
+      testCoverageEnabled = false
       buildConfigField "Boolean", "DEBUG_MODE", "true"
     }
   }
@@ -306,11 +307,6 @@ android {
 
     test {
       resources.srcDirs += ['src/test/resources']
-      java.srcDirs += ['src/sharedTest/java']
-    }
-
-    androidTest {
-      java.srcDirs += ['src/sharedTest/java']
     }
 
     fatweb {
@@ -410,13 +406,17 @@ dependencies {
   testImplementation 'org.powermock:powermock-module-junit4-rule:2.0.9'
   testImplementation group: 'com.tngtech.java', name: 'junit-dataprovider', version: '1.10.0'
 
-  androidTestImplementation 'org.mockito:mockito-core:3.6.0'
-  androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
-  androidTestImplementation 'androidx.test.espresso:espresso-contrib:3.4.0'
-  androidTestImplementation 'androidx.test.espresso:espresso-intents:3.4.0'
-  //TODO: remove that library
-  androidTestImplementation 'com.jayway.android.robotium:robotium-solo:5.6.3'
+  androidTestImplementation 'org.mockito:mockito-core:3.9.0'
+  androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.0'
+  androidTestImplementation 'androidx.test.espresso:espresso-contrib:3.5.0'
+  androidTestImplementation 'androidx.test.espresso:espresso-intents:3.5.0'
+
+  androidTestImplementation 'tools.fastlane:screengrab:2.1.1'
+  testImplementation 'tools.fastlane:screengrab:2.1.1'
+
+
   testImplementation 'org.json:json:20180813'
+  androidTestImplementation 'androidx.test.ext:junit:1.1.4'
   debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.9.1'
   annotationProcessor 'com.jakewharton:butterknife-compiler:10.2.3'
   annotationProcessor 'com.squareup.dagger:dagger-compiler:1.2.2'
diff --git a/app/src/androidTest/java/se/leap/bitmaskclient/base/ProviderBaseTest.java b/app/src/androidTest/java/se/leap/bitmaskclient/base/ProviderBaseTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..bbfcdc8b6223c27fa4b4a0fcc6a1e4bf5febcf70
--- /dev/null
+++ b/app/src/androidTest/java/se/leap/bitmaskclient/base/ProviderBaseTest.java
@@ -0,0 +1,157 @@
+package se.leap.bitmaskclient.base;
+
+import static android.content.Context.MODE_PRIVATE;
+import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
+import static androidx.test.espresso.Espresso.onData;
+import static androidx.test.espresso.Espresso.onView;
+import static androidx.test.espresso.action.ViewActions.click;
+import static androidx.test.espresso.assertion.ViewAssertions.matches;
+import static androidx.test.espresso.contrib.DrawerMatchers.isClosed;
+import static androidx.test.espresso.matcher.RootMatchers.isDialog;
+import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
+import static androidx.test.espresso.matcher.ViewMatchers.withId;
+import static androidx.test.espresso.matcher.ViewMatchers.withTagValue;
+import static androidx.test.espresso.matcher.ViewMatchers.withText;
+import static org.hamcrest.Matchers.allOf;
+import static org.hamcrest.Matchers.anything;
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.hasToString;
+import static org.hamcrest.Matchers.is;
+import static se.leap.bitmaskclient.base.models.Constants.SHARED_PREFERENCES;
+import static utils.CustomInteractions.tryResolve;
+
+import android.content.SharedPreferences;
+import android.view.Gravity;
+
+import androidx.test.espresso.DataInteraction;
+import androidx.test.espresso.NoMatchingViewException;
+import androidx.test.espresso.ViewInteraction;
+import androidx.test.espresso.contrib.DrawerActions;
+import androidx.test.ext.junit.rules.ActivityScenarioRule;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.LargeTest;
+
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.FixMethodOrder;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.MethodSorters;
+
+import se.leap.bitmaskclient.R;
+import tools.fastlane.screengrab.Screengrab;
+import tools.fastlane.screengrab.UiAutomatorScreenshotStrategy;
+import tools.fastlane.screengrab.locale.LocaleTestRule;
+
+@LargeTest
+@RunWith(AndroidJUnit4.class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public abstract class ProviderBaseTest {
+
+    @ClassRule
+    public static final LocaleTestRule localeTestRule = new LocaleTestRule();
+
+    @Rule
+    public ActivityScenarioRule<StartActivity> mActivityScenarioRule =
+            new ActivityScenarioRule<>(StartActivity.class);
+
+    @Before
+    public void setup() {
+        Screengrab.setDefaultScreenshotStrategy(new UiAutomatorScreenshotStrategy());
+        SharedPreferences preferences = getApplicationContext().getSharedPreferences(SHARED_PREFERENCES, MODE_PRIVATE);
+        preferences.edit().clear().commit();
+    }
+
+    @Test
+    public void test01_vpnStartTest() throws InterruptedException {
+        boolean configurationNeeded = configureProviderIfNeeded();
+
+        ViewInteraction mainButtonStop;
+        if (!configurationNeeded) {
+            // click on Main on/off button and start VPN
+            ViewInteraction mainButton = tryResolve(
+                    onView(withId(R.id.main_button)),
+                    matches(isDisplayed())
+            );
+
+            mainButton.perform(click());
+            Thread.sleep(50);
+            Screengrab.screenshot("VPN_connecting");
+
+            mainButtonStop = tryResolve(
+                    onView(allOf(
+                            withId(R.id.button),
+                            withTagValue(is("button_circle_stop")))),
+                    matches(isDisplayed()),
+                    20);
+            Screengrab.screenshot("VPN_connected");
+        } else {
+            // on new configurations the VPN is automatically started
+            Screengrab.screenshot("VPN_connecting");
+            mainButtonStop = tryResolve(
+                    onView(allOf(
+                            withId(R.id.button),
+                            withTagValue(is("button_circle_stop")))),
+                    matches(isDisplayed()),
+                    20);
+            Screengrab.screenshot("VPN_connected");
+        }
+
+        mainButtonStop.perform(click());
+        Screengrab.screenshot("VPN_ask_disconnect");
+
+        onView(withText(android.R.string.yes))
+                .inRoot(isDialog())
+                .check(matches(isDisplayed()))
+                .perform(click());
+        Screengrab.screenshot("VPN_disconnected");
+    }
+
+    @Test
+    public void test02_SettingsFragmentScreenshots() {
+        onView(withId(R.id.drawer_layout))
+                .check(matches(isClosed(Gravity.LEFT))) // Left Drawer should be closed.
+                .perform(DrawerActions.open()); // Open Drawer
+
+        Screengrab.screenshot("navigationDrawer");
+
+        // Start the screen of your activity.
+        onView(withId(R.id.advancedSettings))
+                .perform(click());
+
+        Screengrab.screenshot("settingsFragment");
+    }
+
+    @Test
+    public void test03_LocationSelectionFragmentScreenshots() {
+        onView(withId(R.id.drawer_layout))
+                .check(matches(isClosed(Gravity.LEFT))) // Left Drawer should be closed.
+                .perform(DrawerActions.open()); // Open Drawer
+
+        onView(withId(R.id.manualGatewaySelection))
+                .perform(click());
+
+        Screengrab.screenshot("GatewaySelectionFragment");
+    }
+
+    @Test
+    public void test04_AppExclusionFragmentScreenshots() {
+        onView(withId(R.id.drawer_layout))
+                .check(matches(isClosed(Gravity.LEFT))) // Left Drawer should be closed.
+                .perform(DrawerActions.open()); // Open Drawer
+
+        onView(withId(R.id.advancedSettings)).perform(click());
+
+        onView(withId(R.id.exclude_apps)).perform(click());
+
+        tryResolve(
+                onData(anything()).inAdapterView(withId(android.R.id.list)).atPosition(2),
+                matches(isDisplayed()),
+                5);
+
+        Screengrab.screenshot("App_Exclusion_Fragment");
+    }
+
+    public abstract boolean configureProviderIfNeeded();
+}
diff --git a/app/src/androidTest/java/utils/CustomInteractions.java b/app/src/androidTest/java/utils/CustomInteractions.java
new file mode 100644
index 0000000000000000000000000000000000000000..9e3a8f9d2a245c7dee7ebd0265af97563a5abfa7
--- /dev/null
+++ b/app/src/androidTest/java/utils/CustomInteractions.java
@@ -0,0 +1,82 @@
+package utils;
+
+import androidx.annotation.Nullable;
+import androidx.test.espresso.DataInteraction;
+import androidx.test.espresso.NoMatchingViewException;
+import androidx.test.espresso.ViewAssertion;
+import androidx.test.espresso.ViewInteraction;
+
+public class CustomInteractions {
+
+    public static @Nullable
+    ViewInteraction tryResolve(ViewInteraction viewInteraction, int maxTries) {
+        return tryResolve(viewInteraction, null, maxTries);
+    }
+
+    public static @Nullable
+    ViewInteraction tryResolve(ViewInteraction viewInteraction, ViewAssertion assertion) {
+        return tryResolve(viewInteraction, assertion, 10);
+    }
+
+    public static @Nullable ViewInteraction tryResolve(ViewInteraction viewInteraction, ViewAssertion assertion, int maxTries) {
+        ViewInteraction resolvedViewInteraction = null;
+        int attempt = 0;
+        boolean hasFound = false;
+        while (!hasFound && attempt < maxTries) {
+            try {
+                resolvedViewInteraction = viewInteraction;
+                if (assertion != null) {
+                    resolvedViewInteraction.check(assertion);
+                }
+                hasFound = true;
+            } catch (NoMatchingViewException exception) {
+                System.out.println("NoMatchingViewException attempt: " + attempt);
+                exception.printStackTrace();
+                attempt++;
+                if (attempt == maxTries) {
+                    throw exception;
+                }
+                try {
+                    Thread.sleep(1000);
+                } catch (InterruptedException e) {
+                    e.printStackTrace();
+                    break;
+                }
+            }
+        }
+        return resolvedViewInteraction;
+    }
+
+    public static @Nullable
+    DataInteraction tryResolve(DataInteraction dataInteraction, ViewAssertion assertion, int maxTries) {
+        DataInteraction resolvedDataInteraction = null;
+        int attempt = 0;
+        boolean hasFound = false;
+        while (!hasFound && attempt < maxTries) {
+            try {
+                resolvedDataInteraction = dataInteraction;
+                if (assertion != null) {
+                    resolvedDataInteraction.check(assertion);
+                }
+                hasFound = true;
+            } catch (Exception exception) {
+                // TODO: specify expected exception
+                attempt++;
+                if (attempt == maxTries) {
+                    throw exception;
+                }
+                try {
+                    Thread.sleep(1000);
+                } catch (InterruptedException e) {
+                    e.printStackTrace();
+                    break;
+                }
+            }
+        }
+        return resolvedDataInteraction;
+    }
+    public static @Nullable
+    DataInteraction tryResolve(DataInteraction dataInteraction, int maxTries) {
+        return tryResolve(dataInteraction, null, maxTries);
+    }
+}
diff --git a/app/src/androidTest/java/utils/CustomMatchers.java b/app/src/androidTest/java/utils/CustomMatchers.java
new file mode 100644
index 0000000000000000000000000000000000000000..5fe11cd2c52c8db3affdc5749c8f7d13751ce18b
--- /dev/null
+++ b/app/src/androidTest/java/utils/CustomMatchers.java
@@ -0,0 +1,31 @@
+package utils;
+
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewParent;
+
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
+import org.hamcrest.TypeSafeMatcher;
+
+public class CustomMatchers {
+
+    public static Matcher<View> childAtPosition(
+            final Matcher<View> parentMatcher, final int position) {
+
+        return new TypeSafeMatcher<View>() {
+            @Override
+            public void describeTo(Description description) {
+                description.appendText("Child at position " + position + " in parent ");
+                parentMatcher.describeTo(description);
+            }
+
+            @Override
+            public boolean matchesSafely(View view) {
+                ViewParent parent = view.getParent();
+                return parent instanceof ViewGroup && parentMatcher.matches(parent)
+                        && view.equals(((ViewGroup) parent).getChildAt(position));
+            }
+        };
+    }
+}
diff --git a/app/src/androidTest/java/utils/CustomViewActions.java b/app/src/androidTest/java/utils/CustomViewActions.java
new file mode 100644
index 0000000000000000000000000000000000000000..fb0dcc276cda72181939a1ee8ca592e056e46daa
--- /dev/null
+++ b/app/src/androidTest/java/utils/CustomViewActions.java
@@ -0,0 +1,89 @@
+package utils;
+
+import static androidx.test.espresso.matcher.ViewMatchers.isRoot;
+import static androidx.test.espresso.matcher.ViewMatchers.withId;
+import static androidx.test.espresso.matcher.ViewMatchers.withText;
+
+import android.view.View;
+
+import androidx.annotation.StringRes;
+import androidx.test.espresso.UiController;
+import androidx.test.espresso.ViewAction;
+import androidx.test.espresso.util.TreeIterables;
+
+import org.hamcrest.Matcher;
+
+public class CustomViewActions {
+
+    public static ViewAction waitForView(int viewId, long timeout) {
+        return new ViewAction() {
+
+            @Override
+            public String getDescription() {
+                return "Wait for view with specific id \n@param viewId: resource ID \n@param timeout: timeout in milli seconds.";
+            }
+
+            @Override
+            public Matcher<View> getConstraints() {
+                return isRoot();
+            }
+
+            @Override
+            public void perform(UiController uiController, View view) {
+                uiController.loopMainThreadUntilIdle();
+                long startTime = System.currentTimeMillis();
+                long endTime = startTime + timeout;
+                Matcher viewMatcher = withId(viewId);
+
+                while (System.currentTimeMillis() < endTime) {
+                    // Iterate through all views on the screen and see if the view we are looking for is there already
+                    for (View child : TreeIterables.breadthFirstViewTraversal(view)) {
+                        // found view with required ID
+                        if (viewMatcher.matches(child)) {
+                            return;
+                        }
+                    }
+                    // Loops the main thread for a specified period of time.
+                    // Control may not return immediately, instead it'll return after the provided delay has passed and the queue is in an idle state again.
+                    uiController.loopMainThreadForAtLeast(100);
+                }
+            }
+        };
+    }
+
+    public static ViewAction waitForText(@StringRes int textId, long timeout) {
+        return new ViewAction() {
+
+            @Override
+            public String getDescription() {
+                return "Wait for view with specific id \n@param viewId: resource ID \n@param timeout: timeout in milli seconds.";
+            }
+
+            @Override
+            public Matcher<View> getConstraints() {
+                return isRoot();
+            }
+
+            @Override
+            public void perform(UiController uiController, View view) {
+                uiController.loopMainThreadUntilIdle();
+                long startTime = System.currentTimeMillis();
+                long endTime = startTime + timeout;
+                Matcher viewMatcher = withText(textId);
+
+                while (System.currentTimeMillis() < endTime) {
+                    // Iterate through all views on the screen and see if the view we are looking for is there already
+                    for (View child : TreeIterables.breadthFirstViewTraversal(view)) {
+                        // found view with required ID
+                        if (viewMatcher.matches(child)) {
+                            return;
+                        }
+                    }
+                    // Loops the main thread for a specified period of time.
+                    // Control may not return immediately, instead it'll return after the provided delay has passed and the queue is in an idle state again.
+                    uiController.loopMainThreadForAtLeast(100);
+                }
+            }
+        };
+    }
+}
\ No newline at end of file
diff --git a/app/src/androidTestCustom/java/se/leap/bitmaskclient/base/CustomProviderTest.java b/app/src/androidTestCustom/java/se/leap/bitmaskclient/base/CustomProviderTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..1a0814b6f12ef554578f24b0909bb6845e468800
--- /dev/null
+++ b/app/src/androidTestCustom/java/se/leap/bitmaskclient/base/CustomProviderTest.java
@@ -0,0 +1,49 @@
+package se.leap.bitmaskclient.base;
+
+import static androidx.test.espresso.Espresso.onView;
+import static androidx.test.espresso.action.ViewActions.click;
+import static androidx.test.espresso.assertion.ViewAssertions.matches;
+import static androidx.test.espresso.matcher.RootMatchers.isDialog;
+import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
+import static androidx.test.espresso.matcher.ViewMatchers.withId;
+import static androidx.test.espresso.matcher.ViewMatchers.withText;
+import static utils.CustomInteractions.tryResolve;
+
+import androidx.test.espresso.ViewInteraction;
+
+import org.junit.Test;
+
+import se.leap.bitmaskclient.R;
+import tools.fastlane.screengrab.Screengrab;
+
+public class CustomProviderTest extends ProviderBaseTest {
+
+    @Test
+    @Override
+    public void test01_vpnStartTest() throws InterruptedException {
+        ViewInteraction mainButtonStop;
+        mainButtonStop = tryResolve(
+                onView(withId(R.id.main_button)),
+                matches(isDisplayed()),
+                30);
+        Screengrab.screenshot("VPN_connected");
+
+        mainButtonStop.perform(click());
+        Screengrab.screenshot("VPN_ask_disconnect");
+
+        ViewInteraction alertDialogOKbutton = tryResolve(onView(withText(android.R.string.yes))
+                .inRoot(isDialog()),
+                matches(isDisplayed()));
+        alertDialogOKbutton.perform(click());
+        Screengrab.screenshot("VPN_disconnected");
+
+        mainButtonStop.perform(click());
+        Thread.sleep(50);
+        Screengrab.screenshot("VPN_connecting");
+    }
+
+    @Override
+    public boolean configureProviderIfNeeded() {
+        return false;
+    }
+}
\ No newline at end of file
diff --git a/app/src/androidTestCustom/java/se/leap/bitmaskclient/suite/ScreenshotTest.java b/app/src/androidTestCustom/java/se/leap/bitmaskclient/suite/ScreenshotTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..a19b0ffd03b9a8653e19dda130607ade45faa8e1
--- /dev/null
+++ b/app/src/androidTestCustom/java/se/leap/bitmaskclient/suite/ScreenshotTest.java
@@ -0,0 +1,17 @@
+package se.leap.bitmaskclient.suite;
+
+
+import androidx.test.filters.LargeTest;
+
+import org.junit.runner.RunWith;
+import org.junit.runners.Suite;
+
+import se.leap.bitmaskclient.base.CustomProviderTest;
+
+@LargeTest
+@RunWith(Suite.class)
+@Suite.SuiteClasses({
+        CustomProviderTest.class
+})
+public class ScreenshotTest {
+}
diff --git a/app/src/androidTestNormal/java/se/leap/bitmaskclient/base/BitmaskTest.java b/app/src/androidTestNormal/java/se/leap/bitmaskclient/base/BitmaskTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..aa437c74477f1863af59c7db1569df4f08126c52
--- /dev/null
+++ b/app/src/androidTestNormal/java/se/leap/bitmaskclient/base/BitmaskTest.java
@@ -0,0 +1,42 @@
+package se.leap.bitmaskclient.base;
+
+
+import static androidx.test.espresso.Espresso.onData;
+import static androidx.test.espresso.action.ViewActions.click;
+import static androidx.test.espresso.matcher.ViewMatchers.withId;
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.hasToString;
+import static utils.CustomInteractions.tryResolve;
+
+import androidx.test.espresso.DataInteraction;
+import androidx.test.espresso.NoMatchingViewException;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.LargeTest;
+
+import org.junit.FixMethodOrder;
+import org.junit.runner.RunWith;
+import org.junit.runners.MethodSorters;
+
+import se.leap.bitmaskclient.R;
+
+@LargeTest
+@RunWith(AndroidJUnit4.class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class BitmaskTest extends ProviderBaseTest {
+
+    @Override
+    public boolean configureProviderIfNeeded() {
+        try {
+            DataInteraction linearLayout = tryResolve(onData(hasToString(containsString("riseup.net")))
+                            .inAdapterView(withId(R.id.provider_list)),
+                    2);
+            linearLayout.perform(click());
+            return true;
+        } catch (NoMatchingViewException e) {
+            // it might be that the provider was already configured, so we print the stack
+            // trace here and try to continue
+            e.printStackTrace();
+        }
+        return false;
+    }
+}
diff --git a/app/src/androidTestNormal/java/se/leap/bitmaskclient/base/ProviderSetupTest.java b/app/src/androidTestNormal/java/se/leap/bitmaskclient/base/ProviderSetupTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..23db85821c527235350349cad9914704aa26fd62
--- /dev/null
+++ b/app/src/androidTestNormal/java/se/leap/bitmaskclient/base/ProviderSetupTest.java
@@ -0,0 +1,68 @@
+package se.leap.bitmaskclient.base;
+
+
+import static android.content.Context.MODE_PRIVATE;
+import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
+import static androidx.test.espresso.Espresso.onData;
+import static androidx.test.espresso.action.ViewActions.click;
+import static androidx.test.espresso.matcher.ViewMatchers.withId;
+import static org.hamcrest.Matchers.anything;
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.hasToString;
+import static se.leap.bitmaskclient.base.models.Constants.SHARED_PREFERENCES;
+import static utils.CustomInteractions.tryResolve;
+
+import android.content.SharedPreferences;
+
+import androidx.test.espresso.DataInteraction;
+import androidx.test.espresso.NoMatchingViewException;
+import androidx.test.ext.junit.rules.ActivityScenarioRule;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.LargeTest;
+
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import se.leap.bitmaskclient.R;
+import se.leap.bitmaskclient.providersetup.ProviderListActivity;
+import tools.fastlane.screengrab.Screengrab;
+import tools.fastlane.screengrab.UiAutomatorScreenshotStrategy;
+import tools.fastlane.screengrab.locale.LocaleTestRule;
+
+@LargeTest
+@RunWith(AndroidJUnit4.class)
+public class ProviderSetupTest {
+
+    @ClassRule
+    public static final LocaleTestRule localeTestRule = new LocaleTestRule();
+
+    @Rule
+    public ActivityScenarioRule<ProviderListActivity> mActivityScenarioRule =
+            new ActivityScenarioRule<>(ProviderListActivity.class);
+
+    @Before
+    public void setup() {
+        Screengrab.setDefaultScreenshotStrategy(new UiAutomatorScreenshotStrategy());
+        SharedPreferences preferences = getApplicationContext().getSharedPreferences(SHARED_PREFERENCES, MODE_PRIVATE);
+        preferences.edit().clear().commit();
+    }
+
+    @Test
+    public void testConfigureRiseupVPNScreenshot() {
+        DataInteraction linearLayout = tryResolve(onData(hasToString(containsString("riseup.net")))
+                            .inAdapterView(withId(R.id.provider_list)),
+                    2);
+        Screengrab.screenshot("ProviderListActivity");
+        linearLayout.perform(click());
+        Screengrab.screenshot("ProviderListActivity_configureRiseup");
+    }
+
+    @Test
+    public void testaddManuallyNewProviderScreenshot() {
+        onData(anything()).inAdapterView(withId(R.id.provider_list)).atPosition(3).perform(click());
+        Screengrab.screenshot("ProviderListActivity_addManuallyNewProvider");
+    }
+}
diff --git a/app/src/androidTestNormal/java/se/leap/bitmaskclient/suite/ScreenshotTest.java b/app/src/androidTestNormal/java/se/leap/bitmaskclient/suite/ScreenshotTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..5fa45a95c1e4650e8cbcd92608921f349ab39483
--- /dev/null
+++ b/app/src/androidTestNormal/java/se/leap/bitmaskclient/suite/ScreenshotTest.java
@@ -0,0 +1,19 @@
+package se.leap.bitmaskclient.suite;
+
+
+import androidx.test.filters.LargeTest;
+
+import org.junit.runner.RunWith;
+import org.junit.runners.Suite;
+
+import se.leap.bitmaskclient.base.ProviderSetupTest;
+import se.leap.bitmaskclient.base.BitmaskTest;
+
+@LargeTest
+@RunWith(Suite.class)
+@Suite.SuiteClasses({
+        ProviderSetupTest.class,
+        BitmaskTest.class,
+})
+public class ScreenshotTest {
+}
diff --git a/app/src/debug/AndroidManifest.xml b/app/src/debug/AndroidManifest.xml
new file mode 100644
index 0000000000000000000000000000000000000000..51e17015e2f15e789079e07808d178d8192e0f49
--- /dev/null
+++ b/app/src/debug/AndroidManifest.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2011 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    package="se.leap.bitmaskclient"
+    android:requestLegacyExternalStorage="true"
+    >
+    <!-- package is overwritten in build.gradle -->
+
+    <!-- The following permissions are required by fastlane / espresso -->
+    <!-- _____________________________________________________________ -->
+    <!-- Allows unlocking your device and activating its screen so UI tests can succeed -->
+    <uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
+    <uses-permission android:name="android.permission.WAKE_LOCK"/>
+
+    <!-- Allows for storing and retrieving screenshots -->
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
+        tools:ignore="ScopedStorage" />
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+
+    <!-- Allows changing locales -->
+    <uses-permission android:name="android.permission.CHANGE_CONFIGURATION"
+        tools:ignore="ProtectedPermissions" />
+
+    <uses-permission android:name="android.permission.DUMP"
+        tools:ignore="ProtectedPermissions" />
+    <!-- _____________________________________________________________ -->
+
+</manifest>
diff --git a/app/src/main/java/se/leap/bitmaskclient/base/fragments/NavigationDrawerFragment.java b/app/src/main/java/se/leap/bitmaskclient/base/fragments/NavigationDrawerFragment.java
index 68435fec6cad061b684b660195c8a82bcd4ebfde..aa894ccaeb465369349b8ec94225b7979acdb65a 100644
--- a/app/src/main/java/se/leap/bitmaskclient/base/fragments/NavigationDrawerFragment.java
+++ b/app/src/main/java/se/leap/bitmaskclient/base/fragments/NavigationDrawerFragment.java
@@ -424,11 +424,11 @@ public class NavigationDrawerFragment extends Fragment implements SharedPreferen
         Provider currentProvider = ProviderObservable.getInstance().getCurrentProvider();
         account.setText(currentProvider.getName());
         initManualGatewayEntry();
-    }
+    } 
 
     @Override
     public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
-        if (key.equals(PREFERRED_CITY)) {
+        if (key != null && key.equals(PREFERRED_CITY)) {
             initManualGatewayEntry();
         }
     }
diff --git a/app/src/main/java/se/leap/bitmaskclient/base/views/MainButton.java b/app/src/main/java/se/leap/bitmaskclient/base/views/MainButton.java
index fc86fc0bc409f1cdde4b27451b1b7b3d6def638a..c72736135c383759816d98e7efdc927330c690f6 100644
--- a/app/src/main/java/se/leap/bitmaskclient/base/views/MainButton.java
+++ b/app/src/main/java/se/leap/bitmaskclient/base/views/MainButton.java
@@ -48,10 +48,12 @@ public class MainButton extends RelativeLayout {
     public void updateState(boolean isOn, boolean isProcessing) {
         if (isProcessing) {
             button.setImageDrawable(ContextCompat.getDrawable(getContext(), R.drawable.button_circle_cancel));
+            button.setTag("button_circle_cancel");
         } else {
             button.setImageDrawable(
                     ContextCompat.getDrawable(getContext(),
                     isOn ? R.drawable.button_circle_stop : R.drawable.button_circle_start));
+            button.setTag(isOn ? "button_circle_stop" : "button_circle_start");
         }
     }
 }
diff --git a/app/src/sharedTest/java/se.leap.bitmaskclient/testutils/TestSetupHelper.java b/app/src/test/java/se/leap/bitmaskclient/testutils/TestSetupHelper.java
similarity index 100%
rename from app/src/sharedTest/java/se.leap.bitmaskclient/testutils/TestSetupHelper.java
rename to app/src/test/java/se/leap/bitmaskclient/testutils/TestSetupHelper.java
diff --git a/app/src/test/resources/v4/riseup.net.cert b/app/src/test/resources/v4/riseup.net.cert
index 1beff4bd128b54ef4104b199d194d971aab5c1fa..2708a8c821b74c61aae427b136736c72a8aa844b 100644
--- a/app/src/test/resources/v4/riseup.net.cert
+++ b/app/src/test/resources/v4/riseup.net.cert
@@ -1,54 +1,54 @@
 -----BEGIN RSA PRIVATE KEY-----
-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
+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
 -----END RSA PRIVATE KEY-----
 -----BEGIN CERTIFICATE-----
-MIIEmzCCAoOgAwIBAgIQejc1yqfKGehUKDMdybDH+DANBgkqhkiG9w0BAQsFADB1
+MIIEmzCCAoOgAwIBAgIQcAUo8ZeIKRBNyIAyEg7QFDANBgkqhkiG9w0BAQsFADB1
 MRgwFgYDVQQKDA9SaXNldXAgTmV0d29ya3MxGzAZBgNVBAsMEmh0dHBzOi8vcmlz
 ZXVwLm5ldDE8MDoGA1UEAwwzUmlzZXVwIE5ldHdvcmtzIFJvb3QgQ0EgKGNsaWVu
-dCBjZXJ0aWZpY2F0ZXMgb25seSEpMB4XDTIyMTAxNDAwMDAwMFoXDTIzMDExNDAw
-MDAwMFowLTErMCkGA1UEAwwiVU5MSU1JVEVEMTMxaXF4cHB0cHljYnN3eGRueXRu
-aHhiMDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKkpHeVGyofh21kh
-+tNzCTYEpKJngZz6KBNHZcWFyDkGvx505efuwleOyQ1oxVko9Yfs3gJl3IJEMQ0R
-da22fqKL9ukmMhop4AUIJ/ar6AmcCHXrBDeV4Xb7dtxSvHnWpwjDf7Z9Qp8mjucb
-x3Vkb4aZhH/cwLFcKPan8db5kbCX873Jx6x6AoB958cytVWZucTRkXQJvYwNqHWG
-CLkijdYUmII3J11bzR5t21rPgeEc4La2Rr8vr7EknegGmBkOLAo9wzrijTlql2fM
-haX424tK2LDps8RBYmYnqrsYa2n0jPtrsjW3T7D1yndL3mYdQKQxB1v+iPqj9G5x
-xOg7a/sCAwEAAaNvMG0wHQYDVR0OBBYEFEFB2IaA9Z5TOAPtMw3vgKvRR+tpMAsG
+dCBjZXJ0aWZpY2F0ZXMgb25seSEpMB4XDTIyMTIyMDAwMDAwMFoXDTIzMDMyMDAw
+MDAwMFowLTErMCkGA1UEAwwiVU5MSU1JVEVEYnpnMWZicmd3MWdsa29qMWlhZG4w
+d2ZiaTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANvaq2cVUM9HK5fV
+Emwac2QjH2w56PLWVYyP4pqi4HX4Jmltg+WBJgzD669xhKlQSgGE/Sx0YJk0outz
+1tzfIG/QijHaELUi9+V3C214LXbCRM0ksgm7V5geGK6djYczEvaoRQI2y4452ZGh
+Ai5mQ5GuXcuo+iVo0TZaROLpqhkgswbWgCF7lIzBd4cmfZGNqI3UQ0qNyP4Hm6Nr
+8GlI/5cGG7NZZA39C1QnMJqS5KacZ3/goE/DAPR0bzpPE2SaUoQD1ljgTVlY4e/T
+heNTAmyAK/cp6zUfIKzkyVee6tVdOPxovlHtAZL8bBrTRsU60uJQuu9mt2fJ1vzQ
+rjJ1Ux8CAwEAAaNvMG0wHQYDVR0OBBYEFF1cHd5fjHXKbGrW0qd4zNg6KquiMAsG
 A1UdDwQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAjAJBgNVHRMEAjAAMB8GA1Ud
-IwQYMBaAFBf0G9XlKgEBTWuiXTYKKQmWZYBGMA0GCSqGSIb3DQEBCwUAA4ICAQB0
-x+RhbfmWuKQ7clT2oBhLOep1USJ9y98BLMsEmqZ84q0CnG+tI2QLhZvaMiGiMplI
-saHhK1lIHbO/UATHOhE+ZbY9vAaGK1JaBdlOTshjo+cijZ2nWwfwa9Qj/Cx64sSO
-cee3Lbg5IcsFc/0KGCTw0k9PheD/CudRSnLCxImA8DmJO+1w1NUoBBm48tdD6usD
-fLU35FvChvNwW/7GO/q3TGXF/jrreBnrJZMjsffwFOJC8SWZIAScmF/OrqWyruYb
-ZS9/0ZXR21TT1mvUFjIs+0uY2crfli/f91ZbB9eRoiyHkck8RtotiH9PrmYt7j02
-5cwos2BqRs+kZ7AgYvZjWErwIyCsXSzrk0WztLa2vslqZSZSt12VlW5iIyKTyx8T
-snQLEdHwzRT5C+9daq3PneOvD5KhgyySlwWSoMuJdg2n3cTs/0uMDQE7FIim11XM
-Fz8liZf4PcWv8YLFUBHQ367SvbAiyZLzpXZETQyDaYNYGTwYBCGs9Yhq3KM85Kmz
-f0rgrBpm61ujLQ/OBSCq6/RA9BN8UYdO+YS0vengYtIdc+aw1PzLhcC9dLLXC4ef
-zH25zfe7vkt58UcuG+YCsaIR8Hy3I/yDQWVkbNawEYUboEHHRd8pQmUIkHLaYibt
-eq1SXtuulUUDIkyfRATiA/GkPVPPnJImL8XK12WFPQ==
------END CERTIFICATE-----
+IwQYMBaAFBf0G9XlKgEBTWuiXTYKKQmWZYBGMA0GCSqGSIb3DQEBCwUAA4ICAQCS
+sDwNJmNqgrOeNgEuszIGE5V2wTj89S2TtHggBfWErKGUn7N0+gcIRvxxCHG9X7ne
+WjhTS/kEMblR3OcaB/8kfBGCZD6Hc+d0nXv7G5Iqpy5eJuWnv8L4n7QUxIkYiyRr
+bp3Mg31MCFKlDVi2RoTbm3rvcWYPsfBARu6tZlvfNb2Q6dP9/zPU1N2b69fyNwXV
+IOjF/5KPt4n/abxyFuty6xv1G9B3Bq072wZrUSQbZsOLNUtSyLJVyhHRLBBzOOJ/
+Cw8awAEeXXwF1+qAO4MAq/ILcA/v8hAR6AlZez6nbZOBfk5JvEPumZPdIQN6PQ6J
+jODFuJRFFemea3uYyUEnv5MD3BcsApIrM7eRc82Edc9a73CkuMP4ekG68OFl2qHl
+h+XzEchQNVG3t+Ka+8FN1RyeXhPRjzf1JG0elP+CIuyHBOd1SJLv4elGK2ZJWk2L
+cPfLt+tFATKy3PEuftXasLG9g/K8PVaNh8gmh4en88vM0XxZ7npygPv84Cl5wV4r
+LUuR6Cy8quE8QHx01D2EAspkHqk4d/xClyxfT1KCKEQvOk5gOlSrEFsB8TTqtYfr
+G1LiVDcwEq+ExmeDwGIW0EY2mOre7yTHva0p0JZ4AcyK4lNIPXpGBOQcyZ++7/jh
+vdNyERG7ZAjCsj7YTy72On5cS6DQDEHnKmrAIejLLw==
+-----END CERTIFICATE-----
\ No newline at end of file
diff --git a/docker/android-fastlane/Dockerfile b/docker/android-fastlane/Dockerfile
new file mode 100644
index 0000000000000000000000000000000000000000..07240c2107f366e2cd2c165ccdb80a97fccf97ca
--- /dev/null
+++ b/docker/android-fastlane/Dockerfile
@@ -0,0 +1,89 @@
+FROM registry.0xacab.org/leap/bitmask_android/android-sdk:latest
+
+MAINTAINER LEAP Encryption Access Project <info@leap.se>
+LABEL Description="Android emulator image based on android-sdk" Vendor="LEAP" Version="2"
+
+# Make sure debconf doesn't complain about lack of interactivity
+ENV DEBIAN_FRONTEND noninteractive
+# ensure GL compatibility
+ENV ANDROID_EMULATOR_USE_SYSTEM_LIBS=1
+
+# ------------------------------------------------------
+# --- System Dependencies
+
+# Need docker package in order to do Docker-in-Docker (DIND)
+RUN lsb_release -a
+
+RUN apt-get update -qq && \
+    apt-get -y dist-upgrade && \
+    apt-get -y install gnupg apt-transport-https
+
+# Docker apt details should be inherited from android_sdk
+#RUN curl -fsSL https://download.docker.com/linux/debian/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg && \
+#    echo \
+#      "deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/debian \
+#      $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
+
+# JNI build dependencies w/ 32-bit compatible C libs
+# fastlane dependencies
+
+RUN apt-get update -qq && \
+    apt-get install -y docker-ce docker-ce-cli make gcc swig file lib32stdc++6 lib32z1 \
+    autoconf autogen automake autopoint autotools-dev gettext-base libtool patch pkg-config po4a \
+    curl git openjdk-11-jdk openjdk-11-jre-headless imagemagick libpulse0 po4a \
+    make build-essential ruby-dev imagemagick docker-ce-cli mesa-utils xvfb \
+    libstdc++6 libncurses5 libsdl1.2debian imagemagick libpulse-java libpulse0 libxkbcommon-x11-0 && \
+    apt-get clean && \
+    apt-get autoclean && \
+    rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
+
+# ------------------------------------------------------
+# --- Install Android Emulator
+
+RUN echo "accept all licenses"
+# Accept all licenses
+RUN echo y | sdkmanager --licenses
+RUN sdkmanager --list
+
+# Install Android SDK emulator package
+RUN echo y | sdkmanager "emulator"
+
+# Install System Images for emulators
+# seems like every version has its own quirks
+RUN echo y | sdkmanager "system-images;android-31;google_apis;x86_64"
+#RUN echo y | sdkmanager "system-images;android-27;google_apis;x86"
+#RUN echo y | sdkmanager "system-images;android-25;google_apis;x86_64"
+#RUN echo y | sdkmanager "system-images;android-23;google_apis;x86_64"
+
+# fastlane wants 2 emulators. Starting happens in the scripts
+RUN echo no | avdmanager create avd --force --name testApi31 --abi google_apis/x86_64 --package 'system-images;android-31;google_apis;x86_64'
+# TODO: fastlane has been troublesome for newer versions, need to dive deep to update
+#RUN echo no | avdmanager create avd --force --name testApi27 --abi google_apis/x86_64 --package 'system-images;android-27;google_apis;x86_64'
+#RUN echo no | avdmanager create avd --force --name testApi27-duet --abi google_apis/x86_64 --package 'system-images;android-27;google_apis;x86_64'
+
+# Install Android cmake
+RUN sdkmanager "cmake;3.10.2.4988404"
+
+# Fastlane:
+RUN gem install bundler fastlane
+
+
+###############################################
+
+# this stuff is all just notes... really, don't call it a mess
+# ------------------------------------------------------
+# --- Install Android NDK (for running C code)
+
+#ENV ANDROID_NDK_VERSION "r21e"
+#ENV ANDROID_NDK_HOME ${ANDROID_HOME}/android-ndk-${ANDROID_NDK_VERSION}
+#ENV ANDROID_NDK_URL http://dl.google.com/android/repository/android-ndk-${ANDROID_NDK_VERSION}-linux-x86_64.zip
+#ENV ANDROID_SDK_ROOT ${ANDROID_HOME}/latest/cmdline-tools
+#
+#RUN curl -L $ANDROID_NDK_URL -o ndk.zip  \
+#    && unzip ndk.zip -d $ANDROID_HOME/ndk  \
+#    && rm -rf ndk.zip
+#RUN cat $ANDROID_HOME/ndk/android-ndk-${ANDROID_NDK_VERSION}/source.properties | \
+#    grep Pkg.Revision | cut -d "=" -f 2 | \
+#    xargs -I '{}' mv $ANDROID_HOME/ndk/android-ndk-${ANDROID_NDK_VERSION}/ $ANDROID_HOME/ndk/'{}'
+#ENV PATH ${PATH}:${ANDROID_NDK_HOME}
+
diff --git a/fastlane/.env.custom b/fastlane/.env.custom
new file mode 100644
index 0000000000000000000000000000000000000000..5ec9ffd02d14f0aa8e93e1407e01bc1555b740c2
--- /dev/null
+++ b/fastlane/.env.custom
@@ -0,0 +1,4 @@
+SCREENGRAB_APP_PACKAGE_NAME="se.leap.riseupvpn"
+SCREENGRAB_APP_APK_PATH="app/build/outputs/apk/customProductionFat/debug/RiseupVPN_debug.apk"
+SCREENGRAB_TESTS_APK_PATH="app/build/outputs/apk/androidTest/customProductionFat/debug/app-custom-production-fat-debug-androidTest.apk"
+SCREENGRAB_OUTPUT_DIRECTORY="src/custom/fastlane/metadata/android"
\ No newline at end of file
diff --git a/fastlane/.env.default b/fastlane/.env.default
new file mode 100644
index 0000000000000000000000000000000000000000..1362c5f1609a9c8cc19d6d173ea7793d20ac4859
--- /dev/null
+++ b/fastlane/.env.default
@@ -0,0 +1,4 @@
+SCREENGRAB_APP_PACKAGE_NAME="se.leap.bitmaskclient"
+SCREENGRAB_APP_APK_PATH="app/build/outputs/apk/normalProductionFat/debug/Bitmask_debug.apk"
+SCREENGRAB_TESTS_APK_PATH="app/build/outputs/apk/androidTest/normalProductionFat/debug/app-normal-production-fat-debug-androidTest.apk"
+SCREENGRAB_OUTPUT_DIRECTORY="src/normal/fastlane/metadata/android"
\ No newline at end of file
diff --git a/fastlane/Appfile b/fastlane/Appfile
new file mode 100644
index 0000000000000000000000000000000000000000..97be52077b942cdb6d06bf818653ff101e028dca
--- /dev/null
+++ b/fastlane/Appfile
@@ -0,0 +1,2 @@
+json_key_file("") # Path to the json secret file - Follow https://docs.fastlane.tools/actions/supply/#setup to get one
+package_name("se.leap.bitmaskclient") # e.g. com.krausefx.app
diff --git a/fastlane/Fastfile b/fastlane/Fastfile
new file mode 100644
index 0000000000000000000000000000000000000000..99d540ede7d758aad138df478c945f2c8ef63842
--- /dev/null
+++ b/fastlane/Fastfile
@@ -0,0 +1,99 @@
+# This file contains the fastlane.tools configuration
+# You can find the documentation at https://docs.fastlane.tools
+#
+# For a list of all available actions, check out
+#
+#     https://docs.fastlane.tools/actions
+#
+# For a list of all available plugins, check out
+#
+#     https://docs.fastlane.tools/plugins/available-plugins
+#
+
+# Uncomment the line if you want fastlane to automatically update itself
+# update_fastlane
+
+default_platform(:android)
+
+platform :android do
+  desc "Runs all the tests"
+  lane :test do
+    gradle(task: "test")
+  end
+
+   desc "Build debug and test APK for screenshots"
+   lane :build_bitmask_for_screengrab do
+      gradle(
+        task: 'clean'
+      )
+      gradle(
+        task: 'assemble',
+        build_type: 'Debug',
+        flavor: 'NormalProductionFat'
+      )
+      gradle(
+        task: 'assemble',
+        build_type: 'DebugAndroidTest',
+        flavor: 'NormalProductionFat'
+      )
+  end
+
+     desc "Build debug and test APK for screenshots"
+     lane :build_custom_for_screengrab do
+        gradle(
+          task: 'clean'
+        )
+        gradle(
+          task: 'assemble',
+          build_type: 'Debug',
+          flavor: 'CustomProductionFat'
+        )
+        gradle(
+          task: 'assemble',
+          build_type: 'DebugAndroidTest',
+          flavor: 'CustomProductionFat'
+        )
+    end
+
+  lane :bitmask_screenshots do
+    # Prepare builds for Automatic UI Tests
+    build_bitmask_for_screengrab
+    capture_android_screenshots
+    Dir.chdir("../src/normal/fastlane/metadata") do
+         frameit(
+             white: true,
+             path: "."
+         )
+    end
+    # deliver
+  end
+
+   lane :custom_build_screenshots do
+     # Prepare builds for Automatic UI Tests
+     build_custom_for_screengrab
+     capture_android_screenshots
+     Dir.chdir("../src/custom/fastlane/metadata") do
+         frameit(
+             white: true,
+             path: "."
+         )
+     end
+
+     # deliver
+   end
+
+  desc "Submit a new Beta Build to Crashlytics Beta"
+  lane :beta do
+    gradle(task: "clean assembleRelease")
+    crashlytics
+  
+    # sh "your_script.sh"
+    # You can also use other beta testing services here
+  end
+
+  desc "Deploy a new version to the Google Play"
+  lane :deploy do
+    gradle(task: "clean assembleRelease")
+    upload_to_play_store
+  end
+end
diff --git a/fastlane/README.md b/fastlane/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..a41fc4c908f78211ca2480ed202ad7f46e4a4696
--- /dev/null
+++ b/fastlane/README.md
@@ -0,0 +1,80 @@
+fastlane documentation
+----
+
+# Installation
+
+Make sure you have the latest version of the Xcode command line tools installed:
+
+```sh
+xcode-select --install
+```
+
+For _fastlane_ installation instructions, see [Installing _fastlane_](https://docs.fastlane.tools/#installing-fastlane)
+
+# Available Actions
+
+## Android
+
+### android test
+
+```sh
+[bundle exec] fastlane android test
+```
+
+Runs all the tests
+
+### android build_bitmask_for_screengrab
+
+```sh
+[bundle exec] fastlane android build_bitmask_for_screengrab
+```
+
+Build debug and test APK for screenshots
+
+### android build_custom_for_screengrab
+
+```sh
+[bundle exec] fastlane android build_custom_for_screengrab
+```
+
+Build debug and test APK for screenshots
+
+### android bitmask_screenshots
+
+```sh
+[bundle exec] fastlane android bitmask_screenshots
+```
+
+
+
+### android custom_build_screenshots
+
+```sh
+[bundle exec] fastlane android custom_build_screenshots
+```
+
+
+
+### android beta
+
+```sh
+[bundle exec] fastlane android beta
+```
+
+Submit a new Beta Build to Crashlytics Beta
+
+### android deploy
+
+```sh
+[bundle exec] fastlane android deploy
+```
+
+Deploy a new version to the Google Play
+
+----
+
+This README.md is auto-generated and will be re-generated every time [_fastlane_](https://fastlane.tools) is run.
+
+More information about _fastlane_ can be found on [fastlane.tools](https://fastlane.tools).
+
+The documentation of _fastlane_ can be found on [docs.fastlane.tools](https://docs.fastlane.tools).
diff --git a/fastlane/Screengrabfile b/fastlane/Screengrabfile
new file mode 100644
index 0000000000000000000000000000000000000000..317583b017d3a89a6b4b0304bc0b689aa519fe4f
--- /dev/null
+++ b/fastlane/Screengrabfile
@@ -0,0 +1,17 @@
+# remove the leading '#' to uncomment lines
+
+use_tests_in_packages(['se.leap.bitmaskclient.suite'])
+use_timestamp_suffix(false)
+
+# all locales
+# locales(['ar', 'az', 'bg', 'bn', 'br', 'ca', 'cs', 'de', 'el', 'es', 'es-AR', 'et', 'eu', 'fa-IR', 'fi', 'fr', 'gl', 'he', 'hr', 'hu', 'id', 'it', 'ja', 'my', 'nl', 'no', 'pl', 'pt-BR', 'pt-PT', 'ro', 'ru', 'tr', 'ug', 'uk', 'vi', 'zh-CN', 'zh-TW'])
+# prioritized locales
+# locales(['ar', 'bn', 'de', 'es', 'fa-IR', 'fr', 'he', 'hu', 'id', 'it', 'ja', 'my', 'nl', 'pt-BR', 'pt-PT', 'ru', 'tr', 'ug', 'uk', 'zh-CN', 'zh-TW'])
+# development locales
+locales(['ar', 'de', 'ru'])
+
+# clear all previously generated screenshots in your local output directory before creating new ones
+clear_previous_screenshots(true)
+
+# For more information about all available options run
+#   fastlane screengrab --help
diff --git a/scripts/fastlane.sh b/scripts/fastlane.sh
new file mode 100755
index 0000000000000000000000000000000000000000..1ce06ab8c3162b8a5335b5e7c99aefd5c041cd50
--- /dev/null
+++ b/scripts/fastlane.sh
@@ -0,0 +1,33 @@
+#!/bin/bash
+
+GREEN='\033[0;32m'
+RED='\033[0;31m'
+NC='\033[0m'
+
+# init parameters
+if [[ ${1} = "custom" ]]; then
+  BUILD_CUSTOM=true
+elif [[ ! -z ${1} ]]; then
+  echo -e """${RED}Failed due to wrong arguments.${NC}
+  Usage:
+  ======
+  ${GREEN}create screenshots for Bitmask:${NC}
+  ./fastlane.sh
+
+  ${GREEN}create screenshots for your custom build${NC} (please adopt the environment variables in './fastlane/.env.custom'):
+  ./fastlane.sh custom
+  """
+  exit 1
+fi;
+
+#screengrab related environment variables can be found in ./fastlane/.env.*
+SCRIPT_DIR=$(dirname "$0")
+BASE_DIR="$SCRIPT_DIR/.."
+
+cd $BASE_DIR
+if [[ -z $BUILD_CUSTOM ]]; then
+  fastlane --verbose  android bitmask_screenshots
+else
+  fastlane android custom_build_screenshots --env custom
+fi;
+cd -
diff --git a/scripts/installFastlane.sh b/scripts/installFastlane.sh
new file mode 100755
index 0000000000000000000000000000000000000000..9abc6211b3020ba7124f8367c62d706a2e545e19
--- /dev/null
+++ b/scripts/installFastlane.sh
@@ -0,0 +1,39 @@
+#!/bin/bash
+
+## --- System Dependencies for fastlane
+apt-get update -qq && \
+apt-get -y dist-upgrade && \
+apt-get -y install make build-essential ruby ruby-dev imagemagick xvfb libxcb1 libname-dev
+
+gem install fastlane
+
+## ------------------------------------------------------
+## --- Android Emulator 
+
+sdkmanager "platforms;android-30"
+#
+# Install Android SDK emulator package
+echo y | sdkmanager "emulator"
+
+echo y | sdkmanager "system-images;android-31;google_apis;x86_64"
+#echo y | sdkmanager "system-images;android-30;google_apis;x86_64"
+#echo y | sdkmanager "system-images;android-28;google_apis;x86_64"
+#echo y | sdkmanager "system-images;android-27;google_apis;x86"
+#echo y | sdkmanager "system-images;android-25;google_apis;x86_64"
+
+echo no | avdmanager create avd --force --name testApi31 --abi google_apis/x86_64 --package 'system-images;android-31;google_apis;x86_64'
+echo no | avdmanager create avd --force --name testApiduet --abi google_apis/x86_64 --package 'system-images;android-31;google_apis;x86_64'
+
+#echo no | avdmanager create avd --force --name testApi31 --abi google_apis/x86_64 --package 'system-images;android-30;google_apis;x86_64'
+#echo no | avdmanager create avd --force --name testApiduet --abi google_apis/x86_64 --package 'system-images;android-30;google_apis;x86_64'
+
+#echo no | avdmanager create avd --force --name testApi28 --abi google_apis/x86_64 --package 'system-images;android-28;google_apis;x86_64'
+#echo no | avdmanager create avd --force --name testApiduet --abi google_apis/x86_64 --package 'system-images;android-28;google_apis;x86_64'
+ 
+#echo no | avdmanager create avd --force --name testApi27 --abi google_apis/x86 --package 'system-images;android-27;google_apis;x86'
+#echo no | avdmanager create avd --force --name testApiduet --abi google_apis/x86 --package 'system-images;android-27;google_apis;x86'
+ 
+#echo no | avdmanager create avd --force --name testApi25 --abi google_apis/x86_64 --package 'system-images;android-25;google_apis;x86_64'
+#echo no | avdmanager create avd --force --name testApiduet --abi google_apis/x86_64 --package 'system-images;android-25;google_apis;x86_64'
+
+##bundle exec fastlane android bitmask_screenshots
diff --git a/scripts/prepareForScreenshots.sh b/scripts/prepareForScreenshots.sh
new file mode 100755
index 0000000000000000000000000000000000000000..c2ae1bd5f3821ab018b0cb0c8c66563f4a322d19
--- /dev/null
+++ b/scripts/prepareForScreenshots.sh
@@ -0,0 +1,29 @@
+#!/bin/bash
+
+# Copyright (c) 2023 LEAP Encryption Access Project and contributors
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+SCRIPT_DIR=$(dirname "$0")
+BASE_DIR="$SCRIPT_DIR/.."
+
+rm -r $BASE_DIR/bitmaskcore/lib/*
+
+git checkout -- \*
+git checkout -- \.\*
+git submodule foreach --recursive git reset --hard HEAD
+git submodule sync --recursive
+git submodule update --init --recursive
+
+BUILD_TOR=false BUILD_OPENVPN_LIBS=false ./scripts/build_deps.sh
diff --git a/scripts/startEmulators.sh b/scripts/startEmulators.sh
index d1bc829224ba56ece4e3d1fea1d290fe9c855775..1d73dee7ddefb803238347e2e41a30e03338e7c1 100755
--- a/scripts/startEmulators.sh
+++ b/scripts/startEmulators.sh
@@ -1,5 +1,13 @@
 #!/bin/bash
 
+PATH=$PATH:$ANDROID_HOME/cmdline-tools/latest/bin:$ANDROID_HOME/Sdk/tools:$ANDROID_HOME/emulator
+apt-get update
+apt-get install -y libpulse-java libpulse0 imagemagick libxkbcommon-x11-0 xvfb vulkan-tools
+# there's a QT thing missing
+emulator -accel-check
+docker info
+export DISPLAY=:99.0
+
 # init parameters
 for ((i=1;i<=$#;i++)); 
 do
@@ -28,6 +36,13 @@ err() {
 }
 sec=0
 timeout=30
+
+# make sure the emulator is there - and in the PATH
+echo y | sdkmanager "emulator"
+avdmanager list avd
+emulator -version
+find /opt -iname emulator -type f
+
 waitForAdbDevices() {
 	while true; do
         	if [[ $sec -ge $timeout ]]; then
@@ -37,7 +52,7 @@ waitForAdbDevices() {
        		if [[ "$out" == "$N" ]]; then
                 	break
         	fi
-        	let "r = sec % 5"
+        	let "r = sec % 50"
         	if [[ $r -eq 0 ]]; then
                 	echo "Waiting for adb devices to start: $out / $N"
         	fi
@@ -47,12 +62,14 @@ waitForAdbDevices() {
 }
 
 #start first N avd images
-avdmanager list avd | grep Name: | cut -d ':' -f2 | head -n $N |  xargs -I{} -P$N -n1 emulator -no-snapshot -avd {} &
+Xvfb :0 -screen 0 800x600x16 &
+#avdmanager list avd | grep 'Name:' | cut -d ':' -f2 | head -n $N |  xargs -I{} -P$N -n1 emulator -no-snapshot -avd {} &
+avdmanager list avd | grep 'Name:' | cut -d ':' -f2 | head -n $N |  xargs -I{} -P$N -n1 emulator -no-window -no-audio -no-snapshot -avd {} &
+#avdmanager list avd | grep 'Name:' | cut -d ':' -f2 | head -n $N |  xargs -I{} -P$N -n1 emulator -no-snapshot -no-window -avd {} &
+# avdmanager list avd | grep 'Name:' | cut -d ':' -f2 | head -n $N |  xargs -I{} -P$N -n1 emulator -no-snapshot -no-window -no-boot-anim -accel on  -avd {} &
 waitForAdbDevices
 echo "adb found all emulators..."
 
 #wait for each emulator that booting completed
 adb devices | grep -v List | awk '$2{print $1}' | xargs -I{} .gitlab/wait-for-emulator.sh -s {}
 echo "all emulators successfully booted"
-
-
diff --git a/src/README.md b/src/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..dfdcf286377fa0d253e525d0d8e45f8dcc1df0f9
--- /dev/null
+++ b/src/README.md
@@ -0,0 +1,7 @@
+# F-Droid compatible fastlane metadata directory
+
+This source folder only contains the generated metadata for f-droid builds for both Bitmask and a custom flavored client.
+Currently neither F-Droid nor Fastlane support metadata dirs directly within build flavor dirs of a module, like 
+`/<module>/src/<buildFlavor>/fastlane/metadata/android/` (which would be preferable). 
+
+Keep an eye on this [issue](https://gitlab.com/fdroid/fdroidserver/-/issues/829) to track the state of the fastlane improvements for F-Droid.
diff --git a/src/custom/fastlane/metadata/android/README.md b/src/custom/fastlane/metadata/android/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/src/normal/fastlane/metadata/android/README.md b/src/normal/fastlane/metadata/android/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391