summaryrefslogtreecommitdiffhomepage
path: root/app/src/main/java/com/wireguard/android/QuickTileService.java
diff options
context:
space:
mode:
Diffstat (limited to 'app/src/main/java/com/wireguard/android/QuickTileService.java')
-rw-r--r--app/src/main/java/com/wireguard/android/QuickTileService.java164
1 files changed, 116 insertions, 48 deletions
diff --git a/app/src/main/java/com/wireguard/android/QuickTileService.java b/app/src/main/java/com/wireguard/android/QuickTileService.java
index e512b1ad..9de4322a 100644
--- a/app/src/main/java/com/wireguard/android/QuickTileService.java
+++ b/app/src/main/java/com/wireguard/android/QuickTileService.java
@@ -1,40 +1,57 @@
package com.wireguard.android;
import android.annotation.TargetApi;
-import android.content.ComponentName;
-import android.content.Context;
import android.content.Intent;
-import android.content.ServiceConnection;
import android.content.SharedPreferences;
+import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
+import android.databinding.Observable;
+import android.databinding.Observable.OnPropertyChangedCallback;
+import android.databinding.ObservableMap.OnMapChangedCallback;
import android.graphics.drawable.Icon;
import android.os.Build;
-import android.os.IBinder;
-import android.preference.PreferenceManager;
import android.service.quicksettings.Tile;
import android.service.quicksettings.TileService;
+import android.util.Log;
+import android.widget.Toast;
-import com.wireguard.android.backends.VpnService;
-import com.wireguard.config.Config;
+import com.wireguard.android.Application.ApplicationComponent;
+import com.wireguard.android.activity.MainActivity;
+import com.wireguard.android.activity.SettingsActivity;
+import com.wireguard.android.model.Tunnel;
+import com.wireguard.android.model.Tunnel.State;
+import com.wireguard.android.model.TunnelCollection;
+import com.wireguard.android.model.TunnelManager;
+
+import java.util.Objects;
+
+/**
+ * Service that maintains the application's custom Quick Settings tile. This service is bound by the
+ * system framework as necessary to update the appearance of the tile in the system UI, and to
+ * forward click events to the application.
+ */
@TargetApi(Build.VERSION_CODES.N)
-public class QuickTileService extends TileService {
- private Config config;
+public class QuickTileService extends TileService implements OnSharedPreferenceChangeListener {
+ private static final String TAG = QuickTileService.class.getSimpleName();
+
+ private final OnTunnelStateChangedCallback tunnelCallback = new OnTunnelStateChangedCallback();
+ private final OnTunnelMapChangedCallback tunnelMapCallback = new OnTunnelMapChangedCallback();
private SharedPreferences preferences;
- private VpnService service;
+ private Tunnel tunnel;
+ private TunnelManager tunnelManager;
@Override
public void onClick() {
- if (service != null && config != null) {
- if (config.isEnabled())
- service.disable(config.getName());
- else
- service.enable(config.getName());
+ if (tunnel != null) {
+ tunnel.setState(State.TOGGLE).handle(this::onToggleFinished);
} else {
- if (service != null && service.getConfigs().isEmpty()) {
- startActivityAndCollapse(new Intent(this, ConfigActivity.class));
+ if (tunnelManager.getTunnels().isEmpty()) {
+ // 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("showQuickTile", true);
+ intent.putExtra(SettingsActivity.KEY_SHOW_QUICK_TILE_SETTINGS, true);
startActivityAndCollapse(intent);
}
}
@@ -42,50 +59,101 @@ public class QuickTileService extends TileService {
@Override
public void onCreate() {
- preferences = PreferenceManager.getDefaultSharedPreferences(this);
- service = VpnService.getInstance();
- if (service == null)
- bindService(new Intent(this, VpnService.class), new ServiceConnectionCallbacks(),
- Context.BIND_AUTO_CREATE);
- TileService.requestListeningState(this, new ComponentName(this, getClass()));
+ super.onCreate();
+ final ApplicationComponent component = Application.getComponent();
+ 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
public void onStartListening() {
- // Since this is an active tile, this only gets called when we want to update the tile.
+ preferences.registerOnSharedPreferenceChangeListener(this);
+ tunnelManager.getTunnels().addOnMapChangedCallback(tunnelMapCallback);
+ if (tunnel != null)
+ tunnel.addOnPropertyChangedCallback(tunnelCallback);
+ updateTile();
+ }
+
+ @Override
+ public void onStopListening() {
+ preferences.unregisterOnSharedPreferenceChangeListener(this);
+ tunnelManager.getTunnels().removeOnMapChangedCallback(tunnelMapCallback);
+ if (tunnel != null)
+ tunnel.removeOnPropertyChangedCallback(tunnelCallback);
+ }
+
+ @SuppressWarnings("unused")
+ private Void onToggleFinished(final State state, final Throwable throwable) {
+ if (throwable == null)
+ return null;
+ Log.e(TAG, "Cannot toggle tunnel", throwable);
+ final String message = "Cannot toggle tunnel: " + throwable.getCause().getMessage();
+ Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
+ return null;
+ }
+
+ private void updateTile() {
+ // Update the tunnel.
+ final String currentName = tunnel != null ? tunnel.getName() : null;
+ final String newName = preferences.getString(TunnelManager.KEY_PRIMARY_TUNNEL, null);
+ if (!Objects.equals(currentName, newName)) {
+ final TunnelCollection tunnels = tunnelManager.getTunnels();
+ final Tunnel newTunnel = newName != null ? tunnels.get(newName) : null;
+ if (tunnel != null)
+ tunnel.removeOnPropertyChangedCallback(tunnelCallback);
+ tunnel = newTunnel;
+ if (tunnel != null)
+ tunnel.addOnPropertyChangedCallback(tunnelCallback);
+ }
+ // Update the tile contents.
+ final String label;
+ final int state;
final Tile tile = getQsTile();
- final String configName = preferences.getString(VpnService.KEY_PRIMARY_CONFIG, null);
- config = configName != null && service != null ? service.get(configName) : null;
- if (config != null) {
- tile.setLabel(config.getName());
- final int state = config.isEnabled() ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
- if (tile.getState() != state) {
- // The icon must be changed every time the state changes, or the color won't change.
- final Integer iconResource = (state == Tile.STATE_ACTIVE) ?
- R.drawable.ic_tile : R.drawable.ic_tile_disabled;
- tile.setIcon(Icon.createWithResource(this, iconResource));
- tile.setState(state);
- }
+ if (tunnel != null) {
+ label = tunnel.getName();
+ state = tunnel.getState() == Tunnel.State.UP ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
} else {
- tile.setIcon(Icon.createWithResource(this, R.drawable.ic_tile_disabled));
- tile.setLabel(getString(R.string.app_name));
- tile.setState(Tile.STATE_INACTIVE);
+ label = getString(R.string.app_name);
+ state = Tile.STATE_INACTIVE;
+ }
+ tile.setLabel(label);
+ if (tile.getState() != state) {
+ // The icon must be changed every time the state changes, or the shade will not change.
+ final Integer iconResource = (state == Tile.STATE_ACTIVE)
+ ? R.drawable.ic_tile : R.drawable.ic_tile_disabled;
+ tile.setIcon(Icon.createWithResource(this, iconResource));
+ tile.setState(state);
}
tile.updateTile();
}
- private class ServiceConnectionCallbacks implements ServiceConnection {
+ private final class OnTunnelMapChangedCallback
+ extends OnMapChangedCallback<TunnelCollection, String, Tunnel> {
@Override
- public void onServiceConnected(final ComponentName component, final IBinder binder) {
- // We don't actually need a binding, only notification that the service is started.
- unbindService(this);
- service = VpnService.getInstance();
+ public void onMapChanged(final TunnelCollection sender, final String key) {
+ if (!key.equals(preferences.getString(TunnelManager.KEY_PRIMARY_TUNNEL, null)))
+ return;
+ updateTile();
}
+ }
+ private final class OnTunnelStateChangedCallback extends OnPropertyChangedCallback {
@Override
- public void onServiceDisconnected(final ComponentName component) {
- // This can never happen; the service runs in the same thread as this service.
- throw new IllegalStateException();
+ public void onPropertyChanged(final Observable sender, final int propertyId) {
+ if (!Objects.equals(sender, tunnel)) {
+ sender.removeOnPropertyChangedCallback(this);
+ return;
+ }
+ if (propertyId != 0 && propertyId != BR.state)
+ return;
+ updateTile();
}
}
}