From 307309593109a068a8d925fe6dce70e3c844a3be Mon Sep 17 00:00:00 2001 From: Denys Podymskyy Date: Thu, 10 Sep 2020 13:08:59 +0300 Subject: [PATCH] Fixed autoconnect to VPN after phone restart (always-on VPN setting must be enabled manually) --- .../android/model/TunnelManager.kt | 76 ++++++++++++++++--- .../android/repository/DataRepository.java | 56 ++++++-------- .../android/viewmodel/MainViewModel.java | 4 - 3 files changed, 89 insertions(+), 47 deletions(-) diff --git a/ui/src/main/java/com/getbubblenow/android/model/TunnelManager.kt b/ui/src/main/java/com/getbubblenow/android/model/TunnelManager.kt index 5ee3506..8325d56 100644 --- a/ui/src/main/java/com/getbubblenow/android/model/TunnelManager.kt +++ b/ui/src/main/java/com/getbubblenow/android/model/TunnelManager.kt @@ -40,12 +40,24 @@ class TunnelManager(private val configStore: ConfigStore) : BaseObservable() { return tunnel } + /** + * Get first found tunnel in map with given name + */ + fun getTunnelByName(tunnelName: String): ObservableTunnel? { + return tunnelMap.find { it.name == tunnelName } + } + fun create(name: String, config: Config?): CompletionStage { if (Tunnel.isNameInvalid(name)) return CompletableFuture.failedFuture(IllegalArgumentException(context.getString(R.string.tunnel_error_invalid_name))) - if (tunnelMap.containsKey(name)) + if (tunnelMap.containsKey(name)) { return CompletableFuture.failedFuture(IllegalArgumentException(context.getString(R.string.tunnel_error_already_exists, name))) - return getAsyncWorker().supplyAsync { configStore.create(name, config!!) }.thenApply { addToList(name, it, Tunnel.State.DOWN) } + } + return getAsyncWorker().supplyAsync { + configStore.create(name, config!!) + }.thenApply { + addToList(name, it, Tunnel.State.DOWN) + } } fun delete(tunnel: ObservableTunnel): CompletionStage { @@ -94,7 +106,9 @@ class TunnelManager(private val configStore: ConfigStore) : BaseObservable() { fun onCreate() { getAsyncWorker().supplyAsync { configStore.enumerate() } - .thenAcceptBoth(getAsyncWorker().supplyAsync { getBackend().runningTunnelNames }, this::onTunnelsLoaded) + .thenAcceptBoth( + getAsyncWorker().supplyAsync { getBackend().runningTunnelNames } + ) { present, running -> this.onTunnelsLoaded(present, running) } .whenComplete(ExceptionLoggers.E) } @@ -102,8 +116,10 @@ class TunnelManager(private val configStore: ConfigStore) : BaseObservable() { for (name in present) addToList(name, null, if (running.contains(name)) Tunnel.State.UP else Tunnel.State.DOWN) val lastUsedName = getSharedPreferences().getString(KEY_LAST_USED_TUNNEL, null) - if (lastUsedName != null) + + if (lastUsedName != null) { lastUsedTunnel = tunnelMap[lastUsedName] + } var toComplete: Array> synchronized(delayedLoadRestoreTunnels) { haveLoaded = true @@ -123,7 +139,17 @@ class TunnelManager(private val configStore: ConfigStore) : BaseObservable() { fun refreshTunnelStates() { getAsyncWorker().supplyAsync { getBackend().runningTunnelNames } - .thenAccept { running: Set -> for (tunnel in tunnelMap) tunnel.onStateChanged(if (running.contains(tunnel.name)) Tunnel.State.UP else Tunnel.State.DOWN) } + .thenAccept { running: Set -> + for (tunnel in tunnelMap) { + tunnel.onStateChanged( + if (running.contains(tunnel.name)) { + Tunnel.State.UP + } else { + Tunnel.State.DOWN + } + ) + } + } .whenComplete(ExceptionLoggers.E) } @@ -139,12 +165,36 @@ class TunnelManager(private val configStore: ConfigStore) : BaseObservable() { } val previouslyRunning = getSharedPreferences().getStringSet(KEY_RUNNING_TUNNELS, null) ?: return CompletableFuture.completedFuture(null) - return CompletableFuture.allOf(*tunnelMap.filter { previouslyRunning.contains(it.name) }.map { setTunnelState(it, Tunnel.State.UP).toCompletableFuture() }.toTypedArray()) + + return CompletableFuture.allOf( + *tunnelMap + .filter { previouslyRunning.contains(it.name) } + .map { setTunnelState(it, Tunnel.State.UP).toCompletableFuture() } + .toTypedArray()) } @SuppressLint("ApplySharedPref") fun saveState() { - getSharedPreferences().edit().putStringSet(KEY_RUNNING_TUNNELS, tunnelMap.filter { it.state == Tunnel.State.UP }.map { it.name }.toSet()).commit() + getSharedPreferences().edit().putStringSet( + KEY_RUNNING_TUNNELS, + tunnelMap + .filter { it.state == Tunnel.State.UP } + .map { it.name } + .toSet() + ).commit() + getSharedPreferences().edit().putBoolean(KEY_RESTORE_ON_BOOT, upTunnelsExist()).commit() + } + + /** + * Check tunnel's map for tunnels with UP state. + */ + private fun upTunnelsExist() : Boolean { + tunnelMap.forEach { + if (it.state == Tunnel.State.UP) { + return true + } + } + return false } fun setTunnelConfig(tunnel: ObservableTunnel, config: Config): CompletionStage = getAsyncWorker().supplyAsync { @@ -183,13 +233,15 @@ class TunnelManager(private val configStore: ConfigStore) : BaseObservable() { } } - fun setTunnelState(tunnel: ObservableTunnel, state: Tunnel.State): CompletionStage = tunnel.configAsync - .thenCompose { getAsyncWorker().supplyAsync { getBackend().setState(tunnel, state, it) } } - .whenComplete { newState, e -> + fun setTunnelState(tunnel: ObservableTunnel, state: Tunnel.State): CompletionStage = + tunnel.configAsync.thenCompose { + getAsyncWorker().supplyAsync { getBackend().setState(tunnel, state, it) } + }.whenComplete { newState, e -> // Ensure onStateChanged is always called (failure or not), and with the correct state. tunnel.onStateChanged(if (e == null) newState else tunnel.state) - if (e == null && newState == Tunnel.State.UP) + if (e == null && newState == Tunnel.State.UP) { lastUsedTunnel = tunnel + } saveState() } @@ -218,7 +270,7 @@ class TunnelManager(private val configStore: ConfigStore) : BaseObservable() { } } - fun getTunnelState(tunnel: ObservableTunnel): CompletionStage = getAsyncWorker() + private fun getTunnelState(tunnel: ObservableTunnel): CompletionStage = getAsyncWorker() .supplyAsync { getBackend().getState(tunnel) }.thenApply(tunnel::onStateChanged) fun getTunnelStatistics(tunnel: ObservableTunnel): CompletionStage = getAsyncWorker() diff --git a/ui/src/main/java/com/getbubblenow/android/repository/DataRepository.java b/ui/src/main/java/com/getbubblenow/android/repository/DataRepository.java index 980a020..1624368 100644 --- a/ui/src/main/java/com/getbubblenow/android/repository/DataRepository.java +++ b/ui/src/main/java/com/getbubblenow/android/repository/DataRepository.java @@ -114,7 +114,6 @@ public final class DataRepository { baseUrl = url; clientApi = ClientService.getInstance().createClientApi(url); compositeDisposable = new CompositeDisposable(); - Application.setTunnelManager(new TunnelManager(new FileConfigStore(context))); } public static void buildRepositoryInstance(final Context context, final String url) { @@ -793,7 +792,7 @@ public final class DataRepository { }.getMutableLiveData(); } - public MutableLiveData> createTunnel(Context context, String dataConfig) { + public MutableLiveData> createTunnel(final Context context, final String dataConfig) { return new NetworkBoundStatusResource() { @Override protected void createCall() { @@ -865,52 +864,47 @@ public final class DataRepository { } - private Config parseConfig(String data) throws IOException, BadConfigException { + private Config parseConfig(final String data) throws IOException, BadConfigException { final byte[] configText = data.getBytes(); - final Config config = Config.parse(new ByteArrayInputStream(configText)); - return config; + return Config.parse(new ByteArrayInputStream(configText)); } private ObservableTunnel createTunnel(final Context context, final boolean stateTunnel) { //TODO implement config is null case - Config config = null; + final Config config; try { config = parseConfig(TunnelStore.getInstance(context).getConfig()); } catch (final IOException | BadConfigException e) { return null; } final String name = TunnelStore.getInstance(context).getTunnelName(); - final ObservableTunnel tunnel; - if (stateTunnel) { - tunnel = new ObservableTunnel(Application.getTunnelManager(), name, config, State.UP); - } else { - tunnel = new ObservableTunnel(Application.getTunnelManager(), name, config, State.DOWN); - } -// Application.getTunnelManager().setTunnelState(tunnel, tunnel.getState()); - pendingTunnel = tunnel; - return tunnel; + return (stateTunnel) ? new ObservableTunnel(Application.getTunnelManager(), name, config, State.UP) + : new ObservableTunnel(Application.getTunnelManager(), name, config, State.DOWN); } - public ObservableTunnel getTunnel(final Context context, final boolean connectionStateFlag) { - ObservableTunnel tunnel = Application.getTunnelManager().getLastUsedTunnel(); - if (tunnel == null) { - tunnel = createTunnel(context, connectionStateFlag); + private void getTunnel(final Context context, final boolean connectionStateFlag) { + + final String tunnelName = TunnelStore.getInstance(context).getTunnelName(); + + pendingTunnel = Application.getTunnelManager().getLastUsedTunnel(); + + if (pendingTunnel == null) { + pendingTunnel = Application.getTunnelManager().getTunnelByName(tunnelName); } - pendingTunnel = tunnel; - return tunnel; - } - public boolean isVPNConnected(final Context context, final boolean connectionStateFlag) { - pendingTunnel = getTunnel(context, connectionStateFlag); if (pendingTunnel == null) { - return false; + pendingTunnel = createTunnel(context, connectionStateFlag); } + } + + public boolean isVPNConnected(final Context context, final boolean connectionStateFlag) { + getTunnel(context, connectionStateFlag); return pendingTunnel.getState() == State.DOWN; } - public MutableLiveData connect(final Boolean checked, Context context) { - MutableLiveData liveData = new MutableLiveData<>(); + public MutableLiveData connect(final Boolean checked, final Context context) { + final MutableLiveData liveData = new MutableLiveData<>(); if (pendingTunnel != null) { Application.getBackendAsync().thenAccept(backend -> { if (backend instanceof GoBackend) { @@ -930,11 +924,11 @@ public final class DataRepository { return liveData; } - public MutableLiveData connectWithPermission(final boolean checked, Context context) { - MutableLiveData liveData = new MutableLiveData<>(); - pendingTunnel.setStateAsync(Tunnel.State.of(checked)).whenComplete((observableTunnel, throwable) -> { + public MutableLiveData connectWithPermission(final boolean checked, final Context context) { + final MutableLiveData liveData = new MutableLiveData<>(); + pendingTunnel.setStateAsync(Tunnel.State.of(checked)).whenComplete((observableTunnelState, throwable) -> { if (throwable == null) { - if (observableTunnel == State.DOWN) { + if (observableTunnelState == State.DOWN) { liveData.postValue(false); } else { liveData.postValue(true); diff --git a/ui/src/main/java/com/getbubblenow/android/viewmodel/MainViewModel.java b/ui/src/main/java/com/getbubblenow/android/viewmodel/MainViewModel.java index 560f864..a334689 100644 --- a/ui/src/main/java/com/getbubblenow/android/viewmodel/MainViewModel.java +++ b/ui/src/main/java/com/getbubblenow/android/viewmodel/MainViewModel.java @@ -38,10 +38,6 @@ public class MainViewModel extends BaseViewModel { return !UserStore.USER_TOKEN_DEFAULT_VALUE.equals(UserStore.getInstance(context).getToken()); } - public ObservableTunnel getTunnel(Context context, boolean stateTunnel) { - return DataRepository.getRepositoryInstance().getTunnel(context,stateTunnel); - } - public MutableLiveData connectWithPermission(final boolean checked , Context context) { return DataRepository.getRepositoryInstance().connectWithPermission(checked,context); }