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