Skip to content

Commit 938399d

Browse files
committed
ui: queue up tunnel mutating on activity scope instead of fragment scope
Fragment scopes get cancelled when the fragment goes away, but we don't actually want to cancel an in-flight transition in that case. Also, before when the fragment would cancel, there'd be an exception, and the exception handler would call Fragment::getString, which in turn called requireContext, which caused an exception. Work around this by using the `activity ?: Application.get()` idiom to always have a context for strings and toasts. Signed-off-by: Jason A. Donenfeld <[email protected]>
1 parent 53ca421 commit 938399d

File tree

6 files changed

+80
-69
lines changed

6 files changed

+80
-69
lines changed

ui/src/main/java/com/wireguard/android/fragment/BaseFragment.kt

Lines changed: 11 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ import kotlinx.coroutines.launch
3131
* attached to a `BaseActivity`.
3232
*/
3333
abstract class BaseFragment : Fragment(), OnSelectedTunnelChangedListener {
34-
private var baseActivity: BaseActivity? = null
3534
private var pendingTunnel: ObservableTunnel? = null
3635
private var pendingTunnelUp: Boolean? = null
3736
private val permissionActivityResultLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
@@ -44,24 +43,18 @@ abstract class BaseFragment : Fragment(), OnSelectedTunnelChangedListener {
4443
}
4544

4645
protected var selectedTunnel: ObservableTunnel?
47-
get() = baseActivity?.selectedTunnel
46+
get() = (activity as? BaseActivity)?.selectedTunnel
4847
protected set(tunnel) {
49-
baseActivity?.selectedTunnel = tunnel
48+
(activity as? BaseActivity)?.selectedTunnel = tunnel
5049
}
5150

5251
override fun onAttach(context: Context) {
5352
super.onAttach(context)
54-
if (context is BaseActivity) {
55-
baseActivity = context
56-
baseActivity?.addOnSelectedTunnelChangedListener(this)
57-
} else {
58-
baseActivity = null
59-
}
53+
(activity as? BaseActivity)?.addOnSelectedTunnelChangedListener(this)
6054
}
6155

6256
override fun onDetach() {
63-
baseActivity?.removeOnSelectedTunnelChangedListener(this)
64-
baseActivity = null
57+
(activity as? BaseActivity)?.removeOnSelectedTunnelChangedListener(this)
6558
super.onDetach()
6659
}
6760

@@ -71,9 +64,10 @@ abstract class BaseFragment : Fragment(), OnSelectedTunnelChangedListener {
7164
is TunnelListItemBinding -> binding.item
7265
else -> return
7366
} ?: return
74-
lifecycleScope.launch {
67+
val activity = activity ?: return
68+
activity.lifecycleScope.launch {
7569
if (Application.getBackend() is GoBackend) {
76-
val intent = GoBackend.VpnService.prepare(view.context)
70+
val intent = GoBackend.VpnService.prepare(activity)
7771
if (intent != null) {
7872
pendingTunnel = tunnel
7973
pendingTunnelUp = checked
@@ -86,20 +80,21 @@ abstract class BaseFragment : Fragment(), OnSelectedTunnelChangedListener {
8680
}
8781

8882
private fun setTunnelStateWithPermissionsResult(tunnel: ObservableTunnel, checked: Boolean) {
89-
lifecycleScope.launch {
83+
val activity = activity ?: return
84+
activity.lifecycleScope.launch {
9085
try {
9186
tunnel.setStateAsync(Tunnel.State.of(checked))
9287
} catch (e: Throwable) {
9388
val error = ErrorMessages[e]
9489
val messageResId = if (checked) R.string.error_up else R.string.error_down
95-
val message = getString(messageResId, error)
90+
val message = activity.getString(messageResId, error)
9691
val view = view
9792
if (view != null)
9893
Snackbar.make(view, message, Snackbar.LENGTH_LONG)
9994
.setAnchorView(view.findViewById(R.id.create_fab))
10095
.show()
10196
else
102-
Toast.makeText(activity ?: Application.get(), message, Toast.LENGTH_LONG).show()
97+
Toast.makeText(activity, message, Toast.LENGTH_LONG).show()
10398
Log.e(TAG, message, e)
10499
}
105100
}

ui/src/main/java/com/wireguard/android/fragment/ConfigNamingDialogFragment.kt

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -29,15 +29,15 @@ class ConfigNamingDialogFragment : DialogFragment() {
2929
private var imm: InputMethodManager? = null
3030

3131
private fun createTunnelAndDismiss() {
32-
binding?.let {
33-
val name = it.tunnelNameText.text.toString()
34-
lifecycleScope.launch {
35-
try {
36-
Application.getTunnelManager().create(name, config)
37-
dismiss()
38-
} catch (e: Throwable) {
39-
it.tunnelNameTextLayout.error = e.message
40-
}
32+
val binding = binding ?: return
33+
val activity = activity ?: return
34+
val name = binding.tunnelNameText.text.toString()
35+
activity.lifecycleScope.launch {
36+
try {
37+
Application.getTunnelManager().create(name, config)
38+
dismiss()
39+
} catch (e: Throwable) {
40+
binding.tunnelNameTextLayout.error = e.message
4141
}
4242
}
4343
}

ui/src/main/java/com/wireguard/android/fragment/TunnelEditorFragment.kt

Lines changed: 36 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -39,24 +39,27 @@ class TunnelEditorFragment : BaseFragment() {
3939
private var haveShownKeys = false
4040
private var binding: TunnelEditorFragmentBinding? = null
4141
private var tunnel: ObservableTunnel? = null
42+
4243
private fun onConfigLoaded(config: Config) {
4344
binding?.config = ConfigProxy(config)
4445
}
4546

4647
private fun onConfigSaved(savedTunnel: Tunnel, throwable: Throwable?) {
47-
val message: String
48+
val ctx = activity ?: Application.get()
4849
if (throwable == null) {
49-
message = getString(R.string.config_save_success, savedTunnel.name)
50+
val message = ctx.getString(R.string.config_save_success, savedTunnel.name)
5051
Log.d(TAG, message)
51-
Toast.makeText(activity ?: Application.get(), message, Toast.LENGTH_SHORT).show()
52+
Toast.makeText(ctx, message, Toast.LENGTH_SHORT).show()
5253
onFinished()
5354
} else {
5455
val error = ErrorMessages[throwable]
55-
message = getString(R.string.config_save_error, savedTunnel.name, error)
56+
val message = ctx.getString(R.string.config_save_error, savedTunnel.name, error)
5657
Log.e(TAG, message, throwable)
57-
binding?.let {
58-
Snackbar.make(it.mainContainer, message, Snackbar.LENGTH_LONG).show()
59-
}
58+
val binding = binding
59+
if (binding != null)
60+
Snackbar.make(binding.mainContainer, message, Snackbar.LENGTH_LONG).show()
61+
else
62+
Toast.makeText(ctx, message, Toast.LENGTH_SHORT).show()
6063
}
6164
}
6265

@@ -115,7 +118,8 @@ class TunnelEditorFragment : BaseFragment() {
115118
Snackbar.make(binding!!.mainContainer, error, Snackbar.LENGTH_LONG).show()
116119
return false
117120
}
118-
lifecycleScope.launch {
121+
val activity = requireActivity()
122+
activity.lifecycleScope.launch {
119123
when {
120124
tunnel == null -> {
121125
Log.d(TAG, "Attempting to create new tunnel " + binding!!.name)
@@ -209,46 +213,48 @@ class TunnelEditorFragment : BaseFragment() {
209213
}
210214

211215
private fun onTunnelCreated(newTunnel: ObservableTunnel?, throwable: Throwable?) {
212-
val message: String
216+
val ctx = activity ?: Application.get()
213217
if (throwable == null) {
214218
tunnel = newTunnel
215-
message = getString(R.string.tunnel_create_success, tunnel!!.name)
219+
val message = ctx.getString(R.string.tunnel_create_success, tunnel!!.name)
216220
Log.d(TAG, message)
217-
Toast.makeText(activity ?: Application.get(), message, Toast.LENGTH_SHORT).show()
221+
Toast.makeText(ctx, message, Toast.LENGTH_SHORT).show()
218222
onFinished()
219223
} else {
220224
val error = ErrorMessages[throwable]
221-
message = getString(R.string.tunnel_create_error, error)
225+
val message = ctx.getString(R.string.tunnel_create_error, error)
222226
Log.e(TAG, message, throwable)
223-
binding?.let {
224-
Snackbar.make(it.mainContainer, message, Snackbar.LENGTH_LONG).show()
225-
}
227+
val binding = binding
228+
if (binding != null)
229+
Snackbar.make(binding.mainContainer, message, Snackbar.LENGTH_LONG).show()
230+
else
231+
Toast.makeText(ctx, message, Toast.LENGTH_SHORT).show()
226232
}
227233
}
228234

229-
private fun onTunnelRenamed(renamedTunnel: ObservableTunnel, newConfig: Config,
230-
throwable: Throwable?) {
231-
val message: String
235+
private suspend fun onTunnelRenamed(renamedTunnel: ObservableTunnel, newConfig: Config,
236+
throwable: Throwable?) {
237+
val ctx = activity ?: Application.get()
232238
if (throwable == null) {
233-
message = getString(R.string.tunnel_rename_success, renamedTunnel.name)
239+
val message = ctx.getString(R.string.tunnel_rename_success, renamedTunnel.name)
234240
Log.d(TAG, message)
235241
// Now save the rest of configuration changes.
236242
Log.d(TAG, "Attempting to save config of renamed tunnel " + tunnel!!.name)
237-
lifecycleScope.launch {
238-
try {
239-
renamedTunnel.setConfigAsync(newConfig)
240-
onConfigSaved(renamedTunnel, null)
241-
} catch (e: Throwable) {
242-
onConfigSaved(renamedTunnel, e)
243-
}
243+
try {
244+
renamedTunnel.setConfigAsync(newConfig)
245+
onConfigSaved(renamedTunnel, null)
246+
} catch (e: Throwable) {
247+
onConfigSaved(renamedTunnel, e)
244248
}
245249
} else {
246250
val error = ErrorMessages[throwable]
247-
message = getString(R.string.tunnel_rename_error, error)
251+
val message = ctx.getString(R.string.tunnel_rename_error, error)
248252
Log.e(TAG, message, throwable)
249-
binding?.let {
250-
Snackbar.make(it.mainContainer, message, Snackbar.LENGTH_LONG).show()
251-
}
253+
val binding = binding
254+
if (binding != null)
255+
Snackbar.make(binding.mainContainer, message, Snackbar.LENGTH_LONG).show()
256+
else
257+
Toast.makeText(ctx, message, Toast.LENGTH_SHORT).show()
252258
}
253259
}
254260

ui/src/main/java/com/wireguard/android/fragment/TunnelListFragment.kt

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import android.view.View
1515
import android.view.ViewGroup
1616
import android.view.animation.Animation
1717
import android.view.animation.AnimationUtils
18+
import android.widget.Toast
1819
import androidx.activity.result.contract.ActivityResultContracts
1920
import androidx.appcompat.app.AppCompatActivity
2021
import androidx.appcompat.view.ActionMode
@@ -46,17 +47,20 @@ class TunnelListFragment : BaseFragment() {
4647
private var actionMode: ActionMode? = null
4748
private var binding: TunnelListFragmentBinding? = null
4849
private val tunnelFileImportResultLauncher = registerForActivityResult(ActivityResultContracts.GetContent()) { data ->
49-
lifecycleScope.launch {
50-
if (data == null) return@launch
51-
val contentResolver = activity?.contentResolver ?: return@launch
50+
if (data == null) return@registerForActivityResult
51+
val activity = activity ?: return@registerForActivityResult
52+
val contentResolver = activity.contentResolver ?: return@registerForActivityResult
53+
activity.lifecycleScope.launch {
5254
TunnelImporter.importTunnel(contentResolver, data) { showSnackbar(it) }
5355
}
5456
}
5557

5658
private val qrImportResultLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
5759
val qrCode = IntentIntegrator.parseActivityResult(result.resultCode, result.data)?.contents
5860
?: return@registerForActivityResult
59-
lifecycleScope.launch { TunnelImporter.importTunnel(parentFragmentManager, qrCode) { showSnackbar(it) } }
61+
val activity = activity ?: return@registerForActivityResult
62+
val fragManager = parentFragmentManager
63+
activity.lifecycleScope.launch { TunnelImporter.importTunnel(fragManager, qrCode) { showSnackbar(it) } }
6064
}
6165

6266
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
@@ -121,11 +125,12 @@ class TunnelListFragment : BaseFragment() {
121125

122126
private fun onTunnelDeletionFinished(count: Int, throwable: Throwable?) {
123127
val message: String
128+
val ctx = activity ?: Application.get()
124129
if (throwable == null) {
125-
message = resources.getQuantityString(R.plurals.delete_success, count, count)
130+
message = ctx.resources.getQuantityString(R.plurals.delete_success, count, count)
126131
} else {
127132
val error = ErrorMessages[throwable]
128-
message = resources.getQuantityString(R.plurals.delete_error, count, count, error)
133+
message = ctx.resources.getQuantityString(R.plurals.delete_error, count, count, error)
129134
Log.e(TAG, message, throwable)
130135
}
131136
showSnackbar(message)
@@ -159,11 +164,13 @@ class TunnelListFragment : BaseFragment() {
159164
}
160165

161166
private fun showSnackbar(message: CharSequence) {
162-
binding?.let {
163-
Snackbar.make(it.mainContainer, message, Snackbar.LENGTH_LONG)
164-
.setAnchorView(it.createFab)
167+
val binding = binding
168+
if (binding != null)
169+
Snackbar.make(binding.mainContainer, message, Snackbar.LENGTH_LONG)
170+
.setAnchorView(binding.createFab)
165171
.show()
166-
}
172+
else
173+
Toast.makeText(activity ?: Application.get(), message, Toast.LENGTH_SHORT).show()
167174
}
168175

169176
private fun viewForTunnel(tunnel: ObservableTunnel, tunnels: List<*>): MultiselectableRelativeLayout? {
@@ -181,13 +188,14 @@ class TunnelListFragment : BaseFragment() {
181188
override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean {
182189
return when (item.itemId) {
183190
R.id.menu_action_delete -> {
191+
val activity = activity ?: return true
184192
val copyCheckedItems = HashSet(checkedItems)
185193
binding?.createFab?.apply {
186194
visibility = View.VISIBLE
187195
scaleX = 1f
188196
scaleY = 1f
189197
}
190-
lifecycleScope.launch {
198+
activity.lifecycleScope.launch {
191199
try {
192200
val tunnels = Application.getTunnelManager().getTunnels()
193201
val tunnelsToDelete = ArrayList<ObservableTunnel>()

ui/src/main/java/com/wireguard/android/preference/KernelModuleDisablerPreference.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,15 @@ import android.content.Context
88
import android.content.Intent
99
import android.util.AttributeSet
1010
import android.util.Log
11+
import androidx.lifecycle.lifecycleScope
1112
import androidx.preference.Preference
1213
import com.wireguard.android.Application
1314
import com.wireguard.android.R
1415
import com.wireguard.android.activity.SettingsActivity
1516
import com.wireguard.android.backend.Tunnel
1617
import com.wireguard.android.backend.WgQuickBackend
1718
import com.wireguard.android.util.UserKnobs
19+
import com.wireguard.android.util.activity
1820
import com.wireguard.android.util.lifecycleScope
1921
import kotlinx.coroutines.Dispatchers
2022
import kotlinx.coroutines.SupervisorJob
@@ -39,7 +41,7 @@ class KernelModuleDisablerPreference(context: Context, attrs: AttributeSet?) : P
3941
override fun getTitle() = if (state == State.UNKNOWN) "" else context.getString(state.titleResourceId)
4042

4143
override fun onClick() {
42-
lifecycleScope.launch {
44+
activity.lifecycleScope.launch {
4345
if (state == State.DISABLED) {
4446
setState(State.ENABLING)
4547
UserKnobs.setDisableKernelModule(false)

ui/src/main/java/com/wireguard/android/util/ErrorMessages.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ package com.wireguard.android.util
66

77
import android.content.res.Resources
88
import android.os.RemoteException
9-
import com.wireguard.android.Application.Companion.get
9+
import com.wireguard.android.Application
1010
import com.wireguard.android.R
1111
import com.wireguard.android.backend.BackendException
1212
import com.wireguard.android.util.RootShell.RootShellException
@@ -63,7 +63,7 @@ object ErrorMessages {
6363
)
6464

6565
operator fun get(throwable: Throwable?): String {
66-
val resources = get().resources
66+
val resources = Application.get().resources
6767
if (throwable == null) return resources.getString(R.string.unknown_error)
6868
val rootCause = rootCause(throwable)
6969
return when {

0 commit comments

Comments
 (0)