From 636130f7921504f1e7339732e54dc0acdb3f74ae Mon Sep 17 00:00:00 2001
From: cyBerta <cyberta@riseup.net>
Date: Tue, 28 Jan 2025 11:16:41 +0100
Subject: [PATCH 01/12] fix automatic bridge selection settings string

---
 app/src/main/res/values/strings.xml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 57da5bf2a..6351ade78 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -250,7 +250,7 @@
     <string name="scan_qr_code">Scan QR Code</string>
     <string name="invalid_code">Invalid code</string>
     <string name="automatic_bridge">Automatic (recommended)</string>
-    <string name="automatic_bridge_description">Connection wil be attemptd using the best available birdges and protocols.</string>
+    <string name="automatic_bridge_description">Connection will be attempted using the best available bridges and protocols.</string>
     <string name="manual_bridge">Manual Configuration</string>
     <string name="manual_bridge_description">Select private bridges and specific protocols</string>
     <string name="censorship_circumvention_description">Manual configuration requires technical understanding. Proceed with caution.</string>
-- 
GitLab


From 7b780c06de54d6188a08c4e87334501b342c3e6d Mon Sep 17 00:00:00 2001
From: cyBerta <cyberta@riseup.net>
Date: Tue, 28 Jan 2025 14:02:11 +0100
Subject: [PATCH 02/12] ensure we default to use bridges in case we switch the
 provider using an invite code

---
 .../providersetup/fragments/ConfigureProviderFragment.java    | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/ConfigureProviderFragment.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/ConfigureProviderFragment.java
