summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorJason A. Donenfeld <Jason@zx2c4.com>2020-03-26 01:55:44 -0600
committerJason A. Donenfeld <Jason@zx2c4.com>2020-03-26 01:55:44 -0600
commit85dd303c88f636024ce8b32e17ff76bcb3a19911 (patch)
tree4608314368a6b72262c1f97b67bd5b1d25d9a6eb
parent2958144fd0082489a996fbc7a11572f3edbe42eb (diff)
ui: root: rewrite in kotlin
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
-rw-r--r--ui/src/main/java/com/wireguard/android/Application.java180
-rw-r--r--ui/src/main/java/com/wireguard/android/Application.kt159
-rw-r--r--ui/src/main/java/com/wireguard/android/BootShutdownReceiver.java40
-rw-r--r--ui/src/main/java/com/wireguard/android/BootShutdownReceiver.kt34
-rw-r--r--ui/src/main/java/com/wireguard/android/QuickTileService.java177
-rw-r--r--ui/src/main/java/com/wireguard/android/QuickTileService.kt158
-rw-r--r--ui/src/main/java/com/wireguard/android/preference/VersionPreference.kt2
7 files changed, 352 insertions, 398 deletions
diff --git a/ui/src/main/java/com/wireguard/android/Application.java b/ui/src/main/java/com/wireguard/android/Application.java
deleted file mode 100644
index 62cf25a1..00000000
--- a/ui/src/main/java/com/wireguard/android/Application.java
+++ /dev/null
@@ -1,180 +0,0 @@
-/*
- * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-package com.wireguard.android;
-
-import android.content.Context;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.os.AsyncTask;
-import android.os.Build;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.StrictMode;
-import android.util.Log;
-
-import com.wireguard.android.backend.Backend;
-import com.wireguard.android.backend.GoBackend;
-import com.wireguard.android.backend.WgQuickBackend;
-import com.wireguard.android.configStore.FileConfigStore;
-import com.wireguard.android.model.TunnelManager;
-import com.wireguard.android.util.AsyncWorker;
-import com.wireguard.android.util.ExceptionLoggers;
-import com.wireguard.android.util.ModuleLoader;
-import com.wireguard.android.util.RootShell;
-import com.wireguard.android.util.ToolsInstaller;
-import com.wireguard.util.NonNullForAll;
-
-import java.lang.ref.WeakReference;
-import java.util.Locale;
-
-import androidx.annotation.Nullable;
-import androidx.appcompat.app.AppCompatDelegate;
-import androidx.preference.PreferenceManager;
-import java9.util.concurrent.CompletableFuture;
-
-@NonNullForAll
-public class Application extends android.app.Application implements SharedPreferences.OnSharedPreferenceChangeListener {
- public static final String USER_AGENT = String.format(Locale.ENGLISH, "WireGuard/%s (Android %d; %s; %s; %s %s; %s)", BuildConfig.VERSION_NAME, Build.VERSION.SDK_INT, Build.SUPPORTED_ABIS.length > 0 ? Build.SUPPORTED_ABIS[0] : "unknown ABI", Build.BOARD, Build.MANUFACTURER, Build.MODEL, Build.FINGERPRINT);
- private static final String TAG = "WireGuard/" + Application.class.getSimpleName();
- @SuppressWarnings("NullableProblems") private static WeakReference<Application> weakSelf;
- private final CompletableFuture<Backend> futureBackend = new CompletableFuture<>();
- @SuppressWarnings("NullableProblems") private AsyncWorker asyncWorker;
- @Nullable private Backend backend;
- @SuppressWarnings("NullableProblems") private ModuleLoader moduleLoader;
- @SuppressWarnings("NullableProblems") private RootShell rootShell;
- @SuppressWarnings("NullableProblems") private SharedPreferences sharedPreferences;
- @SuppressWarnings("NullableProblems") private ToolsInstaller toolsInstaller;
- @SuppressWarnings("NullableProblems") private TunnelManager tunnelManager;
-
- public Application() {
- weakSelf = new WeakReference<>(this);
- }
-
- public static Application get() {
- return weakSelf.get();
- }
-
- public static AsyncWorker getAsyncWorker() {
- return get().asyncWorker;
- }
-
- public static Backend getBackend() {
- final Application app = get();
- synchronized (app.futureBackend) {
- if (app.backend == null) {
- Backend backend = null;
- boolean didStartRootShell = false;
- if (!ModuleLoader.isModuleLoaded() && app.moduleLoader.moduleMightExist()) {
- try {
- app.rootShell.start();
- didStartRootShell = true;
- app.moduleLoader.loadModule();
- } catch (final Exception ignored) {
- }
- }
- if (!app.sharedPreferences.getBoolean("disable_kernel_module", false) && ModuleLoader.isModuleLoaded()) {
- try {
- if (!didStartRootShell)
- app.rootShell.start();
- final WgQuickBackend wgQuickBackend = new WgQuickBackend(app.getApplicationContext(), app.rootShell, app.toolsInstaller);
- wgQuickBackend.setMultipleTunnels(app.sharedPreferences.getBoolean("multiple_tunnels", false));
- backend = wgQuickBackend;
- } catch (final Exception ignored) {
- }
- }
- if (backend == null) {
- backend = new GoBackend(app.getApplicationContext());
- GoBackend.setAlwaysOnCallback(() -> {
- get().tunnelManager.restoreState(true).whenComplete(ExceptionLoggers.D);
- });
- }
- app.backend = backend;
- }
- return app.backend;
- }
- }
-
- public static CompletableFuture<Backend> getBackendAsync() {
- return get().futureBackend;
- }
-
- public static ModuleLoader getModuleLoader() {
- return get().moduleLoader;
- }
-
- public static RootShell getRootShell() {
- return get().rootShell;
- }
-
- public static SharedPreferences getSharedPreferences() {
- return get().sharedPreferences;
- }
-
- public static ToolsInstaller getToolsInstaller() {
- return get().toolsInstaller;
- }
-
- public static TunnelManager getTunnelManager() {
- return get().tunnelManager;
- }
-
- @Override
- protected void attachBaseContext(final Context context) {
- super.attachBaseContext(context);
-
- if (BuildConfig.MIN_SDK_VERSION > Build.VERSION.SDK_INT) {
- final Intent intent = new Intent(Intent.ACTION_MAIN);
- intent.addCategory(Intent.CATEGORY_HOME);
- intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- startActivity(intent);
- System.exit(0);
- }
-
- if (BuildConfig.DEBUG) {
- StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder().detectAll().penaltyLog().build());
- }
- }
-
- @Override
- public void onCreate() {
- Log.i(TAG, USER_AGENT);
- super.onCreate();
-
- asyncWorker = new AsyncWorker(AsyncTask.SERIAL_EXECUTOR, new Handler(Looper.getMainLooper()));
- rootShell = new RootShell(getApplicationContext());
- toolsInstaller = new ToolsInstaller(getApplicationContext(), rootShell);
- moduleLoader = new ModuleLoader(getApplicationContext(), rootShell, USER_AGENT);
-
- sharedPreferences = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
- AppCompatDelegate.setDefaultNightMode(
- sharedPreferences.getBoolean("dark_theme", false) ?
- AppCompatDelegate.MODE_NIGHT_YES : AppCompatDelegate.MODE_NIGHT_NO);
- } else {
- AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM);
- }
-
- tunnelManager = new TunnelManager(new FileConfigStore(getApplicationContext()));
- tunnelManager.onCreate();
-
- asyncWorker.supplyAsync(Application::getBackend).thenAccept(futureBackend::complete);
-
- sharedPreferences.registerOnSharedPreferenceChangeListener(this);
- }
-
- @Override
- public void onSharedPreferenceChanged(final SharedPreferences sharedPreferences, final String key) {
- if ("multiple_tunnels".equals(key) && backend != null && backend instanceof WgQuickBackend)
- ((WgQuickBackend) backend).setMultipleTunnels(sharedPreferences.getBoolean(key, false));
- }
-
- @Override
- public void onTerminate() {
- sharedPreferences.unregisterOnSharedPreferenceChangeListener(this);
- super.onTerminate();
- }
-}
diff --git a/ui/src/main/java/com/wireguard/android/Application.kt b/ui/src/main/java/com/wireguard/android/Application.kt
new file mode 100644
index 00000000..0e522abf
--- /dev/null
+++ b/ui/src/main/java/com/wireguard/android/Application.kt
@@ -0,0 +1,159 @@
+/*
+ * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package com.wireguard.android
+
+import android.content.Context
+import android.content.Intent
+import android.content.SharedPreferences
+import android.content.SharedPreferences.OnSharedPreferenceChangeListener
+import android.os.AsyncTask
+import android.os.Build
+import android.os.Handler
+import android.os.Looper
+import android.os.StrictMode
+import android.os.StrictMode.VmPolicy
+import android.util.Log
+import androidx.appcompat.app.AppCompatDelegate
+import androidx.preference.PreferenceManager
+import com.wireguard.android.backend.Backend
+import com.wireguard.android.backend.GoBackend
+import com.wireguard.android.backend.WgQuickBackend
+import com.wireguard.android.configStore.FileConfigStore
+import com.wireguard.android.model.TunnelManager
+import com.wireguard.android.util.AsyncWorker
+import com.wireguard.android.util.ExceptionLoggers
+import com.wireguard.android.util.ModuleLoader
+import com.wireguard.android.util.RootShell
+import com.wireguard.android.util.ToolsInstaller
+import java9.util.concurrent.CompletableFuture
+import java.lang.ref.WeakReference
+import java.util.Locale
+
+class Application : android.app.Application(), OnSharedPreferenceChangeListener {
+ private val futureBackend = CompletableFuture<Backend>()
+ private lateinit var asyncWorker: AsyncWorker
+ private var backend: Backend? = null
+ private lateinit var moduleLoader: ModuleLoader
+ private lateinit var rootShell: RootShell
+ private lateinit var sharedPreferences: SharedPreferences
+ private lateinit var toolsInstaller: ToolsInstaller
+ private lateinit var tunnelManager: TunnelManager
+
+ override fun attachBaseContext(context: Context) {
+ super.attachBaseContext(context)
+ if (BuildConfig.MIN_SDK_VERSION > Build.VERSION.SDK_INT) {
+ val intent = Intent(Intent.ACTION_MAIN)
+ intent.addCategory(Intent.CATEGORY_HOME)
+ intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ startActivity(intent)
+ System.exit(0)
+ }
+ if (BuildConfig.DEBUG) {
+ StrictMode.setVmPolicy(VmPolicy.Builder().detectAll().penaltyLog().build())
+ }
+ }
+
+ override fun onCreate() {
+ Log.i(TAG, USER_AGENT)
+ super.onCreate()
+ asyncWorker = AsyncWorker(AsyncTask.SERIAL_EXECUTOR, Handler(Looper.getMainLooper()))
+ rootShell = RootShell(applicationContext)
+ toolsInstaller = ToolsInstaller(applicationContext, rootShell)
+ moduleLoader = ModuleLoader(applicationContext, rootShell, USER_AGENT)
+ sharedPreferences = PreferenceManager.getDefaultSharedPreferences(applicationContext)
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
+ AppCompatDelegate.setDefaultNightMode(
+ if (sharedPreferences.getBoolean("dark_theme", false)) AppCompatDelegate.MODE_NIGHT_YES else AppCompatDelegate.MODE_NIGHT_NO)
+ } else {
+ AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
+ }
+ tunnelManager = TunnelManager(FileConfigStore(applicationContext))
+ tunnelManager.onCreate()
+ asyncWorker.supplyAsync { getBackend() }.thenAccept { futureBackend.complete(it) }
+ sharedPreferences.registerOnSharedPreferenceChangeListener(this)
+ }
+
+ override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) {
+ if ("multiple_tunnels" == key && backend != null && backend is WgQuickBackend)
+ (backend as WgQuickBackend).setMultipleTunnels(sharedPreferences.getBoolean(key, false))
+ }
+
+ override fun onTerminate() {
+ sharedPreferences.unregisterOnSharedPreferenceChangeListener(this)
+ super.onTerminate()
+ }
+
+ companion object {
+ val USER_AGENT = String.format(Locale.ENGLISH, "WireGuard/%s (Android %d; %s; %s; %s %s; %s)", BuildConfig.VERSION_NAME, Build.VERSION.SDK_INT, if (Build.SUPPORTED_ABIS.isNotEmpty()) Build.SUPPORTED_ABIS[0] else "unknown ABI", Build.BOARD, Build.MANUFACTURER, Build.MODEL, Build.FINGERPRINT)
+ private val TAG = "WireGuard/" + Application::class.java.simpleName
+ private lateinit var weakSelf: WeakReference<Application>
+
+ @JvmStatic
+ fun get(): Application {
+ return weakSelf.get()!!
+ }
+
+ @JvmStatic
+ fun getAsyncWorker() = get().asyncWorker
+
+ @JvmStatic
+ fun getBackend(): Backend {
+ val app = get()
+ synchronized(app.futureBackend) {
+ if (app.backend == null) {
+ var backend: Backend? = null
+ var didStartRootShell = false
+ if (!ModuleLoader.isModuleLoaded() && app.moduleLoader.moduleMightExist()) {
+ try {
+ app.rootShell.start()
+ didStartRootShell = true
+ app.moduleLoader.loadModule()
+ } catch (ignored: Exception) {
+ }
+ }
+ if (!app.sharedPreferences.getBoolean("disable_kernel_module", false) && ModuleLoader.isModuleLoaded()) {
+ try {
+ if (!didStartRootShell)
+ app.rootShell.start()
+ val wgQuickBackend = WgQuickBackend(app.applicationContext, app.rootShell, app.toolsInstaller)
+ wgQuickBackend.setMultipleTunnels(app.sharedPreferences.getBoolean("multiple_tunnels", false))
+ backend = wgQuickBackend
+ } catch (ignored: Exception) {
+ }
+ }
+ if (backend == null) {
+ backend = GoBackend(app.applicationContext)
+ GoBackend.setAlwaysOnCallback { get().tunnelManager.restoreState(true).whenComplete(ExceptionLoggers.D) }
+ }
+ app.backend = backend
+ }
+ return app.backend!!
+ }
+ }
+
+ @JvmStatic
+ fun getBackendAsync() = get().futureBackend
+
+ @JvmStatic
+ fun getModuleLoader() = get().moduleLoader
+
+ @JvmStatic
+ fun getRootShell() = get().rootShell
+
+ @JvmStatic
+ fun getSharedPreferences() = get().sharedPreferences
+
+ @JvmStatic
+ fun getToolsInstaller() = get().toolsInstaller
+
+ @JvmStatic
+ fun getTunnelManager() = get().tunnelManager
+ }
+
+ init {
+ weakSelf = WeakReference(this)
+ }
+}
diff --git a/ui/src/main/java/com/wireguard/android/BootShutdownReceiver.java b/ui/src/main/java/com/wireguard/android/BootShutdownReceiver.java
deleted file mode 100644
index 3f4bcd83..00000000
--- a/ui/src/main/java/com/wireguard/android/BootShutdownReceiver.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-package com.wireguard.android;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.util.Log;
-
-import com.wireguard.android.backend.WgQuickBackend;
-import com.wireguard.android.model.TunnelManager;
-import com.wireguard.android.util.ExceptionLoggers;
-import com.wireguard.util.NonNullForAll;
-
-@NonNullForAll
-public class BootShutdownReceiver extends BroadcastReceiver {
- private static final String TAG = "WireGuard/" + BootShutdownReceiver.class.getSimpleName();
-
- @Override
- public void onReceive(final Context context, final Intent intent) {
- Application.getBackendAsync().thenAccept(backend -> {
- if (!(backend instanceof WgQuickBackend))
- return;
- final String action = intent.getAction();
- if (action == null)
- return;
- final TunnelManager tunnelManager = Application.getTunnelManager();
- if (Intent.ACTION_BOOT_COMPLETED.equals(action)) {
- Log.i(TAG, "Broadcast receiver restoring state (boot)");
- tunnelManager.restoreState(false).whenComplete(ExceptionLoggers.D);
- } else if (Intent.ACTION_SHUTDOWN.equals(action)) {
- Log.i(TAG, "Broadcast receiver saving state (shutdown)");
- tunnelManager.saveState();
- }
- });
- }
-}
diff --git a/ui/src/main/java/com/wireguard/android/BootShutdownReceiver.kt b/ui/src/main/java/com/wireguard/android/BootShutdownReceiver.kt
new file mode 100644
index 00000000..e9759143
--- /dev/null
+++ b/ui/src/main/java/com/wireguard/android/BootShutdownReceiver.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package com.wireguard.android
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.util.Log
+import com.wireguard.android.backend.Backend
+import com.wireguard.android.backend.WgQuickBackend
+import com.wireguard.android.util.ExceptionLoggers
+
+class BootShutdownReceiver : BroadcastReceiver() {
+ override fun onReceive(context: Context, intent: Intent) {
+ Application.getBackendAsync().thenAccept { backend: Backend? ->
+ if (backend !is WgQuickBackend) return@thenAccept
+ val action = intent.action ?: return@thenAccept
+ val tunnelManager = Application.getTunnelManager()
+ if (Intent.ACTION_BOOT_COMPLETED == action) {
+ Log.i(TAG, "Broadcast receiver restoring state (boot)")
+ tunnelManager.restoreState(false).whenComplete(ExceptionLoggers.D)
+ } else if (Intent.ACTION_SHUTDOWN == action) {
+ Log.i(TAG, "Broadcast receiver saving state (shutdown)")
+ tunnelManager.saveState()
+ }
+ }
+ }
+
+ companion object {
+ private val TAG = "WireGuard/" + BootShutdownReceiver::class.java.simpleName
+ }
+}
diff --git a/ui/src/main/java/com/wireguard/android/QuickTileService.java b/ui/src/main/java/com/wireguard/android/QuickTileService.java
deleted file mode 100644
index a485ae1e..00000000
--- a/ui/src/main/java/com/wireguard/android/QuickTileService.java
+++ /dev/null
@@ -1,177 +0,0 @@
-/*
- * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-package com.wireguard.android;
-
-import android.content.Intent;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.drawable.Icon;
-import android.os.Build;
-import android.os.IBinder;
-import android.service.quicksettings.Tile;
-import android.service.quicksettings.TileService;
-import android.util.Log;
-
-import com.wireguard.android.activity.MainActivity;
-import com.wireguard.android.activity.TunnelToggleActivity;
-import com.wireguard.android.backend.Tunnel.State;
-import com.wireguard.android.model.ObservableTunnel;
-import com.wireguard.android.widget.SlashDrawable;
-import com.wireguard.util.NonNullForAll;
-
-import java.util.Objects;
-
-import androidx.annotation.Nullable;
-import androidx.annotation.RequiresApi;
-import androidx.databinding.Observable;
-import androidx.databinding.Observable.OnPropertyChangedCallback;
-
-/**
- * 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.
- */
-
-@RequiresApi(Build.VERSION_CODES.N)
-@NonNullForAll
-public class QuickTileService extends TileService {
- private static final String TAG = "WireGuard/" + QuickTileService.class.getSimpleName();
-
- private final OnStateChangedCallback onStateChangedCallback = new OnStateChangedCallback();
- private final OnTunnelChangedCallback onTunnelChangedCallback = new OnTunnelChangedCallback();
- @Nullable private Icon iconOff;
- @Nullable private Icon iconOn;
- @Nullable private ObservableTunnel tunnel;
-
- /* This works around an annoying unsolved frameworks bug some people are hitting. */
- @Override
- @Nullable
- public IBinder onBind(final Intent intent) {
- IBinder ret = null;
- try {
- ret = super.onBind(intent);
- } catch (final Exception e) {
- Log.d(TAG, "Failed to bind to TileService", e);
- }
- return ret;
- }
-
- @Override
- public void onClick() {
- if (tunnel != null) {
- unlockAndRun(() -> {
- final Tile tile = getQsTile();
- if (tile != null) {
- tile.setIcon(tile.getIcon() == iconOn ? iconOff : iconOn);
- tile.updateTile();
- }
- tunnel.setState(State.TOGGLE).whenComplete((v, t) -> {
- if (t == null) {
- updateTile();
- } else {
- final Intent toggleIntent = new Intent(this, TunnelToggleActivity.class);
- toggleIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- startActivity(toggleIntent);
- }
- });
- });
- } else {
- final Intent intent = new Intent(this, MainActivity.class);
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- startActivityAndCollapse(intent);
- }
- }
-
- @Override
- public void onCreate() {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
- iconOff = iconOn = Icon.createWithResource(this, R.drawable.ic_tile);
- return;
- }
- final SlashDrawable icon = new SlashDrawable(getResources().getDrawable(R.drawable.ic_tile, Application.get().getTheme()));
- icon.setAnimationEnabled(false); /* Unfortunately we can't have animations, since Icons are marshaled. */
- icon.setSlashed(false);
- Bitmap b = Bitmap.createBitmap(icon.getIntrinsicWidth(), icon.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
- Canvas c = new Canvas(b);
- icon.setBounds(0, 0, c.getWidth(), c.getHeight());
- icon.draw(c);
- iconOn = Icon.createWithBitmap(b);
- icon.setSlashed(true);
- b = Bitmap.createBitmap(icon.getIntrinsicWidth(), icon.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
- c = new Canvas(b);
- icon.setBounds(0, 0, c.getWidth(), c.getHeight());
- icon.draw(c);
- iconOff = Icon.createWithBitmap(b);
- }
-
- @Override
- public void onStartListening() {
- Application.getTunnelManager().addOnPropertyChangedCallback(onTunnelChangedCallback);
- if (tunnel != null)
- tunnel.addOnPropertyChangedCallback(onStateChangedCallback);
- updateTile();
- }
-
- @Override
- public void onStopListening() {
- if (tunnel != null)
- tunnel.removeOnPropertyChangedCallback(onStateChangedCallback);
- Application.getTunnelManager().removeOnPropertyChangedCallback(onTunnelChangedCallback);
- }
-
- private void updateTile() {
- // Update the tunnel.
- final ObservableTunnel newTunnel = Application.getTunnelManager().getLastUsedTunnel();
- if (newTunnel != tunnel) {
- if (tunnel != null)
- tunnel.removeOnPropertyChangedCallback(onStateChangedCallback);
- tunnel = newTunnel;
- if (tunnel != null)
- tunnel.addOnPropertyChangedCallback(onStateChangedCallback);
- }
- // Update the tile contents.
- final String label;
- final int state;
- final Tile tile = getQsTile();
- if (tunnel != null) {
- label = tunnel.getName();
- state = tunnel.getState() == State.UP ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
- } else {
- label = getString(R.string.app_name);
- state = Tile.STATE_INACTIVE;
- }
- if (tile == null)
- return;
- tile.setLabel(label);
- if (tile.getState() != state) {
- tile.setIcon(state == Tile.STATE_ACTIVE ? iconOn : iconOff);
- tile.setState(state);
- }
- tile.updateTile();
- }
-
- private final class OnStateChangedCallback extends OnPropertyChangedCallback {
- @Override
- 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();
- }
- }
-
- private final class OnTunnelChangedCallback extends OnPropertyChangedCallback {
- @Override
- public void onPropertyChanged(final Observable sender, final int propertyId) {
- if (propertyId != 0 && propertyId != BR.lastUsedTunnel)
- return;
- updateTile();
- }
- }
-}
diff --git a/ui/src/main/java/com/wireguard/android/QuickTileService.kt b/ui/src/main/java/com/wireguard/android/QuickTileService.kt
new file mode 100644
index 00000000..cd95f12d
--- /dev/null
+++ b/ui/src/main/java/com/wireguard/android/QuickTileService.kt
@@ -0,0 +1,158 @@
+/*
+ * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package com.wireguard.android
+
+import android.content.Intent
+import android.graphics.Bitmap
+import android.graphics.Canvas
+import android.graphics.drawable.Icon
+import android.os.Build
+import android.os.IBinder
+import android.service.quicksettings.Tile
+import android.service.quicksettings.TileService
+import android.util.Log
+import androidx.annotation.RequiresApi
+import androidx.databinding.Observable
+import androidx.databinding.Observable.OnPropertyChangedCallback
+import com.wireguard.android.activity.MainActivity
+import com.wireguard.android.activity.TunnelToggleActivity
+import com.wireguard.android.backend.Tunnel
+import com.wireguard.android.model.ObservableTunnel
+import com.wireguard.android.widget.SlashDrawable
+
+/**
+ * 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.
+ */
+@RequiresApi(Build.VERSION_CODES.N)
+class QuickTileService : TileService() {
+ private val onStateChangedCallback = OnStateChangedCallback()
+ private val onTunnelChangedCallback = OnTunnelChangedCallback()
+ private var iconOff: Icon? = null
+ private var iconOn: Icon? = null
+ private var tunnel: ObservableTunnel? = null
+
+ /* This works around an annoying unsolved frameworks bug some people are hitting. */
+ override fun onBind(intent: Intent): IBinder? {
+ var ret: IBinder? = null
+ try {
+ ret = super.onBind(intent)
+ } catch (e: Exception) {
+ Log.d(TAG, "Failed to bind to TileService", e)
+ }
+ return ret
+ }
+
+ override fun onClick() {
+ if (tunnel != null) {
+ unlockAndRun {
+ val tile = qsTile
+ if (tile != null) {
+ tile.icon = if (tile.icon == iconOn) iconOff else iconOn
+ tile.updateTile()
+ }
+ tunnel!!.setState(Tunnel.State.TOGGLE).whenComplete { _, t ->
+ if (t == null) {
+ updateTile()
+ } else {
+ val toggleIntent = Intent(this, TunnelToggleActivity::class.java)
+ toggleIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ startActivity(toggleIntent)
+ }
+ }
+ }
+ } else {
+ val intent = Intent(this, MainActivity::class.java)
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ startActivityAndCollapse(intent)
+ }
+ }
+
+ override fun onCreate() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
+ iconOn = Icon.createWithResource(this, R.drawable.ic_tile)
+ iconOff = iconOn
+ return
+ }
+ val icon = SlashDrawable(resources.getDrawable(R.drawable.ic_tile, Application.get().theme))
+ icon.setAnimationEnabled(false) /* Unfortunately we can't have animations, since Icons are marshaled. */
+ icon.setSlashed(false)
+ var b = Bitmap.createBitmap(icon.intrinsicWidth, icon.intrinsicHeight, Bitmap.Config.ARGB_8888)
+ ?: return
+ var c = Canvas(b)
+ icon.setBounds(0, 0, c.width, c.height)
+ icon.draw(c)
+ iconOn = Icon.createWithBitmap(b)
+ icon.setSlashed(true)
+ b = Bitmap.createBitmap(icon.intrinsicWidth, icon.intrinsicHeight, Bitmap.Config.ARGB_8888)
+ ?: return
+ c = Canvas(b)
+ icon.setBounds(0, 0, c.width, c.height)
+ icon.draw(c)
+ iconOff = Icon.createWithBitmap(b)
+ }
+
+ override fun onStartListening() {
+ Application.getTunnelManager().addOnPropertyChangedCallback(onTunnelChangedCallback)
+ if (tunnel != null) tunnel!!.addOnPropertyChangedCallback(onStateChangedCallback)
+ updateTile()
+ }
+
+ override fun onStopListening() {
+ if (tunnel != null) tunnel!!.removeOnPropertyChangedCallback(onStateChangedCallback)
+ Application.getTunnelManager().removeOnPropertyChangedCallback(onTunnelChangedCallback)
+ }
+
+ private fun updateTile() {
+ // Update the tunnel.
+ val newTunnel = Application.getTunnelManager().lastUsedTunnel
+ if (newTunnel != tunnel) {
+ if (tunnel != null) tunnel!!.removeOnPropertyChangedCallback(onStateChangedCallback)
+ tunnel = newTunnel
+ if (tunnel != null) tunnel!!.addOnPropertyChangedCallback(onStateChangedCallback)
+ }
+ // Update the tile contents.
+ val label: String
+ val state: Int
+ val tile = qsTile
+ if (tunnel != null) {
+ label = tunnel!!.name
+ state = if (tunnel!!.state == Tunnel.State.UP) Tile.STATE_ACTIVE else Tile.STATE_INACTIVE
+ } else {
+ label = getString(R.string.app_name)
+ state = Tile.STATE_INACTIVE
+ }
+ if (tile == null) return
+ tile.label = label
+ if (tile.state != state) {
+ tile.icon = if (state == Tile.STATE_ACTIVE) iconOn else iconOff
+ tile.state = state
+ }
+ tile.updateTile()
+ }
+
+ private inner class OnStateChangedCallback : OnPropertyChangedCallback() {
+ override fun onPropertyChanged(sender: Observable, propertyId: Int) {
+ if (sender != tunnel) {
+ sender.removeOnPropertyChangedCallback(this)
+ return
+ }
+ if (propertyId != 0 && propertyId != BR.state) return
+ updateTile()
+ }
+ }
+
+ private inner class OnTunnelChangedCallback : OnPropertyChangedCallback() {
+ override fun onPropertyChanged(sender: Observable, propertyId: Int) {
+ if (propertyId != 0 && propertyId != BR.lastUsedTunnel) return
+ updateTile()
+ }
+ }
+
+ companion object {
+ private val TAG = "WireGuard/" + QuickTileService::class.java.simpleName
+ }
+}
diff --git a/ui/src/main/java/com/wireguard/android/preference/VersionPreference.kt b/ui/src/main/java/com/wireguard/android/preference/VersionPreference.kt
index 6c5284ec..0734df45 100644
--- a/ui/src/main/java/com/wireguard/android/preference/VersionPreference.kt
+++ b/ui/src/main/java/com/wireguard/android/preference/VersionPreference.kt
@@ -43,7 +43,7 @@ class VersionPreference(context: Context, attrs: AttributeSet?) : Preference(con
}
init {
- Application.getBackendAsync().thenAccept { backend: Backend ->
+ Application.getBackendAsync().thenAccept { backend ->
versionSummary = getContext().getString(R.string.version_summary_checking, getBackendPrettyName(context, backend).toLowerCase(Locale.ENGLISH))
Application.getAsyncWorker().supplyAsync(backend::getVersion).whenComplete { version, exception ->
versionSummary = if (exception == null)