From 2522e75f41b2bc2f3d21baee09338527f271ba7c Mon Sep 17 00:00:00 2001 From: cyBerta Date: Sun, 25 Feb 2024 09:54:41 +0100 Subject: save manually added provider --- .../main/java/se/leap/bitmaskclient/providersetup/ProviderAPI.java | 7 +++++++ .../leap/bitmaskclient/providersetup/ProviderApiManagerBase.java | 6 ++++++ .../java/se/leap/bitmaskclient/providersetup/ProviderManager.java | 2 +- 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderAPI.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderAPI.java index ed30c454..bb6f5e01 100644 --- a/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderAPI.java +++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderAPI.java @@ -30,6 +30,7 @@ import androidx.localbroadcastmanager.content.LocalBroadcastManager; import java.util.concurrent.TimeoutException; +import se.leap.bitmaskclient.base.models.Provider; import se.leap.bitmaskclient.base.utils.PreferenceHelper; import se.leap.bitmaskclient.providersetup.connectivity.OkHttpClientGenerator; import se.leap.bitmaskclient.tor.TorServiceCommand; @@ -177,6 +178,12 @@ public class ProviderAPI extends JobIntentService implements ProviderApiManagerB } } + @Override + public void saveProvider(Provider p) { + ProviderManager pm = ProviderManager.getInstance(this.getAssets(), this.getExternalFilesDir(null)); + pm.add(p); + pm.saveCustomProvidersToFile(); + } private ProviderApiManager initApiManager() { diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderApiManagerBase.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderApiManagerBase.java index 1f737b0c..ae55f81c 100644 --- a/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderApiManagerBase.java +++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderApiManagerBase.java @@ -170,6 +170,7 @@ public abstract class ProviderApiManagerBase { void stopTorService(); int getTorHttpTunnelPort(); boolean hasNetworkConnection(); + void saveProvider(Provider p); } private final ProviderApiServiceCallback serviceCallback; @@ -295,6 +296,7 @@ public abstract class ProviderApiManagerBase { ProviderObservable.getInstance().setProviderForDns(provider); result = updateVpnCertificate(provider); if (result.getBoolean(BROADCAST_RESULT_KEY)) { + serviceCallback.saveProvider(provider); ProviderSetupObservable.updateProgress(DOWNLOADED_VPN_CERTIFICATE); sendToReceiverOrBroadcast(receiver, CORRECTLY_DOWNLOADED_VPN_CERTIFICATE, result, provider); } else { @@ -362,6 +364,10 @@ public abstract class ProviderApiManagerBase { } } + private void saveCustomProvider() { + + } + protected boolean startTorProxy() throws InterruptedException, IllegalStateException, TimeoutException { if (EipStatus.getInstance().isDisconnected() && PreferenceHelper.getUseSnowflake() && 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 38198f89..39f117ea 100644 --- a/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderManager.java +++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderManager.java @@ -225,7 +225,7 @@ public class ProviderManager { defaultProviderURLs.clear(); } - void saveCustomProvidersToFile() { + public void saveCustomProvidersToFile() { try { deleteLegacyCustomProviders(); -- cgit v1.2.3 From 5b07bb3df03cc95d6b50a8eec2bd30563a6d5b43 Mon Sep 17 00:00:00 2001 From: cyBerta Date: Mon, 26 Feb 2024 21:33:52 +0100 Subject: save manually added providers in encrypted shared preferences instead of external files dir --- .../leap/bitmaskclient/base/models/Constants.java | 1 + .../base/utils/InputStreamHelper.java | 24 ----- .../bitmaskclient/base/utils/PreferenceHelper.java | 45 +++++++- .../bitmaskclient/providersetup/ProviderAPI.java | 5 +- .../providersetup/ProviderManager.java | 113 ++++++--------------- .../fragments/CircumventionSetupFragment.java | 3 +- .../fragments/ProviderSelectionFragment.java | 5 +- .../viewmodel/ProviderSelectionViewModel.java | 5 +- .../ProviderSelectionViewModelFactory.java | 8 +- .../bitmaskclient/eip/ProviderApiManagerTest.java | 5 + .../providersetup/ProviderManagerTest.java | 83 +++++++-------- .../leap/bitmaskclient/testutils/MockHelper.java | 10 -- .../testutils/MockSharedPreferences.java | 1 + 13 files changed, 124 insertions(+), 184 deletions(-) diff --git a/app/src/main/java/se/leap/bitmaskclient/base/models/Constants.java b/app/src/main/java/se/leap/bitmaskclient/base/models/Constants.java index 18590f0b..754491f8 100644 --- a/app/src/main/java/se/leap/bitmaskclient/base/models/Constants.java +++ b/app/src/main/java/se/leap/bitmaskclient/base/models/Constants.java @@ -53,6 +53,7 @@ public interface Constants { String OBFUSCATION_PINNING_KCP = "obfuscation_pinning_udp"; String OBFUSCATION_PINNING_LOCATION = "obfuscation_pinning_location"; String USE_SYSTEM_PROXY = "usesystemproxy"; + String CUSTOM_PROVIDER_DOMAINS = "custom_provider_domains"; ////////////////////////////////////////////// diff --git a/app/src/main/java/se/leap/bitmaskclient/base/utils/InputStreamHelper.java b/app/src/main/java/se/leap/bitmaskclient/base/utils/InputStreamHelper.java index 6dfe0861..34082bcb 100644 --- a/app/src/main/java/se/leap/bitmaskclient/base/utils/InputStreamHelper.java +++ b/app/src/main/java/se/leap/bitmaskclient/base/utils/InputStreamHelper.java @@ -15,30 +15,6 @@ import de.blinkt.openvpn.core.NativeUtils; */ public class InputStreamHelper { - public interface InputStreamHelperInterface { - InputStream getInputStreamFrom(String filePath) throws FileNotFoundException; - - } - - private static InputStreamHelperInterface instance = new DefaultInputStreamHelper(); - - private static class DefaultInputStreamHelper implements InputStreamHelperInterface { - @Override - public InputStream getInputStreamFrom(String filePath) throws FileNotFoundException { - return new FileInputStream(filePath); - } - } - - public InputStreamHelper(InputStreamHelperInterface helperInterface) { - if (!NativeUtils.isUnitTest()) { - throw new IllegalStateException("InputStreamHelper injected with InputStreamHelperInterface outside of an unit test"); - } - instance = helperInterface; - } - //allows us to mock FileInputStream - public static InputStream getInputStreamFrom(String filePath) throws FileNotFoundException { - return instance.getInputStreamFrom(filePath); - } public static String loadInputStreamAsString(InputStream is) { java.util.Scanner s = new java.util.Scanner(is).useDelimiter("\\A"); 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 2420a797..eee3bfb2 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 @@ -7,6 +7,7 @@ import static se.leap.bitmaskclient.base.models.Constants.ALLOW_TETHERING_USB; import static se.leap.bitmaskclient.base.models.Constants.ALLOW_TETHERING_WIFI; import static se.leap.bitmaskclient.base.models.Constants.ALWAYS_ON_SHOW_DIALOG; import static se.leap.bitmaskclient.base.models.Constants.CLEARLOG; +import static se.leap.bitmaskclient.base.models.Constants.CUSTOM_PROVIDER_DOMAINS; import static se.leap.bitmaskclient.base.models.Constants.DEFAULT_SHARED_PREFS_BATTERY_SAVER; import static se.leap.bitmaskclient.base.models.Constants.EIP_IS_ALWAYS_ON; import static se.leap.bitmaskclient.base.models.Constants.EIP_RESTART_ON_BOOT; @@ -59,6 +60,7 @@ import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; import java.security.GeneralSecurityException; +import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; @@ -185,8 +187,49 @@ public class PreferenceHelper { } } + /** + * + * @return HashMap with main URL string as key and Provider as value + */ + public static HashMap getCustomProviders() { + Set providerDomains = getCustomProviderDomains(); + HashMap customProviders = new HashMap<>(); + for (String domain : providerDomains) { + String mainURL = preferences.getString(Provider.MAIN_URL + "." + domain, null); + if (mainURL != null) { + customProviders.put(mainURL, Provider.createCustomProvider(mainURL, domain)); + } + } + return customProviders; + } + + public static void setCustomProviders(Set providers) { + Set newProviderDomains = new HashSet<>(); + + // add + SharedPreferences.Editor editor = preferences.edit(); + for (Provider provider : providers) { + String providerDomain = provider.getDomain(); + editor.putString(Provider.MAIN_URL + "." + providerDomain, provider.getMainUrlString()); + newProviderDomains.add(providerDomain); + } + + // remove + Set removedProviderDomains = getCustomProviderDomains(); + removedProviderDomains.removeAll(newProviderDomains); + for (String providerDomain : removedProviderDomains) { + editor.remove(Provider.MAIN_URL + "." + providerDomain); + } + + editor.putStringSet(CUSTOM_PROVIDER_DOMAINS, newProviderDomains); + editor.apply(); + } + + static Set getCustomProviderDomains() { + return preferences.getStringSet(CUSTOM_PROVIDER_DOMAINS, new HashSet<>()); + } + // TODO: replace commit with apply after refactoring EIP - //FIXME: don't save private keys in shared preferences! use the keystore public static void storeProviderInPreferences(Provider provider, boolean async) { synchronized (LOCK) { SharedPreferences.Editor editor = preferences.edit(); diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderAPI.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderAPI.java index bb6f5e01..68699da2 100644 --- a/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderAPI.java +++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderAPI.java @@ -31,7 +31,6 @@ import androidx.localbroadcastmanager.content.LocalBroadcastManager; import java.util.concurrent.TimeoutException; import se.leap.bitmaskclient.base.models.Provider; -import se.leap.bitmaskclient.base.utils.PreferenceHelper; import se.leap.bitmaskclient.providersetup.connectivity.OkHttpClientGenerator; import se.leap.bitmaskclient.tor.TorServiceCommand; @@ -180,9 +179,9 @@ public class ProviderAPI extends JobIntentService implements ProviderApiManagerB @Override public void saveProvider(Provider p) { - ProviderManager pm = ProviderManager.getInstance(this.getAssets(), this.getExternalFilesDir(null)); + ProviderManager pm = ProviderManager.getInstance(this.getAssets()); pm.add(p); - pm.saveCustomProvidersToFile(); + pm.saveCustomProviders(); } 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 39f117ea..9eacae5d 100644 --- a/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderManager.java +++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderManager.java @@ -3,15 +3,11 @@ package se.leap.bitmaskclient.providersetup; import static se.leap.bitmaskclient.base.models.Constants.EXT_JSON; import static se.leap.bitmaskclient.base.models.Constants.EXT_PEM; import static se.leap.bitmaskclient.base.models.Constants.URLS; -import static se.leap.bitmaskclient.base.models.Provider.DOMAIN; import static se.leap.bitmaskclient.base.models.Provider.GEOIP_URL; import static se.leap.bitmaskclient.base.models.Provider.MAIN_URL; import static se.leap.bitmaskclient.base.models.Provider.MOTD_URL; import static se.leap.bitmaskclient.base.models.Provider.PROVIDER_API_IP; import static se.leap.bitmaskclient.base.models.Provider.PROVIDER_IP; -import static se.leap.bitmaskclient.base.utils.FileHelper.createFile; -import static se.leap.bitmaskclient.base.utils.FileHelper.persistFile; -import static se.leap.bitmaskclient.base.utils.InputStreamHelper.getInputStreamFrom; import static se.leap.bitmaskclient.base.utils.InputStreamHelper.inputStreamToJson; import static se.leap.bitmaskclient.base.utils.InputStreamHelper.loadInputStreamAsString; @@ -21,18 +17,18 @@ import androidx.annotation.VisibleForTesting; import org.json.JSONObject; -import java.io.File; -import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Collection; +import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; import se.leap.bitmaskclient.base.models.Provider; +import se.leap.bitmaskclient.base.utils.PreferenceHelper; /** * Created by parmegv on 4/12/14. @@ -40,18 +36,17 @@ import se.leap.bitmaskclient.base.models.Provider; public class ProviderManager { private final AssetManager assetsManager; - private File externalFilesDir; private Set defaultProviders; - private Set customProviders; + // key: MainURL String, value: Provider + private HashMap customProviders; private Set defaultProviderURLs; - private Set customProviderURLs; private static ProviderManager instance; private boolean addDummyEntry = false; - public static ProviderManager getInstance(AssetManager assetsManager, File externalFilesDir) { + public static ProviderManager getInstance(AssetManager assetsManager) { if (instance == null) - instance = new ProviderManager(assetsManager, externalFilesDir); + instance = new ProviderManager(assetsManager); return instance; } @@ -65,10 +60,10 @@ public class ProviderManager { this.addDummyEntry = addDummyEntry; } - private ProviderManager(AssetManager assetManager, File externalFilesDir) { + private ProviderManager(AssetManager assetManager) { this.assetsManager = assetManager; addDefaultProviders(assetManager); - addCustomProviders(externalFilesDir); + addCustomProviders(); } private void addDefaultProviders(AssetManager assetManager) { @@ -122,29 +117,8 @@ public class ProviderManager { } - private void addCustomProviders(File externalFilesDir) { - this.externalFilesDir = externalFilesDir; - customProviders = externalFilesDir != null && externalFilesDir.isDirectory() ? - customProvidersFromFiles(externalFilesDir.list()) : - new HashSet<>(); - customProviderURLs = getProviderUrlSetFromProviderSet(customProviders); - } - - private Set customProvidersFromFiles(String[] files) { - Set providers = new HashSet<>(); - try { - for (String file : files) { - InputStream inputStream = getInputStreamFrom(externalFilesDir.getAbsolutePath() + "/" + file); - JSONObject providerConfig = inputStreamToJson(inputStream); - String mainUrl = providerConfig.optString(MAIN_URL); - String domain = providerConfig.optString(DOMAIN); - providers.add(Provider.createCustomProvider(mainUrl, domain)); - } - } catch (FileNotFoundException | NullPointerException e) { - e.printStackTrace(); - } - - return providers; + private void addCustomProviders() { + customProviders = PreferenceHelper.getCustomProviders(); } public List providers() { @@ -155,7 +129,7 @@ public class ProviderManager { List allProviders = new ArrayList<>(); allProviders.addAll(defaultProviders); if(customProviders != null) - allProviders.addAll(customProviders); + allProviders.addAll(customProviders.values()); if (addEmptyProvider) { //add an option to add a custom provider allProviders.add(new Provider()); @@ -177,16 +151,19 @@ public class ProviderManager { } public boolean add(Provider element) { - return element != null && - !defaultProviderURLs.contains(element.getMainUrl().toString()) && - customProviders.add(element) && - customProviderURLs.add(element.getMainUrl().toString()); + boolean addElement = element != null && + !defaultProviderURLs.contains(element.getMainUrlString()) && + !customProviders.containsKey(element.getMainUrlString()); + if (addElement) { + customProviders.put(element.getMainUrlString(), element); + return true; + } + return false; } public boolean remove(Object element) { return element instanceof Provider && - customProviders.remove(element) && - customProviderURLs.remove(((Provider) element).getMainUrl().toString()); + customProviders.remove(((Provider) element).getMainUrlString()) != null; } public boolean addAll(Collection elements) { @@ -194,9 +171,11 @@ public class ProviderManager { boolean addedAll = true; while (iterator.hasNext()) { Provider p = (Provider) iterator.next(); - addedAll = customProviders.add(p) && - customProviderURLs.add(p.getMainUrl().toString()) && - addedAll; + boolean containsKey = customProviders.containsKey(p.getMainUrlString()); + if (!containsKey) { + customProviders.put(p.getMainUrlString(), p); + } + addedAll = !containsKey && addedAll; } return addedAll; } @@ -204,15 +183,8 @@ public class ProviderManager { public boolean removeAll(Collection elements) { Iterator iterator = elements.iterator(); boolean removedAll = true; - try { - while (iterator.hasNext()) { - Provider p = (Provider) iterator.next(); - removedAll = ((defaultProviders.remove(p) && defaultProviderURLs.remove(p.getMainUrl().toString())) || - (customProviders.remove(p) && customProviderURLs.remove(p.getMainUrl().toString()))) && - removedAll; - } - } catch (ClassCastException e) { - return false; + while (iterator.hasNext()) { + removedAll = remove(iterator.next()) && removedAll; } return removedAll; @@ -221,37 +193,10 @@ public class ProviderManager { public void clear() { defaultProviders.clear(); customProviders.clear(); - customProviderURLs.clear(); defaultProviderURLs.clear(); } - public void saveCustomProvidersToFile() { - try { - deleteLegacyCustomProviders(); - - for (Provider provider : customProviders) { - File providerFile = createFile(externalFilesDir, provider.getName() + EXT_JSON); - if (!providerFile.exists()) { - persistFile(providerFile, provider.toJson().toString()); - } - } - } catch (IOException | SecurityException e) { - e.printStackTrace(); - } - } - - /** - * Deletes persisted custom providers from from internal storage that are not in customProviders list anymore - */ - private void deleteLegacyCustomProviders() throws IOException, SecurityException { - Set persistedCustomProviders = externalFilesDir != null && externalFilesDir.isDirectory() ? - customProvidersFromFiles(externalFilesDir.list()) : new HashSet(); - persistedCustomProviders.removeAll(customProviders); - for (Provider providerToDelete : persistedCustomProviders) { - File providerFile = createFile(externalFilesDir, providerToDelete.getName() + EXT_JSON); - if (providerFile.exists()) { - providerFile.delete(); - } - } + public void saveCustomProviders() { + PreferenceHelper.setCustomProviders(new HashSet<>(customProviders.values())); } } 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 cdb8bd78..58fccc65 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 @@ -78,8 +78,7 @@ public class CircumventionSetupFragment extends BaseSetupFragment implements Can } private void loadProviderFromAssets() { - ProviderManager providerManager = ProviderManager.getInstance(getContext().getApplicationContext().getAssets(), - getContext().getExternalFilesDir(null)); + ProviderManager providerManager = ProviderManager.getInstance(getContext().getApplicationContext().getAssets()); providerManager.setAddDummyEntry(false); setupActivityCallback.onProviderSelected(providerManager.providers().get(0)); } 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 e8f37e43..8ccfee22 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 @@ -2,7 +2,6 @@ package se.leap.bitmaskclient.providersetup.fragments; import static se.leap.bitmaskclient.providersetup.fragments.viewmodel.ProviderSelectionViewModel.ADD_PROVIDER; -import android.content.Context; import android.graphics.Typeface; import android.os.Bundle; import android.text.Editable; @@ -20,7 +19,6 @@ import java.util.ArrayList; import se.leap.bitmaskclient.R; import se.leap.bitmaskclient.base.models.Provider; -import se.leap.bitmaskclient.base.models.ProviderObservable; import se.leap.bitmaskclient.base.utils.ViewHelper; import se.leap.bitmaskclient.databinding.FProviderSelectionBinding; import se.leap.bitmaskclient.providersetup.activities.CancelCallback; @@ -46,8 +44,7 @@ public class ProviderSelectionFragment extends BaseSetupFragment implements Canc super.onCreate(savedInstanceState); viewModel = new ViewModelProvider(this, new ProviderSelectionViewModelFactory( - getContext().getApplicationContext().getAssets(), - getContext().getExternalFilesDir(null))). + getContext().getApplicationContext().getAssets())). get(ProviderSelectionViewModel.class); } diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/viewmodel/ProviderSelectionViewModel.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/viewmodel/ProviderSelectionViewModel.java index aa2fe7cb..53d02b46 100644 --- a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/viewmodel/ProviderSelectionViewModel.java +++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/viewmodel/ProviderSelectionViewModel.java @@ -8,7 +8,6 @@ import android.webkit.URLUtil; import androidx.lifecycle.ViewModel; -import java.io.File; import java.util.List; import se.leap.bitmaskclient.R; @@ -22,8 +21,8 @@ public class ProviderSelectionViewModel extends ViewModel { private int selected = 0; private String customUrl; - public ProviderSelectionViewModel(AssetManager assetManager, File externalFilesDir) { - providerManager = ProviderManager.getInstance(assetManager, externalFilesDir); + public ProviderSelectionViewModel(AssetManager assetManager) { + providerManager = ProviderManager.getInstance(assetManager); providerManager.setAddDummyEntry(false); } diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/viewmodel/ProviderSelectionViewModelFactory.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/viewmodel/ProviderSelectionViewModelFactory.java index a21e4924..f6d86e07 100644 --- a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/viewmodel/ProviderSelectionViewModelFactory.java +++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/viewmodel/ProviderSelectionViewModelFactory.java @@ -6,22 +6,18 @@ import androidx.annotation.NonNull; import androidx.lifecycle.ViewModel; import androidx.lifecycle.ViewModelProvider; -import java.io.File; - public class ProviderSelectionViewModelFactory implements ViewModelProvider.Factory { private final AssetManager assetManager; - private final File externalFilesDir; - public ProviderSelectionViewModelFactory(AssetManager assetManager, File externalFilesDir) { + public ProviderSelectionViewModelFactory(AssetManager assetManager) { this.assetManager = assetManager; - this.externalFilesDir = externalFilesDir; } @NonNull @Override public T create(@NonNull Class modelClass) { if (modelClass.isAssignableFrom(ProviderSelectionViewModel.class)) { - return (T) new ProviderSelectionViewModel(assetManager, externalFilesDir); + return (T) new ProviderSelectionViewModel(assetManager); } throw new IllegalArgumentException("Unknown ViewModel class"); } diff --git a/app/src/test/java/se/leap/bitmaskclient/eip/ProviderApiManagerTest.java b/app/src/test/java/se/leap/bitmaskclient/eip/ProviderApiManagerTest.java index c6e548ce..e78db39c 100644 --- a/app/src/test/java/se/leap/bitmaskclient/eip/ProviderApiManagerTest.java +++ b/app/src/test/java/se/leap/bitmaskclient/eip/ProviderApiManagerTest.java @@ -160,6 +160,11 @@ public class ProviderApiManagerTest { return hasNetworkConnection; } + @Override + public void saveProvider(Provider p) { + + } + } @Before diff --git a/app/src/test/java/se/leap/bitmaskclient/providersetup/ProviderManagerTest.java b/app/src/test/java/se/leap/bitmaskclient/providersetup/ProviderManagerTest.java index 7584fb3f..925d2464 100644 --- a/app/src/test/java/se/leap/bitmaskclient/providersetup/ProviderManagerTest.java +++ b/app/src/test/java/se/leap/bitmaskclient/providersetup/ProviderManagerTest.java @@ -5,11 +5,9 @@ import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import static se.leap.bitmaskclient.testutils.MockHelper.mockInputStreamHelper; +import android.content.SharedPreferences; import android.content.res.AssetManager; import org.junit.After; @@ -18,14 +16,15 @@ import org.junit.Test; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; -import java.io.File; import java.io.InputStream; -import java.util.ArrayList; +import java.util.HashSet; +import java.util.Set; +import se.leap.bitmaskclient.base.models.Constants; import se.leap.bitmaskclient.base.models.Provider; -import se.leap.bitmaskclient.base.utils.FileHelper; -import se.leap.bitmaskclient.base.utils.InputStreamHelper; +import se.leap.bitmaskclient.base.utils.PreferenceHelper; import se.leap.bitmaskclient.testutils.MockHelper; +import se.leap.bitmaskclient.testutils.MockSharedPreferences; /** * Created by cyberta on 20.02.18. @@ -33,16 +32,14 @@ import se.leap.bitmaskclient.testutils.MockHelper; public class ProviderManagerTest { private AssetManager assetManager; - private File file; private ProviderManager providerManager; - InputStreamHelper inputStreamHelper; - FileHelper fileHelper; + PreferenceHelper preferenceHelper; + SharedPreferences mockSharedPrefs; MockHelper.MockFileHelper mockedFileHelperInterface; @Before public void setup() throws Exception { assetManager = mock(AssetManager.class); - file = mock(File.class); when(assetManager.open(anyString())).thenAnswer(new Answer() { @Override @@ -66,22 +63,12 @@ public class ProviderManagerTest { } }); - //mock File methods - //------------------ - when(file.isDirectory()).thenReturn(true); - - ArrayList mockedCustomProviderList = new ArrayList<>(); - mockedCustomProviderList.add("leapcolombia.json"); - String[] mockedCustomProviderArray = new String[mockedCustomProviderList.size()]; - mockedCustomProviderArray = mockedCustomProviderList.toArray(mockedCustomProviderArray); - when(file.list()).thenReturn(mockedCustomProviderArray); - - when(file.getAbsolutePath()).thenReturn("externalDir"); - when(file.getPath()).thenReturn("externalDir"); - mockedFileHelperInterface = new MockHelper.MockFileHelper(file); - fileHelper = new FileHelper(mockedFileHelperInterface); - inputStreamHelper = mockInputStreamHelper(); + mockSharedPrefs = new MockSharedPreferences(); + preferenceHelper = new PreferenceHelper(mockSharedPrefs); + HashSet customProviders = new HashSet<>(); + customProviders.add(Provider.createCustomProvider("https://leapcolombia.org", "leapcolombia.org")); + PreferenceHelper.setCustomProviders(customProviders); } @After @@ -91,21 +78,21 @@ public class ProviderManagerTest { @Test public void testSize_dummyEntry_has5ProvidersWithCurrentTestSetup() { - providerManager = ProviderManager.getInstance(assetManager, file); + providerManager = ProviderManager.getInstance(assetManager); providerManager.setAddDummyEntry(true); assertEquals("3 preconfigured, 1 custom provider, 1 dummy provider", 5, providerManager.size()); } @Test public void testSize_has4ProvidersWithCurrentTestSetup() { - providerManager = ProviderManager.getInstance(assetManager, file); + providerManager = ProviderManager.getInstance(assetManager); assertEquals("3 preconfigured, 1 custom provider", 4, providerManager.size()); } @Test public void testAdd_dummyEntry_newCustomProviderThatIsNotPartOfDefaultNorCustomList_returnTrue() throws Exception { - providerManager = ProviderManager.getInstance(assetManager, file); + providerManager = ProviderManager.getInstance(assetManager); providerManager.setAddDummyEntry(true); Provider customProvider = new Provider("https://anewprovider.org"); assertTrue("custom provider added: ", providerManager.add(customProvider)); @@ -114,7 +101,7 @@ public class ProviderManagerTest { @Test public void testAdd_newCustomProviderThatIsNotPartOfDefaultNorCustomList_returnTrue() throws Exception { - providerManager = ProviderManager.getInstance(assetManager, file); + providerManager = ProviderManager.getInstance(assetManager); Provider customProvider = new Provider("https://anewprovider.org"); assertTrue("custom provider added: ", providerManager.add(customProvider)); assertEquals("3 preconfigured, 2 custom providers", 5, providerManager.providers().size()); @@ -122,7 +109,7 @@ public class ProviderManagerTest { @Test public void testAdd_dummyEntry_newCustomProviderThatIsNotPartOfDefaultButOfCustomList_returnFalse() throws Exception { - providerManager = ProviderManager.getInstance(assetManager, file); + providerManager = ProviderManager.getInstance(assetManager); providerManager.setAddDummyEntry(true); Provider customProvider = new Provider("https://leapcolombia.org"); assertFalse("custom provider added: ", providerManager.add(customProvider)); @@ -131,7 +118,7 @@ public class ProviderManagerTest { @Test public void testAdd_newCustomProviderThatIsNotPartOfDefaultButOfCustomList_returnFalse() throws Exception { - providerManager = ProviderManager.getInstance(assetManager, file); + providerManager = ProviderManager.getInstance(assetManager); Provider customProvider = new Provider("https://leapcolombia.org"); assertFalse("custom provider added: ", providerManager.add(customProvider)); assertEquals("3 preconfigured, 1 custom provider", 4, providerManager.providers().size()); @@ -139,7 +126,7 @@ public class ProviderManagerTest { @Test public void testAdd_newCustomProviderThatIsPartOfDefaultButNotOfCustomList_returnFalse() throws Exception { - providerManager = ProviderManager.getInstance(assetManager, file); + providerManager = ProviderManager.getInstance(assetManager); Provider customProvider = new Provider("https://demo.bitmask.net"); assertFalse("custom provider added: ", providerManager.add(customProvider)); assertEquals("3 preconfigured, 1 custom provider", 4, providerManager.providers().size()); @@ -147,7 +134,7 @@ public class ProviderManagerTest { @Test public void testRemove_ProviderIsPartOfDefaultButNotCustomList_returnsFalse() throws Exception { - providerManager = ProviderManager.getInstance(assetManager, file); + providerManager = ProviderManager.getInstance(assetManager); Provider customProvider = new Provider("https://demo.bitmask.net"); assertFalse("custom provider not removed: ", providerManager.remove(customProvider)); assertEquals("3 preconfigured, 1 custom provider", 4, providerManager.providers().size()); @@ -155,7 +142,7 @@ public class ProviderManagerTest { @Test public void testRemove_ProviderIsNotPartOfDefaultButOfCustomList_returnsTrue() throws Exception { - providerManager = ProviderManager.getInstance(assetManager, file); + providerManager = ProviderManager.getInstance(assetManager); Provider customProvider = new Provider("https://leapcolombia.org"); assertTrue("custom provider not removed: ", providerManager.remove(customProvider)); assertEquals("3 preconfigured, 0 custom providers", 3, providerManager.providers().size()); @@ -163,7 +150,7 @@ public class ProviderManagerTest { @Test public void testRemove_ProviderIsNotPartOfDefaultNorOfCustomList_returnsFalse() throws Exception { - providerManager = ProviderManager.getInstance(assetManager, file); + providerManager = ProviderManager.getInstance(assetManager); Provider customProvider = new Provider("https://anotherprovider.org"); assertFalse("custom provider not removed: ", providerManager.remove(customProvider)); assertEquals("3 preconfigured, 1 custom providers", 4, providerManager.providers().size()); @@ -171,7 +158,7 @@ public class ProviderManagerTest { @Test public void testClear_dummyEntry_ProvidersListHasOnlyDummyProvider() throws Exception { - providerManager = ProviderManager.getInstance(assetManager, file); + providerManager = ProviderManager.getInstance(assetManager); providerManager.setAddDummyEntry(true); providerManager.clear(); assertEquals("1 providers", 1, providerManager.providers().size()); @@ -180,35 +167,37 @@ public class ProviderManagerTest { @Test public void testClear_noEntries() throws Exception { - providerManager = ProviderManager.getInstance(assetManager, file); + providerManager = ProviderManager.getInstance(assetManager); providerManager.clear(); assertEquals("no providers", 0, providerManager.providers().size()); } @Test public void testSaveCustomProvidersToFile_CustomProviderDeleted_deletesFromDir() throws Exception { - when(file.exists()).thenReturn(true); - providerManager = ProviderManager.getInstance(assetManager, file); + providerManager = ProviderManager.getInstance(assetManager); //leapcolombia is mocked custom provider from setup Provider customProvider = new Provider("https://leapcolombia.org"); providerManager.remove(customProvider); - providerManager.saveCustomProvidersToFile(); - verify(file, times(1)).delete(); + providerManager.saveCustomProviders(); + Set providerSet = mockSharedPrefs.getStringSet(Constants.CUSTOM_PROVIDER_DOMAINS, new HashSet<>()); + assertEquals("provider was removed", 0, providerSet.size()); + assertEquals("preference helper has 0 custom providers", 0, PreferenceHelper.getCustomProviders().size()); + } @Test public void testSaveCustomProvidersToFile_newCustomProviders_persistNew() throws Exception { - when(file.list()).thenReturn(new String[0]); - when(file.exists()).thenReturn(false); - providerManager = ProviderManager.getInstance(assetManager, file); + providerManager = ProviderManager.getInstance(assetManager); Provider customProvider = new Provider("https://anotherprovider.org"); Provider secondCustomProvider = new Provider("https://yetanotherprovider.org"); providerManager.add(customProvider); providerManager.add(secondCustomProvider); - providerManager.saveCustomProvidersToFile(); + providerManager.saveCustomProviders(); + Set providerSet = mockSharedPrefs.getStringSet(Constants.CUSTOM_PROVIDER_DOMAINS, new HashSet<>()); + assertEquals("persist was called twice", 2, providerSet.size()); + assertEquals("PreferenceHelper has 2 providers", 2, PreferenceHelper.getCustomProviders().size()); - assertEquals("persist was called twice", 2, mockedFileHelperInterface.getPersistFileCounter()); } diff --git a/app/src/test/java/se/leap/bitmaskclient/testutils/MockHelper.java b/app/src/test/java/se/leap/bitmaskclient/testutils/MockHelper.java index 3175b0ad..a7d3e19c 100644 --- a/app/src/test/java/se/leap/bitmaskclient/testutils/MockHelper.java +++ b/app/src/test/java/se/leap/bitmaskclient/testutils/MockHelper.java @@ -133,16 +133,6 @@ public class MockHelper { return resultReceiver; } - - public static InputStreamHelper mockInputStreamHelper() { - return new InputStreamHelper(new InputStreamHelper.InputStreamHelperInterface() { - @Override - public InputStream getInputStreamFrom(String filePath) { - return getClass().getClassLoader().getResourceAsStream(filePath); - } - }); - } - public static class MockFileHelper implements FileHelper.FileHelperInterface { private final File file; private int persistFileCounter = 0; diff --git a/app/src/test/java/se/leap/bitmaskclient/testutils/MockSharedPreferences.java b/app/src/test/java/se/leap/bitmaskclient/testutils/MockSharedPreferences.java index 30ced782..2fd2a2a8 100644 --- a/app/src/test/java/se/leap/bitmaskclient/testutils/MockSharedPreferences.java +++ b/app/src/test/java/se/leap/bitmaskclient/testutils/MockSharedPreferences.java @@ -158,6 +158,7 @@ public class MockSharedPreferences implements SharedPreferences { mockedStringPrefs = new HashMap<>(tempStrings); mockedBooleanPrefs = new HashMap<>(tempBoolean); mockedIntPrefs = new HashMap<>(tempIntegers); + mockedStringSetPrefs = new HashMap<>(tempStringSets); } }; } -- cgit v1.2.3 From b8f3fbd25f19498e7a6a9080369ff5815c18d6d7 Mon Sep 17 00:00:00 2001 From: cyBerta Date: Mon, 26 Feb 2024 21:34:25 +0100 Subject: fix runtime exception on location switch --- .../se/leap/bitmaskclient/base/fragments/NavigationDrawerFragment.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 16aea065..cbab1d32 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 @@ -439,7 +439,7 @@ public class NavigationDrawerFragment extends Fragment implements SharedPreferen @Override public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { if (key != null && key.equals(PREFERRED_CITY)) { - initManualGatewayEntry(); + getActivity().runOnUiThread(this::initManualGatewayEntry); } } -- cgit v1.2.3 From b8adf36cbf732da19dcd100ba89d1b01af6db694 Mon Sep 17 00:00:00 2001 From: cyBerta Date: Tue, 27 Feb 2024 00:25:34 +0100 Subject: scroll EditText to visible area after the keyboard appeared --- .../java/se/leap/bitmaskclient/base/utils/ViewHelper.java | 11 +++++++++++ .../providersetup/fragments/ProviderSelectionFragment.java | 6 ++++++ 2 files changed, 17 insertions(+) diff --git a/app/src/main/java/se/leap/bitmaskclient/base/utils/ViewHelper.java b/app/src/main/java/se/leap/bitmaskclient/base/utils/ViewHelper.java index ed7bd9f2..e04ba70d 100644 --- a/app/src/main/java/se/leap/bitmaskclient/base/utils/ViewHelper.java +++ b/app/src/main/java/se/leap/bitmaskclient/base/utils/ViewHelper.java @@ -229,8 +229,19 @@ public class ViewHelper { } public static void hideKeyboardFrom(Context context, View view) { + if (context == null) { + return; + } InputMethodManager imm = (InputMethodManager) context.getSystemService(Activity.INPUT_METHOD_SERVICE); imm.hideSoftInputFromWindow(view.getWindowToken(), 0); } + public static boolean isKeyboardShown(Context context) { + if (context == null) { + return false; + } + InputMethodManager imm = (InputMethodManager) context.getSystemService(Activity.INPUT_METHOD_SERVICE); + return imm.isActive(); + } + } 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 8ccfee22..0b0c5034 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 @@ -121,6 +121,12 @@ public class ProviderSelectionFragment extends BaseSetupFragment implements Canc ViewHelper.hideKeyboardFrom(getContext(), v); } }); + + binding.getRoot().getViewTreeObserver().addOnGlobalLayoutListener(() -> { + if(ViewHelper.isKeyboardShown(getContext())) { + binding.getRoot().smoothScrollTo(binding.editCustomProvider.getLeft(), binding.getRoot().getBottom()); + } + }); binding.providerRadioGroup.check(viewModel.getSelected()); } -- cgit v1.2.3 From 3a012bbd4bc662be8c0678759dd6a35e7b42204d Mon Sep 17 00:00:00 2001 From: cyBerta Date: Tue, 27 Feb 2024 01:30:33 +0100 Subject: Allow entering valid domains instead of URLs including protocol. Don't allow multi-line, replace enter button with OK button in keyboard layout --- .../providersetup/fragments/ProviderSelectionFragment.java | 2 +- .../fragments/viewmodel/ProviderSelectionViewModel.java | 9 ++++++++- app/src/main/res/layout/f_provider_selection.xml | 3 +++ 3 files changed, 12 insertions(+), 2 deletions(-) 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 0b0c5034..f15aaa43 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 @@ -107,7 +107,7 @@ public class ProviderSelectionFragment extends BaseSetupFragment implements Canc if (viewModel.isCustomProviderSelected()) { setupActivityCallback.onSetupStepValidationChanged(viewModel.isValidConfig()); if (viewModel.isValidConfig()) { - setupActivityCallback.onProviderSelected(new Provider(s.toString())); + setupActivityCallback.onProviderSelected(new Provider(viewModel.getCustomUrl())); } } } diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/viewmodel/ProviderSelectionViewModel.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/viewmodel/ProviderSelectionViewModel.java index 53d02b46..29dab98a 100644 --- a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/viewmodel/ProviderSelectionViewModel.java +++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/viewmodel/ProviderSelectionViewModel.java @@ -48,7 +48,7 @@ public class ProviderSelectionViewModel extends ViewModel { public boolean isValidConfig() { if (selected == ADD_PROVIDER) { - return URLUtil.isValidUrl(customUrl) && Patterns.WEB_URL.matcher(customUrl).matches(); + return customUrl != null && (Patterns.DOMAIN_NAME.matcher(customUrl).matches() || (URLUtil.isNetworkUrl(customUrl) && Patterns.WEB_URL.matcher(customUrl).matches())); } return true; } @@ -82,6 +82,13 @@ public class ProviderSelectionViewModel extends ViewModel { customUrl = url; } + public String getCustomUrl() { + if (customUrl != null && Patterns.DOMAIN_NAME.matcher(customUrl).matches()) { + return "https://" + customUrl; + } + return customUrl; + } + public String getProviderName(int pos) { String domain = getProvider(pos).getDomain(); diff --git a/app/src/main/res/layout/f_provider_selection.xml b/app/src/main/res/layout/f_provider_selection.xml index 7c861a14..48d5bdd3 100644 --- a/app/src/main/res/layout/f_provider_selection.xml +++ b/app/src/main/res/layout/f_provider_selection.xml @@ -81,6 +81,9 @@ android:layout_height="wrap_content" android:background="@color/white" android:hint="https://example.org" + android:inputType="textWebEditText" + android:imeOptions="actionDone" + android:maxLines="1" android:textAppearance="@style/TextAppearance.AppCompat.Body1" android:textColorHint="@color/black800_transparent" /> -- cgit v1.2.3