summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorAlbin <albin@mullvad.net>2022-05-31 14:37:45 +0200
committerAlbin <albin@mullvad.net>2022-05-31 14:37:45 +0200
commit3bc1c244c32b324202f972fa7ed840b57129304e (patch)
tree528478e55283aec682f046b1783d683b46198d9a
parent31ec1f1449a7633f9fa977a0bae1611a4658fb59 (diff)
parente18b1b4b645d5e2de947f65261ff6fbbec5645b1 (diff)
downloadmullvadvpn-3bc1c244c32b324202f972fa7ed840b57129304e.tar.xz
mullvadvpn-3bc1c244c32b324202f972fa7ed840b57129304e.zip
Merge branch 'improve-android-service-connection-management'
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/di/UiModule.kt7
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/AccountFragment.kt30
-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/ConnectFragment.kt5
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/LaunchFragment.kt30
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/LoginFragment.kt30
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/MainActivity.kt67
-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.kt29
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/SettingsFragment.kt36
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/WelcomeFragment.kt4
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/DeviceRepository.kt35
-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)16
-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.kt14
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/LoginViewModel.kt14
-rw-r--r--android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/LoginViewModelTest.kt3
18 files changed, 265 insertions, 187 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..7e503e4a33 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,7 +6,10 @@ 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.DeviceRepository
+import net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnectionManager
import net.mullvad.mullvadvpn.ui.serviceconnection.SplitTunneling
+import net.mullvad.mullvadvpn.viewmodel.LoginViewModel
import net.mullvad.mullvadvpn.viewmodel.SplitTunnelingViewModel
import org.koin.android.ext.koin.androidContext
import org.koin.androidx.viewmodel.dsl.viewModel
@@ -30,6 +33,10 @@ val uiModule = module {
SplitTunneling(messenger, dispatcher)
}
}
+
+ single { ServiceConnectionManager(androidContext()) }
+ single { DeviceRepository(get()) }
+ viewModel { LoginViewModel() }
}
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/AccountFragment.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/AccountFragment.kt
index 526b5356f4..4b5fda7bbe 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/AccountFragment.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/AccountFragment.kt
@@ -5,12 +5,16 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.FragmentManager
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.flowWithLifecycle
+import androidx.lifecycle.lifecycleScope
import java.text.DateFormat
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.launch
import net.mullvad.mullvadvpn.R
import net.mullvad.mullvadvpn.model.TunnelState
+import net.mullvad.mullvadvpn.ui.serviceconnection.DeviceRepository
import net.mullvad.mullvadvpn.ui.widget.Button
import net.mullvad.mullvadvpn.ui.widget.CopyableInformationView
import net.mullvad.mullvadvpn.ui.widget.InformationView
@@ -19,8 +23,11 @@ import net.mullvad.mullvadvpn.ui.widget.SitePaymentButton
import net.mullvad.mullvadvpn.util.capitalizeFirstCharOfEachWord
import net.mullvad.talpid.tunnel.ErrorStateCause
import org.joda.time.DateTime
+import org.koin.android.ext.android.inject
class AccountFragment : ServiceDependentFragment(OnNoService.GoBack) {
+ private val deviceRepository: DeviceRepository by inject()
+
override val isSecureScreen = true
private val dateStyle = DateFormat.MEDIUM
@@ -97,27 +104,20 @@ class AccountFragment : ServiceDependentFragment(OnNoService.GoBack) {
return view
}
- override fun onSafelyStart() {
- jobTracker.newUiJob("updateAccountNumber") {
- deviceRepository.deviceState
- .onEach { state ->
- if (state.isInitialState()) deviceRepository.refreshDeviceState()
- }
- .collect { state ->
- accountNumberView.information = state.token()
- }
- }
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
- jobTracker.newUiJob("updateDeviceName") {
+ lifecycleScope.launch {
deviceRepository.deviceState
- .onEach { state ->
- if (state.isInitialState()) deviceRepository.refreshDeviceState()
- }
+ .flowWithLifecycle(lifecycle, Lifecycle.State.RESUMED)
.collect { state ->
+ accountNumberView.information = state.token()
deviceNameView.information = state.deviceName()?.capitalizeFirstCharOfEachWord()
}
}
+ }
+ override fun onSafelyStart() {
jobTracker.newUiJob("updateAccountExpiry") {
accountCache.accountExpiryState
.map { state -> state.date() }
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/ConnectFragment.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/ConnectFragment.kt
index efc32a303f..43d23445ce 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/ConnectFragment.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/ConnectFragment.kt
@@ -5,7 +5,6 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.content.ContextCompat
-import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.map
@@ -96,7 +95,7 @@ class ConnectFragment :
}
connectionProxy.onUiStateChange.subscribe(this) { uiState ->
- viewLifecycleOwner.lifecycleScope.launchWhenStarted {
+ jobTracker.newUiJob("updateTunnelState") {
updateTunnelState(uiState, connectionProxy.state)
}
}
@@ -114,7 +113,7 @@ class ConnectFragment :
}
override fun onSafelyStop() {
- jobTracker.cancelJob("updateAccountExpiry")
+ jobTracker.cancelAllJobs()
locationInfoCache.onNewLocation = null
relayListListener.onRelayListChange = null
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..420581785b 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
@@ -4,14 +4,19 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
-import kotlinx.coroutines.flow.first
-import kotlinx.coroutines.flow.onEach
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.flowWithLifecycle
+import androidx.lifecycle.lifecycleScope
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.launch
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
+import org.koin.android.ext.android.inject
class LaunchFragment : ServiceAwareFragment() {
+ private val deviceRepository: DeviceRepository by inject()
override fun onCreateView(
inflater: LayoutInflater,
@@ -27,23 +32,16 @@ class LaunchFragment : ServiceAwareFragment() {
return view
}
- override fun onStop() {
- jobTracker.cancelJob("advanceToNextScreen")
- super.onStop()
+ override fun onNewServiceConnection(serviceConnectionContainer: ServiceConnectionContainer) {
}
- override fun onNewServiceConnection(serviceConnection: ServiceConnection) {
- advanceToNextScreen(serviceConnection.deviceRepository)
- }
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
- private fun advanceToNextScreen(deviceRepository: DeviceRepository) {
- jobTracker.newUiJob("advanceToNextScreen") {
+ lifecycleScope.launch {
deviceRepository.deviceState
- .onEach { state ->
- if (state.isInitialState()) deviceRepository.refreshDeviceState()
- }
- .first { state -> state.isInitialState().not() }
- .let { deviceState ->
+ .flowWithLifecycle(lifecycle, Lifecycle.State.RESUMED)
+ .collect { deviceState ->
when (deviceState) {
is DeviceState.LoggedIn -> advanceToConnectScreen()
is DeviceState.LoggedOut -> advanceToLoginScreen()
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..d16cf2f0a9 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
@@ -10,23 +10,23 @@ import android.widget.TextView
import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment
import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
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
+import org.koin.androidx.viewmodel.ext.android.viewModel
class LoginFragment :
ServiceDependentFragment(OnNoService.GoToLaunchScreen),
NavigationBarPainter {
- private lateinit var loginViewModel: LoginViewModel
+ private val loginViewModel: LoginViewModel by viewModel()
private lateinit var title: TextView
private lateinit var subtitle: TextView
@@ -52,10 +52,7 @@ class LoginFragment :
loggedInStatus = view.findViewById(R.id.logged_in_status)
loginFailStatus = view.findViewById(R.id.login_fail_status)
- val factory = LoginViewModel.Factory(requireActivity().application)
- loginViewModel = ViewModelProvider(this, factory)[LoginViewModel::class.java].apply {
- updateAccountCacheInstance(accountCache)
- }
+ loginViewModel.updateAccountCacheInstance(accountCache)
accountLogin = view.findViewById<AccountLogin>(R.id.account_login).apply {
onLogin = loginViewModel::login
@@ -73,26 +70,19 @@ class LoginFragment :
scrollToShow(accountLogin)
- return view
- }
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
setupLifecycleSubscriptionsToViewModel()
+
+ return view
}
- override fun onNewServiceConnection(serviceConnection: ServiceConnection) {
- super.onNewServiceConnection(serviceConnection)
- if (this::loginViewModel.isInitialized) {
- loginViewModel.updateAccountCacheInstance(accountCache)
- }
+ override fun onNewServiceConnection(serviceConnectionContainer: ServiceConnectionContainer) {
+ super.onNewServiceConnection(serviceConnectionContainer)
+ loginViewModel.updateAccountCacheInstance(accountCache)
}
override fun onNoServiceConnection() {
super.onNoServiceConnection()
- if (this::loginViewModel.isInitialized) {
- loginViewModel.updateAccountCacheInstance(null)
- }
+ loginViewModel.updateAccountCacheInstance(null)
}
override fun onSafelyStart() {
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 16673ea65d..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,19 +78,16 @@ open class MainActivity : FragmentActivity() {
}
override fun onStop() {
- Log.d("mullvad", "Stoping main activity")
- unbindService(serviceConnectionManager)
- unloadKoinModules(uiModule)
-
+ 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()
}
@@ -175,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..2911dcfb9f 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
@@ -10,10 +10,10 @@ import net.mullvad.mullvadvpn.ui.serviceconnection.AppVersionInfoCache
import net.mullvad.mullvadvpn.ui.serviceconnection.AuthTokenCache
import net.mullvad.mullvadvpn.ui.serviceconnection.ConnectionProxy
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.ServiceConnectionDeviceDataSource
import net.mullvad.mullvadvpn.ui.serviceconnection.SettingsListener
import net.mullvad.mullvadvpn.ui.serviceconnection.SplitTunneling
@@ -48,7 +48,7 @@ abstract class ServiceDependentFragment(private val onNoService: OnNoService) :
lateinit var customDns: CustomDns
private set
- lateinit var deviceRepository: DeviceRepository
+ lateinit var deviceDataSource: ServiceConnectionDeviceDataSource
private set
lateinit var locationInfoCache: LocationInfoCache
@@ -63,19 +63,20 @@ 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
+ deviceDataSource = serviceConnectionContainer.deviceDataSource
+ 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..2cf83fc4c7 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
@@ -6,21 +6,26 @@ import android.view.View
import android.view.ViewGroup
import android.widget.ImageButton
import androidx.core.content.ContextCompat
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.flowWithLifecycle
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.launch
import net.mullvad.mullvadvpn.R
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
+import org.koin.android.ext.android.inject
class SettingsFragment : ServiceAwareFragment(), StatusBarPainter, NavigationBarPainter {
+ private val deviceRepository: DeviceRepository by inject()
+
private lateinit var accountMenu: AccountCell
private lateinit var appVersionMenu: AppVersionCell
private lateinit var preferencesMenu: View
@@ -30,13 +35,11 @@ class SettingsFragment : ServiceAwareFragment(), StatusBarPainter, NavigationBar
private var active = false
private var accountCache: AccountCache? = null
- 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
+ versionInfoCache = serviceConnectionContainer.appVersionInfoCache
if (active) {
configureListeners()
@@ -45,7 +48,6 @@ class SettingsFragment : ServiceAwareFragment(), StatusBarPainter, NavigationBar
override fun onNoServiceConnection() {
accountCache = null
- deviceRepository = null
versionInfoCache = null
}
@@ -90,6 +92,14 @@ class SettingsFragment : ServiceAwareFragment(), StatusBarPainter, NavigationBar
paintStatusBar(ContextCompat.getColor(requireContext(), R.color.darkBlue))
}
}
+
+ lifecycleScope.launch {
+ deviceRepository.deviceState
+ .flowWithLifecycle(lifecycle, Lifecycle.State.RESUMED)
+ .collect { device ->
+ updateLoggedInStatus(device is DeviceState.LoggedIn)
+ }
+ }
}
override fun onResume() {
@@ -131,16 +141,6 @@ class SettingsFragment : ServiceAwareFragment(), StatusBarPainter, NavigationBar
fetchAccountExpiry()
}
- jobTracker.newUiJob("updateLoggedInStatus") {
- deviceRepository?.let { repository ->
- repository.deviceState
- .onEach { state -> if (state.isInitialState()) repository.refreshDeviceState() }
- .collect { device ->
- updateLoggedInStatus(device is DeviceState.LoggedIn)
- }
- }
- }
-
versionInfoCache?.onUpdate = {
jobTracker.newUiJob("updateVersionInfo") {
updateVersionInfo()
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/WelcomeFragment.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/WelcomeFragment.kt
index 92364fdf0f..f83660bd06 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/WelcomeFragment.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/WelcomeFragment.kt
@@ -13,14 +13,18 @@ import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.collect
import net.mullvad.mullvadvpn.R
import net.mullvad.mullvadvpn.model.TunnelState
+import net.mullvad.mullvadvpn.ui.serviceconnection.DeviceRepository
import net.mullvad.mullvadvpn.ui.widget.HeaderBar
import net.mullvad.mullvadvpn.ui.widget.RedeemVoucherButton
import net.mullvad.mullvadvpn.ui.widget.SitePaymentButton
import org.joda.time.DateTime
+import org.koin.android.ext.android.inject
val POLL_INTERVAL: Long = 15 /* s */ * 1000 /* ms */
class WelcomeFragment : ServiceDependentFragment(OnNoService.GoToLaunchScreen) {
+ private val deviceRepository: DeviceRepository by inject()
+
private lateinit var accountLabel: TextView
private lateinit var sitePaymentButton: SitePaymentButton
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/DeviceRepository.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/DeviceRepository.kt
index 7d1eed2961..f2a6fe7ff1 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/DeviceRepository.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/DeviceRepository.kt
@@ -1,20 +1,35 @@
package net.mullvad.mullvadvpn.ui.serviceconnection
import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.SharingStarted.Companion.Eagerly
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.stateIn
import net.mullvad.mullvadvpn.model.DeviceState
class DeviceRepository(
- private val dataSource: ServiceConnectionDeviceDataSource,
- externalScope: CoroutineScope
+ private val serviceConnectionManager: ServiceConnectionManager
) {
- val deviceState = dataSource.deviceStateUpdates
- .stateIn(
- externalScope,
- Eagerly,
- DeviceState.InitialState
- )
+ val deviceState = serviceConnectionManager.connectionState
+ .flatMapLatest { state ->
+ if (state is ServiceConnectionState.ConnectedReady) {
+ state.container.deviceDataSource.deviceStateUpdates
+ .onStart {
+ state.container.deviceDataSource.refreshDevice()
+ }
+ } else {
+ emptyFlow()
+ }
+ }
+ .stateIn(CoroutineScope(Dispatchers.IO), SharingStarted.Lazily, DeviceState.InitialState)
- fun refreshDeviceState() = dataSource.refreshDevice()
+ fun refreshDeviceState() {
+ container()?.deviceDataSource?.refreshDevice()
+ }
+
+ private fun container(): ServiceConnectionContainer? {
+ return serviceConnectionManager.connectionState.value.readyContainer()
+ }
}
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 3e3496fa14..e430c2d81f 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
@@ -4,7 +4,6 @@ import android.os.Looper
import android.os.Messenger
import android.os.RemoteException
import android.util.Log
-import kotlinx.coroutines.MainScope
import net.mullvad.mullvadvpn.di.SERVICE_CONNECTION_SCOPE
import net.mullvad.mullvadvpn.ipc.DispatchingHandler
import net.mullvad.mullvadvpn.ipc.Event
@@ -20,15 +19,16 @@ 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)
}
- override val scope = getKoin().createScope(
+ override val scope = getKoin().getOrCreateScope(
SERVICE_CONNECTION_SCOPE,
named(SERVICE_CONNECTION_SCOPE), this
)
@@ -36,10 +36,10 @@ class ServiceConnection(
val accountCache = AccountCache(connection, dispatcher)
val authTokenCache = AuthTokenCache(connection, dispatcher)
val connectionProxy = ConnectionProxy(connection, dispatcher)
- val deviceRepository =
- DeviceRepository(ServiceConnectionDeviceDataSource(connection, dispatcher), MainScope())
+ val deviceDataSource = ServiceConnectionDeviceDataSource(connection, dispatcher)
val locationInfoCache = LocationInfoCache(dispatcher)
val settingsListener = SettingsListener(connection, dispatcher)
+ // NOTE: `org.koin.core.scope.get` must be used here rather than `org.koin.core.component.get`.
val splitTunneling = get<SplitTunneling>(parameters = { parametersOf(connection, dispatcher) })
val voucherRedeemer = VoucherRedeemer(connection, dispatcher)
val vpnPermission = VpnPermission(connection, dispatcher)
@@ -49,8 +49,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..ca868e5cfa
--- /dev/null
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/ServiceConnectionState.kt
@@ -0,0 +1,14 @@
+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()
+
+ fun readyContainer(): ServiceConnectionContainer? {
+ return (this as? ConnectedReady)?.container
+ }
+}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/LoginViewModel.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/LoginViewModel.kt
index cb4f151fd1..e9cb27fda6 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/LoginViewModel.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/LoginViewModel.kt
@@ -1,9 +1,6 @@
package net.mullvad.mullvadvpn.viewmodel
-import android.app.Application
-import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.ViewModel
-import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@@ -15,9 +12,7 @@ import net.mullvad.mullvadvpn.model.AccountHistory
import net.mullvad.mullvadvpn.model.LoginResult
import net.mullvad.mullvadvpn.ui.serviceconnection.AccountCache
-class LoginViewModel(
- application: Application
-) : AndroidViewModel(application) {
+class LoginViewModel : ViewModel() {
private val _uiState = MutableStateFlow<LoginUiState>(LoginUiState.Default)
val uiState: StateFlow<LoginUiState> = _uiState
@@ -99,11 +94,4 @@ class LoginViewModel(
else -> LoginUiState.OtherError(errorMessage = this.toString())
}
}
-
- class Factory(val application: Application) :
- ViewModelProvider.Factory {
- override fun <T : ViewModel> create(modelClass: Class<T>): T {
- return LoginViewModel(application) as T
- }
- }
}
diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/LoginViewModelTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/LoginViewModelTest.kt
index 2a615fcc72..a0dc80957b 100644
--- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/LoginViewModelTest.kt
+++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/LoginViewModelTest.kt
@@ -5,7 +5,6 @@ import app.cash.turbine.test
import io.mockk.MockKAnnotations
import io.mockk.every
import io.mockk.impl.annotations.MockK
-import io.mockk.mockk
import io.mockk.verify
import junit.framework.Assert.assertEquals
import kotlinx.coroutines.Dispatchers
@@ -41,7 +40,7 @@ class LoginViewModelTest {
every { mockedAccountCache.accountHistoryEvents } returns accountHistoryTestEvents
every { mockedAccountCache.loginEvents } returns loginTestEvents
- loginViewModel = LoginViewModel(mockk())
+ loginViewModel = LoginViewModel()
}
@Test