diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 11c6eecb..5ffcb928 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -24,7 +24,11 @@
android:label="@string/edit_activity_title" />
+ android:label="@string/settings">
+
+
+
+
@@ -32,6 +36,17 @@
+
+
+
+
+
+
diff --git a/app/src/main/java/com/wireguard/android/QuickTileService.java b/app/src/main/java/com/wireguard/android/QuickTileService.java
new file mode 100644
index 00000000..dcd51b66
--- /dev/null
+++ b/app/src/main/java/com/wireguard/android/QuickTileService.java
@@ -0,0 +1,81 @@
+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.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 com.wireguard.config.Config;
+
+@TargetApi(Build.VERSION_CODES.N)
+public class QuickTileService extends TileService {
+ private Config config;
+ private SharedPreferences preferences;
+ private VpnService service;
+
+ @Override
+ public void onClick() {
+ if (service != null && config != null) {
+ if (config.isEnabled())
+ service.disable(config.getName());
+ else
+ service.enable(config.getName());
+ }
+ }
+
+ @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);
+ }
+
+ @Override
+ public void onStartListening() {
+ // Since this is an active tile, this only gets called when we want to update the tile.
+ 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);
+ }
+ } else {
+ tile.setIcon(Icon.createWithResource(this, R.drawable.ic_tile_disabled));
+ tile.setLabel(getString(R.string.loading));
+ tile.setState(Tile.STATE_UNAVAILABLE);
+ }
+ tile.updateTile();
+ }
+
+ private class ServiceConnectionCallbacks implements ServiceConnection {
+ @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();
+ }
+
+ @Override
+ public void onServiceDisconnected(final ComponentName component) {
+ // This can never happen; the service runs in the same thread as this service.
+ throw new IllegalStateException();
+ }
+ }
+}
diff --git a/app/src/main/java/com/wireguard/android/VpnService.java b/app/src/main/java/com/wireguard/android/VpnService.java
index e4523080..19040c56 100644
--- a/app/src/main/java/com/wireguard/android/VpnService.java
+++ b/app/src/main/java/com/wireguard/android/VpnService.java
@@ -1,12 +1,15 @@
package com.wireguard.android;
import android.app.Service;
+import android.content.ComponentName;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.AsyncTask;
import android.os.Binder;
+import android.os.Build;
import android.os.IBinder;
import android.preference.PreferenceManager;
+import android.service.quicksettings.TileService;
import android.util.Log;
import com.wireguard.config.Config;
@@ -137,11 +140,13 @@ public class VpnService extends Service
final String key) {
if (!KEY_PRIMARY_CONFIG.equals(key))
return;
+ boolean changed = false;
final String newName = preferences.getString(key, null);
if (primaryName != null && !primaryName.equals(newName)) {
final Config oldConfig = configurations.get(primaryName);
if (oldConfig != null)
oldConfig.setIsPrimary(false);
+ changed = true;
}
if (newName != null && !newName.equals(primaryName)) {
final Config newConfig = configurations.get(newName);
@@ -149,8 +154,11 @@ public class VpnService extends Service
newConfig.setIsPrimary(true);
else
preferences.edit().remove(KEY_PRIMARY_CONFIG).apply();
+ changed = true;
}
primaryName = newName;
+ if (changed)
+ updateTile();
}
@Override
@@ -198,6 +206,13 @@ public class VpnService extends Service
new ConfigUpdater(oldConfig, config, wasEnabled).execute();
}
+ private void updateTile() {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N)
+ return;
+ Log.v(TAG, "Requesting quick tile update");
+ TileService.requestListeningState(this, new ComponentName(this, QuickTileService.class));
+ }
+
private class ConfigDisabler extends AsyncTask {
private final Config config;
@@ -219,6 +234,8 @@ public class VpnService extends Service
config.setIsEnabled(false);
enabledConfigs.remove(config.getName());
preferences.edit().putStringSet(KEY_ENABLED_CONFIGS, enabledConfigs).apply();
+ if (config.getName().equals(primaryName))
+ updateTile();
}
}
@@ -243,6 +260,8 @@ public class VpnService extends Service
config.setIsEnabled(true);
enabledConfigs.add(config.getName());
preferences.edit().putStringSet(KEY_ENABLED_CONFIGS, enabledConfigs).apply();
+ if (config.getName().equals(primaryName))
+ updateTile();
}
}
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 2c0ce517..4c781c21 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -18,6 +18,7 @@
(optional)
(random)
Listen port
+ Loading
MTU
No configuration selected
Primary configuration