summaryrefslogtreecommitdiffhomepage
path: root/android
diff options
context:
space:
mode:
authorAlbin <albin@mullvad.net>2022-05-24 10:21:58 +0200
committerAlbin <albin@mullvad.net>2022-05-31 14:23:23 +0200
commit817353a54b271e6d37671f56d02156faa54c685c (patch)
tree15cea89234341c20eae7a1209db4f517c0b13b1a /android
parentecb75765ff679454cf252af552a99a3e3380b145 (diff)
downloadmullvadvpn-817353a54b271e6d37671f56d02156faa54c685c.tar.xz
mullvadvpn-817353a54b271e6d37671f56d02156faa54c685c.zip
Refactor Android service connection management
This refactor aims to be a step in decoupling the service connection from the main activity and fragment inheritance. The goal is to avoid or minimize the fragment inheritance and instead let each domain observe service connection changes and adjust accordingly.
Diffstat (limited to 'android')
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/di/UiModule.kt3
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/AdvancedFragment.kt6
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/LaunchFragment.kt6
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/LoginFragment.kt6
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/MainActivity.kt66
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/RedeemVoucherDialogFragment.kt8
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/ServiceAwareFragment.kt24
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/ServiceDependentFragment.kt24
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/SettingsFragment.kt10
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/ServiceConnectionContainer.kt (renamed from android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/ServiceConnection.kt)9
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/ServiceConnectionManager.kt94
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/ServiceConnectionState.kt10
12 files changed, 172 insertions, 94 deletions
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/di/UiModule.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/di/UiModule.kt
index 17cfd3c922..ea1ae2b79b 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/di/UiModule.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/di/UiModule.kt
@@ -6,6 +6,7 @@ import kotlinx.coroutines.Dispatchers
import net.mullvad.mullvadvpn.applist.ApplicationsIconManager
import net.mullvad.mullvadvpn.applist.ApplicationsProvider
import net.mullvad.mullvadvpn.ipc.EventDispatcher
+import net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnectionManager
import net.mullvad.mullvadvpn.ui.serviceconnection.SplitTunneling
import net.mullvad.mullvadvpn.viewmodel.SplitTunnelingViewModel
import org.koin.android.ext.koin.androidContext
@@ -30,6 +31,8 @@ val uiModule = module {
SplitTunneling(messenger, dispatcher)
}
}
+
+ single { ServiceConnectionManager(androidContext()) }
}
const val APPS_SCOPE = "APPS_SCOPE"
const val SERVICE_CONNECTION_SCOPE = "SERVICE_CONNECTION_SCOPE"
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/AdvancedFragment.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/AdvancedFragment.kt
index bfb24c4f33..275981bd83 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/AdvancedFragment.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/AdvancedFragment.kt
@@ -11,7 +11,7 @@ import net.mullvad.mullvadvpn.R
import net.mullvad.mullvadvpn.model.Settings
import net.mullvad.mullvadvpn.ui.customdns.CustomDnsAdapter
import net.mullvad.mullvadvpn.ui.fragments.SplitTunnelingFragment
-import net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnection
+import net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnectionContainer
import net.mullvad.mullvadvpn.ui.widget.CellSwitch
import net.mullvad.mullvadvpn.ui.widget.CustomRecyclerView
import net.mullvad.mullvadvpn.ui.widget.MtuCell
@@ -84,8 +84,8 @@ class AdvancedFragment : ServiceDependentFragment(OnNoService.GoBack) {
return view
}
- override fun onNewServiceConnection(serviceConnection: ServiceConnection) {
- super.onNewServiceConnection(serviceConnection)
+ override fun onNewServiceConnection(serviceConnectionContainer: ServiceConnectionContainer) {
+ super.onNewServiceConnection(serviceConnectionContainer)
subscribeToCustomDnsChanges()
}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/LaunchFragment.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/LaunchFragment.kt
index 9499d1c9f1..95d72fea9c 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/LaunchFragment.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/LaunchFragment.kt
@@ -9,7 +9,7 @@ import kotlinx.coroutines.flow.onEach
import net.mullvad.mullvadvpn.R
import net.mullvad.mullvadvpn.model.DeviceState
import net.mullvad.mullvadvpn.ui.serviceconnection.DeviceRepository
-import net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnection
+import net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnectionContainer
class LaunchFragment : ServiceAwareFragment() {
@@ -32,8 +32,8 @@ class LaunchFragment : ServiceAwareFragment() {
super.onStop()
}
- override fun onNewServiceConnection(serviceConnection: ServiceConnection) {
- advanceToNextScreen(serviceConnection.deviceRepository)
+ override fun onNewServiceConnection(serviceConnectionContainer: ServiceConnectionContainer) {
+ advanceToNextScreen(serviceConnectionContainer.deviceRepository)
}
private fun advanceToNextScreen(deviceRepository: DeviceRepository) {
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/LoginFragment.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/LoginFragment.kt
index 056bff9227..aa6c1a3ab0 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/LoginFragment.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/LoginFragment.kt
@@ -17,7 +17,7 @@ import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
import net.mullvad.mullvadvpn.R
import net.mullvad.mullvadvpn.model.AccountHistory
-import net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnection
+import net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnectionContainer
import net.mullvad.mullvadvpn.ui.widget.AccountLogin
import net.mullvad.mullvadvpn.ui.widget.HeaderBar
import net.mullvad.mullvadvpn.viewmodel.LoginViewModel
@@ -81,8 +81,8 @@ class LoginFragment :
setupLifecycleSubscriptionsToViewModel()
}
- override fun onNewServiceConnection(serviceConnection: ServiceConnection) {
- super.onNewServiceConnection(serviceConnection)
+ override fun onNewServiceConnection(serviceConnectionContainer: ServiceConnectionContainer) {
+ super.onNewServiceConnection(serviceConnectionContainer)
if (this::loginViewModel.isInitialized) {
loginViewModel.updateAccountCacheInstance(accountCache)
}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/MainActivity.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/MainActivity.kt
index db092882d9..cb39336651 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/MainActivity.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/MainActivity.kt
@@ -2,34 +2,26 @@ package net.mullvad.mullvadvpn.ui
import android.app.Activity
import android.app.UiModeManager
-import android.content.ComponentName
import android.content.Intent
import android.content.pm.ActivityInfo
import android.content.res.Configuration
import android.net.VpnService
import android.os.Bundle
-import android.os.IBinder
-import android.os.Messenger
import android.util.Log
import android.view.WindowManager
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.fragment.app.FragmentManager
-import kotlin.properties.Delegates.observable
import net.mullvad.mullvadvpn.BuildConfig
import net.mullvad.mullvadvpn.R
import net.mullvad.mullvadvpn.dataproxy.MullvadProblemReport
import net.mullvad.mullvadvpn.di.uiModule
-import net.mullvad.mullvadvpn.service.MullvadVpnService
-import net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnection
-import net.mullvad.talpid.util.EventNotifier
+import net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnectionManager
+import org.koin.android.ext.android.getKoin
import org.koin.core.context.loadKoinModules
-import org.koin.core.context.unloadKoinModules
open class MainActivity : FragmentActivity() {
-
val problemReport = MullvadProblemReport()
- val serviceNotifier = EventNotifier<ServiceConnection?>(null)
private var visibleSecureScreens = HashSet<Fragment>()
@@ -39,37 +31,13 @@ open class MainActivity : FragmentActivity() {
uiModeManager.currentModeType == Configuration.UI_MODE_TYPE_TELEVISION
}
- private var serviceConnection by observable<ServiceConnection?>(
- null
- ) { _, oldConnection, newConnection ->
- oldConnection?.onDestroy()
-
- if (newConnection == null) {
- serviceNotifier.notify(null)
- } else {
- newConnection.vpnPermission.onRequest = { ->
- Unit
- this.requestVpnPermission()
- }
- }
- }
-
- private val serviceConnectionManager = object : android.content.ServiceConnection {
- override fun onServiceConnected(className: ComponentName, binder: IBinder) {
- android.util.Log.d("mullvad", "UI successfully connected to the service")
- serviceConnection = ServiceConnection(Messenger(binder), ::handleNewServiceConnection)
- }
-
- override fun onServiceDisconnected(className: ComponentName) {
- android.util.Log.d("mullvad", "UI lost the connection to the service")
- serviceConnection = null
- }
- }
-
var backButtonHandler: (() -> Boolean)? = null
+ private lateinit var serviceConnectionManager: ServiceConnectionManager
+
override fun onCreate(savedInstanceState: Bundle?) {
loadKoinModules(uiModule)
+ serviceConnectionManager = getKoin().get()
requestedOrientation = if (deviceIsTv) {
ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE
@@ -94,15 +62,11 @@ open class MainActivity : FragmentActivity() {
override fun onStart() {
Log.d("mullvad", "Starting main activity")
super.onStart()
-
- val intent = Intent(this, MullvadVpnService::class.java)
-
- startService(intent)
- bindService(intent, serviceConnectionManager, 0)
+ serviceConnectionManager.bind(vpnPermissionRequestHandler = ::requestVpnPermission)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, resultData: Intent?) {
- serviceConnection?.vpnPermission?.grant(resultCode == Activity.RESULT_OK)
+ serviceConnectionManager.onVpnPermissionResult(resultCode == Activity.RESULT_OK)
}
override fun onBackPressed() {
@@ -114,18 +78,16 @@ open class MainActivity : FragmentActivity() {
}
override fun onStop() {
- Log.d("mullvad", "Stoping main activity")
- unbindService(serviceConnectionManager)
-
+ Log.d("mullvad", "Stopping main activity")
super.onStop()
- serviceConnection = null
+ // NOTE: `super.onStop()` must be called before unbinding due to the fragment state handling
+ // otherwise the fragments will believe there was an unexpected disconnect.
+ serviceConnectionManager.unbind()
}
override fun onDestroy() {
- serviceNotifier.unsubscribeAll()
- serviceConnection = null
-
+ serviceConnectionManager.onDestroy()
super.onDestroy()
}
@@ -174,10 +136,6 @@ open class MainActivity : FragmentActivity() {
}
}
- private fun handleNewServiceConnection(connection: ServiceConnection) {
- serviceNotifier.notify(connection)
- }
-
@Suppress("DEPRECATION")
private fun requestVpnPermission() {
val intent = VpnService.prepare(this)
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/RedeemVoucherDialogFragment.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/RedeemVoucherDialogFragment.kt
index eb64642a45..dce06a445c 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/RedeemVoucherDialogFragment.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/RedeemVoucherDialogFragment.kt
@@ -18,15 +18,19 @@ import net.mullvad.mullvadvpn.R
import net.mullvad.mullvadvpn.model.VoucherSubmissionError
import net.mullvad.mullvadvpn.model.VoucherSubmissionResult
import net.mullvad.mullvadvpn.ui.serviceconnection.AccountCache
+import net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnectionManager
import net.mullvad.mullvadvpn.ui.serviceconnection.VoucherRedeemer
import net.mullvad.mullvadvpn.ui.widget.Button
import net.mullvad.mullvadvpn.util.JobTracker
import net.mullvad.mullvadvpn.util.SegmentedInputFormatter
import org.joda.time.DateTime
+import org.koin.android.ext.android.inject
const val FULL_VOUCHER_CODE_LENGTH = "XXXX-XXXX-XXXX-XXXX".length
class RedeemVoucherDialogFragment : DialogFragment() {
+ private val serviceConnectionManager: ServiceConnectionManager by inject()
+
private val jobTracker = JobTracker()
private lateinit var parentActivity: MainActivity
@@ -49,7 +53,7 @@ class RedeemVoucherDialogFragment : DialogFragment() {
parentActivity = context as MainActivity
- parentActivity.serviceNotifier.subscribe(this) { connection ->
+ serviceConnectionManager.serviceNotifier.subscribe(this) { connection ->
accountCache = connection?.accountCache
voucherRedeemer = connection?.voucherRedeemer
}
@@ -123,7 +127,7 @@ class RedeemVoucherDialogFragment : DialogFragment() {
override fun onDetach() {
jobTracker.cancelJob("updateExpiry")
- parentActivity.serviceNotifier.unsubscribe(this)
+ serviceConnectionManager.serviceNotifier.unsubscribe(this)
super.onDetach()
}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/ServiceAwareFragment.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/ServiceAwareFragment.kt
index 5788c60ad8..32ad70daaa 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/ServiceAwareFragment.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/ServiceAwareFragment.kt
@@ -2,10 +2,14 @@ package net.mullvad.mullvadvpn.ui
import android.content.Context
import net.mullvad.mullvadvpn.ui.fragments.BaseFragment
-import net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnection
+import net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnectionContainer
+import net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnectionManager
import net.mullvad.mullvadvpn.util.JobTracker
+import org.koin.android.ext.android.inject
abstract class ServiceAwareFragment : BaseFragment() {
+ private val serviceConnectionManager: ServiceConnectionManager by inject()
+
val jobTracker = JobTracker()
open val isSecureScreen = false
@@ -13,7 +17,7 @@ abstract class ServiceAwareFragment : BaseFragment() {
lateinit var parentActivity: MainActivity
private set
- var serviceConnection: ServiceConnection? = null
+ var serviceConnectionContainer: ServiceConnectionContainer? = null
private set
override fun onAttach(context: Context) {
@@ -25,7 +29,7 @@ abstract class ServiceAwareFragment : BaseFragment() {
parentActivity.enterSecureScreen(this)
}
- parentActivity.serviceNotifier.subscribe(this) { connection ->
+ serviceConnectionManager.serviceNotifier.subscribe(this) { connection ->
configureServiceConnection(connection)
}
}
@@ -37,7 +41,7 @@ abstract class ServiceAwareFragment : BaseFragment() {
}
override fun onDetach() {
- parentActivity.serviceNotifier.unsubscribe(this)
+ serviceConnectionManager.serviceNotifier.unsubscribe(this)
if (isSecureScreen) {
parentActivity.leaveSecureScreen(this)
@@ -46,16 +50,18 @@ abstract class ServiceAwareFragment : BaseFragment() {
super.onDetach()
}
- abstract fun onNewServiceConnection(serviceConnection: ServiceConnection)
+ abstract fun onNewServiceConnection(serviceConnectionContainer: ServiceConnectionContainer)
open fun onNoServiceConnection() {
}
- private fun configureServiceConnection(connection: ServiceConnection?) {
- serviceConnection = connection
+ private fun configureServiceConnection(
+ serviceConnectionContainer: ServiceConnectionContainer?
+ ) {
+ this.serviceConnectionContainer = serviceConnectionContainer
- if (connection != null) {
- onNewServiceConnection(connection)
+ if (serviceConnectionContainer != null) {
+ onNewServiceConnection(serviceConnectionContainer)
} else {
onNoServiceConnection()
}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/ServiceDependentFragment.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/ServiceDependentFragment.kt
index f40f497857..5202960e81 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/ServiceDependentFragment.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/ServiceDependentFragment.kt
@@ -13,7 +13,7 @@ import net.mullvad.mullvadvpn.ui.serviceconnection.CustomDns
import net.mullvad.mullvadvpn.ui.serviceconnection.DeviceRepository
import net.mullvad.mullvadvpn.ui.serviceconnection.LocationInfoCache
import net.mullvad.mullvadvpn.ui.serviceconnection.RelayListListener
-import net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnection
+import net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnectionContainer
import net.mullvad.mullvadvpn.ui.serviceconnection.SettingsListener
import net.mullvad.mullvadvpn.ui.serviceconnection.SplitTunneling
@@ -63,19 +63,19 @@ abstract class ServiceDependentFragment(private val onNoService: OnNoService) :
lateinit var splitTunneling: SplitTunneling
private set
- override fun onNewServiceConnection(serviceConnection: ServiceConnection) {
+ override fun onNewServiceConnection(serviceConnectionContainer: ServiceConnectionContainer) {
// This method is always either called first or after an `onNoServiceConnection`, so the
// initialization of the fields doesn't have to be synchronized
- accountCache = serviceConnection.accountCache
- appVersionInfoCache = serviceConnection.appVersionInfoCache
- authTokenCache = serviceConnection.authTokenCache
- connectionProxy = serviceConnection.connectionProxy
- deviceRepository = serviceConnection.deviceRepository
- customDns = serviceConnection.customDns
- locationInfoCache = serviceConnection.locationInfoCache
- relayListListener = serviceConnection.relayListListener
- settingsListener = serviceConnection.settingsListener
- splitTunneling = serviceConnection.splitTunneling
+ accountCache = serviceConnectionContainer.accountCache
+ appVersionInfoCache = serviceConnectionContainer.appVersionInfoCache
+ authTokenCache = serviceConnectionContainer.authTokenCache
+ connectionProxy = serviceConnectionContainer.connectionProxy
+ deviceRepository = serviceConnectionContainer.deviceRepository
+ customDns = serviceConnectionContainer.customDns
+ locationInfoCache = serviceConnectionContainer.locationInfoCache
+ relayListListener = serviceConnectionContainer.relayListListener
+ settingsListener = serviceConnectionContainer.settingsListener
+ splitTunneling = serviceConnectionContainer.splitTunneling
synchronized(this) {
when (state) {
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/SettingsFragment.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/SettingsFragment.kt
index 08afd5c59e..cc0de9d2d7 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/SettingsFragment.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/SettingsFragment.kt
@@ -15,7 +15,7 @@ import net.mullvad.mullvadvpn.model.DeviceState
import net.mullvad.mullvadvpn.ui.serviceconnection.AccountCache
import net.mullvad.mullvadvpn.ui.serviceconnection.AppVersionInfoCache
import net.mullvad.mullvadvpn.ui.serviceconnection.DeviceRepository
-import net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnection
+import net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnectionContainer
import net.mullvad.mullvadvpn.ui.widget.AccountCell
import net.mullvad.mullvadvpn.ui.widget.AppVersionCell
import net.mullvad.mullvadvpn.ui.widget.NavigateCell
@@ -33,10 +33,10 @@ class SettingsFragment : ServiceAwareFragment(), StatusBarPainter, NavigationBar
private var deviceRepository: DeviceRepository? = null
private var versionInfoCache: AppVersionInfoCache? = null
- override fun onNewServiceConnection(serviceConnection: ServiceConnection) {
- accountCache = serviceConnection.accountCache
- deviceRepository = serviceConnection.deviceRepository
- versionInfoCache = serviceConnection.appVersionInfoCache
+ override fun onNewServiceConnection(serviceConnectionContainer: ServiceConnectionContainer) {
+ accountCache = serviceConnectionContainer.accountCache
+ deviceRepository = serviceConnectionContainer.deviceRepository
+ versionInfoCache = serviceConnectionContainer.appVersionInfoCache
if (active) {
configureListeners()
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/ServiceConnection.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/ServiceConnectionContainer.kt
index d957fc8614..d0d13ac330 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/ServiceConnection.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/ServiceConnectionContainer.kt
@@ -20,9 +20,10 @@ import org.koin.core.scope.get
// The properties of this class can be used to send events to the service, to listen for events from
// the service and to get values received from events.
@OptIn(KoinApiExtension::class)
-class ServiceConnection(
+class ServiceConnectionContainer(
connection: Messenger,
- onServiceReady: ((ServiceConnection) -> Unit)? = null
+ onServiceReady: (ServiceConnectionContainer) -> Unit,
+ onVpnPermissionRequest: () -> Unit
) : KoinScopeComponent {
private val dispatcher = DispatchingHandler(Looper.getMainLooper()) { message ->
Event.fromMessage(message)
@@ -50,8 +51,10 @@ class ServiceConnection(
var relayListListener = RelayListListener(connection, dispatcher, settingsListener)
init {
+ vpnPermission.onRequest = onVpnPermissionRequest
+
dispatcher.registerHandler(Event.ListenerReady::class) { _ ->
- onServiceReady?.invoke(this@ServiceConnection)
+ onServiceReady.invoke(this@ServiceConnectionContainer)
}
registerListener(connection)
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/ServiceConnectionManager.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/ServiceConnectionManager.kt
new file mode 100644
index 0000000000..55270863e8
--- /dev/null
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/ServiceConnectionManager.kt
@@ -0,0 +1,94 @@
+package net.mullvad.mullvadvpn.ui.serviceconnection
+
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.os.IBinder
+import android.os.Messenger
+import android.util.Log
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import net.mullvad.mullvadvpn.service.MullvadVpnService
+import net.mullvad.talpid.util.EventNotifier
+
+class ServiceConnectionManager(
+ private val context: Context
+) {
+ private val _connectionState =
+ MutableStateFlow<ServiceConnectionState>(ServiceConnectionState.Disconnected)
+
+ val connectionState = _connectionState.asStateFlow()
+
+ // TODO: Remove after refactoring fragments to support flow.
+ @Deprecated(message = "Use connectionState")
+ val serviceNotifier = EventNotifier<ServiceConnectionContainer?>(null)
+
+ private var vpnPermissionRequestHandler: (() -> Unit)? = null
+
+ private val serviceConnection = object : android.content.ServiceConnection {
+ override fun onServiceConnected(className: ComponentName, binder: IBinder) {
+ Log.d("mullvad", "UI successfully connected to the service")
+
+ notify(
+ ServiceConnectionState.ConnectedNotReady(
+ ServiceConnectionContainer(
+ Messenger(binder),
+ ::handleNewServiceConnection,
+ ::handleVpnPermissionRequest
+ )
+ )
+ )
+ }
+
+ override fun onServiceDisconnected(className: ComponentName) {
+ Log.d("mullvad", "UI lost the connection to the service")
+ notify(ServiceConnectionState.Disconnected)
+ }
+ }
+
+ fun bind(vpnPermissionRequestHandler: () -> Unit) {
+ this.vpnPermissionRequestHandler = vpnPermissionRequestHandler
+ val intent = Intent(context, MullvadVpnService::class.java)
+ context.startService(intent)
+ context.bindService(intent, serviceConnection, 0)
+ }
+
+ fun unbind() {
+ context.unbindService(serviceConnection)
+ notify(ServiceConnectionState.Disconnected)
+ vpnPermissionRequestHandler = null
+ }
+
+ fun onDestroy() {
+ serviceNotifier.unsubscribeAll()
+ notify(ServiceConnectionState.Disconnected)
+ vpnPermissionRequestHandler = null
+ }
+
+ fun onVpnPermissionResult(isGranted: Boolean) {
+ _connectionState.value.let { state ->
+ if (state is ServiceConnectionState.ConnectedReady) {
+ state.container.vpnPermission.grant(isGranted)
+ }
+ }
+ }
+
+ private fun notify(state: ServiceConnectionState) {
+ _connectionState.value = state
+
+ // TODO: Remove once `serviceNotifier` is no longer used.
+ if (state is ServiceConnectionState.ConnectedReady) {
+ serviceNotifier.notify(state.container)
+ } else if (state is ServiceConnectionState.Disconnected) {
+ serviceNotifier.notify(null)
+ }
+ }
+
+ private fun handleVpnPermissionRequest() {
+ vpnPermissionRequestHandler?.invoke()
+ }
+
+ private fun handleNewServiceConnection(serviceConnectionContainer: ServiceConnectionContainer) {
+ notify(ServiceConnectionState.ConnectedReady(serviceConnectionContainer))
+ }
+}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/ServiceConnectionState.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/ServiceConnectionState.kt
new file mode 100644
index 0000000000..8e48f3a85c
--- /dev/null
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/ServiceConnectionState.kt
@@ -0,0 +1,10 @@
+package net.mullvad.mullvadvpn.ui.serviceconnection
+
+sealed class ServiceConnectionState {
+ data class ConnectedReady(val container: ServiceConnectionContainer) : ServiceConnectionState()
+
+ data class ConnectedNotReady(val container: ServiceConnectionContainer) :
+ ServiceConnectionState()
+
+ object Disconnected : ServiceConnectionState()
+}