index 8b4b7ad8b..2aeaba7fd 100644
--- a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/ConfigureProviderFragment.java
+++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/ConfigureProviderFragment.java
@@ -138,6 +138,10 @@ public class ConfigureProviderFragment extends BaseSetupFragment implements Prop
         if (ProviderSetupObservable.isSetupRunning()) {
             handleResult(ProviderSetupObservable.getResultCode(), ProviderSetupObservable.getResultData(), true);
         } else {
+            Provider provider = setupActivityCallback.getSelectedProvider();
+            if (provider != null && provider.hasIntroducer()) {
+                    PreferenceHelper.useBridges(true);
+            }
             ProviderSetupObservable.startSetup();
             Bundle parameters = new Bundle();
             parameters.putString(Constants.COUNTRYCODE, PreferenceHelper.getBaseCountry());
-- 
GitLab


From bb0128e8c9262d77a8857f12b95f38b2cafee9d8 Mon Sep 17 00:00:00 2001
From: cyBerta <cyberta@riseup.net>
Date: Tue, 28 Jan 2025 21:14:58 +0100
Subject: [PATCH 03/12] rename TUNNELING_NONE to TUNNELING_AUTOMATICALLY and
 DISCOVERY_NONE to DISCOVERY_AUTOMATICALLY

---
 .../fragments/CensorshipCircumventionFragment.java     | 10 +++++-----
 .../bitmaskclient/base/fragments/SettingsFragment.java |  6 +++---
 .../bitmaskclient/base/utils/PreferenceHelper.java     |  4 ++--
 3 files changed, 10 insertions(+), 10 deletions(-)

diff --git a/app/src/main/java/se/leap/bitmaskclient/base/fragments/CensorshipCircumventionFragment.java b/app/src/main/java/se/leap/bitmaskclient/base/fragments/CensorshipCircumventionFragment.java
index 37f9eb18e..3d6bb52cc 100644
--- a/app/src/main/java/se/leap/bitmaskclient/base/fragments/CensorshipCircumventionFragment.java
+++ b/app/src/main/java/se/leap/bitmaskclient/base/fragments/CensorshipCircumventionFragment.java
@@ -29,11 +29,11 @@ import se.leap.bitmaskclient.databinding.FCensorshipCircumventionBinding;
 import se.leap.bitmaskclient.eip.EipCommand;
 
 public class CensorshipCircumventionFragment extends Fragment {
-    public static int DISCOVERY_NONE = 100200000;
+    public static int DISCOVERY_AUTOMATICALLY = 100200000;
     public static int DISCOVERY_SNOWFLAKE = 100200001;
     public static int DISCOVERY_INVITE_PROXY = 100200002;
 
-    public static int TUNNELING_NONE = 100300000;
+    public static int TUNNELING_AUTOMATICALLY = 100300000;
     public static int TUNNELING_OBFS4 = 100300001;
     public static int TUNNELING_OBFS4_KCP = 100300002;
 
@@ -71,7 +71,7 @@ public class CensorshipCircumventionFragment extends Fragment {
 
         RadioButton noneRadioButton = new RadioButton(binding.getRoot().getContext());
         noneRadioButton.setText(getText(R.string.automatically_select));
-        noneRadioButton.setId(DISCOVERY_NONE);
+        noneRadioButton.setId(DISCOVERY_AUTOMATICALLY);
         noneRadioButton.setChecked(!(hasSnowflakePrefs() && getUseSnowflake()) && !ProviderObservable.getInstance().getCurrentProvider().hasIntroducer());
         binding.discoveryRadioGroup.addView(noneRadioButton);
 
@@ -91,7 +91,7 @@ public class CensorshipCircumventionFragment extends Fragment {
 
         binding.discoveryRadioGroup.setOnCheckedChangeListener((group, checkedId) -> {
             useBridges(true);
-            if (checkedId == DISCOVERY_NONE) {
+            if (checkedId == DISCOVERY_AUTOMATICALLY) {
                 useSnowflake(false);
             } else if (checkedId == DISCOVERY_SNOWFLAKE) {
                 useSnowflake(true);
@@ -115,7 +115,7 @@ public class CensorshipCircumventionFragment extends Fragment {
         RadioButton noneRadioButton = new RadioButton(binding.getRoot().getContext());
         noneRadioButton.setText(getText(R.string.automatically_select));
         noneRadioButton.setChecked(!getUseObfs4() && !getUseObfs4Kcp());
-        noneRadioButton.setId(TUNNELING_NONE);
+        noneRadioButton.setId(TUNNELING_AUTOMATICALLY);
         binding.tunnelingRadioGroup.addView(noneRadioButton);
 
         if (ProviderObservable.getInstance().getCurrentProvider().supportsObfs4()) {
diff --git a/app/src/main/java/se/leap/bitmaskclient/base/fragments/SettingsFragment.java b/app/src/main/java/se/leap/bitmaskclient/base/fragments/SettingsFragment.java
index eb777abc5..1a14995c2 100644
--- a/app/src/main/java/se/leap/bitmaskclient/base/fragments/SettingsFragment.java
+++ b/app/src/main/java/se/leap/bitmaskclient/base/fragments/SettingsFragment.java
@@ -3,7 +3,7 @@ package se.leap.bitmaskclient.base.fragments;
 import static android.view.View.GONE;
 import static android.view.View.VISIBLE;
 import static se.leap.bitmaskclient.R.string.advanced_settings;
-import static se.leap.bitmaskclient.base.fragments.CensorshipCircumventionFragment.TUNNELING_NONE;
+import static se.leap.bitmaskclient.base.fragments.CensorshipCircumventionFragment.TUNNELING_AUTOMATICALLY;
 import static se.leap.bitmaskclient.base.models.Constants.GATEWAY_PINNING;
 import static se.leap.bitmaskclient.base.models.Constants.PREFER_UDP;
 import static se.leap.bitmaskclient.base.models.Constants.USE_BRIDGES;
@@ -105,7 +105,7 @@ public class SettingsFragment extends Fragment implements SharedPreferences.OnSh
 
                 if (isChecked) {
                     useSnowflake(false);
-                    setUseTunnel(TUNNELING_NONE);
+                    setUseTunnel(TUNNELING_AUTOMATICALLY);
                     setUsePortHopping(false);
                 }
                 useBridges(isChecked);
@@ -153,7 +153,7 @@ public class SettingsFragment extends Fragment implements SharedPreferences.OnSh
 
     private void resetManualConfig() {
         useSnowflake(false);
-        setUseTunnel(TUNNELING_NONE);
+        setUseTunnel(TUNNELING_AUTOMATICALLY);
         setUsePortHopping(false);
         useBridges(false);
         if (VpnStatus.isVPNActive()) {
diff --git a/app/src/main/java/se/leap/bitmaskclient/base/utils/PreferenceHelper.java b/app/src/main/java/se/leap/bitmaskclient/base/utils/PreferenceHelper.java
index 25a9a3fbb..a698630c1 100644
--- a/app/src/main/java/se/leap/bitmaskclient/base/utils/PreferenceHelper.java
+++ b/app/src/main/java/se/leap/bitmaskclient/base/utils/PreferenceHelper.java
@@ -1,7 +1,7 @@
 package se.leap.bitmaskclient.base.utils;
 
 import static android.content.Context.MODE_PRIVATE;
-import static se.leap.bitmaskclient.base.fragments.CensorshipCircumventionFragment.TUNNELING_NONE;
+import static se.leap.bitmaskclient.base.fragments.CensorshipCircumventionFragment.TUNNELING_AUTOMATICALLY;
 import static se.leap.bitmaskclient.base.fragments.CensorshipCircumventionFragment.TUNNELING_OBFS4;
 import static se.leap.bitmaskclient.base.fragments.CensorshipCircumventionFragment.TUNNELING_OBFS4_KCP;
 import static se.leap.bitmaskclient.base.models.Constants.ALLOW_EXPERIMENTAL_TRANSPORTS;
@@ -632,7 +632,7 @@ public class PreferenceHelper {
     }
 
     public static int getUseTunnel() {
-        return getInt(USE_TUNNEL, TUNNELING_NONE);
+        return getInt(USE_TUNNEL, TUNNELING_AUTOMATICALLY);
     }
 
     public static boolean useIpv6Firewall() {
-- 
GitLab


From 457ae5f4d7ebc10c70f6712f2bcf9e10a5b22f26 Mon Sep 17 00:00:00 2001
From: cyBerta <cyberta@riseup.net>
Date: Wed, 29 Jan 2025 01:21:43 +0100
Subject: [PATCH 04/12] Always show autommatic circumvention settings. If a
 provider doesn't support bridges, this settings still influences the API
 communication obfuscation.

---
 .../CensorshipCircumventionFragment.java      | 16 +++---
 .../base/fragments/SettingsFragment.java      | 56 ++++++++++---------
 .../base/utils/PreferenceHelper.java          | 24 ++++----
 .../bitmaskclient/eip/GatewaysManager.java    |  3 -
 .../fragments/CircumventionSetupFragment.java |  8 ++-
 app/src/main/res/layout/f_settings.xml        |  2 +
 6 files changed, 56 insertions(+), 53 deletions(-)

diff --git a/app/src/main/java/se/leap/bitmaskclient/base/fragments/CensorshipCircumventionFragment.java b/app/src/main/java/se/leap/bitmaskclient/base/fragments/CensorshipCircumventionFragment.java
index 3d6bb52cc..888a6f6f0 100644
--- a/app/src/main/java/se/leap/bitmaskclient/base/fragments/CensorshipCircumventionFragment.java
+++ b/app/src/main/java/se/leap/bitmaskclient/base/fragments/CensorshipCircumventionFragment.java
@@ -5,6 +5,7 @@ import static se.leap.bitmaskclient.base.utils.PreferenceHelper.getUseObfs4Kcp;
 import static se.leap.bitmaskclient.base.utils.PreferenceHelper.getUsePortHopping;
 import static se.leap.bitmaskclient.base.utils.PreferenceHelper.getUseSnowflake;
 import static se.leap.bitmaskclient.base.utils.PreferenceHelper.hasSnowflakePrefs;
+import static se.leap.bitmaskclient.base.utils.PreferenceHelper.resetSnowflakeSettings;
 import static se.leap.bitmaskclient.base.utils.PreferenceHelper.setUsePortHopping;
 import static se.leap.bitmaskclient.base.utils.PreferenceHelper.setUseTunnel;
 import static se.leap.bitmaskclient.base.utils.PreferenceHelper.useBridges;
@@ -68,12 +69,11 @@ public class CensorshipCircumventionFragment extends Fragment {
 
 
     private void initDiscovery() {
-
-        RadioButton noneRadioButton = new RadioButton(binding.getRoot().getContext());
-        noneRadioButton.setText(getText(R.string.automatically_select));
-        noneRadioButton.setId(DISCOVERY_AUTOMATICALLY);
-        noneRadioButton.setChecked(!(hasSnowflakePrefs() && getUseSnowflake()) && !ProviderObservable.getInstance().getCurrentProvider().hasIntroducer());
-        binding.discoveryRadioGroup.addView(noneRadioButton);
+        RadioButton automaticallyRadioButton = new RadioButton(binding.getRoot().getContext());
+        automaticallyRadioButton.setText(getText(R.string.automatically_select));
+        automaticallyRadioButton.setId(DISCOVERY_AUTOMATICALLY);
+        automaticallyRadioButton.setChecked(!(hasSnowflakePrefs() && getUseSnowflake()) && !ProviderObservable.getInstance().getCurrentProvider().hasIntroducer());
+        binding.discoveryRadioGroup.addView(automaticallyRadioButton);
 
         RadioButton snowflakeRadioButton = new RadioButton(binding.getRoot().getContext());
         snowflakeRadioButton.setText(getText(R.string.snowflake));
@@ -92,14 +92,12 @@ public class CensorshipCircumventionFragment extends Fragment {
         binding.discoveryRadioGroup.setOnCheckedChangeListener((group, checkedId) -> {
             useBridges(true);
             if (checkedId == DISCOVERY_AUTOMATICALLY) {
-                useSnowflake(false);
+                resetSnowflakeSettings();
             } else if (checkedId == DISCOVERY_SNOWFLAKE) {
                 useSnowflake(true);
             } else if (checkedId == DISCOVERY_INVITE_PROXY) {
                 useSnowflake(false);
             }
-
-            tryReconnectVpn();
         });
     }
 
diff --git a/app/src/main/java/se/leap/bitmaskclient/base/fragments/SettingsFragment.java b/app/src/main/java/se/leap/bitmaskclient/base/fragments/SettingsFragment.java
index 1a14995c2..4567bf974 100644
--- a/app/src/main/java/se/leap/bitmaskclient/base/fragments/SettingsFragment.java
+++ b/app/src/main/java/se/leap/bitmaskclient/base/fragments/SettingsFragment.java
@@ -16,6 +16,7 @@ import static se.leap.bitmaskclient.base.utils.PreferenceHelper.getPreferUDP;
 import static se.leap.bitmaskclient.base.utils.PreferenceHelper.getShowAlwaysOnDialog;
 import static se.leap.bitmaskclient.base.utils.PreferenceHelper.getUseBridges;
 import static se.leap.bitmaskclient.base.utils.PreferenceHelper.preferUDP;
+import static se.leap.bitmaskclient.base.utils.PreferenceHelper.resetSnowflakeSettings;
 import static se.leap.bitmaskclient.base.utils.PreferenceHelper.setAllowExperimentalTransports;
 import static se.leap.bitmaskclient.base.utils.PreferenceHelper.setUseObfuscationPinning;
 import static se.leap.bitmaskclient.base.utils.PreferenceHelper.setUsePortHopping;
@@ -23,7 +24,7 @@ import static se.leap.bitmaskclient.base.utils.PreferenceHelper.setUseTunnel;
 import static se.leap.bitmaskclient.base.utils.PreferenceHelper.useBridges;
 import static se.leap.bitmaskclient.base.utils.PreferenceHelper.useObfuscationPinning;
 import static se.leap.bitmaskclient.base.utils.PreferenceHelper.useSnowflake;
-import static se.leap.bitmaskclient.base.utils.PreferenceHelper.usesManualBridges;
+import static se.leap.bitmaskclient.base.utils.PreferenceHelper.useManualBridgeSettings;
 import static se.leap.bitmaskclient.base.utils.ViewHelper.setActionBarSubtitle;
 
 import android.app.AlertDialog;
@@ -37,10 +38,12 @@ import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.EditText;
+import android.widget.LinearLayout;
 import android.widget.Toast;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.appcompat.widget.AppCompatTextView;
 import androidx.appcompat.widget.SwitchCompat;
 import androidx.fragment.app.DialogFragment;
 import androidx.fragment.app.Fragment;
@@ -94,43 +97,42 @@ public class SettingsFragment extends Fragment implements SharedPreferences.OnSh
 
     private void initAutomaticCircumventionEntry(View rootView) {
         IconSwitchEntry automaticCircumvention = rootView.findViewById(R.id.bridge_automatic_switch);
-        if (ProviderObservable.getInstance().getCurrentProvider().supportsPluggableTransports()) {
-            automaticCircumvention.setVisibility(VISIBLE);
-
-            automaticCircumvention.setChecked(getUseBridges() && !usesManualBridges());
-            automaticCircumvention.setOnCheckedChangeListener((buttonView, isChecked) -> {
-                if (!buttonView.isPressed()) {
-                    return;
-                }
+        automaticCircumvention.setChecked(getUseBridges() && !useManualBridgeSettings());
+        automaticCircumvention.setOnCheckedChangeListener((buttonView, isChecked) -> {
+            if (!buttonView.isPressed()) {
+                return;
+            }
 
-                if (isChecked) {
-                    useSnowflake(false);
-                    setUseTunnel(TUNNELING_AUTOMATICALLY);
-                    setUsePortHopping(false);
-                }
+            if (isChecked) {
+                resetSnowflakeSettings();
+                setUseTunnel(TUNNELING_AUTOMATICALLY);
+                setUsePortHopping(false);
+            } else {
+                useSnowflake(false);
+            }
+            if (ProviderObservable.getInstance().getCurrentProvider().supportsPluggableTransports()) {
                 useBridges(isChecked);
                 if (VpnStatus.isVPNActive()) {
                     EipCommand.startVPN(getContext(), false);
                     Toast.makeText(getContext(), R.string.reconnecting, Toast.LENGTH_LONG).show();
                 }
-            });
+            }
+        });
 
-            //We check the UI state of the useUdpEntry here as well, in order to avoid a situation
-            //where both entries are disabled, because both preferences are enabled.
-            //bridges can be enabled not only from here but also from error handling
-            boolean useUDP = getPreferUDP() && useUdpEntry.isEnabled();
-            automaticCircumvention.setEnabled(!useUDP);
-            automaticCircumvention.setSubtitle(getString(useUDP?R.string.disabled_while_udp_on:R.string.automatic_bridge_description));
-        } else {
-            automaticCircumvention.setVisibility(GONE);
-        }
+        //We check the UI state of the useUdpEntry here as well, in order to avoid a situation
+        //where both entries are disabled, because both preferences are enabled.
+        //bridges can be enabled not only from here but also from error handling
+        boolean useUDP = getPreferUDP() && useUdpEntry.isEnabled();
+        automaticCircumvention.setEnabled(!useUDP);
+        automaticCircumvention.setSubtitle(getString(useUDP ? R.string.disabled_while_udp_on : R.string.automatic_bridge_description));
     }
 
     private void initManualCircumventionEntry(View rootView) {
+        LinearLayout manualConfigRoot = rootView.findViewById(R.id.bridge_manual_switch_entry);
+        manualConfigRoot.setVisibility(ProviderObservable.getInstance().getCurrentProvider().supportsPluggableTransports() ? VISIBLE : GONE);
         IconTextEntry manualConfiguration = rootView.findViewById(R.id.bridge_manual_switch);
-        manualConfiguration.setVisibility(ProviderObservable.getInstance().getCurrentProvider().supportsPluggableTransports() ? VISIBLE : GONE);
         SwitchCompat manualConfigurationSwitch = rootView.findViewById(R.id.bridge_manual_switch_control);
-        boolean usesManualBridge = usesManualBridges();
+        boolean usesManualBridge = useManualBridgeSettings();
         manualConfigurationSwitch.setChecked(usesManualBridge);
         manualConfigurationSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> {
             if (!buttonView.isPressed()) {
@@ -148,7 +150,7 @@ public class SettingsFragment extends Fragment implements SharedPreferences.OnSh
         //bridges can be enabled not only from here but also from error handling
         boolean useUDP = getPreferUDP() && useUdpEntry.isEnabled();
         manualConfiguration.setEnabled(!useUDP);
-        manualConfiguration.setSubtitle(getString(useUDP? R.string.disabled_while_udp_on:R.string.manual_bridge_description));
+        manualConfiguration.setSubtitle(getString(useUDP ? R.string.disabled_while_udp_on : R.string.manual_bridge_description));
     }
 
     private void resetManualConfig() {
diff --git a/app/src/main/java/se/leap/bitmaskclient/base/utils/PreferenceHelper.java b/app/src/main/java/se/leap/bitmaskclient/base/utils/PreferenceHelper.java
index a698630c1..0bf5dfb25 100644
--- a/app/src/main/java/se/leap/bitmaskclient/base/utils/PreferenceHelper.java
+++ b/app/src/main/java/se/leap/bitmaskclient/base/utils/PreferenceHelper.java
@@ -65,13 +65,10 @@ import androidx.annotation.WorkerThread;
 import androidx.security.crypto.EncryptedSharedPreferences;
 import androidx.security.crypto.MasterKey;
 
-import com.google.gson.Gson;
-
 import org.json.JSONException;
 import org.json.JSONObject;
 
 import java.io.IOException;
-import java.net.MalformedURLException;
 import java.net.URL;
 import java.security.GeneralSecurityException;
 import java.util.HashMap;
@@ -81,9 +78,6 @@ import java.util.Set;
 
 import de.blinkt.openvpn.VpnProfile;
 import de.blinkt.openvpn.core.NativeUtils;
-import io.swagger.client.JSON;
-import mobile.BitmaskMobile;
-import mobilemodels.BitmaskMobileCore;
 import se.leap.bitmaskclient.BuildConfig;
 import se.leap.bitmaskclient.base.models.Introducer;
 import se.leap.bitmaskclient.base.models.Provider;
@@ -491,6 +485,10 @@ public class PreferenceHelper {
         return hasKey(USE_SNOWFLAKE);
     }
 
+    public static void resetSnowflakeSettings() {
+        removeKey(USE_SNOWFLAKE);
+    }
+
     public static Boolean getUseSnowflake() {
         return getBoolean(USE_SNOWFLAKE, true);
     }
@@ -619,12 +617,8 @@ public class PreferenceHelper {
         return getUseTunnel() == TUNNELING_OBFS4_KCP;
     }
 
-    public static boolean usesManualBridges(){
-        return getUseSnowflake() || usesSpecificTunnel() || getUsePortHopping();
-    }
-
-    public static boolean usesSpecificTunnel() {
-        return getUseObfs4() || getUseObfs4Kcp();
+    public static boolean useManualBridgeSettings(){
+        return (hasSnowflakePrefs() && getUseSnowflake()) || getUseObfs4() || getUseObfs4Kcp() || getUsePortHopping();
     }
 
     public static void setUseTunnel(int tunnel) {
@@ -709,6 +703,12 @@ public class PreferenceHelper {
         }
     }
 
+    public static void removeKey(String key) {
+        synchronized (LOCK) {
+            preferences.edit().remove(key).apply();
+        }
+    }
+
     public static long getLong(String key, long defValue) {
         synchronized (LOCK) {
             return preferences.getLong(key, defValue);
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 d7f3b42ea..9655013c6 100644
--- a/app/src/main/java/se/leap/bitmaskclient/eip/GatewaysManager.java
+++ b/app/src/main/java/se/leap/bitmaskclient/eip/GatewaysManager.java
@@ -37,7 +37,6 @@ import static se.leap.bitmaskclient.base.utils.PreferenceHelper.getUseBridges;
 import static se.leap.bitmaskclient.base.utils.PreferenceHelper.getUseObfs4;
 import static se.leap.bitmaskclient.base.utils.PreferenceHelper.getUseObfs4Kcp;
 import static se.leap.bitmaskclient.base.utils.PreferenceHelper.getUsePortHopping;
-import static se.leap.bitmaskclient.base.utils.PreferenceHelper.usesSpecificTunnel;
 
 import android.content.Context;
 import android.util.Log;
@@ -54,10 +53,8 @@ import org.json.JSONObject;
 import java.io.IOException;
 import java.lang.reflect.Type;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Set;
diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/CircumventionSetupFragment.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/CircumventionSetupFragment.java
index 58fccc651..d7d8516eb 100644
--- a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/CircumventionSetupFragment.java
+++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/CircumventionSetupFragment.java
@@ -1,5 +1,6 @@
 package se.leap.bitmaskclient.providersetup.fragments;
 
+import static se.leap.bitmaskclient.base.fragments.CensorshipCircumventionFragment.TUNNELING_AUTOMATICALLY;
 import static se.leap.bitmaskclient.base.utils.BuildConfigHelper.isDefaultBitmask;
 
 import android.graphics.Typeface;
@@ -35,14 +36,17 @@ public class CircumventionSetupFragment extends BaseSetupFragment implements Can
             if (binding.rbCircumvention.getId() == checkedId) {
                 PreferenceHelper.useBridges(true);
                 PreferenceHelper.useSnowflake(true);
+                PreferenceHelper.setUseTunnel(TUNNELING_AUTOMATICALLY);
                 binding.tvCircumventionDetailDescription.setVisibility(View.VISIBLE);
                 binding.rbCircumvention.setTypeface(Typeface.DEFAULT, Typeface.BOLD);
                 binding.rbPlainVpn.setTypeface(Typeface.DEFAULT, Typeface.NORMAL);
                 return;
             }
-
+            // otherwise don't use obfuscation
             PreferenceHelper.useBridges(false);
-            PreferenceHelper.useSnowflake(false);
+            PreferenceHelper.resetSnowflakeSettings();
+            PreferenceHelper.setUsePortHopping(false);
+            PreferenceHelper.setUseTunnel(TUNNELING_AUTOMATICALLY);
             binding.tvCircumventionDetailDescription.setVisibility(View.GONE);
             binding.rbPlainVpn.setTypeface(Typeface.DEFAULT, Typeface.BOLD);
             binding.rbCircumvention.setTypeface(Typeface.DEFAULT, Typeface.NORMAL);
diff --git a/app/src/main/res/layout/f_settings.xml b/app/src/main/res/layout/f_settings.xml
index e7b4356f9..87a974553 100644
--- a/app/src/main/res/layout/f_settings.xml
+++ b/app/src/main/res/layout/f_settings.xml
@@ -60,9 +60,11 @@
             app:text="@string/automatic_bridge"
             app:subtitle="@string/automatic_bridge_description"
             app:icon="@drawable/bridge_automatic"
+            android:visibility="visible"
             app:singleLine="false" />
 
         <LinearLayout
+            android:id="@+id/bridge_manual_switch_entry"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:orientation="horizontal">
-- 
GitLab


From a6494501cf6475fcc4c72610894e56b584f63fa4 Mon Sep 17 00:00:00 2001
From: cyBerta <cyberta@riseup.net>
Date: Wed, 29 Jan 2025 01:26:03 +0100
Subject: [PATCH 05/12] provider setup: reset circumvention settings to
 defaults if a provider gets configured via an invite code. This ensures that
 the circumvention settings are correctly reset when switching between
 providers

---
 .../fragments/ConfigureProviderFragment.java           | 10 +++++++++-
 1 file changed, 9 insertions(+), 1 deletion(-)

diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/ConfigureProviderFragment.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/ConfigureProviderFragment.java
index 2aeaba7fd..fa705c309 100644
--- a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/ConfigureProviderFragment.java
+++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/ConfigureProviderFragment.java
@@ -7,11 +7,15 @@ import static androidx.recyclerview.widget.RecyclerView.SCROLL_STATE_IDLE;
 import static se.leap.bitmaskclient.R.string.app_name;
 import static se.leap.bitmaskclient.R.string.description_configure_provider;
 import static se.leap.bitmaskclient.R.string.description_configure_provider_circumvention;
+import static se.leap.bitmaskclient.base.fragments.CensorshipCircumventionFragment.TUNNELING_AUTOMATICALLY;
 import static se.leap.bitmaskclient.base.models.Constants.BROADCAST_RESULT_CODE;
 import static se.leap.bitmaskclient.base.models.Constants.BROADCAST_RESULT_KEY;
 import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_KEY;
 import static se.leap.bitmaskclient.base.utils.BuildConfigHelper.isDefaultBitmask;
 import static se.leap.bitmaskclient.base.utils.PreferenceHelper.getUseSnowflake;
+import static se.leap.bitmaskclient.base.utils.PreferenceHelper.setUsePortHopping;
+import static se.leap.bitmaskclient.base.utils.PreferenceHelper.setUseTunnel;
+import static se.leap.bitmaskclient.base.utils.PreferenceHelper.useSnowflake;
 import static se.leap.bitmaskclient.base.utils.ViewHelper.animateContainerVisibility;
 import static se.leap.bitmaskclient.providersetup.ProviderAPI.CORRECTLY_DOWNLOADED_VPN_CERTIFICATE;
 import static se.leap.bitmaskclient.providersetup.ProviderAPI.DOWNLOAD_VPN_CERTIFICATE;
@@ -140,7 +144,11 @@ public class ConfigureProviderFragment extends BaseSetupFragment implements Prop
         } else {
             Provider provider = setupActivityCallback.getSelectedProvider();
             if (provider != null && provider.hasIntroducer()) {
-                    PreferenceHelper.useBridges(true);
+                // enable automatic selection of bridges
+                useSnowflake(false);
+                setUseTunnel(TUNNELING_AUTOMATICALLY);
+                setUsePortHopping(false);
+                PreferenceHelper.useBridges(true);
             }
             ProviderSetupObservable.startSetup();
             Bundle parameters = new Bundle();
-- 
GitLab


From ba06108aa2efa042b28b41e7834080789120e6b6 Mon Sep 17 00:00:00 2001
From: cyBerta <cyberta@riseup.net>
Date: Wed, 29 Jan 2025 01:39:23 +0100
Subject: [PATCH 06/12] show configuration logs detail container only if VPN is
 not running

---
 .../providersetup/fragments/ConfigureProviderFragment.java    | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/ConfigureProviderFragment.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/ConfigureProviderFragment.java
index fa705c309..b9051b1e2 100644
--- a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/ConfigureProviderFragment.java
+++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/ConfigureProviderFragment.java
@@ -13,6 +13,7 @@ import static se.leap.bitmaskclient.base.models.Constants.BROADCAST_RESULT_KEY;
 import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_KEY;
 import static se.leap.bitmaskclient.base.utils.BuildConfigHelper.isDefaultBitmask;
 import static se.leap.bitmaskclient.base.utils.PreferenceHelper.getUseSnowflake;
+import static se.leap.bitmaskclient.base.utils.PreferenceHelper.hasSnowflakePrefs;
 import static se.leap.bitmaskclient.base.utils.PreferenceHelper.setUsePortHopping;
 import static se.leap.bitmaskclient.base.utils.PreferenceHelper.setUseTunnel;
 import static se.leap.bitmaskclient.base.utils.PreferenceHelper.useSnowflake;
@@ -52,6 +53,7 @@ import java.beans.PropertyChangeEvent;
 import java.beans.PropertyChangeListener;
 import java.util.List;
 
+import de.blinkt.openvpn.core.VpnStatus;
 import se.leap.bitmaskclient.R;
 import se.leap.bitmaskclient.base.models.Constants;
 import se.leap.bitmaskclient.base.models.Provider;
@@ -130,7 +132,7 @@ public class ConfigureProviderFragment extends BaseSetupFragment implements Prop
     public void onFragmentSelected() {
         super.onFragmentSelected();
         ignoreProviderAPIUpdates = false;
-        binding.detailContainer.setVisibility(getUseSnowflake() ? VISIBLE : GONE);
+        binding.detailContainer.setVisibility(!VpnStatus.isVPNActive() && hasSnowflakePrefs() && getUseSnowflake() ? VISIBLE : GONE);
         binding.tvCircumventionDescription.setText(getUseSnowflake() ? getString(description_configure_provider_circumvention, getString(app_name)) : getString(description_configure_provider, getString(app_name)));
         if (!isDefaultBitmask()) {
             Drawable drawable = ResourcesCompat.getDrawable(getResources(), R.drawable.setup_progress_spinner, null);
-- 
GitLab


From 8feb66a1e6067540ae30c691a6ed585e10be4abd Mon Sep 17 00:00:00 2001
From: cyBerta <cyberta@riseup.net>
Date: Wed, 29 Jan 2025 02:28:39 +0100
Subject: [PATCH 07/12] remove demo provider from debug builds

---
 .../assets/demo.bitmask.net.json              | 37 -------------------
 .../assets/demo.bitmask.net.pem               | 10 -----
 .../assets/urls/demo.bitmask.net.url          |  4 --
 3 files changed, 51 deletions(-)
 delete mode 100644 app/src/normalProductionFatDebug/assets/demo.bitmask.net.json
 delete mode 100644 app/src/normalProductionFatDebug/assets/demo.bitmask.net.pem
 delete mode 100644 app/src/normalProductionFatDebug/assets/urls/demo.bitmask.net.url

diff --git a/app/src/normalProductionFatDebug/assets/demo.bitmask.net.json b/app/src/normalProductionFatDebug/assets/demo.bitmask.net.json
deleted file mode 100644
index f5998c9bd..000000000
--- a/app/src/normalProductionFatDebug/assets/demo.bitmask.net.json
+++ /dev/null
@@ -1,37 +0,0 @@
-{
-  "api_uri":"https://api.demo.bitmask.net:4430",
-  "api_version":"3",
-  "ca_cert_fingerprint":"SHA256: 40ed9d9c13872c1fcba25abdcf26a7b1bdeded433d74fbe1ccb58fbaaab58e23",
-  "ca_cert_uri":"https://api.demo.bitmask.net/ca.crt",
-  "default_language":"en",
-  "description":{
-    "en":"This is a demo provider"
-  },
-  "domain":"demo.bitmask.net",
-  "enrollment_policy":"open",
-  "languages":[
-    "en"
-  ],
-  "name":{
-    "en":"Demo provider"
-  },
-  "service":{
-    "allow_anonymous":true,
-    "allow_free":true,
-    "allow_limited_bandwidth":false,
-    "allow_paid":false,
-    "allow_registration":false,
-    "allow_unlimited_bandwidth":true,
-    "bandwidth_limit":102400,
-    "default_service_level":1,
-    "levels":{
-      "1":{
-        "description":"Please donate.",
-        "name":"free"
-      }
-    }
-  },
-  "services":[
-    "openvpn"
-  ]
-}
\ No newline at end of file
diff --git a/app/src/normalProductionFatDebug/assets/demo.bitmask.net.pem b/app/src/normalProductionFatDebug/assets/demo.bitmask.net.pem
deleted file mode 100644
index f1ede7ab4..000000000
--- a/app/src/normalProductionFatDebug/assets/demo.bitmask.net.pem
+++ /dev/null
@@ -1,10 +0,0 @@
------BEGIN CERTIFICATE-----
-MIIBYTCCAQigAwIBAgIBATAKBggqhkjOPQQDAjAXMRUwEwYDVQQDEwxMRUFQIFJv
-b3QgQ0EwHhcNMjQwMjIxMTEzMTUwWhcNMjkwMjIxMTEzNjUwWjAXMRUwEwYDVQQD
-EwxMRUFQIFJvb3QgQ0EwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARKTm8AKkqK
-aMI7dEarRRGEOPa3i49YE4bGNHxO97h14urXOROJWjnwHJdJ3dJk16oR0HKohXR7
-jSxyukoonJkgo0UwQzAOBgNVHQ8BAf8EBAMCAqQwEgYDVR0TAQH/BAgwBgEB/wIB
-ATAdBgNVHQ4EFgQUMVywfKRY9Ec3n98PVIEu7kyWKHwwCgYIKoZIzj0EAwIDRwAw
-RAIgeSMNJ51+EvNJzqsISauhOTbFxiUnnmV2z/+dxYeCPzUCIEMXM/X2ekzHEz6V
-l7zSfosiYvtQQL3ML3sLnVMmxdmd
------END CERTIFICATE-----
\ No newline at end of file
diff --git a/app/src/normalProductionFatDebug/assets/urls/demo.bitmask.net.url b/app/src/normalProductionFatDebug/assets/urls/demo.bitmask.net.url
deleted file mode 100644
index 459d6d10b..000000000
--- a/app/src/normalProductionFatDebug/assets/urls/demo.bitmask.net.url
+++ /dev/null
@@ -1,4 +0,0 @@
-{
-	"main_url" : "https://demo.bitmask.net",
-	"geoip_url" : "https://menshen.demo.bitmask.net/json"
-}
-- 
GitLab


From 1d4883b2434ef261bc45b5967f697ee7d34c42d9 Mon Sep 17 00:00:00 2001
From: cyBerta <cyberta@riseup.net>
Date: Wed, 29 Jan 2025 02:31:25 +0100
Subject: [PATCH 08/12] update preshipped riseup provider.json

---
 app/src/custom/assets/riseup.net.json | 52 +++++++++++++--------------
 app/src/normal/assets/riseup.net.json | 52 +++++++++++++--------------
 2 files changed, 52 insertions(+), 52 deletions(-)

diff --git a/app/src/custom/assets/riseup.net.json b/app/src/custom/assets/riseup.net.json
index 5e14abc38..407e2e22d 100644
--- a/app/src/custom/assets/riseup.net.json
+++ b/app/src/custom/assets/riseup.net.json
@@ -1,37 +1,37 @@
 {
-  "api_uri":"https://api.black.riseup.net:4430",
-  "api_version":"3",
-  "ca_cert_fingerprint":"SHA256: dd919b7513b4a1368faa20e38cd3314156805677f48b787cdd9b4a92dec64eb0",
-  "ca_cert_uri":"https://black.riseup.net/ca.crt",
-  "default_language":"en",
-  "description":{
-    "en": "Riseup is a non-profit collective in Seattle that provides online communication tools for people and groups working toward liberatory social change."
+  "api_uri": "https://api.black.riseup.net:4430",
+  "api_version": "3",
+  "ca_cert_fingerprint": "SHA256: dd919b7513b4a1368faa20e38cd3314156805677f48b787cdd9b4a92dec64eb0",
+  "ca_cert_uri": "https://black.riseup.net/ca.crt",
+  "default_language": "en",
+  "description": {
+    "en": "Riseup Networks"
   },
-  "domain":"riseup.net",
-  "enrollment_policy":"open",
-  "languages":[
+  "domain": "riseup.net",
+  "enrollment_policy": "open",
+  "languages": [
     "en"
   ],
-  "name":{
-    "en":"Riseup Networks"
+  "name": {
+    "en": "Riseup Networks"
   },
-  "service":{
-    "allow_anonymous":true,
-    "allow_free":true,
-    "allow_limited_bandwidth":false,
-    "allow_paid":false,
-    "allow_registration":false,
-    "allow_unlimited_bandwidth":true,
-    "bandwidth_limit":102400,
-    "default_service_level":1,
-    "levels":{
-      "1":{
-        "description":"Please donate.",
-        "name":"free"
+  "service": {
+    "allow_anonymous": true,
+    "allow_free": true,
+    "allow_limited_bandwidth": false,
+    "allow_paid": false,
+    "allow_registration": false,
+    "allow_unlimited_bandwidth": true,
+    "bandwidth_limit": 102400,
+    "default_service_level": 1,
+    "levels": {
+      "1": {
+        "description": "Please donate.",
+        "name": "free"
       }
     }
   },
-  "services":[
+  "services": [
     "openvpn"
   ]
 }
\ No newline at end of file
diff --git a/app/src/normal/assets/riseup.net.json b/app/src/normal/assets/riseup.net.json
index 5e14abc38..407e2e22d 100644
--- a/app/src/normal/assets/riseup.net.json
+++ b/app/src/normal/assets/riseup.net.json
@@ -1,37 +1,37 @@
 {
-  "api_uri":"https://api.black.riseup.net:4430",
-  "api_version":"3",
-  "ca_cert_fingerprint":"SHA256: dd919b7513b4a1368faa20e38cd3314156805677f48b787cdd9b4a92dec64eb0",
-  "ca_cert_uri":"https://black.riseup.net/ca.crt",
-  "default_language":"en",
-  "description":{
-    "en": "Riseup is a non-profit collective in Seattle that provides online communication tools for people and groups working toward liberatory social change."
+  "api_uri": "https://api.black.riseup.net:4430",
+  "api_version": "3",
+  "ca_cert_fingerprint": "SHA256: dd919b7513b4a1368faa20e38cd3314156805677f48b787cdd9b4a92dec64eb0",
+  "ca_cert_uri": "https://black.riseup.net/ca.crt",
+  "default_language": "en",
+  "description": {
+    "en": "Riseup Networks"
   },
-  "domain":"riseup.net",
-  "enrollment_policy":"open",
-  "languages":[
+  "domain": "riseup.net",
+  "enrollment_policy": "open",
+  "languages": [
     "en"
   ],
-  "name":{
-    "en":"Riseup Networks"
+  "name": {
+    "en": "Riseup Networks"
   },
-  "service":{
-    "allow_anonymous":true,
-    "allow_free":true,
-    "allow_limited_bandwidth":false,
-    "allow_paid":false,
-    "allow_registration":false,
-    "allow_unlimited_bandwidth":true,
-    "bandwidth_limit":102400,
-    "default_service_level":1,
-    "levels":{
-      "1":{
-        "description":"Please donate.",
-        "name":"free"
+  "service": {
+    "allow_anonymous": true,
+    "allow_free": true,
+    "allow_limited_bandwidth": false,
+    "allow_paid": false,
+    "allow_registration": false,
+    "allow_unlimited_bandwidth": true,
+    "bandwidth_limit": 102400,
+    "default_service_level": 1,
+    "levels": {
+      "1": {
+        "description": "Please donate.",
+        "name": "free"
       }
     }
   },
-  "services":[
+  "services": [
     "openvpn"
   ]
 }
\ No newline at end of file
-- 
GitLab


From 5dfe7ac7fa0ed2389dc5615ff9ec599f141dfb58 Mon Sep 17 00:00:00 2001
From: cyBerta <cyberta@riseup.net>
Date: Wed, 29 Jan 2025 02:36:45 +0100
Subject: [PATCH 09/12] provider selection UI: add some comments about where
 which provider selection entries are added

---
 .../providersetup/fragments/ProviderSelectionFragment.java    | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/ProviderSelectionFragment.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/ProviderSelectionFragment.java
index 66b1dd006..823004b23 100644
--- a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/ProviderSelectionFragment.java
+++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/ProviderSelectionFragment.java
@@ -75,6 +75,7 @@ public class ProviderSelectionFragment extends BaseSetupFragment implements Canc
         binding = FProviderSelectionBinding.inflate(inflater, container, false);
 
         radioButtons = new ArrayList<>();
+        // add configured providers
         for (int i = 0; i < viewModel.size(); i++) {
             RadioButton radioButton = new RadioButton(binding.getRoot().getContext());
             radioButton.setText(viewModel.getProviderName(i));
@@ -83,13 +84,14 @@ public class ProviderSelectionFragment extends BaseSetupFragment implements Canc
             radioButtons.add(radioButton);
         }
 
+        // add new provider entry
         RadioButton addProviderRadioButton = new RadioButton(binding.getRoot().getContext());
         addProviderRadioButton.setText(getText(R.string.add_provider));
         addProviderRadioButton.setId(ADD_PROVIDER);
         binding.providerRadioGroup.addView(addProviderRadioButton);
         radioButtons.add(addProviderRadioButton);
 
-
+        // invite code entry
         RadioButton inviteCodeRadioButton = new RadioButton(binding.getRoot().getContext());
         inviteCodeRadioButton.setText(R.string.enter_invite_code);
         inviteCodeRadioButton.setId(INVITE_CODE_PROVIDER);
-- 
GitLab


From 383840d7f875bb2447890714c4850da3924a2684 Mon Sep 17 00:00:00 2001
From: cyBerta <cyberta@riseup.net>
Date: Fri, 31 Jan 2025 15:07:39 +0100
Subject: [PATCH 10/12] persist v5 provider on successful provider setup,
 ensure introducer is also saved

---
 .../base/utils/PreferenceHelper.java          | 24 +++++++++----------
 .../providersetup/ProviderApiManagerV5.java   |  1 +
 .../providersetup/ProviderManager.java        |  9 ++++---
 3 files changed, 18 insertions(+), 16 deletions(-)

diff --git a/app/src/main/java/se/leap/bitmaskclient/base/utils/PreferenceHelper.java b/app/src/main/java/se/leap/bitmaskclient/base/utils/PreferenceHelper.java
index 0bf5dfb25..b7c6db5d7 100644
--- a/app/src/main/java/se/leap/bitmaskclient/base/utils/PreferenceHelper.java
+++ b/app/src/main/java/se/leap/bitmaskclient/base/utils/PreferenceHelper.java
@@ -71,6 +71,7 @@ import org.json.JSONObject;
 import java.io.IOException;
 import java.net.URL;
 import java.security.GeneralSecurityException;
+import java.util.Arrays;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Map;
@@ -212,20 +213,17 @@ public class PreferenceHelper {
     public static HashMap<String, Provider> getCustomProviders() {
         Set<String> providerDomains = getCustomProviderDomains();
         HashMap<String, Provider> customProviders = new HashMap<>();
-        if (providerDomains.size() > 0) {
-            for (String domain : providerDomains) {
-                String mainURL = preferences.getString(Provider.MAIN_URL + "." + domain, null);
-                if (mainURL != null) {
-                    Introducer introducer = null;
-                    try {
-                       introducer = Introducer.fromUrl(BitmaskCoreProvider.getBitmaskMobile().getIntroducerURLByDomain(domain));
-                    } catch (Exception e) {
-                        e.printStackTrace();
-                    }
-                    customProviders.put(mainURL, Provider.createCustomProvider(mainURL, domain, introducer));
+        for (String domain : providerDomains) {
+            String mainURL = preferences.getString(Provider.MAIN_URL + "." + domain, null);
+            if (mainURL != null) {
+                Introducer introducer = null;
+                try {
+                   introducer = Introducer.fromUrl(BitmaskCoreProvider.getBitmaskMobile().getIntroducerURLByDomain(domain));
+                } catch (Exception e) {
+                    e.printStackTrace();
                 }
+                customProviders.put(mainURL, Provider.createCustomProvider(mainURL, domain, introducer));
             }
-
         }
 
         return customProviders;
@@ -851,7 +849,7 @@ public class PreferenceHelper {
 
             @Override
             public byte[] getByteArray(String s) {
-                String encodedString = preferences.getString(s, "");
+                String encodedString = preferences.getString(s, Arrays.toString(Base64.encode(new byte[0], Base64.DEFAULT)));
                 try {
                     return Base64.decode(encodedString, Base64.DEFAULT);
                 } catch (IllegalArgumentException e) {
diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderApiManagerV5.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderApiManagerV5.java
index 9af14eda4..2e2497d51 100644
--- a/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderApiManagerV5.java
+++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderApiManagerV5.java
@@ -66,6 +66,7 @@ public class ProviderApiManagerV5 extends ProviderApiManagerBase implements IPro
             case SET_UP_PROVIDER:
                 result = setupProvider(provider, parameters);
                 if (result.getBoolean(BROADCAST_RESULT_KEY)) {
+                    serviceCallback.saveProvider(provider);
                     eventSender.sendToReceiverOrBroadcast(receiver, PROVIDER_OK, result, provider);
                 } else {
                     eventSender.sendToReceiverOrBroadcast(receiver, PROVIDER_NOK, result, provider);
diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderManager.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderManager.java
index 63fbde09e..bcb177e2b 100644
--- a/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderManager.java
+++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderManager.java
@@ -45,8 +45,11 @@ public class ProviderManager {
     private boolean addDummyEntry = false;
 
     public static ProviderManager getInstance(AssetManager assetsManager) {
-        if (instance == null)
+        if (instance == null) {
             instance = new ProviderManager(assetsManager);
+        } else {
+            instance.updateCustomProviders();
+        }
 
         return instance;
     }
@@ -63,7 +66,7 @@ public class ProviderManager {
     private ProviderManager(AssetManager assetManager) {
         this.assetsManager = assetManager;
         addDefaultProviders(assetManager);
-        addCustomProviders();
+        updateCustomProviders();
     }
 
     private void addDefaultProviders(AssetManager assetManager) {
@@ -117,7 +120,7 @@ public class ProviderManager {
     }
 
 
-    private void addCustomProviders() {
+    public void updateCustomProviders() {
         customProviders = PreferenceHelper.getCustomProviders();
     }
 
-- 
GitLab


From c5ee72fb3181df7cff17bed335acf1bcd983997b Mon Sep 17 00:00:00 2001
From: cyBerta <cyberta@riseup.net>
Date: Fri, 31 Jan 2025 18:51:52 +0100
Subject: [PATCH 11/12] udpate bitmask-core-android, including golang fixes
 wrt. introducer handling

---
 bitmask-core-android | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/bitmask-core-android b/bitmask-core-android
index 8b802cc79..46b7f4829 160000
--- a/bitmask-core-android
+++ b/bitmask-core-android
@@ -1 +1 @@
-Subproject commit 8b802cc791d1d913c03fe57c251d3eb40a6a59a2
+Subproject commit 46b7f48299017d7851f8a16ff395dcac9792df97
-- 
GitLab


From 447cfa0ce606257b5c976ca8cc8cd8656211b9c9 Mon Sep 17 00:00:00 2001
From: cyBerta <cyberta@riseup.net>
Date: Mon, 3 Feb 2025 16:11:18 +0100
Subject: [PATCH 12/12] deduplicate code and improve intialization of discovery
 settings in circumvention settings screen

---
 .../base/fragments/CensorshipCircumventionFragment.java    | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/app/src/main/java/se/leap/bitmaskclient/base/fragments/CensorshipCircumventionFragment.java b/app/src/main/java/se/leap/bitmaskclient/base/fragments/CensorshipCircumventionFragment.java
index 888a6f6f0..e8789b32c 100644
--- a/app/src/main/java/se/leap/bitmaskclient/base/fragments/CensorshipCircumventionFragment.java
+++ b/app/src/main/java/se/leap/bitmaskclient/base/fragments/CensorshipCircumventionFragment.java
@@ -69,19 +69,20 @@ public class CensorshipCircumventionFragment extends Fragment {
 
 
     private void initDiscovery() {
+        boolean hasIntroducer = ProviderObservable.getInstance().getCurrentProvider().hasIntroducer();
         RadioButton automaticallyRadioButton = new RadioButton(binding.getRoot().getContext());
         automaticallyRadioButton.setText(getText(R.string.automatically_select));
         automaticallyRadioButton.setId(DISCOVERY_AUTOMATICALLY);
-        automaticallyRadioButton.setChecked(!(hasSnowflakePrefs() && getUseSnowflake()) && !ProviderObservable.getInstance().getCurrentProvider().hasIntroducer());
+        automaticallyRadioButton.setChecked(!hasSnowflakePrefs() && !hasIntroducer);
         binding.discoveryRadioGroup.addView(automaticallyRadioButton);
 
         RadioButton snowflakeRadioButton = new RadioButton(binding.getRoot().getContext());
         snowflakeRadioButton.setText(getText(R.string.snowflake));
         snowflakeRadioButton.setId(DISCOVERY_SNOWFLAKE);
-        snowflakeRadioButton.setChecked(hasSnowflakePrefs() && getUseSnowflake());
+        snowflakeRadioButton.setChecked(!hasIntroducer && hasSnowflakePrefs() && getUseSnowflake());
         binding.discoveryRadioGroup.addView(snowflakeRadioButton);
 
-        if (ProviderObservable.getInstance().getCurrentProvider().hasIntroducer()) {
+        if (hasIntroducer) {
             RadioButton inviteProxyRadioButton = new RadioButton(binding.getRoot().getContext());
             inviteProxyRadioButton.setText(getText(R.string.invite_proxy));
             inviteProxyRadioButton.setId(DISCOVERY_INVITE_PROXY);
-- 
GitLab