ui: root: rewrite in kotlin
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
This commit is contained in:
parent
2958144fd0
commit
85dd303c88
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
159
ui/src/main/java/com/wireguard/android/Application.kt
Normal file
159
ui/src/main/java/com/wireguard/android/Application.kt
Normal file
@ -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)
|
||||||
|
}
|
||||||
|
}
|
@ -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();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
158
ui/src/main/java/com/wireguard/android/QuickTileService.kt
Normal file
158
ui/src/main/java/com/wireguard/android/QuickTileService.kt
Normal file
@ -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
|
||||||
|
}
|
||||||
|
}
|
@ -43,7 +43,7 @@ class VersionPreference(context: Context, attrs: AttributeSet?) : Preference(con
|
|||||||
}
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
Application.getBackendAsync().thenAccept { backend: Backend ->
|
Application.getBackendAsync().thenAccept { backend ->
|
||||||
versionSummary = getContext().getString(R.string.version_summary_checking, getBackendPrettyName(context, backend).toLowerCase(Locale.ENGLISH))
|
versionSummary = getContext().getString(R.string.version_summary_checking, getBackendPrettyName(context, backend).toLowerCase(Locale.ENGLISH))
|
||||||
Application.getAsyncWorker().supplyAsync(backend::getVersion).whenComplete { version, exception ->
|
Application.getAsyncWorker().supplyAsync(backend::getVersion).whenComplete { version, exception ->
|
||||||
versionSummary = if (exception == null)
|
versionSummary = if (exception == null)
|
||||||
|
Loading…
Reference in New Issue
Block a user