Convert the list of tunnels to a KeyedObservableList

Signed-off-by: Samuel Holland <samuel@sholland.org>
This commit is contained in:
Samuel Holland 2018-01-06 04:04:42 -06:00
parent c73287f64b
commit ff0bb081a0
8 changed files with 73 additions and 45 deletions

View File

@ -6,7 +6,8 @@ import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener; 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.ObservableMap.OnMapChangedCallback; 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;
@ -19,8 +20,8 @@ import com.wireguard.android.activity.MainActivity;
import com.wireguard.android.activity.SettingsActivity; 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.TunnelCollection;
import com.wireguard.android.model.TunnelManager; import com.wireguard.android.model.TunnelManager;
import com.wireguard.android.util.KeyedObservableList;
import java.util.Objects; import java.util.Objects;
@ -33,9 +34,8 @@ 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 implements OnSharedPreferenceChangeListener {
private static final String TAG = QuickTileService.class.getSimpleName(); private static final String TAG = QuickTileService.class.getSimpleName();
private final OnTunnelListChangedCallback listCallback = new OnTunnelListChangedCallback();
private final OnTunnelStateChangedCallback tunnelCallback = new OnTunnelStateChangedCallback(); private final OnTunnelStateChangedCallback tunnelCallback = new OnTunnelStateChangedCallback();
private final OnTunnelMapChangedCallback tunnelMapCallback = new OnTunnelMapChangedCallback();
private SharedPreferences preferences; private SharedPreferences preferences;
private Tunnel tunnel; private Tunnel tunnel;
private TunnelManager tunnelManager; private TunnelManager tunnelManager;
@ -75,7 +75,7 @@ public class QuickTileService extends TileService implements OnSharedPreferenceC
@Override @Override
public void onStartListening() { public void onStartListening() {
preferences.registerOnSharedPreferenceChangeListener(this); preferences.registerOnSharedPreferenceChangeListener(this);
tunnelManager.getTunnels().addOnMapChangedCallback(tunnelMapCallback); tunnelManager.getTunnels().addOnListChangedCallback(listCallback);
if (tunnel != null) if (tunnel != null)
tunnel.addOnPropertyChangedCallback(tunnelCallback); tunnel.addOnPropertyChangedCallback(tunnelCallback);
updateTile(); updateTile();
@ -84,7 +84,7 @@ public class QuickTileService extends TileService implements OnSharedPreferenceC
@Override @Override
public void onStopListening() { public void onStopListening() {
preferences.unregisterOnSharedPreferenceChangeListener(this); preferences.unregisterOnSharedPreferenceChangeListener(this);
tunnelManager.getTunnels().removeOnMapChangedCallback(tunnelMapCallback); tunnelManager.getTunnels().removeOnListChangedCallback(listCallback);
if (tunnel != null) if (tunnel != null)
tunnel.removeOnPropertyChangedCallback(tunnelCallback); tunnel.removeOnPropertyChangedCallback(tunnelCallback);
} }
@ -104,7 +104,7 @@ public class QuickTileService extends TileService implements OnSharedPreferenceC
final String currentName = tunnel != null ? tunnel.getName() : null; final String currentName = tunnel != null ? tunnel.getName() : null;
final String newName = preferences.getString(TunnelManager.KEY_PRIMARY_TUNNEL, null); final String newName = preferences.getString(TunnelManager.KEY_PRIMARY_TUNNEL, null);
if (!Objects.equals(currentName, newName)) { if (!Objects.equals(currentName, newName)) {
final TunnelCollection tunnels = tunnelManager.getTunnels(); final KeyedObservableList<String, Tunnel> tunnels = tunnelManager.getTunnels();
final Tunnel newTunnel = newName != null ? tunnels.get(newName) : null; final Tunnel newTunnel = newName != null ? tunnels.get(newName) : null;
if (tunnel != null) if (tunnel != null)
tunnel.removeOnPropertyChangedCallback(tunnelCallback); tunnel.removeOnPropertyChangedCallback(tunnelCallback);
@ -134,12 +134,35 @@ public class QuickTileService extends TileService implements OnSharedPreferenceC
tile.updateTile(); tile.updateTile();
} }
private final class OnTunnelMapChangedCallback private final class OnTunnelListChangedCallback
extends OnMapChangedCallback<TunnelCollection, String, Tunnel> { extends OnListChangedCallback<ObservableList<Tunnel>> {
@Override @Override
public void onMapChanged(final TunnelCollection sender, final String key) { public void onChanged(final ObservableList<Tunnel> sender) {
if (!key.equals(preferences.getString(TunnelManager.KEY_PRIMARY_TUNNEL, null))) updateTile();
return; }
@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(); updateTile();
} }
} }

View File

@ -10,6 +10,8 @@ import android.widget.ListView;
import android.widget.TextView; import android.widget.TextView;
import com.wireguard.android.R; import com.wireguard.android.R;
import com.wireguard.android.util.Keyed;
import com.wireguard.android.util.KeyedObservableList;
import com.wireguard.android.widget.ToggleSwitch; import com.wireguard.android.widget.ToggleSwitch;
import org.threeten.bp.Instant; import org.threeten.bp.Instant;
@ -63,14 +65,15 @@ public final class BindingAdapters {
} }
@BindingAdapter({"items", "layout"}) @BindingAdapter({"items", "layout"})
public static <T> void setItems(final ListView view, public static <K, E extends Keyed<? extends K>>
final ObservableList<T> oldList, final int oldLayoutId, void setItems(final ListView view,
final ObservableList<T> newList, final int newLayoutId) { final KeyedObservableList<K, E> oldList, final int oldLayoutId,
final KeyedObservableList<K, E> newList, final int newLayoutId) {
if (oldList == newList && oldLayoutId == newLayoutId) if (oldList == newList && oldLayoutId == newLayoutId)
return; return;
// The ListAdapter interface is not generic, so this cannot be checked. // The ListAdapter interface is not generic, so this cannot be checked.
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked") KeyedObservableListAdapter<K, E> adapter =
ObservableListAdapter<T> adapter = (ObservableListAdapter<T>) view.getAdapter(); (KeyedObservableListAdapter<K, E>) view.getAdapter();
// If the layout changes, any existing adapter must be replaced. // If the layout changes, any existing adapter must be replaced.
if (adapter != null && oldList != null && oldLayoutId != newLayoutId) { if (adapter != null && oldList != null && oldLayoutId != newLayoutId) {
adapter.setList(null); adapter.setList(null);
@ -80,7 +83,7 @@ public final class BindingAdapters {
if (newList == null || newLayoutId == 0) if (newList == null || newLayoutId == 0)
return; return;
if (adapter == null) { if (adapter == null) {
adapter = new ObservableListAdapter<>(view.getContext(), newLayoutId, newList); adapter = new KeyedObservableListAdapter<>(view.getContext(), newLayoutId, newList);
view.setAdapter(adapter); view.setAdapter(adapter);
} }
// Either the list changed, or this is an entirely new listener because the layout changed. // Either the list changed, or this is an entirely new listener because the layout changed.

View File

@ -9,6 +9,7 @@ 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.util.ExceptionLoggers; import com.wireguard.android.util.ExceptionLoggers;
import com.wireguard.android.util.Keyed;
import com.wireguard.config.Config; import com.wireguard.config.Config;
import org.threeten.bp.Instant; import org.threeten.bp.Instant;
@ -23,7 +24,7 @@ import java9.util.concurrent.CompletionStage;
* Encapsulates the volatile and nonvolatile state of a WireGuard tunnel. * Encapsulates the volatile and nonvolatile state of a WireGuard tunnel.
*/ */
public class Tunnel extends BaseObservable implements Comparable<Tunnel> { public class Tunnel extends BaseObservable implements Keyed<String> {
public static final int NAME_MAX_LENGTH = 16; public static final int NAME_MAX_LENGTH = 16;
private static final Pattern NAME_PATTERN = Pattern.compile("[a-zA-Z0-9_=+.-]{1,16}"); private static final Pattern NAME_PATTERN = Pattern.compile("[a-zA-Z0-9_=+.-]{1,16}");
private static final String TAG = Tunnel.class.getSimpleName(); private static final String TAG = Tunnel.class.getSimpleName();
@ -48,11 +49,6 @@ public class Tunnel extends BaseObservable implements Comparable<Tunnel> {
return name != null && NAME_PATTERN.matcher(name).matches(); return name != null && NAME_PATTERN.matcher(name).matches();
} }
@Override
public int compareTo(@NonNull final Tunnel tunnel) {
return name.compareTo(tunnel.name);
}
@Bindable @Bindable
public Config getConfig() { public Config getConfig() {
if (config == null) if (config == null)
@ -66,6 +62,11 @@ public class Tunnel extends BaseObservable implements Comparable<Tunnel> {
return CompletableFuture.completedFuture(config); return CompletableFuture.completedFuture(config);
} }
@Override
public String getKey() {
return name;
}
@Bindable @Bindable
public Instant getLastStateChange() { public Instant getLastStateChange() {
return lastStateChange; return lastStateChange;

View File

@ -1,10 +0,0 @@
package com.wireguard.android.model;
import com.wireguard.android.databinding.ObservableTreeMap;
/**
* Created by samuel on 12/19/17.
*/
public class TunnelCollection extends ObservableTreeMap<String, Tunnel> {
}

View File

@ -8,6 +8,8 @@ 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;
import com.wireguard.android.util.ExceptionLoggers; import com.wireguard.android.util.ExceptionLoggers;
import com.wireguard.android.util.KeyedObservableList;
import com.wireguard.android.util.SortedKeyedObservableArrayList;
import com.wireguard.config.Config; import com.wireguard.config.Config;
import java.util.Collections; import java.util.Collections;
@ -35,7 +37,8 @@ public final class TunnelManager {
private final Backend backend; private final Backend backend;
private final ConfigStore configStore; private final ConfigStore configStore;
private final SharedPreferences preferences; private final SharedPreferences preferences;
private final TunnelCollection tunnels = new TunnelCollection(); private final KeyedObservableList<String, Tunnel> tunnels =
new SortedKeyedObservableArrayList<>();
@Inject @Inject
public TunnelManager(final Backend backend, final ConfigStore configStore, public TunnelManager(final Backend backend, final ConfigStore configStore,
@ -47,7 +50,7 @@ public final class TunnelManager {
private Tunnel add(final String name, final Config config) { private Tunnel add(final String name, final Config config) {
final Tunnel tunnel = new Tunnel(backend, configStore, name, config); final Tunnel tunnel = new Tunnel(backend, configStore, name, config);
tunnels.put(name, tunnel); tunnels.add(tunnel);
return tunnel; return tunnel;
} }
@ -71,13 +74,13 @@ public final class TunnelManager {
return backend.setState(tunnel, State.DOWN) return backend.setState(tunnel, State.DOWN)
.thenCompose(x -> configStore.delete(tunnel.getName())) .thenCompose(x -> configStore.delete(tunnel.getName()))
.thenAccept(x -> { .thenAccept(x -> {
tunnels.remove(tunnel.getName()); tunnels.remove(tunnel);
if (tunnel.getName().equals(preferences.getString(KEY_PRIMARY_TUNNEL, null))) if (tunnel.getName().equals(preferences.getString(KEY_PRIMARY_TUNNEL, null)))
preferences.edit().remove(KEY_PRIMARY_TUNNEL).apply(); preferences.edit().remove(KEY_PRIMARY_TUNNEL).apply();
}); });
} }
public TunnelCollection getTunnels() { public KeyedObservableList<String, Tunnel> getTunnels() {
return tunnels; return tunnels;
} }
@ -105,7 +108,7 @@ public final class TunnelManager {
} }
public CompletionStage<Void> saveState() { public CompletionStage<Void> saveState() {
final Set<String> runningTunnels = StreamSupport.stream(tunnels.values()) final Set<String> runningTunnels = StreamSupport.stream(tunnels)
.filter(tunnel -> tunnel.getState() == State.UP) .filter(tunnel -> tunnel.getState() == State.UP)
.map(Tunnel::getName) .map(Tunnel::getName)
.collect(Collectors.toUnmodifiableSet()); .collect(Collectors.toUnmodifiableSet());

View File

@ -5,19 +5,23 @@ import android.preference.ListPreference;
import android.util.AttributeSet; import android.util.AttributeSet;
import com.wireguard.android.Application; import com.wireguard.android.Application;
import com.wireguard.android.model.Tunnel;
import com.wireguard.android.model.TunnelManager;
import java.util.Set; import java9.util.stream.StreamSupport;
/** /**
* ListPreference that is automatically filled with the list of configurations. * ListPreference that is automatically filled with the list of tunnels.
*/ */
public class TunnelListPreference extends ListPreference { public class TunnelListPreference extends ListPreference {
public TunnelListPreference(final Context context, final AttributeSet attrs, public TunnelListPreference(final Context context, final AttributeSet attrs,
final int defStyleAttr, final int defStyleRes) { final int defStyleAttr, final int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes); super(context, attrs, defStyleAttr, defStyleRes);
final Set<String> entrySet = Application.getComponent().getTunnelManager().getTunnels().keySet(); final TunnelManager tunnelManager = Application.getComponent().getTunnelManager();
final CharSequence[] entries = entrySet.toArray(new CharSequence[entrySet.size()]); final CharSequence[] entries = StreamSupport.stream(tunnelManager.getTunnels())
.map(Tunnel::getName)
.toArray(String[]::new);
setEntries(entries); setEntries(entries);
setEntryValues(entries); setEntryValues(entries);
} }

View File

@ -4,13 +4,15 @@
<data> <data>
<import type="com.wireguard.android.model.Tunnel" />
<variable <variable
name="fragment" name="fragment"
type="com.wireguard.android.fragment.TunnelListFragment" /> type="com.wireguard.android.fragment.TunnelListFragment" />
<variable <variable
name="tunnels" name="tunnels"
type="com.wireguard.android.model.TunnelCollection" /> type="com.wireguard.android.util.KeyedObservableList&lt;String, Tunnel&gt;" />
</data> </data>
<com.commonsware.cwac.crossport.design.widget.CoordinatorLayout <com.commonsware.cwac.crossport.design.widget.CoordinatorLayout

View File

@ -4,11 +4,13 @@
<data> <data>
<import type="com.wireguard.android.model.Tunnel" />
<import type="com.wireguard.android.model.Tunnel.State" /> <import type="com.wireguard.android.model.Tunnel.State" />
<variable <variable
name="collection" name="collection"
type="com.wireguard.android.model.TunnelCollection" /> type="com.wireguard.android.util.KeyedObservableList&lt;String, Tunnel&gt;" />
<variable <variable
name="key" name="key"