aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/ui/src/main
diff options
context:
space:
mode:
authorJason A. Donenfeld <Jason@zx2c4.com>2020-09-14 19:46:49 +0200
committerJason A. Donenfeld <Jason@zx2c4.com>2020-09-15 12:30:15 +0200
commitbab70ab51ecc02c2e8afd1843cdd4d90ae9cc257 (patch)
treebd7117473f42dc6211d9aad4c78cbdddeb851b3e /ui/src/main
parentcoroutines: convert low-hanging fruits (diff)
downloadwireguard-android-bab70ab51ecc02c2e8afd1843cdd4d90ae9cc257.tar.xz
wireguard-android-bab70ab51ecc02c2e8afd1843cdd4d90ae9cc257.zip
coroutines: convert the rest
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
Diffstat (limited to 'ui/src/main')
-rw-r--r--ui/src/main/java/com/wireguard/android/Application.kt93
-rw-r--r--ui/src/main/java/com/wireguard/android/BootShutdownReceiver.kt13
-rw-r--r--ui/src/main/java/com/wireguard/android/QuickTileService.kt14
-rw-r--r--ui/src/main/java/com/wireguard/android/activity/BaseActivity.kt11
-rw-r--r--ui/src/main/java/com/wireguard/android/activity/LogViewerActivity.kt28
-rw-r--r--ui/src/main/java/com/wireguard/android/activity/SettingsActivity.kt10
-rw-r--r--ui/src/main/java/com/wireguard/android/activity/TunnelToggleActivity.kt28
-rw-r--r--ui/src/main/java/com/wireguard/android/databinding/BindingAdapters.kt2
-rw-r--r--ui/src/main/java/com/wireguard/android/fragment/AppListDialogFragment.kt11
-rw-r--r--ui/src/main/java/com/wireguard/android/fragment/BaseFragment.kt39
-rw-r--r--ui/src/main/java/com/wireguard/android/fragment/ConfigNamingDialogFragment.kt14
-rw-r--r--ui/src/main/java/com/wireguard/android/fragment/TunnelDetailFragment.kt46
-rw-r--r--ui/src/main/java/com/wireguard/android/fragment/TunnelEditorFragment.kt68
-rw-r--r--ui/src/main/java/com/wireguard/android/fragment/TunnelListFragment.kt197
-rw-r--r--ui/src/main/java/com/wireguard/android/model/ObservableTunnel.kt86
-rw-r--r--ui/src/main/java/com/wireguard/android/model/TunnelManager.kt204
-rw-r--r--ui/src/main/java/com/wireguard/android/preference/KernelModuleDisablerPreference.kt32
-rw-r--r--ui/src/main/java/com/wireguard/android/preference/ModuleDownloaderPreference.kt22
-rw-r--r--ui/src/main/java/com/wireguard/android/preference/ToolsInstallerPreference.kt12
-rw-r--r--ui/src/main/java/com/wireguard/android/preference/VersionPreference.kt18
-rw-r--r--ui/src/main/java/com/wireguard/android/preference/ZipExporterPreference.kt83
-rw-r--r--ui/src/main/java/com/wireguard/android/util/AsyncWorker.kt43
-rw-r--r--ui/src/main/java/com/wireguard/android/util/ExceptionLoggers.kt27
23 files changed, 569 insertions, 532 deletions
diff --git a/ui/src/main/java/com/wireguard/android/Application.kt b/ui/src/main/java/com/wireguard/android/Application.kt
index 5563fe62..8b3be8de 100644
--- a/ui/src/main/java/com/wireguard/android/Application.kt
+++ b/ui/src/main/java/com/wireguard/android/Application.kt
@@ -8,13 +8,10 @@ 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.os.StrictMode.ThreadPolicy
+import android.os.StrictMode.VmPolicy
import android.util.Log
import androidx.appcompat.app.AppCompatDelegate
import androidx.preference.PreferenceManager
@@ -23,18 +20,18 @@ 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 kotlinx.coroutines.CompletableDeferred
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.GlobalScope
+import kotlinx.coroutines.launch
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 val futureBackend = CompletableDeferred<Backend>()
private var backend: Backend? = null
private lateinit var moduleLoader: ModuleLoader
private lateinit var rootShell: RootShell
@@ -58,10 +55,37 @@ class Application : android.app.Application(), OnSharedPreferenceChangeListener
}
}
+ private fun determineBackend(): Backend {
+ var backend: Backend? = null
+ var didStartRootShell = false
+ if (!ModuleLoader.isModuleLoaded() && moduleLoader.moduleMightExist()) {
+ try {
+ rootShell.start()
+ didStartRootShell = true
+ moduleLoader.loadModule()
+ } catch (ignored: Exception) {
+ }
+ }
+ if (!sharedPreferences.getBoolean("disable_kernel_module", false) && ModuleLoader.isModuleLoaded()) {
+ try {
+ if (!didStartRootShell)
+ rootShell.start()
+ val wgQuickBackend = WgQuickBackend(applicationContext, rootShell, toolsInstaller)
+ wgQuickBackend.setMultipleTunnels(sharedPreferences.getBoolean("multiple_tunnels", false))
+ backend = wgQuickBackend
+ } catch (ignored: Exception) {
+ }
+ }
+ if (backend == null) {
+ backend = GoBackend(applicationContext)
+ GoBackend.setAlwaysOnCallback { get().tunnelManager.restoreState(true) }
+ }
+ return backend
+ }
+
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)
@@ -74,7 +98,14 @@ class Application : android.app.Application(), OnSharedPreferenceChangeListener
}
tunnelManager = TunnelManager(FileConfigStore(applicationContext))
tunnelManager.onCreate()
- asyncWorker.supplyAsync(Companion::getBackend).thenAccept { futureBackend.complete(it) }
+ GlobalScope.launch(Dispatchers.IO) {
+ try {
+ backend = determineBackend()
+ futureBackend.complete(backend!!)
+ } catch (e: Throwable) {
+ Log.println(Log.ERROR, TAG, Log.getStackTraceString(e))
+ }
+ }
sharedPreferences.registerOnSharedPreferenceChangeListener(this)
}
@@ -99,45 +130,7 @@ class Application : android.app.Application(), OnSharedPreferenceChangeListener
}
@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
+ suspend fun getBackend() = get().futureBackend.await()
@JvmStatic
fun getModuleLoader() = get().moduleLoader
diff --git a/ui/src/main/java/com/wireguard/android/BootShutdownReceiver.kt b/ui/src/main/java/com/wireguard/android/BootShutdownReceiver.kt
index 41aff76d..70899a0c 100644
--- a/ui/src/main/java/com/wireguard/android/BootShutdownReceiver.kt
+++ b/ui/src/main/java/com/wireguard/android/BootShutdownReceiver.kt
@@ -8,19 +8,20 @@ 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
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.GlobalScope
+import kotlinx.coroutines.launch
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
+ GlobalScope.launch(Dispatchers.Main.immediate) {
+ if (Application.getBackend() !is WgQuickBackend) return@launch
+ val action = intent.action ?: return@launch
val tunnelManager = Application.getTunnelManager()
if (Intent.ACTION_BOOT_COMPLETED == action) {
Log.i(TAG, "Broadcast receiver restoring state (boot)")
- tunnelManager.restoreState(false).whenComplete(ExceptionLoggers.D)
+ tunnelManager.restoreState(false)
} else if (Intent.ACTION_SHUTDOWN == action) {
Log.i(TAG, "Broadcast receiver saving state (shutdown)")
tunnelManager.saveState()
diff --git a/ui/src/main/java/com/wireguard/android/QuickTileService.kt b/ui/src/main/java/com/wireguard/android/QuickTileService.kt
index 5099668e..5989499f 100644
--- a/ui/src/main/java/com/wireguard/android/QuickTileService.kt
+++ b/ui/src/main/java/com/wireguard/android/QuickTileService.kt
@@ -21,6 +21,9 @@ import com.wireguard.android.activity.TunnelToggleActivity
import com.wireguard.android.backend.Tunnel
import com.wireguard.android.model.ObservableTunnel
import com.wireguard.android.widget.SlashDrawable
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.GlobalScope
+import kotlinx.coroutines.launch
/**
* Service that maintains the application's custom Quick Settings tile. This service is bound by the
@@ -40,7 +43,7 @@ class QuickTileService : TileService() {
var ret: IBinder? = null
try {
ret = super.onBind(intent)
- } catch (e: Exception) {
+ } catch (e: Throwable) {
Log.d(TAG, "Failed to bind to TileService", e)
}
return ret
@@ -54,11 +57,12 @@ class QuickTileService : TileService() {
tile.icon = if (tile.icon == iconOn) iconOff else iconOn
tile.updateTile()
}
- tunnel!!.setStateAsync(Tunnel.State.TOGGLE).whenComplete { _, t ->
- if (t == null) {
+ GlobalScope.launch(Dispatchers.Main.immediate) {
+ try {
+ tunnel!!.setStateAsync(Tunnel.State.TOGGLE)
updateTile()
- } else {
- val toggleIntent = Intent(this, TunnelToggleActivity::class.java)
+ } catch (_: Throwable) {
+ val toggleIntent = Intent(this@QuickTileService, TunnelToggleActivity::class.java)
toggleIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
startActivity(toggleIntent)
}
diff --git a/ui/src/main/java/com/wireguard/android/activity/BaseActivity.kt b/ui/src/main/java/com/wireguard/android/activity/BaseActivity.kt
index 14ab0bdb..e663c1f2 100644
--- a/ui/src/main/java/com/wireguard/android/activity/BaseActivity.kt
+++ b/ui/src/main/java/com/wireguard/android/activity/BaseActivity.kt
@@ -9,6 +9,9 @@ import androidx.databinding.CallbackRegistry
import androidx.databinding.CallbackRegistry.NotifierCallback
import com.wireguard.android.Application
import com.wireguard.android.model.ObservableTunnel
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.GlobalScope
+import kotlinx.coroutines.launch
/**
* Base class for activities that need to remember the currently-selected tunnel.
@@ -35,11 +38,8 @@ abstract class BaseActivity : ThemeChangeAwareActivity() {
intent != null -> intent.getStringExtra(KEY_SELECTED_TUNNEL)
else -> null
}
- if (savedTunnelName != null) {
- Application.getTunnelManager()
- .tunnels
- .thenAccept { selectedTunnel = it[savedTunnelName] }
- }
+ if (savedTunnelName != null)
+ GlobalScope.launch(Dispatchers.Main.immediate) { selectedTunnel = Application.getTunnelManager().getTunnels()[savedTunnelName] }
// The selected tunnel must be set before the superclass method recreates fragments.
super.onCreate(savedInstanceState)
@@ -51,6 +51,7 @@ abstract class BaseActivity : ThemeChangeAwareActivity() {
}
protected abstract fun onSelectedTunnelChanged(oldTunnel: ObservableTunnel?, newTunnel: ObservableTunnel?)
+
fun removeOnSelectedTunnelChangedListener(
listener: OnSelectedTunnelChangedListener) {
selectionChangeRegistry.remove(listener)
diff --git a/ui/src/main/java/com/wireguard/android/activity/LogViewerActivity.kt b/ui/src/main/java/com/wireguard/android/activity/LogViewerActivity.kt
index 87fdc236..e689f8ea 100644
--- a/ui/src/main/java/com/wireguard/android/activity/LogViewerActivity.kt
+++ b/ui/src/main/java/com/wireguard/android/activity/LogViewerActivity.kt
@@ -42,6 +42,7 @@ import com.wireguard.android.widget.EdgeToEdge.setUpScrollingContent
import com.wireguard.crypto.KeyPair
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@@ -67,7 +68,7 @@ class LogViewerActivity : AppCompatActivity() {
private var rawLogLines = StringBuffer()
private var recyclerView: RecyclerView? = null
private var saveButton: MenuItem? = null
- private val coroutineScope = CoroutineScope(Dispatchers.Default)
+ private val logStreamingScope = CoroutineScope(Dispatchers.IO)
private val year by lazy {
val yearFormatter: DateFormat = SimpleDateFormat("yyyy", Locale.US)
yearFormatter.format(Date())
@@ -114,7 +115,7 @@ class LogViewerActivity : AppCompatActivity() {
addItemDecoration(DividerItemDecoration(context, LinearLayoutManager.VERTICAL))
}
- coroutineScope.launch { streamingLog() }
+ logStreamingScope.launch { streamingLog() }
binding.shareFab.setOnClickListener {
revokeLastUri()
@@ -133,6 +134,11 @@ class LogViewerActivity : AppCompatActivity() {
}
}
+ override fun onDestroy() {
+ super.onDestroy()
+ logStreamingScope.cancel()
+ }
+
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (requestCode == SHARE_ACTIVITY_REQUEST) {
revokeLastUri()
@@ -153,27 +159,21 @@ class LogViewerActivity : AppCompatActivity() {
true
}
R.id.save_log -> {
- coroutineScope.launch { saveLog() }
+ GlobalScope.launch { saveLog() }
true
}
else -> super.onOptionsItemSelected(item)
}
}
- override fun onDestroy() {
- super.onDestroy()
- coroutineScope.cancel()
- }
-
private suspend fun saveLog() {
- val context = this
- withContext(Dispatchers.Main) {
+ withContext(Dispatchers.Main.immediate) {
saveButton?.isEnabled = false
withContext(Dispatchers.IO) {
var exception: Throwable? = null
var outputFile: DownloadsFileSaver.DownloadsFile? = null
try {
- outputFile = DownloadsFileSaver.save(context, "wireguard-log.txt", "text/plain", true)
+ outputFile = DownloadsFileSaver.save(this@LogViewerActivity, "wireguard-log.txt", "text/plain", true)
outputFile.outputStream.use {
it.write(rawLogLines.toString().toByteArray(Charsets.UTF_8))
}
@@ -181,7 +181,7 @@ class LogViewerActivity : AppCompatActivity() {
outputFile?.delete()
exception = e
}
- withContext(Dispatchers.Main) {
+ withContext(Dispatchers.Main.immediate) {
saveButton?.isEnabled = true
Snackbar.make(findViewById(android.R.id.content),
if (exception == null) getString(R.string.log_export_success, outputFile?.fileName)
@@ -212,7 +212,7 @@ class LogViewerActivity : AppCompatActivity() {
rawLogLines.append(line)
rawLogLines.append('\n')
val logLine = parseLine(line)
- withContext(Dispatchers.Main) {
+ withContext(Dispatchers.Main.immediate) {
if (logLine != null) {
recyclerView?.let {
val shouldScroll = haveScrolled && !it.canScrollVertically(1)
@@ -348,7 +348,7 @@ class LogViewerActivity : AppCompatActivity() {
return openPipeHelper(uri, "text/plain", null, log) { output, _, _, _, l ->
try {
FileOutputStream(output.fileDescriptor).write(l!!)
- } catch (_: Exception) {
+ } catch (_: Throwable) {
}
}
}
diff --git a/ui/src/main/java/com/wireguard/android/activity/SettingsActivity.kt b/ui/src/main/java/com/wireguard/android/activity/SettingsActivity.kt
index 81548fe7..3abfe07c 100644
--- a/ui/src/main/java/com/wireguard/android/activity/SettingsActivity.kt
+++ b/ui/src/main/java/com/wireguard/android/activity/SettingsActivity.kt
@@ -19,8 +19,8 @@ import com.wireguard.android.R
import com.wireguard.android.backend.WgQuickBackend
import com.wireguard.android.util.AdminKnobs
import com.wireguard.android.util.ModuleLoader
-import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.util.ArrayList
@@ -102,8 +102,8 @@ class SettingsActivity : ThemeChangeAwareActivity() {
preferenceManager.findPreference<Preference>("multiple_tunnels")
).filterNotNull()
wgQuickOnlyPrefs.forEach { it.isVisible = false }
- Application.getBackendAsync().thenAccept { backend ->
- if (backend is WgQuickBackend) {
+ GlobalScope.launch(Dispatchers.Main.immediate) {
+ if (Application.getBackend() is WgQuickBackend) {
++preferenceScreen.initialExpandedChildrenCount
wgQuickOnlyPrefs.forEach { it.isVisible = true }
} else {
@@ -121,11 +121,11 @@ class SettingsActivity : ThemeChangeAwareActivity() {
moduleInstaller?.parent?.removePreference(moduleInstaller)
} else {
kernelModuleDisabler?.parent?.removePreference(kernelModuleDisabler)
- CoroutineScope(Dispatchers.Main).launch {
+ GlobalScope.launch(Dispatchers.Main.immediate) {
try {
withContext(Dispatchers.IO) { Application.getRootShell().start() }
moduleInstaller?.isVisible = true
- } catch (_: Exception) {
+ } catch (_: Throwable) {
moduleInstaller?.parent?.removePreference(moduleInstaller)
}
}
diff --git a/ui/src/main/java/com/wireguard/android/activity/TunnelToggleActivity.kt b/ui/src/main/java/com/wireguard/android/activity/TunnelToggleActivity.kt
index 44d81c01..004b10be 100644
--- a/ui/src/main/java/com/wireguard/android/activity/TunnelToggleActivity.kt
+++ b/ui/src/main/java/com/wireguard/android/activity/TunnelToggleActivity.kt
@@ -17,27 +17,31 @@ import com.wireguard.android.QuickTileService
import com.wireguard.android.R
import com.wireguard.android.backend.Tunnel
import com.wireguard.android.util.ErrorMessages
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.GlobalScope
+import kotlinx.coroutines.launch
@RequiresApi(Build.VERSION_CODES.N)
class TunnelToggleActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val tunnel = Application.getTunnelManager().lastUsedTunnel ?: return
- tunnel.setStateAsync(Tunnel.State.TOGGLE).whenComplete { _, t ->
- TileService.requestListeningState(this, ComponentName(this, QuickTileService::class.java))
- onToggleFinished(t)
+ GlobalScope.launch(Dispatchers.Main.immediate) {
+ try {
+ tunnel.setStateAsync(Tunnel.State.TOGGLE)
+ } catch (e: Throwable) {
+ TileService.requestListeningState(this@TunnelToggleActivity, ComponentName(this@TunnelToggleActivity, QuickTileService::class.java))
+ val error = ErrorMessages[e]
+ val message = getString(R.string.toggle_error, error)
+ Log.e(TAG, message, e)
+ Toast.makeText(this@TunnelToggleActivity, message, Toast.LENGTH_LONG).show()
+ finishAffinity()
+ return@launch
+ }
+ TileService.requestListeningState(this@TunnelToggleActivity, ComponentName(this@TunnelToggleActivity, QuickTileService::class.java))
finishAffinity()
}
}
-
- private fun onToggleFinished(throwable: Throwable?) {
- if (throwable == null) return
- val error = ErrorMessages[throwable]
- val message = getString(R.string.toggle_error, error)
- Log.e(TAG, message, throwable)
- Toast.makeText(this, message, Toast.LENGTH_LONG).show()
- }
-
companion object {
private const val TAG = "WireGuard/TunnelToggleActivity"
}
diff --git a/ui/src/main/java/com/wireguard/android/databinding/BindingAdapters.kt b/ui/src/main/java/com/wireguard/android/databinding/BindingAdapters.kt
index 055c2f06..b4c27a63 100644
--- a/ui/src/main/java/com/wireguard/android/databinding/BindingAdapters.kt
+++ b/ui/src/main/java/com/wireguard/android/databinding/BindingAdapters.kt
@@ -158,7 +158,7 @@ object BindingAdapters {
return 0
return try {
Integer.parseInt(s)
- } catch (_: Exception) {
+ } catch (_: Throwable) {
0
}
}
diff --git a/ui/src/main/java/com/wireguard/android/fragment/AppListDialogFragment.kt b/ui/src/main/java/com/wireguard/android/fragment/AppListDialogFragment.kt
index 1a29c5e6..966ba7d1 100644
--- a/ui/src/main/java/com/wireguard/android/fragment/AppListDialogFragment.kt
+++ b/ui/src/main/java/com/wireguard/android/fragment/AppListDialogFragment.kt
@@ -14,7 +14,6 @@ import androidx.databinding.Observable
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.Fragment
import com.google.android.material.tabs.TabLayout
-import com.wireguard.android.Application
import com.wireguard.android.BR
import com.wireguard.android.R
import com.wireguard.android.databinding.AppListDialogFragmentBinding
@@ -22,8 +21,8 @@ import com.wireguard.android.databinding.ObservableKeyedArrayList
import com.wireguard.android.model.ApplicationData
import com.wireguard.android.util.ErrorMessages
import com.wireguard.android.util.requireTargetFragment
-import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@@ -37,7 +36,7 @@ class AppListDialogFragment : DialogFragment() {
private fun loadData() {
val activity = activity ?: return
val pm = activity.packageManager
- CoroutineScope(Dispatchers.Default).launch {
+ GlobalScope.launch(Dispatchers.Default) {
try {
val applicationData: MutableList<ApplicationData> = ArrayList()
withContext(Dispatchers.IO) {
@@ -57,12 +56,12 @@ class AppListDialogFragment : DialogFragment() {
}
}
applicationData.sortWith(compareBy(String.CASE_INSENSITIVE_ORDER) { it.name })
- withContext(Dispatchers.Main) {
+ withContext(Dispatchers.Main.immediate) {
appData.clear()
appData.addAll(applicationData)
}
- } catch (e: Exception) {
- withContext(Dispatchers.Main) {
+ } catch (e: Throwable) {
+ withContext(Dispatchers.Main.immediate) {
val error = ErrorMessages[e]
val message = activity.getString(R.string.error_fetching_apps, error)
Toast.makeText(activity, message, Toast.LENGTH_LONG).show()
diff --git a/ui/src/main/java/com/wireguard/android/fragment/BaseFragment.kt b/ui/src/main/java/com/wireguard/android/fragment/BaseFragment.kt
index 82802623..997c2221 100644
--- a/ui/src/main/java/com/wireguard/android/fragment/BaseFragment.kt
+++ b/ui/src/main/java/com/wireguard/android/fragment/BaseFragment.kt
@@ -17,13 +17,15 @@ import com.wireguard.android.Application
import com.wireguard.android.R
import com.wireguard.android.activity.BaseActivity
import com.wireguard.android.activity.BaseActivity.OnSelectedTunnelChangedListener
-import com.wireguard.android.backend.Backend
import com.wireguard.android.backend.GoBackend
import com.wireguard.android.backend.Tunnel
import com.wireguard.android.databinding.TunnelDetailFragmentBinding
import com.wireguard.android.databinding.TunnelListItemBinding
import com.wireguard.android.model.ObservableTunnel
import com.wireguard.android.util.ErrorMessages
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.GlobalScope
+import kotlinx.coroutines.launch
/**
* Base class for fragments that need to know the currently-selected tunnel. Only does anything when
@@ -70,14 +72,14 @@ abstract class BaseFragment : Fragment(), OnSelectedTunnelChangedListener {
is TunnelListItemBinding -> binding.item
else -> return
} ?: return
- Application.getBackendAsync().thenAccept { backend: Backend? ->
- if (backend is GoBackend) {
+ GlobalScope.launch(Dispatchers.Main.immediate) {
+ if (Application.getBackend() is GoBackend) {
val intent = GoBackend.VpnService.prepare(view.context)
if (intent != null) {
pendingTunnel = tunnel
pendingTunnelUp = checked
startActivityForResult(intent, REQUEST_CODE_VPN_PERMISSION)
- return@thenAccept
+ return@launch
}
}
setTunnelStateWithPermissionsResult(tunnel, checked)
@@ -85,19 +87,22 @@ abstract class BaseFragment : Fragment(), OnSelectedTunnelChangedListener {
}
private fun setTunnelStateWithPermissionsResult(tunnel: ObservableTunnel, checked: Boolean) {
- tunnel.setStateAsync(Tunnel.State.of(checked)).whenComplete { _, throwable ->
- if (throwable == null) return@whenComplete
- val error = ErrorMessages[throwable]
- val messageResId = if (checked) R.string.error_up else R.string.error_down
- val message = requireContext().getString(messageResId, error)
- val view = view
- if (view != null)
- Snackbar.make(view, message, Snackbar.LENGTH_LONG)
- .setAnchorView(view.findViewById<View>(R.id.create_fab))
- .show()
- else
- Toast.makeText(requireContext(), message, Toast.LENGTH_LONG).show()
- Log.e(TAG, message, throwable)
+ GlobalScope.launch(Dispatchers.Main.immediate) {
+ try {
+ tunnel.setStateAsync(Tunnel.State.of(checked))
+ } catch (e: Throwable) {
+ val error = ErrorMessages[e]
+ val messageResId = if (checked) R.string.error_up else R.string.error_down
+ val message = requireContext().getString(messageResId, error)
+ val view = view
+ if (view != null)
+ Snackbar.make(view, message, Snackbar.LENGTH_LONG)
+ .setAnchorView(view.findViewById<View>(R.id.create_fab))
+ .show()
+ else
+ Toast.makeText(requireContext(), message, Toast.LENGTH_LONG).show()
+ Log.e(TAG, message, e)
+ }
}
}
diff --git a/ui/src/main/java/com/wireguard/android/fragment/ConfigNamingDialogFragment.kt b/ui/src/main/java/com/wireguard/android/fragment/ConfigNamingDialogFragment.kt
index d1b01944..12406df2 100644
--- a/ui/src/main/java/com/wireguard/android/fragment/ConfigNamingDialogFragment.kt
+++ b/ui/src/main/java/com/wireguard/android/fragment/ConfigNamingDialogFragment.kt
@@ -16,6 +16,9 @@ import com.wireguard.android.R
import com.wireguard.android.databinding.ConfigNamingDialogFragmentBinding
import com.wireguard.config.BadConfigException
import com.wireguard.config.Config
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.GlobalScope
+import kotlinx.coroutines.launch
import java.io.ByteArrayInputStream
import java.io.IOException
import java.nio.charset.StandardCharsets
@@ -28,11 +31,12 @@ class ConfigNamingDialogFragment : DialogFragment() {
private fun createTunnelAndDismiss() {
binding?.let {
val name = it.tunnelNameText.text.toString()
- Application.getTunnelManager().create(name, config).whenComplete { tunnel, throwable ->
- if (tunnel != null) {
+ GlobalScope.launch(Dispatchers.Main.immediate) {
+ try {
+ Application.getTunnelManager().create(name, config)
dismiss()
- } else {
- it.tunnelNameTextLayout.error = throwable.message
+ } catch (e: Throwable) {
+ it.tunnelNameTextLayout.error = e.message
}
}
}
@@ -49,7 +53,7 @@ class ConfigNamingDialogFragment : DialogFragment() {
val configBytes = configText!!.toByteArray(StandardCharsets.UTF_8)
config = try {
Config.parse(ByteArrayInputStream(configBytes))
- } catch (e: Exception) {
+ } catch (e: Throwable) {
when (e) {
is BadConfigException, is IOException -> throw IllegalArgumentException("Invalid config passed to ${javaClass.simpleName}", e)
else -> throw e
diff --git a/ui/src/main/java/com/wireguard/android/fragment/TunnelDetailFragment.kt b/ui/src/main/java/com/wireguard/android/fragment/TunnelDetailFragment.kt
index 2b5a4ba6..e81b4e6d 100644
--- a/ui/src/main/java/com/wireguard/android/fragment/TunnelDetailFragment.kt
+++ b/ui/src/main/java/com/wireguard/android/fragment/TunnelDetailFragment.kt
@@ -18,6 +18,9 @@ import com.wireguard.android.databinding.TunnelDetailPeerBinding
import com.wireguard.android.model.ObservableTunnel
import com.wireguard.android.widget.EdgeToEdge.setUpRoot
import com.wireguard.android.widget.EdgeToEdge.setUpScrollingContent
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.GlobalScope
+import kotlinx.coroutines.launch
import java.util.Timer
import java.util.TimerTask
@@ -79,7 +82,13 @@ class TunnelDetailFragment : BaseFragment() {
override fun onSelectedTunnelChanged(oldTunnel: ObservableTunnel?, newTunnel: ObservableTunnel?) {
binding ?: return
binding!!.tunnel = newTunnel
- if (newTunnel == null) binding!!.config = null else newTunnel.configAsync.thenAccept { config -> binding!!.config = config }
+ if (newTunnel == null) binding!!.config = null else GlobalScope.launch(Dispatchers.Main.immediate) {
+ try {
+ binding!!.config = newTunnel.getConfigAsync()
+ } catch (_: Throwable) {
+ binding!!.config = null
+ }
+ }
lastState = Tunnel.State.TOGGLE
updateStats()
}
@@ -105,30 +114,31 @@ class TunnelDetailFragment : BaseFragment() {
val state = tunnel.state
if (state != Tunnel.State.UP && lastState == state) return
lastState = state
- tunnel.statisticsAsync.whenComplete { statistics, throwable ->
- if (throwable != null) {
+ GlobalScope.launch(Dispatchers.Main.immediate) {
+ try {
+ val statistics = tunnel.getStatisticsAsync()
for (i in 0 until binding!!.peersLayout.childCount) {
val peer: TunnelDetailPeerBinding = DataBindingUtil.getBinding(binding!!.peersLayout.getChildAt(i))
?: continue
- peer.transferLabel.visibility = View.GONE
- peer.transferText.visibility = View.GONE
+ val publicKey = peer.item!!.publicKey
+ val rx = statistics.peerRx(publicKey)
+ val tx = statistics.peerTx(publicKey)
+ if (rx == 0L && tx == 0L) {
+ peer.transferLabel.visibility = View.GONE
+ peer.transferText.visibility = View.GONE
+ continue
+ }
+ peer.transferText.text = requireContext().getString(R.string.transfer_rx_tx, formatBytes(rx), formatBytes(tx))
+ peer.transferLabel.visibility = View.VISIBLE
+ peer.transferText.visibility = View.VISIBLE
}
- return@whenComplete
- }
- for (i in 0 until binding!!.peersLayout.childCount) {
- val peer: TunnelDetailPeerBinding = DataBindingUtil.getBinding(binding!!.peersLayout.getChildAt(i))
- ?: continue
- val publicKey = peer.item!!.publicKey
- val rx = statistics.peerRx(publicKey)
- val tx = statistics.peerTx(publicKey)
- if (rx == 0L && tx == 0L) {
+ } catch (e: Throwable) {
+ for (i in 0 until binding!!.peersLayout.childCount) {
+ val peer: TunnelDetailPeerBinding = DataBindingUtil.getBinding(binding!!.peersLayout.getChildAt(i))
+ ?: continue
peer.transferLabel.visibility = View.GONE
peer.transferText.visibility = View.GONE
- continue
}
- peer.transferText.text = requireContext().getString(R.string.transfer_rx_tx, formatBytes(rx), formatBytes(tx))
- peer.transferLabel.visibility = View.VISIBLE
- peer.transferText.visibility = View.VISIBLE
}
}
}
diff --git a/ui/src/main/java/com/wireguard/android/fragment/TunnelEditorFragment.kt b/ui/src/main/java/com/wireguard/android/fragment/TunnelEditorFragment.kt
index cf39d052..5b556bc2 100644
--- a/ui/src/main/java/com/wireguard/android/fragment/TunnelEditorFragment.kt
+++ b/ui/src/main/java/com/wireguard/android/fragment/TunnelEditorFragment.kt
@@ -25,13 +25,16 @@ import com.wireguard.android.backend.Tunnel
import com.wireguard.android.databinding.TunnelEditorFragmentBinding
import com.wireguard.android.fragment.AppListDialogFragment.AppSelectionListener
import com.wireguard.android.model.ObservableTunnel
-import com.wireguard.android.util.BiometricAuthenticator
import com.wireguard.android.util.AdminKnobs
+import com.wireguard.android.util.BiometricAuthenticator
import com.wireguard.android.util.ErrorMessages
import com.wireguard.android.viewmodel.ConfigProxy
import com.wireguard.android.widget.EdgeToEdge.setUpRoot
import com.wireguard.android.widget.EdgeToEdge.setUpScrollingContent
import com.wireguard.config.Config
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.GlobalScope
+import kotlinx.coroutines.launch
/**
* Fragment for editing a WireGuard configuration.
@@ -130,7 +133,7 @@ class TunnelEditorFragment : BaseFragment(), AppSelectionListener {
binding ?: return false
val newConfig = try {
binding!!.config!!.resolve()
- } catch (e: Exception) {
+ } catch (e: Throwable) {
val error = ErrorMessages[e]
val tunnelName = if (tunnel == null) binding!!.name else tunnel!!.name
val message = getString(R.string.config_save_error, tunnelName, error)
@@ -138,20 +141,35 @@ class TunnelEditorFragment : BaseFragment(), AppSelectionListener {
Snackbar.make(binding!!.mainContainer, error, Snackbar.LENGTH_LONG).show()
return false
}
- when {
- tunnel == null -> {
- Log.d(TAG, "Attempting to create new tunnel " + binding!!.name)
- val manager = Application.getTunnelManager()
- manager.create(binding!!.name!!, newConfig).whenComplete(this::onTunnelCreated)
- }
- tunnel!!.name != binding!!.name -> {
- Log.d(TAG, "Attempting to rename tunnel to " + binding!!.name)
- tunnel!!.setNameAsync(binding!!.name!!).whenComplete { _, t -> onTunnelRenamed(tunnel!!, newConfig, t) }
- }
- else -> {
- Log.d(TAG, "Attempting to save config of " + tunnel!!.name)
- tunnel!!.setConfigAsync(newConfig)
- .whenComplete { _, t -> onConfigSaved(tunnel!!, t) }
+ GlobalScope.launch(Dispatchers.Main.immediate) {
+ when {
+ tunnel == null -> {
+ Log.d(TAG, "Attempting to create new tunnel " + binding!!.name)
+ val manager = Application.getTunnelManager()
+ try {
+ onTunnelCreated(manager.create(binding!!.name!!, newConfig), null)
+ } catch (e: Throwable) {
+ onTunnelCreated(null, e)
+ }
+ }
+ tunnel!!.name != binding!!.name -> {
+ Log.d(TAG, "Attempting to rename tunnel to " + binding!!.name)
+ try {
+ tunnel!!.setNameAsync(binding!!.name!!)
+ onTunnelRenamed(tunnel!!, newConfig, null)
+ } catch (e: Throwable) {
+ onTunnelRenamed(tunnel!!, newConfig, e)
+ }
+ }
+ else -> {
+ Log.d(TAG, "Attempting to save config of " + tunnel!!.name)
+ try {
+ tunnel!!.setConfigAsync(newConfig)
+ onConfigSaved(tunnel!!, null)
+ } catch (e: Throwable) {
+ onConfigSaved(tunnel!!, e)
+ }
+ }
}
}
return true
@@ -187,13 +205,18 @@ class TunnelEditorFragment : BaseFragment(), AppSelectionListener {
binding!!.config = ConfigProxy()
if (tunnel != null) {
binding!!.name = tunnel!!.name
- tunnel!!.configAsync.thenAccept(this::onConfigLoaded)
+ GlobalScope.launch(Dispatchers.Main.immediate) {
+ try {
+ onConfigLoaded(tunnel!!.getConfigAsync())
+ } catch (_: Throwable) {
+ }
+ }
} else {
binding!!.name = ""
}
}
- private fun onTunnelCreated(newTunnel: ObservableTunnel, throwable: Throwable?) {
+ private fun onTunnelCreated(newTunnel: ObservableTunnel?, throwable: Throwable?) {
val message: String
if (throwable == null) {
tunnel = newTunnel
@@ -219,7 +242,14 @@ class TunnelEditorFragment : BaseFragment(), AppSelectionListener {
Log.d(TAG, message)
// Now save the rest of configuration changes.
Log.d(TAG, "Attempting to save config of renamed tunnel " + tunnel!!.name)
- renamedTunnel.setConfigAsync(newConfig).whenComplete { _, t -> onConfigSaved(renamedTunnel, t) }
+ GlobalScope.launch(Dispatchers.Main.immediate) {
+ try {
+ renamedTunnel.setConfigAsync(newConfig)
+ onConfigSaved(renamedTunnel, null)
+ } catch (e: Throwable) {
+ onConfigSaved(renamedTunnel, e)
+ }
+ }
} else {
val error = ErrorMessages[throwable]
message = getString(R.string.tunnel_rename_error, error)
diff --git a/ui/src/main/java/com/wireguard/android/fragment/TunnelListFragment.kt b/ui/src/main/java/com/wireguard/android/fragment/TunnelListFragment.kt
index 7af5e06b..3250db65 100644
--- a/ui/src/main/java/com/wireguard/android/fragment/TunnelListFragment.kt
+++ b/ui/src/main/java/com/wireguard/android/fragment/TunnelListFragment.kt
@@ -36,7 +36,14 @@ import com.wireguard.android.widget.EdgeToEdge.setUpRoot
import com.wireguard.android.widget.EdgeToEdge.setUpScrollingContent
import com.wireguard.android.widget.MultiselectableRelativeLayout
import com.wireguard.config.Config
-import java9.util.concurrent.CompletableFuture
+import kotlinx.coroutines.Deferred
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.GlobalScope
+import kotlinx.coroutines.SupervisorJob
+import kotlinx.coroutines.async
+import kotlinx.coroutines.awaitAll
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
import java.io.BufferedReader
import java.io.ByteArrayInputStream
import java.io.InputStreamReader
@@ -61,108 +68,96 @@ class TunnelListFragment : BaseFragment() {
// Config text is valid, now create the tunnel…
newInstance(configText).show(parentFragmentManager, null)
- } catch (e: Exception) {
+ } catch (e: Throwable) {
onTunnelImportFinished(emptyList(), listOf<Throwable>(e))
}
}
private fun importTunnel(uri: Uri?) {
- val activity = activity
- if (activity == null || uri == null) {
- return
- }
- val contentResolver = activity.contentResolver
-
- val futureTunnels = ArrayList<CompletableFuture<ObservableTunnel>>()
- val throwables = ArrayList<Throwable>()
- Application.getAsyncWorker().supplyAsync {
- val columns = arrayOf(OpenableColumns.DISPLAY_NAME)
- var name = ""
- contentResolver.query(uri, columns, null, null, null)?.use { cursor ->
- if (cursor.moveToFirst() && !cursor.isNull(0)) {
- name = cursor.getString(0)
+ GlobalScope.launch(Dispatchers.Main.immediate) {
+ withContext(Dispatchers.IO) {
+ val activity = activity
+ if (activity == null || uri == null) {
+ return@withContext
}
- }
- if (name.isEmpty()) {
- name = Uri.decode(uri.lastPathSegment)
- }
- var idx = name.lastIndexOf('/')
- if (idx >= 0) {
- require(idx < name.length - 1) { resources.getString(R.string.illegal_filename_error, name) }
- name = name.substring(idx + 1)
- }
- val isZip = name.toLowerCase(Locale.ROOT).endsWith(".zip")
- if (name.toLowerCase(Locale.ROOT).endsWith(".conf")) {
- name = name.substring(0, name.length - ".conf".length)
- } else {
- require(isZip) { resources.getString(R.string.bad_extension_error) }
- }
+ val contentResolver = activity.contentResolver
+ val futureTunnels = ArrayList<Deferred<ObservableTunnel>>()
+ val throwables = ArrayList<Throwable>()
+ try {
+ val columns = arrayOf(OpenableColumns.DISPLAY_NAME)
+ var name = ""
+ contentResolver.query(uri, columns, null, null, null)?.use { cursor ->
+ if (cursor.moveToFirst() && !cursor.isNull(0)) {
+ name = cursor.getString(0)
+ }
+ }
+ if (name.isEmpty()) {
+ name = Uri.decode(uri.lastPathSegment)
+ }
+ var idx = name.lastIndexOf('/')
+ if (idx >= 0) {
+ require(idx < name.length - 1) { resources.getString(R.string.illegal_filename_error, name) }
+ name = name.substring(idx + 1)
+ }
+ val isZip = name.toLowerCase(Locale.ROOT).endsWith(".zip")
+ if (name.toLowerCase(Locale.ROOT).endsWith(".conf")) {
+ name = name.substring(0, name.length - ".conf".length)
+ } else {
+ require(isZip) { resources.getString(R.string.bad_extension_error) }
+ }
- if (isZip) {
- ZipInputStream(contentResolver.openInputStream(uri)).use { zip ->
- val reader = BufferedReader(InputStreamReader(zip, StandardCharsets.UTF_8))
- var entry: ZipEntry?
- while (true) {
- entry = zip.nextEntry ?: break
- name = entry.name
- idx = name.lastIndexOf('/')
- if (idx >= 0) {
- if (idx >= name.length - 1) {
- continue
+ if (isZip) {
+ ZipInputStream(contentResolver.openInputStream(uri)).use { zip ->
+ val reader = BufferedReader(InputStreamReader(zip, StandardCharsets.UTF_8))
+ var entry: ZipEntry?
+ while (true) {
+ entry = zip.nextEntry ?: break
+ name = entry.name
+ idx = name.lastIndexOf('/')
+ if (idx >= 0) {
+ if (idx >= name.length - 1) {
+ continue
+ }
+ name = name.substring(name.lastIndexOf('/') + 1)
+ }
+ if (name.toLowerCase(Locale.ROOT).endsWith(".conf")) {
+ name = name.substring(0, name.length - ".conf".length)
+ } else {
+ continue
+ }
+ try {
+ Config.parse(reader)
+ } catch (e: Throwable) {
+ throwables.add(e)
+ null
+ }?.let {
+ val nameCopy = name
+ futureTunnels.add(async(SupervisorJob()) { Application.getTunnelManager().create(nameCopy, it) })
+ }
}
- name = name.substring(name.lastIndexOf('/') + 1)
}
- if (name.toLowerCase(Locale.ROOT).endsWith(".conf")) {
- name = name.substring(0, name.length - ".conf".length)
+ } else {
+ futureTunnels.add(async(SupervisorJob()) { Application.getTunnelManager().create(name, Config.parse(contentResolver.openInputStream(uri)!!)) })
+ }
+
+ if (futureTunnels.isEmpty()) {
+ if (throwables.size == 1) {
+ throw throwables[0]
} else {
- continue
- }
- try {
- Config.parse(reader)
- } catch (e: Exception) {
- throwables.add(e)
- null
- }?.let {
- futureTunnels.add(Application.getTunnelManager().create(name, it).toCompletableFuture())
+ require(throwables.isNotEmpty()) { resources.getString(R.string.no_configs_error) }
}
}
- }
- } else {
- futureTunnels.add(
- Application.getTunnelManager().create(
- name,
- Config.parse(contentResolver.openInputStream(uri)!!)
- ).toCompletableFuture()
- )
- }
-
- if (futureTunnels.isEmpty()) {
- if (throwables.size == 1) {
- throw throwables[0]
- } else {
- require(throwables.isNotEmpty()) { resources.getString(R.string.no_configs_error) }
- }
- }
- CompletableFuture.allOf(*futureTunnels.toTypedArray())
- }.whenComplete { future, exception ->
- if (exception != null) {
- onTunnelImportFinished(emptyList(), listOf(exception))
- } else {
- future.whenComplete { _, _ ->
- val tunnels = mutableListOf<ObservableTunnel>()
- for (futureTunnel in futureTunnels) {
- val tunnel: ObservableTunnel? = try {
- futureTunnel.getNow(null)
- } catch (e: Exception) {
+ val tunnels = futureTunnels.mapNotNull {
+ try {
+ it.await()
+ } catch (e: Throwable) {
throwables.add(e)
null
}
-
- if (tunnel != null) {
- tunnels.add(tunnel)
- }
}
- onTunnelImportFinished(tunnels, throwables)
+ withContext(Dispatchers.Main.immediate) { onTunnelImportFinished(tunnels, throwables) }
+ } catch (e: Throwable) {
+ withContext(Dispatchers.Main.immediate) { onTunnelImportFinished(emptyList(), listOf(e)) }
}
}
}
@@ -226,7 +221,8 @@ class TunnelListFragment : BaseFragment() {
override fun onSelectedTunnelChanged(oldTunnel: ObservableTunnel?, newTunnel: ObservableTunnel?) {
binding ?: return
- Application.getTunnelManager().tunnels.thenAccept { tunnels ->
+ GlobalScope.launch(Dispatchers.Main.immediate) {
+ val tunnels = Application.getTunnelManager().getTunnels()
if (newTunnel != null) viewForTunnel(newTunnel, tunnels).setSingleSelected(true)
if (oldTunnel != null) viewForTunnel(oldTunnel, tunnels).setSingleSelected(false)
}
@@ -268,11 +264,10 @@ class TunnelListFragment : BaseFragment() {
super.onViewStateRestored(savedInstanceState)
binding ?: return
binding!!.fragment = this
- Application.getTunnelManager().tunnels.thenAccept { tunnels -> binding!!.tunnels = tunnels }
- val parent = this
+ GlobalScope.launch(Dispatchers.Main.immediate) { binding!!.tunnels = Application.getTunnelManager().getTunnels() }
binding!!.rowConfigurationHandler = object : RowConfigurationHandler<TunnelListItemBinding, ObservableTunnel> {
override fun onConfigureRow(binding: TunnelListItemBinding, item: ObservableTunnel, position: Int) {
- binding.fragment = parent
+ binding.fragment = this@TunnelListFragment
binding.root.setOnClickListener {
if (actionMode == null) {
selectedTunnel = item
@@ -321,20 +316,24 @@ class TunnelListFragment : BaseFragment() {
scaleX = 1f
scaleY = 1f
}
- Application.getTunnelManager().tunnels.thenAccept { tunnels ->
- val tunnelsToDelete = ArrayList<ObservableTunnel>()
- for (position in copyCheckedItems) tunnelsToDelete.add(tunnels[position])
- val futures = tunnelsToDelete.map { it.delete().toCompletableFuture() }.toTypedArray()
- CompletableFuture.allOf(*futures)
- .thenApply { futures.size }
- .whenComplete(this@TunnelListFragment::onTunnelDeletionFinished)
+ GlobalScope.launch(Dispatchers.Main.immediate) {
+ try {
+ val tunnels = Application.getTunnelManager().getTunnels()
+ val tunnelsToDelete = ArrayList<ObservableTunnel>()
+ for (position in copyCheckedItems) tunnelsToDelete.add(tunnels[position])
+ val futures = tunnelsToDelete.map { async(SupervisorJob()) { it.deleteAsync() } }
+ onTunnelDeletionFinished(futures.awaitAll().size, null)
+ } catch (e: Throwable) {
+ onTunnelDeletionFinished(0, e)
+ }
}
checkedItems.clear()
mode.finish()
true
}
R.id.menu_action_select_all -> {
- Application.getTunnelManager().tunnels.thenAccept { tunnels ->
+ GlobalScope.launch(Dispatchers.Main.immediate) {
+ val tunnels = Application.getTunnelManager().getTunnels()
for (i in 0 until tunnels.size) {
setItemChecked(i, true)
}
diff --git a/ui/src/main/java/com/wireguard/android/model/ObservableTunnel.kt b/ui/src/main/java/com/wireguard/android/model/ObservableTunnel.kt
index f8691cbb..d9d09b87 100644
--- a/ui/src/main/java/com/wireguard/android/model/ObservableTunnel.kt
+++ b/ui/src/main/java/com/wireguard/android/model/ObservableTunnel.kt
@@ -4,16 +4,18 @@
*/
package com.wireguard.android.model
+import android.util.Log
import androidx.databinding.BaseObservable
import androidx.databinding.Bindable
import com.wireguard.android.BR
import com.wireguard.android.backend.Statistics
import com.wireguard.android.backend.Tunnel
import com.wireguard.android.databinding.Keyed
-import com.wireguard.android.util.ExceptionLoggers
import com.wireguard.config.Config
-import java9.util.concurrent.CompletableFuture
-import java9.util.concurrent.CompletionStage
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.GlobalScope
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
/**
* Encapsulates the volatile and nonvolatile state of a WireGuard tunnel.
@@ -30,10 +32,12 @@ class ObservableTunnel internal constructor(
@Bindable
override fun getName() = name
- fun setNameAsync(name: String): CompletionStage<String> = if (name != this.name)
- manager.setTunnelName(this, name)
- else
- CompletableFuture.completedFuture(this.name)
+ suspend fun setNameAsync(name: String): String = withContext(Dispatchers.Main.immediate) {
+ if (name != this@ObservableTunnel.name)
+ manager.setTunnelName(this@ObservableTunnel, name)
+ else
+ this@ObservableTunnel.name
+ }
fun onNameChanged(name: String): String {
this.name = name
@@ -57,31 +61,42 @@ class ObservableTunnel internal constructor(
return state
}
- fun setStateAsync(state: Tunnel.State): CompletionStage<Tunnel.State> = if (state != this.state)
- manager.setTunnelState(this, state)
- else
- CompletableFuture.completedFuture(this.state)
+ suspend fun setStateAsync(state: Tunnel.State): Tunnel.State = withContext(Dispatchers.Main.immediate) {
+ if (state != this@ObservableTunnel.state)
+ manager.setTunnelState(this@ObservableTunnel, state)
+ else
+ this@ObservableTunnel.state
+ }
@get:Bindable
var config = config
get() {
if (field == null)
- manager.getTunnelConfig(this).whenComplete(ExceptionLoggers.E)
+ // Opportunistically fetch this if we don't have a cached one, and rely on data bindings to update it eventually
+ GlobalScope.launch(Dispatchers.Main.immediate) {
+ try {
+ manager.getTunnelConfig(this@ObservableTunnel)
+ } catch (e: Throwable) {
+ Log.println(Log.ERROR, TAG, Log.getStackTraceString(e))
+ }
+ }
return field
}
private set
- val configAsync: CompletionStage<Config>
- get() = if (config == null)
- manager.getTunnelConfig(this)
- else
- CompletableFuture.completedFuture(config)
+ suspend fun getConfigAsync(): Config = withContext(Dispatchers.Main.immediate) {
+ config ?: manager.getTunnelConfig(this@ObservableTunnel)
+ }
- fun setConfigAsync(config: Config): CompletionStage<Config> = if (config != this.config)
- manager.setTunnelConfig(this, config)
- else
- CompletableFuture.completedFuture(this.config)
+ suspend fun setConfigAsync(config: Config): Config = withContext(Dispatchers.Main.immediate) {
+ this@ObservableTunnel.config.let {
+ if (config != it)
+ manager.setTunnelConfig(this@ObservableTunnel, config)
+ else
+ it
+ }
+ }
fun onConfigChanged(config: Config?): Config? {
this.config = config
@@ -94,16 +109,26 @@ class ObservableTunnel internal constructor(
var statistics: Statistics? = null
get() {
if (field == null || field?.isStale != false)
- manager.getTunnelStatistics(this).whenComplete(ExceptionLoggers.E)
+ // Opportunistically fetch this if we don't have a cached one, and rely on data bindings to update it eventually
+ GlobalScope.launch(Dispatchers.Main.immediate) {
+ try {
+ manager.getTunnelStatistics(this@ObservableTunnel)
+ } catch (e: Throwable) {
+ Log.println(Log.ERROR, TAG, Log.getStackTraceString(e))
+ }
+ }
return field
}
private set
- val statisticsAsync: CompletionStage<Statistics>
- get() = if (statistics == null || statistics?.isStale != false)
- manager.getTunnelStatistics(this)
- else
- CompletableFuture.completedFuture(statistics)
+ suspend fun getStatisticsAsync(): Statistics = withContext(Dispatchers.Main.immediate) {
+ statistics.let {
+ if (it == null || it.isStale)
+ manager.getTunnelStatistics(this@ObservableTunnel)
+ else
+ it
+ }
+ }
fun onStatisticsChanged(statistics: Statistics?): Statistics? {
this.statistics = statistics
@@ -112,5 +137,10 @@ class ObservableTunnel internal constructor(
}
- fun delete(): CompletionStage<Void> = manager.delete(this)
+ suspend fun deleteAsync() = manager.delete(this)
+
+
+ companion object {
+ private const val TAG = "WireGuard/ObservableTunnel"
+ }
}
diff --git a/ui/src/main/java/com/wireguard/android/model/TunnelManager.kt b/ui/src/main/java/com/wireguard/android/model/TunnelManager.kt
index 5091ed3b..b06585e4 100644
--- a/ui/src/main/java/com/wireguard/android/model/TunnelManager.kt
+++ b/ui/src/main/java/com/wireguard/android/model/TunnelManager.kt
@@ -9,10 +9,10 @@ import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.os.Build
+import android.util.Log
import androidx.databinding.BaseObservable
import androidx.databinding.Bindable
import com.wireguard.android.Application.Companion.get
-import com.wireguard.android.Application.Companion.getAsyncWorker
import com.wireguard.android.Application.Companion.getBackend
import com.wireguard.android.Application.Companion.getSharedPreferences
import com.wireguard.android.Application.Companion.getTunnelManager
@@ -22,60 +22,64 @@ import com.wireguard.android.backend.Statistics
import com.wireguard.android.backend.Tunnel
import com.wireguard.android.configStore.ConfigStore
import com.wireguard.android.databinding.ObservableSortedKeyedArrayList
-import com.wireguard.android.util.ExceptionLoggers
import com.wireguard.config.Config
-import java9.util.concurrent.CompletableFuture
-import java9.util.concurrent.CompletionStage
-import java.util.ArrayList
+import kotlinx.coroutines.CompletableDeferred
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.GlobalScope
+import kotlinx.coroutines.SupervisorJob
+import kotlinx.coroutines.async
+import kotlinx.coroutines.awaitAll
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
/**
* Maintains and mediates changes to the set of available WireGuard tunnels,
*/
class TunnelManager(private val configStore: ConfigStore) : BaseObservable() {
- val tunnels = CompletableFuture<ObservableSortedKeyedArrayList<String, ObservableTunnel>>()
+ private val tunnels = CompletableDeferred<ObservableSortedKeyedArrayList<String, ObservableTunnel>>()
private val context: Context = get()
- private val delayedLoadRestoreTunnels = ArrayList<CompletableFuture<Void>>()
private val tunnelMap: ObservableSortedKeyedArrayList<String, ObservableTunnel> = ObservableSortedKeyedArrayList(TunnelComparator)
private var haveLoaded = false
- private fun addToList(name: String, config: Config?, state: Tunnel.State): ObservableTunnel? {
+ private fun addToList(name: String, config: Config?, state: Tunnel.State): ObservableTunnel {
val tunnel = ObservableTunnel(this, name, config, state)
tunnelMap.add(tunnel)
return tunnel
}
- fun create(name: String, config: Config?): CompletionStage<ObservableTunnel> {
+ suspend fun getTunnels(): ObservableSortedKeyedArrayList<String, ObservableTunnel> = tunnels.await()
+
+ suspend fun create(name: String, config: Config?): ObservableTunnel = withContext(Dispatchers.Main.immediate) {
if (Tunnel.isNameInvalid(name))
- return CompletableFuture.failedFuture(IllegalArgumentException(context.getString(R.string.tunnel_error_invalid_name)))
+ throw IllegalArgumentException(context.getString(R.string.tunnel_error_invalid_name))
if (tunnelMap.containsKey(name))
- return CompletableFuture.failedFuture(IllegalArgumentException(context.getString(R.string.tunnel_error_already_exists, name)))
- return getAsyncWorker().supplyAsync { configStore.create(name, config!!) }.thenApply { addToList(name, it, Tunnel.State.DOWN) }
+ throw IllegalArgumentException(context.getString(R.string.tunnel_error_already_exists, name))
+ addToList(name, withContext(Dispatchers.IO) { configStore.create(name, config!!) }, Tunnel.State.DOWN)
}
- fun delete(tunnel: ObservableTunnel): CompletionStage<Void> {
+ suspend fun delete(tunnel: ObservableTunnel) = withContext(Dispatchers.Main.immediate) {
val originalState = tunnel.state
val wasLastUsed = tunnel == lastUsedTunnel
// Make sure nothing touches the tunnel.
if (wasLastUsed)
lastUsedTunnel = null
tunnelMap.remove(tunnel)
- return getAsyncWorker().runAsync {
+ try {
if (originalState == Tunnel.State.UP)
- getBackend().setState(tunnel, Tunnel.State.DOWN, null)
+ withContext(Dispatchers.IO) { getBackend().setState(tunnel, Tunnel.State.DOWN, null) }
try {
- configStore.delete(tunnel.name)
- } catch (e: Exception) {
+ withContext(Dispatchers.IO) { configStore.delete(tunnel.name) }
+ } catch (e: Throwable) {
if (originalState == Tunnel.State.UP)
- getBackend().setState(tunnel, Tunnel.State.UP, tunnel.config)
+ withContext(Dispatchers.IO) { getBackend().setState(tunnel, Tunnel.State.UP, tunnel.config) }
throw e
}
- }.whenComplete { _, e ->
- if (e == null)
- return@whenComplete
+ } catch (e: Throwable) {
// Failure, put the tunnel back.
tunnelMap.add(tunnel)
if (wasLastUsed)
lastUsedTunnel = tunnel
+ throw e
}
}
@@ -92,14 +96,18 @@ class TunnelManager(private val configStore: ConfigStore) : BaseObservable() {
getSharedPreferences().edit().remove(KEY_LAST_USED_TUNNEL).commit()
}
- fun getTunnelConfig(tunnel: ObservableTunnel): CompletionStage<Config> = getAsyncWorker()
- .supplyAsync { configStore.load(tunnel.name) }.thenApply(tunnel::onConfigChanged)
-
+ suspend fun getTunnelConfig(tunnel: ObservableTunnel): Config = withContext(Dispatchers.Main.immediate) {
+ tunnel.onConfigChanged(withContext(Dispatchers.IO) { configStore.load(tunnel.name) })!!
+ }
fun onCreate() {
- getAsyncWorker().supplyAsync { configStore.enumerate() }
- .thenAcceptBoth(getAsyncWorker().supplyAsync { getBackend().runningTunnelNames }, this::onTunnelsLoaded)
- .whenComplete(ExceptionLoggers.E)
+ GlobalScope.launch(Dispatchers.Main.immediate) {
+ try {
+ onTunnelsLoaded(withContext(Dispatchers.IO) { configStore.enumerate() }, withContext(Dispatchers.IO) { getBackend().runningTunnelNames })
+ } catch (e: Throwable) {
+ Log.println(Log.ERROR, TAG, Log.getStackTraceString(e))
+ }
+ }
}
private fun onTunnelsLoaded(present: Iterable<String>, running: Collection<String>) {
@@ -108,42 +116,38 @@ class TunnelManager(private val configStore: ConfigStore) : BaseObservable() {
val lastUsedName = getSharedPreferences().getString(KEY_LAST_USED_TUNNEL, null)
if (lastUsedName != null)
lastUsedTunnel = tunnelMap[lastUsedName]
- var toComplete: Array<CompletableFuture<Void>>
- synchronized(delayedLoadRestoreTunnels) {
- haveLoaded = true
- toComplete = delayedLoadRestoreTunnels.toTypedArray()
- delayedLoadRestoreTunnels.clear()
- }
- restoreState(true).whenComplete { v: Void?, t: Throwable? ->
- for (f in toComplete) {
- if (t == null)
- f.complete(v)
- else
- f.completeExceptionally(t)
- }
- }
+ haveLoaded = true
+ restoreState(true)
tunnels.complete(tunnelMap)
}
- fun refreshTunnelStates() {
- getAsyncWorker().supplyAsync { getBackend().runningTunnelNames }
- .thenAccept { running: Set<String> -> for (tunnel in tunnelMap) tunnel.onStateChanged(if (running.contains(tunnel.name)) Tunnel.State.UP else Tunnel.State.DOWN) }
- .whenComplete(ExceptionLoggers.E)
+ private fun refreshTunnelStates() {
+ GlobalScope.launch(Dispatchers.Main.immediate) {
+ try {
+ val running = withContext(Dispatchers.IO) { getBackend().runningTunnelNames }
+ for (tunnel in tunnelMap)
+ tunnel.onStateChanged(if (running.contains(tunnel.name)) Tunnel.State.UP else Tunnel.State.DOWN)
+ } catch (e: Throwable) {
+ Log.println(Log.ERROR, TAG, Log.getStackTraceString(e))
+ }
+ }
}
- fun restoreState(force: Boolean): CompletionStage<Void> {
- if (!force && !getSharedPreferences().getBoolean(KEY_RESTORE_ON_BOOT, false))
- return CompletableFuture.completedFuture(null)
- synchronized(delayedLoadRestoreTunnels) {
- if (!haveLoaded) {
- val f = CompletableFuture<Void>()
- delayedLoadRestoreTunnels.add(f)
- return f
+ fun restoreState(force: Boolean) {
+ if (!haveLoaded || (!force && !getSharedPreferences().getBoolean(KEY_RESTORE_ON_BOOT, false)))
+ return
+ val previouslyRunning = getSharedPreferences().getStringSet(KEY_RUNNING_TUNNELS, null)
+ ?: return
+ if (previouslyRunning.isEmpty()) return
+ GlobalScope.launch(Dispatchers.Main.immediate) {
+ withContext(Dispatchers.IO) {
+ try {
+ tunnelMap.filter { previouslyRunning.contains(it.name) }.map { async(SupervisorJob()) { setTunnelState(it, Tunnel.State.UP) } }.awaitAll()
+ } catch (e: Throwable) {
+ Log.println(Log.ERROR, TAG, Log.getStackTraceString(e))
+ }
}
}
- val previouslyRunning = getSharedPreferences().getStringSet(KEY_RUNNING_TUNNELS, null)
- ?: return CompletableFuture.completedFuture(null)
- return CompletableFuture.allOf(*tunnelMap.filter { previouslyRunning.contains(it.name) }.map { setTunnelState(it, Tunnel.State.UP).toCompletableFuture() }.toTypedArray())
}
@SuppressLint("ApplySharedPref")
@@ -151,16 +155,18 @@ class TunnelManager(private val configStore: ConfigStore) : BaseObservable() {
getSharedPreferences().edit().putStringSet(KEY_RUNNING_TUNNELS, tunnelMap.filter { it.state == Tunnel.State.UP }.map { it.name }.toSet()).commit()
}
- fun setTunnelConfig(tunnel: ObservableTunnel, config: Config): CompletionStage<Config> = getAsyncWorker().supplyAsync {
- getBackend().setState(tunnel, tunnel.state, config)
- configStore.save(tunnel.name, config)
- }.thenApply { tunnel.onConfigChanged(it) }
+ suspend fun setTunnelConfig(tunnel: ObservableTunnel, config: Config): Config = withContext(Dispatchers.Main.immediate) {
+ tunnel.onConfigChanged(withContext(Dispatchers.IO) {
+ getBackend().setState(tunnel, tunnel.state, config)
+ configStore.save(tunnel.name, config)
+ })!!
+ }
- fun setTunnelName(tunnel: ObservableTunnel, name: String): CompletionStage<String> {
+ suspend fun setTunnelName(tunnel: ObservableTunnel, name: String): String = withContext(Dispatchers.Main.immediate) {
if (Tunnel.isNameInvalid(name))
- return CompletableFuture.failedFuture(IllegalArgumentException(context.getString(R.string.tunnel_error_invalid_name)))
+ throw IllegalArgumentException(context.getString(R.string.tunnel_error_invalid_name))
if (tunnelMap.containsKey(name)) {
- return CompletableFuture.failedFuture(IllegalArgumentException(context.getString(R.string.tunnel_error_already_exists, name)))
+ throw IllegalArgumentException(context.getString(R.string.tunnel_error_already_exists, name))
}
val originalState = tunnel.state
val wasLastUsed = tunnel == lastUsedTunnel
@@ -168,34 +174,45 @@ class TunnelManager(private val configStore: ConfigStore) : BaseObservable() {
if (wasLastUsed)
lastUsedTunnel = null
tunnelMap.remove(tunnel)
- return getAsyncWorker().supplyAsync {
+ var throwable: Throwable? = null
+ var newName: String? = null
+ try {
if (originalState == Tunnel.State.UP)
- getBackend().setState(tunnel, Tunnel.State.DOWN, null)
- configStore.rename(tunnel.name, name)
- val newName = tunnel.onNameChanged(name)
+ withContext(Dispatchers.IO) { getBackend().setState(tunnel, Tunnel.State.DOWN, null) }
+ withContext(Dispatchers.IO) { configStore.rename(tunnel.name, name) }
+ newName = tunnel.onNameChanged(name)
if (originalState == Tunnel.State.UP)
- getBackend().setState(tunnel, Tunnel.State.UP, tunnel.config)
- newName
- }.whenComplete { _, e ->
+ withContext(Dispatchers.IO) { getBackend().setState(tunnel, Tunnel.State.UP, tunnel.config) }
+ } catch (e: Throwable) {
+ throwable = e
// On failure, we don't know what state the tunnel might be in. Fix that.
- if (e != null)
- getTunnelState(tunnel)
- // Add the tunnel back to the manager, under whatever name it thinks it has.
- tunnelMap.add(tunnel)
- if (wasLastUsed)
- lastUsedTunnel = tunnel
+ getTunnelState(tunnel)
}
+ // Add the tunnel back to the manager, under whatever name it thinks it has.
+ tunnelMap.add(tunnel)
+ if (wasLastUsed)
+ lastUsedTunnel = tunnel
+ if (throwable != null)
+ throw throwable
+ newName!!
}
- fun setTunnelState(tunnel: ObservableTunnel, state: Tunnel.State): CompletionStage<Tunnel.State> = tunnel.configAsync
- .thenCompose { getAsyncWorker().supplyAsync { getBackend().setState(tunnel, state, it) } }
- .whenComplete { newState, e ->
- // Ensure onStateChanged is always called (failure or not), and with the correct state.
- tunnel.onStateChanged(if (e == null) newState else tunnel.state)
- if (e == null && newState == Tunnel.State.UP)
- lastUsedTunnel = tunnel
- saveState()
- }
+ suspend fun setTunnelState(tunnel: ObservableTunnel, state: Tunnel.State): Tunnel.State = withContext(Dispatchers.Main.immediate) {
+ var newState = tunnel.state
+ var throwable: Throwable? = null
+ try {
+ newState = withContext(Dispatchers.IO) { getBackend().setState(tunnel, state, tunnel.getConfigAsync()) }
+ if (newState == Tunnel.State.UP)
+ lastUsedTunnel = tunnel
+ } catch (e: Throwable) {
+ throwable = e
+ }
+ tunnel.onStateChanged(newState)
+ saveState()
+ if (throwable != null)
+ throw throwable
+ newState
+ }
class IntentReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent?) {
@@ -215,20 +232,25 @@ class TunnelManager(private val configStore: ConfigStore) : BaseObservable() {
else -> return
}
val tunnelName = intent.getStringExtra("tunnel") ?: return
- manager.tunnels.thenAccept {
- val tunnel = it[tunnelName] ?: return@thenAccept
+ GlobalScope.launch(Dispatchers.Main.immediate) {
+ val tunnels = manager.getTunnels()
+ val tunnel = tunnels[tunnelName] ?: return@launch
manager.setTunnelState(tunnel, state)
}
}
}
- fun getTunnelState(tunnel: ObservableTunnel): CompletionStage<Tunnel.State> = getAsyncWorker()
- .supplyAsync { getBackend().getState(tunnel) }.thenApply(tunnel::onStateChanged)
+ suspend fun getTunnelState(tunnel: ObservableTunnel): Tunnel.State = withContext(Dispatchers.Main.immediate) {
+ tunnel.onStateChanged(withContext(Dispatchers.IO) { getBackend().getState(tunnel) })
+ }
- fun getTunnelStatistics(tunnel: ObservableTunnel): CompletionStage<Statistics> = getAsyncWorker()
- .supplyAsync { getBackend().getStatistics(tunnel) }.thenApply(tunnel::onStatisticsChanged)
+ suspend fun getTunnelStatistics(tunnel: ObservableTunnel): Statistics = withContext(Dispatchers.Main.immediate) {
+ tunnel.onStatisticsChanged(withContext(Dispatchers.IO) { getBackend().getStatistics(tunnel) })!!
+ }
companion object {
+ private const val TAG = "WireGuard/TunnelManager"
+
private const val KEY_LAST_USED_TUNNEL = "last_used_tunnel"
private const val KEY_RESTORE_ON_BOOT = "restore_on_boot"
private const val KEY_RUNNING_TUNNELS = "enabled_configs"
diff --git a/ui/src/main/java/com/wireguard/android/preference/KernelModuleDisablerPreference.kt b/ui/src/main/java/com/wireguard/android/preference/KernelModuleDisablerPreference.kt
index 1479d7b6..6c289073 100644
--- a/ui/src/main/java/com/wireguard/android/preference/KernelModuleDisablerPreference.kt
+++ b/ui/src/main/java/com/wireguard/android/preference/KernelModuleDisablerPreference.kt
@@ -8,22 +8,28 @@ import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.util.AttributeSet
+import android.util.Log
import androidx.preference.Preference
import com.wireguard.android.Application
import com.wireguard.android.R
import com.wireguard.android.activity.SettingsActivity
import com.wireguard.android.backend.Tunnel
import com.wireguard.android.backend.WgQuickBackend
-import java9.util.concurrent.CompletableFuture
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.GlobalScope
+import kotlinx.coroutines.SupervisorJob
+import kotlinx.coroutines.async
+import kotlinx.coroutines.awaitAll
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
import kotlin.system.exitProcess
class KernelModuleDisablerPreference(context: Context, attrs: AttributeSet?) : Preference(context, attrs) {
private var state = State.UNKNOWN
-
init {
isVisible = false
- Application.getBackendAsync().thenAccept { backend ->
- setState(if (backend is WgQuickBackend) State.ENABLED else State.DISABLED)
+ GlobalScope.launch(Dispatchers.Main.immediate) {
+ setState(if (Application.getBackend() is WgQuickBackend) State.ENABLED else State.DISABLED)
}
}
@@ -40,17 +46,21 @@ class KernelModuleDisablerPreference(context: Context, attrs: AttributeSet?) : P
setState(State.DISABLING)
Application.getSharedPreferences().edit().putBoolean("disable_kernel_module", true).commit()
}
- Application.getAsyncWorker().runAsync {
- Application.getTunnelManager().tunnels.thenApply { observableTunnels ->
- val downings = observableTunnels.map { it.setStateAsync(Tunnel.State.DOWN).toCompletableFuture() }.toTypedArray()
- CompletableFuture.allOf(*downings).thenRun {
+ GlobalScope.launch(Dispatchers.Main.immediate) {
+ val observableTunnels = Application.getTunnelManager().getTunnels()
+ val downings = observableTunnels.map { async(SupervisorJob()) { it.setStateAsync(Tunnel.State.DOWN) } }
+ try {
+ downings.awaitAll()
+ withContext(Dispatchers.IO) {
val restartIntent = Intent(context, SettingsActivity::class.java)
restartIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
restartIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
Application.get().startActivity(restartIntent)
exitProcess(0)
}
- }.join()
+ } catch (e: Throwable) {
+ Log.println(Log.ERROR, TAG, Log.getStackTraceString(e))
+ }
}
}
@@ -69,4 +79,8 @@ class KernelModuleDisablerPreference(context: Context, attrs: AttributeSet?) : P
ENABLING(R.string.module_disabler_disabled_title, R.string.success_application_will_restart, false, true),
DISABLING(R.string.module_disabler_enabled_title, R.string.success_application_will_restart, false, true);
}
+
+ companion object {
+ private const val TAG = "WireGuard/KernelModuleDisablerPreference"
+ }
}
diff --git a/ui/src/main/java/com/wireguard/android/preference/ModuleDownloaderPreference.kt b/ui/src/main/java/com/wireguard/android/preference/ModuleDownloaderPreference.kt
index adf0dc27..6960733c 100644
--- a/ui/src/main/java/com/wireguard/android/preference/ModuleDownloaderPreference.kt
+++ b/ui/src/main/java/com/wireguard/android/preference/ModuleDownloaderPreference.kt
@@ -15,16 +15,14 @@ import com.wireguard.android.Application
import com.wireguard.android.R
import com.wireguard.android.activity.SettingsActivity
import com.wireguard.android.util.ErrorMessages
-import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlin.system.exitProcess
class ModuleDownloaderPreference(context: Context, attrs: AttributeSet?) : Preference(context, attrs) {
private var state = State.INITIAL
- private val coroutineScope = CoroutineScope(Dispatchers.Main)
-
override fun getSummary() = context.getString(state.messageResourceId)
override fun getTitle() = context.getString(R.string.module_installer_title)
@@ -32,24 +30,26 @@ class ModuleDownloaderPreference(context: Context, attrs: AttributeSet?) : Prefe
@SuppressLint("ApplySharedPref")
override fun onClick() {
setState(State.WORKING)
- coroutineScope.launch {
+ GlobalScope.launch(Dispatchers.Main.immediate) {
try {
when (withContext(Dispatchers.IO) { Application.getModuleLoader().download() }) {
OsConstants.ENOENT -> setState(State.NOTFOUND)
OsConstants.EXIT_SUCCESS -> {
setState(State.SUCCESS)
Application.getSharedPreferences().edit().remove("disable_kernel_module").commit()
- CoroutineScope(Dispatchers.Default).launch {
- val restartIntent = Intent(context, SettingsActivity::class.java)
- restartIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
- restartIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
- Application.get().startActivity(restartIntent)
- exitProcess(0)
+ GlobalScope.launch(Dispatchers.Main.immediate) {
+ withContext(Dispatchers.IO) {
+ val restartIntent = Intent(context, SettingsActivity::class.java)
+ restartIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
+ restartIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ Application.get().startActivity(restartIntent)
+ exitProcess(0)
+ }
}
}
else -> setState(State.FAILURE)
}
- } catch (e: Exception) {
+ } catch (e: Throwable) {
setState(State.FAILURE)
Toast.makeText(context, ErrorMessages[e], Toast.LENGTH_LONG).show()
}
diff --git a/ui/src/main/java/com/wireguard/android/preference/ToolsInstallerPreference.kt b/ui/src/main/java/com/wireguard/android/preference/ToolsInstallerPreference.kt
index e9c0dc36..f9edb6e1 100644
--- a/ui/src/main/java/com/wireguard/android/preference/ToolsInstallerPreference.kt
+++ b/ui/src/main/java/com/wireguard/android/preference/ToolsInstallerPreference.kt
@@ -10,8 +10,8 @@ import androidx.preference.Preference
import com.wireguard.android.Application
import com.wireguard.android.R
import com.wireguard.android.util.ToolsInstaller
-import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@@ -21,15 +21,13 @@ import kotlinx.coroutines.withContext
*/
class ToolsInstallerPreference(context: Context, attrs: AttributeSet?) : Preference(context, attrs) {
private var state = State.INITIAL
- private val coroutineScope = CoroutineScope(Dispatchers.Main)
-
override fun getSummary() = context.getString(state.messageResourceId)
override fun getTitle() = context.getString(R.string.tools_installer_title)
override fun onAttached() {
super.onAttached()
- coroutineScope.launch {
+ GlobalScope.launch(Dispatchers.Main.immediate) {
try {
val state = withContext(Dispatchers.IO) { Application.getToolsInstaller().areInstalled() }
when {
@@ -39,7 +37,7 @@ class ToolsInstallerPreference(context: Context, attrs: AttributeSet?) : Prefere
state and (ToolsInstaller.SYSTEM or ToolsInstaller.NO) == ToolsInstaller.SYSTEM or ToolsInstaller.NO -> setState(State.INITIAL_SYSTEM)
else -> setState(State.INITIAL)
}
- } catch (_: Exception) {
+ } catch (_: Throwable) {
setState(State.INITIAL)
}
}
@@ -47,7 +45,7 @@ class ToolsInstallerPreference(context: Context, attrs: AttributeSet?) : Prefere
override fun onClick() {
setState(State.WORKING)
- coroutineScope.launch {
+ GlobalScope.launch(Dispatchers.Main.immediate) {
try {
val result = withContext(Dispatchers.IO) { Application.getToolsInstaller().install() }
when {
@@ -55,7 +53,7 @@ class ToolsInstallerPreference(context: Context, attrs: AttributeSet?) : Prefere
result and (ToolsInstaller.YES or ToolsInstaller.SYSTEM) == ToolsInstaller.YES or ToolsInstaller.SYSTEM -> setState(State.SUCCESS_SYSTEM)
else -> setState(State.FAILURE)
}
- } catch (_: Exception) {
+ } catch (_: Throwable) {
setState(State.FAILURE)
}
}
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 f944233b..e3eb0f95 100644
--- a/ui/src/main/java/com/wireguard/android/preference/VersionPreference.kt
+++ b/ui/src/main/java/com/wireguard/android/preference/VersionPreference.kt
@@ -16,8 +16,8 @@ import com.wireguard.android.R
import com.wireguard.android.backend.Backend
import com.wireguard.android.backend.GoBackend
import com.wireguard.android.backend.WgQuickBackend
-import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.util.Locale
@@ -47,16 +47,16 @@ class VersionPreference(context: Context, attrs: AttributeSet?) : Preference(con
}
init {
- Application.getBackendAsync().thenAccept { backend ->
+ GlobalScope.launch(Dispatchers.Main.immediate) {
+ val backend = Application.getBackend()
versionSummary = getContext().getString(R.string.version_summary_checking, getBackendPrettyName(context, backend).toLowerCase(Locale.ENGLISH))
- CoroutineScope(Dispatchers.Main).launch {
- versionSummary = try {
- getContext().getString(R.string.version_summary, getBackendPrettyName(context, backend), withContext(Dispatchers.IO) { backend.version })
- } catch (_: Exception) {
- getContext().getString(R.string.version_summary_unknown, getBackendPrettyName(context, backend).toLowerCase(Locale.ENGLISH))
- }
- notifyChanged()
+ notifyChanged()
+ versionSummary = try {
+ getContext().getString(R.string.version_summary, getBackendPrettyName(context, backend), withContext(Dispatchers.IO) { backend.version })
+ } catch (_: Throwable) {
+ getContext().getString(R.string.version_summary_unknown, getBackendPrettyName(context, backend).toLowerCase(Locale.ENGLISH))
}
+ notifyChanged()
}
}
}
diff --git a/ui/src/main/java/com/wireguard/android/preference/ZipExporterPreference.kt b/ui/src/main/java/com/wireguard/android/preference/ZipExporterPreference.kt
index fe8d39a3..c1eaa9f6 100644
--- a/ui/src/main/java/com/wireguard/android/preference/ZipExporterPreference.kt
+++ b/ui/src/main/java/com/wireguard/android/preference/ZipExporterPreference.kt
@@ -13,13 +13,18 @@ import androidx.preference.Preference
import com.google.android.material.snackbar.Snackbar
import com.wireguard.android.Application
import com.wireguard.android.R
-import com.wireguard.android.model.ObservableTunnel
+import com.wireguard.android.util.AdminKnobs
import com.wireguard.android.util.BiometricAuthenticator
import com.wireguard.android.util.DownloadsFileSaver
-import com.wireguard.android.util.AdminKnobs
import com.wireguard.android.util.ErrorMessages
import com.wireguard.android.util.FragmentUtils
-import java9.util.concurrent.CompletableFuture
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.GlobalScope
+import kotlinx.coroutines.SupervisorJob
+import kotlinx.coroutines.async
+import kotlinx.coroutines.awaitAll
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
import java.nio.charset.StandardCharsets
import java.util.zip.ZipEntry
import java.util.zip.ZipOutputStream
@@ -29,52 +34,40 @@ import java.util.zip.ZipOutputStream
*/
class ZipExporterPreference(context: Context, attrs: AttributeSet?) : Preference(context, attrs) {
private var exportedFilePath: String? = null
-
private fun exportZip() {
- Application.getTunnelManager().tunnels.thenAccept(this::exportZip)
- }
-
- private fun exportZip(tunnels: List<ObservableTunnel>) {
- val futureConfigs = tunnels.map { it.configAsync.toCompletableFuture() }.toTypedArray()
- if (futureConfigs.isEmpty()) {
- exportZipComplete(null, IllegalArgumentException(
- context.getString(R.string.no_tunnels_error)))
- return
- }
- CompletableFuture.allOf(*futureConfigs)
- .whenComplete { _, exception ->
- Application.getAsyncWorker().supplyAsync {
- if (exception != null) throw exception
- val outputFile = DownloadsFileSaver.save(context, "wireguard-export.zip", "application/zip", true)
- try {
- ZipOutputStream(outputFile.outputStream).use { zip ->
- for (i in futureConfigs.indices) {
- zip.putNextEntry(ZipEntry(tunnels[i].name + ".conf"))
- zip.write(futureConfigs[i].getNow(null)!!.toWgQuickString().toByteArray(StandardCharsets.UTF_8))
- }
- zip.closeEntry()
+ GlobalScope.launch(Dispatchers.Main.immediate) {
+ val tunnels = Application.getTunnelManager().getTunnels()
+ try {
+ exportedFilePath = withContext(Dispatchers.IO) {
+ val configs = tunnels.map { async(SupervisorJob()) { it.getConfigAsync() } }.awaitAll()
+ if (configs.isEmpty()) {
+ throw IllegalArgumentException(context.getString(R.string.no_tunnels_error))
+ }
+ val outputFile = DownloadsFileSaver.save(context, "wireguard-export.zip", "application/zip", true)
+ try {
+ ZipOutputStream(outputFile.outputStream).use { zip ->
+ for (i in configs.indices) {
+ zip.putNextEntry(ZipEntry(tunnels[i].name + ".conf"))
+ zip.write(configs[i].toWgQuickString().toByteArray(StandardCharsets.UTF_8))
}
- } catch (e: Exception) {
- outputFile.delete()
- throw e
+ zip.closeEntry()
}
- outputFile.fileName
- }.whenComplete(this::exportZipComplete)
+ } catch (e: Throwable) {
+ outputFile.delete()
+ throw e
+ }
+ outputFile.fileName
}
- }
-
- private fun exportZipComplete(filePath: String?, throwable: Throwable?) {
- if (throwable != null) {
- val error = ErrorMessages[throwable]
- val message = context.getString(R.string.zip_export_error, error)
- Log.e(TAG, message, throwable)
- Snackbar.make(
- FragmentUtils.getPrefActivity(this).findViewById(android.R.id.content),
- message, Snackbar.LENGTH_LONG).show()
- isEnabled = true
- } else {
- exportedFilePath = filePath
- notifyChanged()
+ notifyChanged()
+ } catch (e: Throwable) {
+ val error = ErrorMessages[e]
+ val message = context.getString(R.string.zip_export_error, error)
+ Log.e(TAG, message, e)
+ Snackbar.make(
+ FragmentUtils.getPrefActivity(this@ZipExporterPreference).findViewById(android.R.id.content),
+ message, Snackbar.LENGTH_LONG).show()
+ isEnabled = true
+ }
}
}
diff --git a/ui/src/main/java/com/wireguard/android/util/AsyncWorker.kt b/ui/src/main/java/com/wireguard/android/util/AsyncWorker.kt
deleted file mode 100644
index a6e5d4be..00000000
--- a/ui/src/main/java/com/wireguard/android/util/AsyncWorker.kt
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright © 2017-2020 WireGuard LLC. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-package com.wireguard.android.util
-
-import android.os.Handler
-import java9.util.concurrent.CompletableFuture
-import java9.util.concurrent.CompletionStage
-import java.util.concurrent.Executor
-
-/**
- * Helper class for running asynchronous tasks and ensuring they are completed on the main thread.
- */
-
-class AsyncWorker(private val executor: Executor, private val handler: Handler) {
-
- fun runAsync(run: () -> Unit): CompletionStage<Void> {
- val future = CompletableFuture<Void>()
- executor.execute {
- try {
- run()
- handler.post { future.complete(null) }
- } catch (t: Throwable) {
- handler.post { future.completeExceptionally(t) }
- }
- }
- return future
- }
-
- fun <T> supplyAsync(get: () -> T?): CompletionStage<T> {
- val future = CompletableFuture<T>()
- executor.execute {
- try {
- val result = get()
- handler.post { future.complete(result) }
- } catch (t: Throwable) {
- handler.post { future.completeExceptionally(t) }
- }
- }
- return future
- }
-}
diff --git a/ui/src/main/java/com/wireguard/android/util/ExceptionLoggers.kt b/ui/src/main/java/com/wireguard/android/util/ExceptionLoggers.kt
deleted file mode 100644
index 4470134c..00000000
--- a/ui/src/main/java/com/wireguard/android/util/ExceptionLoggers.kt
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-package com.wireguard.android.util
-
-import android.util.Log
-import java9.util.function.BiConsumer
-
-/**
- * Helpers for logging exceptions from asynchronous tasks. These can be passed to
- * `CompletionStage.whenComplete()` at the end of an asynchronous future chain.
- */
-enum class ExceptionLoggers(private val priority: Int) : BiConsumer<Any?, Throwable?> {
- D(Log.DEBUG), E(Log.ERROR);
-
- override fun accept(result: Any?, throwable: Throwable?) {
- if (throwable != null)
- Log.println(Log.ERROR, TAG, Log.getStackTraceString(throwable))
- else if (priority <= Log.DEBUG)
- Log.println(priority, TAG, "Future completed successfully")
- }
-
- companion object {
- private const val TAG = "WireGuard/ExceptionLoggers"
- }
-}