diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/EBegFragment.kt b/mobile/src/main/java/be/mygod/vpnhotspot/EBegFragment.kt index 2917a9b9..c85588dc 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/EBegFragment.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/EBegFragment.kt @@ -105,7 +105,7 @@ class EBegFragment : AppCompatDialogFragment() { billingClient.startConnection(EBegFragment) } - private fun onBillingConnected() = viewLifecycleOwner.lifecycleScope.launchWhenStarted { + private fun onBillingConnected() = viewLifecycleOwner.lifecycleScope.launch { billingClient.queryProductDetails(QueryProductDetailsParams.newBuilder().apply { setProductList(listOf( "donate001", "donate002", "donate005", "donate010", "donate020", "donate050", diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/SettingsPreferenceFragment.kt b/mobile/src/main/java/be/mygod/vpnhotspot/SettingsPreferenceFragment.kt index 4ca10bdb..c19814e6 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/SettingsPreferenceFragment.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/SettingsPreferenceFragment.kt @@ -49,7 +49,7 @@ class SettingsPreferenceFragment : PreferenceFragmentCompat() { findPreference("system.enableTetherOffload")!!.apply { isChecked = TetherOffloadManager.enabled setOnPreferenceChangeListener { _, newValue -> - if (TetherOffloadManager.enabled != newValue) viewLifecycleOwner.lifecycleScope.launchWhenCreated { + if (TetherOffloadManager.enabled != newValue) viewLifecycleOwner.lifecycleScope.launch { isEnabled = false try { TetherOffloadManager.setEnabled(newValue as Boolean) diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/client/ClientsFragment.kt b/mobile/src/main/java/be/mygod/vpnhotspot/client/ClientsFragment.kt index ab2b097a..33c99af4 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/client/ClientsFragment.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/client/ClientsFragment.kt @@ -21,6 +21,7 @@ import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels import androidx.lifecycle.findViewTreeLifecycleOwner import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.withStarted import androidx.recyclerview.widget.DefaultItemAnimator import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.ListAdapter @@ -158,14 +159,16 @@ class ClientsFragment : Fragment() { true } R.id.stats -> { - binding.client?.let { client -> - viewLifecycleOwner.lifecycleScope.launchWhenCreated { - withContext(Dispatchers.Unconfined) { - StatsDialogFragment().apply { - arg(StatsArg(client.title.value ?: return@withContext, - AppDatabase.instance.trafficRecordDao.queryStats(client.mac))) - }.showAllowingStateLoss(parentFragmentManager) - } + val client = binding.client + val title = client?.title?.value ?: return false + viewLifecycleOwner.lifecycleScope.launch { + val stats = withContext(Dispatchers.Unconfined) { + AppDatabase.instance.trafficRecordDao.queryStats(client.mac) + } + withStarted { + StatsDialogFragment().apply { + arg(StatsArg(title, stats)) + }.showAllowingStateLoss(parentFragmentManager) } } true @@ -238,14 +241,19 @@ class ClientsFragment : Fragment() { override fun onStart() { // icon might be changed due to TetherType changes if (Build.VERSION.SDK_INT >= 30) TetherType.listener[this] = { - lifecycleScope.launchWhenStarted { adapter.notifyItemRangeChanged(0, adapter.size.await()) } + lifecycleScope.launch { + val size = adapter.size.await() + withStarted { adapter.notifyItemRangeChanged(0, size) } + } } super.onStart() // we just put these two thing together as this is the only place we need to use this event for now TrafficRecorder.foregroundListeners[this] = { newRecords, oldRecords -> - lifecycleScope.launchWhenStarted { adapter.updateTraffic(newRecords, oldRecords) } + lifecycleScope.launch { + withStarted { adapter.updateTraffic(newRecords, oldRecords) } + } } - lifecycleScope.launchWhenStarted { + lifecycleScope.launch { withContext(Dispatchers.Default) { TrafficRecorder.rescheduleUpdate() // next schedule time might be 1 min, force reschedule to <= 1s } diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/manage/RepeaterManager.kt b/mobile/src/main/java/be/mygod/vpnhotspot/manage/RepeaterManager.kt index c89e5dc7..d0aabcba 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/manage/RepeaterManager.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/manage/RepeaterManager.kt @@ -22,6 +22,7 @@ import androidx.databinding.Bindable import androidx.fragment.app.viewModels import androidx.lifecycle.ViewModel import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.withStarted import androidx.recyclerview.widget.RecyclerView import be.mygod.vpnhotspot.AlertDialogFragment import be.mygod.vpnhotspot.BR @@ -156,8 +157,10 @@ class RepeaterManager(private val parent: TetheringFragment) : Manager(), Servic fun configure() { if (configuring) return configuring = true - parent.viewLifecycleOwner.lifecycleScope.launchWhenCreated { - getConfiguration()?.let { (config, readOnly) -> + val owner = parent.viewLifecycleOwner + owner.lifecycleScope.launch { + val (config, readOnly) = getConfiguration() ?: return@launch + owner.withStarted { WifiApDialogFragment().apply { arg(WifiApDialogFragment.Arg(config, readOnly, true)) key(this@RepeaterManager.javaClass.name) diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetheringFragment.kt b/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetheringFragment.kt index 9e1a2652..a7b3668f 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetheringFragment.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetheringFragment.kt @@ -1,5 +1,3 @@ -@file:Suppress("DEPRECATION") - package be.mygod.vpnhotspot.manage import android.annotation.TargetApi @@ -18,6 +16,7 @@ import androidx.appcompat.widget.Toolbar import androidx.core.content.getSystemService import androidx.fragment.app.Fragment import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.withStarted import androidx.recyclerview.widget.DefaultItemAnimator import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.ListAdapter @@ -41,6 +40,8 @@ import be.mygod.vpnhotspot.widget.SmartSnackbar import com.google.android.material.snackbar.Snackbar import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch import timber.log.Timber import java.lang.reflect.InvocationTargetException import java.net.NetworkInterface @@ -84,9 +85,13 @@ class TetheringFragment : Fragment(), ServiceConnection, Toolbar.OnMenuItemClick updateEnabledTypes() val lastList = listDeferred.await() var first = lastList.indexOfFirst { it is InterfaceManager } - if (first >= 0) notifyItemRangeChanged(first, lastList.indexOfLast { it is InterfaceManager } - first + 1) - first = lastList.indexOfLast { it !is TetherManager } + 1 - notifyItemRangeChanged(first, lastList.size - first) + withStarted { + if (first >= 0) { + notifyItemRangeChanged(first, lastList.indexOfLast { it is InterfaceManager } - first + 1) + } + first = lastList.indexOfLast { it !is TetherManager } + 1 + notifyItemRangeChanged(first, lastList.size - first) + } } fun update() { @@ -188,33 +193,34 @@ class TetheringFragment : Fragment(), ServiceConnection, Toolbar.OnMenuItemClick } R.id.configuration_ap -> if (apConfigurationRunning) false else { apConfigurationRunning = true - viewLifecycleOwner.lifecycleScope.launchWhenCreated { - try { - if (Build.VERSION.SDK_INT < 30) { + viewLifecycleOwner.lifecycleScope.launch { + val configuration = try { + if (Build.VERSION.SDK_INT < 30) @Suppress("DEPRECATION") { WifiApManager.configurationLegacy?.toCompat() ?: SoftApConfigurationCompat() } else WifiApManager.configuration.toCompat() } catch (e: InvocationTargetException) { if (e.targetException !is SecurityException) Timber.w(e) try { - if (Build.VERSION.SDK_INT < 30) { + if (Build.VERSION.SDK_INT < 30) @Suppress("DEPRECATION") { RootManager.use { it.execute(WifiApCommands.GetConfigurationLegacy()) }?.toCompat() ?: SoftApConfigurationCompat() } else RootManager.use { it.execute(WifiApCommands.GetConfiguration()) }.toCompat() } catch (_: CancellationException) { - null + return@launch } catch (eRoot: Exception) { eRoot.addSuppressed(e) if (Build.VERSION.SDK_INT >= 29 || eRoot.getRootCause() !is SecurityException) { Timber.w(eRoot) } SmartSnackbar.make(eRoot).show() - null + return@launch } } catch (e: IllegalArgumentException) { Timber.w(e) SmartSnackbar.make(e).show() - null - }?.let { configuration -> + return@launch + } + withStarted { WifiApDialogFragment().apply { arg(WifiApDialogFragment.Arg(configuration)) key() @@ -230,7 +236,7 @@ class TetheringFragment : Fragment(), ServiceConnection, Toolbar.OnMenuItemClick override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { AlertDialogFragment.setResultListener(this) { which, ret -> - if (which == DialogInterface.BUTTON_POSITIVE) viewLifecycleOwner.lifecycleScope.launchWhenCreated { + if (which == DialogInterface.BUTTON_POSITIVE) GlobalScope.launch { val configuration = ret!!.configuration @Suppress("DEPRECATION") if (Build.VERSION.SDK_INT < 30 && @@ -241,12 +247,12 @@ class TetheringFragment : Fragment(), ServiceConnection, Toolbar.OnMenuItemClick SmartSnackbar.make(e).show() } val success = try { - if (Build.VERSION.SDK_INT < 30) { + if (Build.VERSION.SDK_INT < 30) @Suppress("DEPRECATION") { WifiApManager.setConfiguration(configuration.toWifiConfiguration()) } else WifiApManager.setConfiguration(configuration.toPlatform()) } catch (e: InvocationTargetException) { try { - if (Build.VERSION.SDK_INT < 30) { + if (Build.VERSION.SDK_INT < 30) @Suppress("DEPRECATION") { val wc = configuration.toWifiConfiguration() RootManager.use { it.execute(WifiApCommands.SetConfigurationLegacy(wc)) } } else { @@ -292,11 +298,17 @@ class TetheringFragment : Fragment(), ServiceConnection, Toolbar.OnMenuItemClick override fun onServiceConnected(name: ComponentName?, service: IBinder?) { binder = service as TetheringService.Binder - service.routingsChanged[this] = { lifecycleScope.launchWhenStarted { adapter.update() } } + service.routingsChanged[this] = { + lifecycleScope.launch { + withStarted { adapter.update() } + } + } requireContext().registerReceiver(receiver, IntentFilter(TetheringManager.ACTION_TETHER_STATE_CHANGED)) if (Build.VERSION.SDK_INT >= 30) { TetheringManager.registerTetheringEventCallback(null, adapter) - TetherType.listener[this] = { lifecycleScope.launchWhenStarted { adapter.notifyTetherTypeChanged() } } + TetherType.listener[this] = { + lifecycleScope.launch { adapter.notifyTetherTypeChanged() } + } } } diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/preference/AutoCompleteNetworkPreferenceDialogFragment.kt b/mobile/src/main/java/be/mygod/vpnhotspot/preference/AutoCompleteNetworkPreferenceDialogFragment.kt index 681efe3b..33df6097 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/preference/AutoCompleteNetworkPreferenceDialogFragment.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/preference/AutoCompleteNetworkPreferenceDialogFragment.kt @@ -10,12 +10,14 @@ import android.view.ViewGroup import android.widget.ArrayAdapter import androidx.core.os.bundleOf import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.withStarted import androidx.preference.EditTextPreferenceDialogFragmentCompat import be.mygod.vpnhotspot.R import be.mygod.vpnhotspot.util.Services import be.mygod.vpnhotspot.util.allInterfaceNames import be.mygod.vpnhotspot.util.globalNetworkRequestBuilder import be.mygod.vpnhotspot.widget.AlwaysAutoCompleteEditText +import kotlinx.coroutines.launch class AutoCompleteNetworkPreferenceDialogFragment : EditTextPreferenceDialogFragmentCompat() { fun setArguments(key: String) { @@ -33,12 +35,16 @@ class AutoCompleteNetworkPreferenceDialogFragment : EditTextPreferenceDialogFrag private val callback = object : ConnectivityManager.NetworkCallback() { override fun onLinkPropertiesChanged(network: Network, properties: LinkProperties) { interfaceNames[network] = properties.allInterfaceNames - lifecycleScope.launchWhenStarted { updateAdapter() } + lifecycleScope.launch { + withStarted { updateAdapter() } + } } override fun onLost(network: Network) { interfaceNames.remove(network) - lifecycleScope.launchWhenStarted { updateAdapter() } + lifecycleScope.launch { + withStarted { updateAdapter() } + } } } diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/preference/UpstreamsPreference.kt b/mobile/src/main/java/be/mygod/vpnhotspot/preference/UpstreamsPreference.kt index e10bc194..b486674a 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/preference/UpstreamsPreference.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/preference/UpstreamsPreference.kt @@ -8,7 +8,6 @@ import android.text.style.StyleSpan import android.util.AttributeSet import androidx.lifecycle.DefaultLifecycleObserver import androidx.lifecycle.LifecycleOwner -import androidx.lifecycle.lifecycleScope import androidx.preference.Preference import be.mygod.vpnhotspot.R import be.mygod.vpnhotspot.net.monitor.FallbackUpstreamMonitor @@ -67,7 +66,7 @@ class UpstreamsPreference(context: Context, attrs: AttributeSet) : Preference(co FallbackUpstreamMonitor.unregisterCallback(fallback) } - private fun onUpdate() = (context as LifecycleOwner).lifecycleScope.launchWhenStarted { + private fun onUpdate() { summary = context.getText(R.string.settings_service_upstream_monitor_summary).format( context.resources.configuration.locales[0], primary.charSequence, fallback.charSequence) }