TunnelManager/QuickTileService: Remember last used tunnel
This is actually a bit simpler than having a manually-selected "primary" tunnel, and is hopefully easier for the user. Signed-off-by: Samuel Holland <samuel@sholland.org>
This commit is contained in:
parent
38b2aafce8
commit
1fd9547f6a
@ -2,12 +2,8 @@ package com.wireguard.android;
|
|||||||
|
|
||||||
import android.annotation.TargetApi;
|
import android.annotation.TargetApi;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.SharedPreferences;
|
|
||||||
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
|
|
||||||
import android.databinding.Observable;
|
import android.databinding.Observable;
|
||||||
import android.databinding.Observable.OnPropertyChangedCallback;
|
import android.databinding.Observable.OnPropertyChangedCallback;
|
||||||
import android.databinding.ObservableList;
|
|
||||||
import android.databinding.ObservableList.OnListChangedCallback;
|
|
||||||
import android.graphics.drawable.Icon;
|
import android.graphics.drawable.Icon;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.service.quicksettings.Tile;
|
import android.service.quicksettings.Tile;
|
||||||
@ -15,13 +11,10 @@ import android.service.quicksettings.TileService;
|
|||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import com.wireguard.android.Application.ApplicationComponent;
|
|
||||||
import com.wireguard.android.activity.MainActivity;
|
import com.wireguard.android.activity.MainActivity;
|
||||||
import com.wireguard.android.activity.SettingsActivity;
|
|
||||||
import com.wireguard.android.model.Tunnel;
|
import com.wireguard.android.model.Tunnel;
|
||||||
import com.wireguard.android.model.Tunnel.State;
|
import com.wireguard.android.model.Tunnel.State;
|
||||||
import com.wireguard.android.model.TunnelManager;
|
import com.wireguard.android.model.TunnelManager;
|
||||||
import com.wireguard.android.util.ObservableKeyedList;
|
|
||||||
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
@ -32,61 +25,40 @@ import java.util.Objects;
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
@TargetApi(Build.VERSION_CODES.N)
|
@TargetApi(Build.VERSION_CODES.N)
|
||||||
public class QuickTileService extends TileService implements OnSharedPreferenceChangeListener {
|
public class QuickTileService extends TileService {
|
||||||
private static final String TAG = QuickTileService.class.getSimpleName();
|
private static final String TAG = QuickTileService.class.getSimpleName();
|
||||||
private final OnTunnelListChangedCallback listCallback = new OnTunnelListChangedCallback();
|
private final OnStateChangedCallback onStateChangedCallback = new OnStateChangedCallback();
|
||||||
private final OnTunnelStateChangedCallback tunnelCallback = new OnTunnelStateChangedCallback();
|
private final OnTunnelChangedCallback onTunnelChangedCallback = new OnTunnelChangedCallback();
|
||||||
private SharedPreferences preferences;
|
|
||||||
private Tunnel tunnel;
|
private Tunnel tunnel;
|
||||||
private TunnelManager tunnelManager;
|
private TunnelManager tunnelManager;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onClick() {
|
public void onClick() {
|
||||||
if (tunnel != null) {
|
if (tunnel != null)
|
||||||
tunnel.setState(State.TOGGLE).handle(this::onToggleFinished);
|
tunnel.setState(State.TOGGLE).whenComplete(this::onToggleFinished);
|
||||||
} else {
|
else
|
||||||
if (tunnelManager.getTunnels().isEmpty()) {
|
startActivityAndCollapse(new Intent(this, MainActivity.class));
|
||||||
// Prompt the user to create or import a tunnel configuration.
|
|
||||||
startActivityAndCollapse(new Intent(this, MainActivity.class));
|
|
||||||
} else {
|
|
||||||
// Prompt the user to select a tunnel for use with the quick settings tile.
|
|
||||||
final Intent intent = new Intent(this, SettingsActivity.class);
|
|
||||||
intent.putExtra(SettingsActivity.KEY_SHOW_QUICK_TILE_SETTINGS, true);
|
|
||||||
startActivityAndCollapse(intent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate() {
|
public void onCreate() {
|
||||||
super.onCreate();
|
super.onCreate();
|
||||||
final ApplicationComponent component = Application.getComponent();
|
tunnelManager = Application.getComponent().getTunnelManager();
|
||||||
preferences = component.getPreferences();
|
|
||||||
tunnelManager = component.getTunnelManager();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onSharedPreferenceChanged(final SharedPreferences preferences, final String key) {
|
|
||||||
if (!TunnelManager.KEY_PRIMARY_TUNNEL.equals(key))
|
|
||||||
return;
|
|
||||||
updateTile();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onStartListening() {
|
public void onStartListening() {
|
||||||
preferences.registerOnSharedPreferenceChangeListener(this);
|
tunnelManager.addOnPropertyChangedCallback(onTunnelChangedCallback);
|
||||||
tunnelManager.getTunnels().addOnListChangedCallback(listCallback);
|
|
||||||
if (tunnel != null)
|
if (tunnel != null)
|
||||||
tunnel.addOnPropertyChangedCallback(tunnelCallback);
|
tunnel.addOnPropertyChangedCallback(onStateChangedCallback);
|
||||||
updateTile();
|
updateTile();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onStopListening() {
|
public void onStopListening() {
|
||||||
preferences.unregisterOnSharedPreferenceChangeListener(this);
|
|
||||||
tunnelManager.getTunnels().removeOnListChangedCallback(listCallback);
|
|
||||||
if (tunnel != null)
|
if (tunnel != null)
|
||||||
tunnel.removeOnPropertyChangedCallback(tunnelCallback);
|
tunnel.removeOnPropertyChangedCallback(onStateChangedCallback);
|
||||||
|
tunnelManager.removeOnPropertyChangedCallback(onTunnelChangedCallback);
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
@ -101,16 +73,13 @@ public class QuickTileService extends TileService implements OnSharedPreferenceC
|
|||||||
|
|
||||||
private void updateTile() {
|
private void updateTile() {
|
||||||
// Update the tunnel.
|
// Update the tunnel.
|
||||||
final String currentName = tunnel != null ? tunnel.getName() : null;
|
final Tunnel newTunnel = tunnelManager.getLastUsedTunnel();
|
||||||
final String newName = preferences.getString(TunnelManager.KEY_PRIMARY_TUNNEL, null);
|
if (newTunnel != tunnel) {
|
||||||
if (!Objects.equals(currentName, newName)) {
|
|
||||||
final ObservableKeyedList<String, Tunnel> tunnels = tunnelManager.getTunnels();
|
|
||||||
final Tunnel newTunnel = newName != null ? tunnels.get(newName) : null;
|
|
||||||
if (tunnel != null)
|
if (tunnel != null)
|
||||||
tunnel.removeOnPropertyChangedCallback(tunnelCallback);
|
tunnel.removeOnPropertyChangedCallback(onStateChangedCallback);
|
||||||
tunnel = newTunnel;
|
tunnel = newTunnel;
|
||||||
if (tunnel != null)
|
if (tunnel != null)
|
||||||
tunnel.addOnPropertyChangedCallback(tunnelCallback);
|
tunnel.addOnPropertyChangedCallback(onStateChangedCallback);
|
||||||
}
|
}
|
||||||
// Update the tile contents.
|
// Update the tile contents.
|
||||||
final String label;
|
final String label;
|
||||||
@ -126,48 +95,15 @@ public class QuickTileService extends TileService implements OnSharedPreferenceC
|
|||||||
tile.setLabel(label);
|
tile.setLabel(label);
|
||||||
if (tile.getState() != state) {
|
if (tile.getState() != state) {
|
||||||
// The icon must be changed every time the state changes, or the shade will not change.
|
// The icon must be changed every time the state changes, or the shade will not change.
|
||||||
final Integer iconResource = (state == Tile.STATE_ACTIVE)
|
final Integer iconResource = state == Tile.STATE_ACTIVE ? R.drawable.ic_tile
|
||||||
? R.drawable.ic_tile : R.drawable.ic_tile_disabled;
|
: R.drawable.ic_tile_disabled;
|
||||||
tile.setIcon(Icon.createWithResource(this, iconResource));
|
tile.setIcon(Icon.createWithResource(this, iconResource));
|
||||||
tile.setState(state);
|
tile.setState(state);
|
||||||
}
|
}
|
||||||
tile.updateTile();
|
tile.updateTile();
|
||||||
}
|
}
|
||||||
|
|
||||||
private final class OnTunnelListChangedCallback
|
private final class OnStateChangedCallback extends OnPropertyChangedCallback {
|
||||||
extends OnListChangedCallback<ObservableList<Tunnel>> {
|
|
||||||
@Override
|
|
||||||
public void onChanged(final ObservableList<Tunnel> sender) {
|
|
||||||
updateTile();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onItemRangeChanged(final ObservableList<Tunnel> sender,
|
|
||||||
final int positionStart, final int itemCount) {
|
|
||||||
updateTile();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onItemRangeInserted(final ObservableList<Tunnel> sender,
|
|
||||||
final int positionStart, final int itemCount) {
|
|
||||||
// Do nothing.
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onItemRangeMoved(final ObservableList<Tunnel> sender,
|
|
||||||
final int fromPosition, final int toPosition,
|
|
||||||
final int itemCount) {
|
|
||||||
// Do nothing.
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onItemRangeRemoved(final ObservableList<Tunnel> sender,
|
|
||||||
final int positionStart, final int itemCount) {
|
|
||||||
updateTile();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private final class OnTunnelStateChangedCallback extends OnPropertyChangedCallback {
|
|
||||||
@Override
|
@Override
|
||||||
public void onPropertyChanged(final Observable sender, final int propertyId) {
|
public void onPropertyChanged(final Observable sender, final int propertyId) {
|
||||||
if (!Objects.equals(sender, tunnel)) {
|
if (!Objects.equals(sender, tunnel)) {
|
||||||
@ -179,4 +115,13 @@ public class QuickTileService extends TileService implements OnSharedPreferenceC
|
|||||||
updateTile();
|
updateTile();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private final class OnTunnelChangedCallback extends OnPropertyChangedCallback {
|
||||||
|
@Override
|
||||||
|
public void onPropertyChanged(final Observable sender, final int propertyId) {
|
||||||
|
if (propertyId != 0 && propertyId != BR.lastUsedTunnel)
|
||||||
|
return;
|
||||||
|
updateTile();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,12 @@
|
|||||||
package com.wireguard.android.model;
|
package com.wireguard.android.model;
|
||||||
|
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
|
import android.databinding.BaseObservable;
|
||||||
|
import android.databinding.Bindable;
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
|
|
||||||
import com.wireguard.android.Application.ApplicationScope;
|
import com.wireguard.android.Application.ApplicationScope;
|
||||||
|
import com.wireguard.android.BR;
|
||||||
import com.wireguard.android.backend.Backend;
|
import com.wireguard.android.backend.Backend;
|
||||||
import com.wireguard.android.configStore.ConfigStore;
|
import com.wireguard.android.configStore.ConfigStore;
|
||||||
import com.wireguard.android.model.Tunnel.State;
|
import com.wireguard.android.model.Tunnel.State;
|
||||||
@ -14,7 +17,6 @@ import com.wireguard.android.util.ObservableKeyedList;
|
|||||||
import com.wireguard.android.util.ObservableSortedKeyedArrayList;
|
import com.wireguard.android.util.ObservableSortedKeyedArrayList;
|
||||||
import com.wireguard.config.Config;
|
import com.wireguard.config.Config;
|
||||||
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
@ -31,10 +33,10 @@ import java9.util.stream.StreamSupport;
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
@ApplicationScope
|
@ApplicationScope
|
||||||
public final class TunnelManager {
|
public final class TunnelManager extends BaseObservable {
|
||||||
public static final String KEY_PRIMARY_TUNNEL = "primary_config";
|
|
||||||
private static final Comparator<String> COMPARATOR = Comparators.<String>thenComparing(
|
private static final Comparator<String> COMPARATOR = Comparators.<String>thenComparing(
|
||||||
String.CASE_INSENSITIVE_ORDER, Comparators.naturalOrder());
|
String.CASE_INSENSITIVE_ORDER, Comparators.naturalOrder());
|
||||||
|
private static final String KEY_LAST_USED_TUNNEL = "last_used_tunnel";
|
||||||
private static final String KEY_RESTORE_ON_BOOT = "restore_on_boot";
|
private static final String KEY_RESTORE_ON_BOOT = "restore_on_boot";
|
||||||
private static final String KEY_RUNNING_TUNNELS = "enabled_configs";
|
private static final String KEY_RUNNING_TUNNELS = "enabled_configs";
|
||||||
private static final String TAG = TunnelManager.class.getSimpleName();
|
private static final String TAG = TunnelManager.class.getSimpleName();
|
||||||
@ -45,6 +47,7 @@ public final class TunnelManager {
|
|||||||
private final SharedPreferences preferences;
|
private final SharedPreferences preferences;
|
||||||
private final ObservableKeyedList<String, Tunnel> tunnels =
|
private final ObservableKeyedList<String, Tunnel> tunnels =
|
||||||
new ObservableSortedKeyedArrayList<>(COMPARATOR);
|
new ObservableSortedKeyedArrayList<>(COMPARATOR);
|
||||||
|
private Tunnel lastUsedTunnel;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public TunnelManager(final AsyncWorker asyncWorker, final Backend backend,
|
public TunnelManager(final AsyncWorker asyncWorker, final Backend backend,
|
||||||
@ -73,16 +76,38 @@ public final class TunnelManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
CompletionStage<Void> delete(final Tunnel tunnel) {
|
CompletionStage<Void> delete(final Tunnel tunnel) {
|
||||||
|
final State originalState = tunnel.getState();
|
||||||
|
final boolean wasLastUsed = tunnel == lastUsedTunnel;
|
||||||
|
// Make sure nothing touches the tunnel.
|
||||||
|
if (wasLastUsed)
|
||||||
|
setLastUsedTunnel(null);
|
||||||
|
tunnels.remove(tunnel);
|
||||||
return asyncWorker.runAsync(() -> {
|
return asyncWorker.runAsync(() -> {
|
||||||
backend.setState(tunnel, State.DOWN);
|
if (originalState == State.UP)
|
||||||
configStore.delete(tunnel.getName());
|
backend.setState(tunnel, State.DOWN);
|
||||||
}).thenAccept(x -> {
|
try {
|
||||||
if (tunnel.getName().equals(preferences.getString(KEY_PRIMARY_TUNNEL, null)))
|
configStore.delete(tunnel.getName());
|
||||||
preferences.edit().remove(KEY_PRIMARY_TUNNEL).apply();
|
} catch (final Exception e) {
|
||||||
tunnels.remove(tunnel);
|
if (originalState == State.UP)
|
||||||
|
backend.setState(tunnel, originalState);
|
||||||
|
// Re-throw the exception to fail the completion.
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}).whenComplete((x, e) -> {
|
||||||
|
if (e == null)
|
||||||
|
return;
|
||||||
|
// Failure, put the tunnel back.
|
||||||
|
tunnels.add(tunnel);
|
||||||
|
if (wasLastUsed)
|
||||||
|
setLastUsedTunnel(tunnel);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Bindable
|
||||||
|
public Tunnel getLastUsedTunnel() {
|
||||||
|
return lastUsedTunnel;
|
||||||
|
}
|
||||||
|
|
||||||
CompletionStage<Config> getTunnelConfig(final Tunnel tunnel) {
|
CompletionStage<Config> getTunnelConfig(final Tunnel tunnel) {
|
||||||
final CompletionStage<Config> completion =
|
final CompletionStage<Config> completion =
|
||||||
asyncWorker.supplyAsync(() -> configStore.load(tunnel.getName()));
|
asyncWorker.supplyAsync(() -> configStore.load(tunnel.getName()));
|
||||||
@ -117,6 +142,9 @@ public final class TunnelManager {
|
|||||||
private void onTunnelsLoaded(final Set<String> present, final Set<String> running) {
|
private void onTunnelsLoaded(final Set<String> present, final Set<String> running) {
|
||||||
for (final String name : present)
|
for (final String name : present)
|
||||||
addToList(name, null, running.contains(name) ? State.UP : State.DOWN);
|
addToList(name, null, running.contains(name) ? State.UP : State.DOWN);
|
||||||
|
final String lastUsedName = preferences.getString(KEY_LAST_USED_TUNNEL, null);
|
||||||
|
if (lastUsedName != null)
|
||||||
|
setLastUsedTunnel(tunnels.get(lastUsedName));
|
||||||
}
|
}
|
||||||
|
|
||||||
CompletionStage<Tunnel> rename(final Tunnel tunnel, final String name) {
|
CompletionStage<Tunnel> rename(final Tunnel tunnel, final String name) {
|
||||||
@ -127,21 +155,42 @@ public final class TunnelManager {
|
|||||||
return CompletableFuture.failedFuture(new IllegalArgumentException(message));
|
return CompletableFuture.failedFuture(new IllegalArgumentException(message));
|
||||||
}
|
}
|
||||||
final State originalState = tunnel.getState();
|
final State originalState = tunnel.getState();
|
||||||
|
final boolean wasLastUsed = tunnel == lastUsedTunnel;
|
||||||
|
// Make sure nothing touches the tunnel.
|
||||||
|
if (wasLastUsed)
|
||||||
|
setLastUsedTunnel(null);
|
||||||
|
tunnels.remove(tunnel);
|
||||||
return asyncWorker.supplyAsync(() -> {
|
return asyncWorker.supplyAsync(() -> {
|
||||||
backend.setState(tunnel, State.DOWN);
|
if (originalState == State.UP)
|
||||||
|
backend.setState(tunnel, State.DOWN);
|
||||||
final Config newConfig = configStore.create(name, tunnel.getConfig());
|
final Config newConfig = configStore.create(name, tunnel.getConfig());
|
||||||
final Tunnel newTunnel = new Tunnel(this, name, newConfig, State.DOWN);
|
final Tunnel newTunnel = new Tunnel(this, name, newConfig, State.DOWN);
|
||||||
if (originalState == State.UP) {
|
try {
|
||||||
backend.setState(newTunnel, originalState);
|
if (originalState == State.UP)
|
||||||
newTunnel.onStateChanged(originalState);
|
backend.setState(newTunnel, originalState);
|
||||||
|
configStore.delete(tunnel.getName());
|
||||||
|
} catch (final Exception e) {
|
||||||
|
// Clean up.
|
||||||
|
configStore.delete(name);
|
||||||
|
if (originalState == State.UP)
|
||||||
|
backend.setState(tunnel, originalState);
|
||||||
|
// Re-throw the exception to fail the completion.
|
||||||
|
throw e;
|
||||||
}
|
}
|
||||||
configStore.delete(tunnel.getName());
|
|
||||||
return newTunnel;
|
return newTunnel;
|
||||||
}).whenComplete((newTunnel, e) -> {
|
}).whenComplete((newTunnel, e) -> {
|
||||||
if (e != null)
|
if (e == null) {
|
||||||
return;
|
// Success, add the new tunnel.
|
||||||
tunnels.remove(tunnel);
|
newTunnel.onStateChanged(originalState);
|
||||||
tunnels.add(newTunnel);
|
tunnels.add(newTunnel);
|
||||||
|
if (wasLastUsed)
|
||||||
|
setLastUsedTunnel(newTunnel);
|
||||||
|
} else {
|
||||||
|
// Failure, put the old tunnel back.
|
||||||
|
tunnels.add(tunnel);
|
||||||
|
if (wasLastUsed)
|
||||||
|
setLastUsedTunnel(tunnel);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -166,6 +215,17 @@ public final class TunnelManager {
|
|||||||
return CompletableFuture.completedFuture(null);
|
return CompletableFuture.completedFuture(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void setLastUsedTunnel(final Tunnel tunnel) {
|
||||||
|
if (tunnel == lastUsedTunnel)
|
||||||
|
return;
|
||||||
|
lastUsedTunnel = tunnel;
|
||||||
|
notifyPropertyChanged(BR.lastUsedTunnel);
|
||||||
|
if (tunnel != null)
|
||||||
|
preferences.edit().putString(KEY_LAST_USED_TUNNEL, tunnel.getName()).apply();
|
||||||
|
else
|
||||||
|
preferences.edit().remove(KEY_LAST_USED_TUNNEL).apply();
|
||||||
|
}
|
||||||
|
|
||||||
CompletionStage<Config> setTunnelConfig(final Tunnel tunnel, final Config config) {
|
CompletionStage<Config> setTunnelConfig(final Tunnel tunnel, final Config config) {
|
||||||
final CompletionStage<Config> completion = asyncWorker.supplyAsync(() -> {
|
final CompletionStage<Config> completion = asyncWorker.supplyAsync(() -> {
|
||||||
final Config appliedConfig = backend.applyConfig(tunnel, config);
|
final Config appliedConfig = backend.applyConfig(tunnel, config);
|
||||||
@ -179,6 +239,10 @@ public final class TunnelManager {
|
|||||||
final CompletionStage<State> completion =
|
final CompletionStage<State> completion =
|
||||||
asyncWorker.supplyAsync(() -> backend.setState(tunnel, state));
|
asyncWorker.supplyAsync(() -> backend.setState(tunnel, state));
|
||||||
completion.thenAccept(tunnel::onStateChanged);
|
completion.thenAccept(tunnel::onStateChanged);
|
||||||
|
completion.thenAccept(newState -> {
|
||||||
|
if (newState == State.UP)
|
||||||
|
setLastUsedTunnel(tunnel);
|
||||||
|
});
|
||||||
return completion;
|
return completion;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user