diff options
author | Jason A. Donenfeld <Jason@zx2c4.com> | 2020-09-14 19:46:49 +0200 |
---|---|---|
committer | Jason A. Donenfeld <Jason@zx2c4.com> | 2020-09-15 12:30:15 +0200 |
commit | bab70ab51ecc02c2e8afd1843cdd4d90ae9cc257 (patch) | |
tree | bd7117473f42dc6211d9aad4c78cbdddeb851b3e /ui/src/main/java/com/wireguard/android/fragment | |
parent | 2fc0bb1a03f624e297d2afdeb95231cf906afc21 (diff) |
coroutines: convert the rest
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
Diffstat (limited to 'ui/src/main/java/com/wireguard/android/fragment')
6 files changed, 211 insertions, 164 deletions
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) } |