Skip to content

Commit

Permalink
Remove the use of intent provider for request vpn permission
Browse files Browse the repository at this point in the history
  • Loading branch information
Pururun committed Dec 2, 2024
1 parent 2e022ba commit d312f47
Show file tree
Hide file tree
Showing 6 changed files with 58 additions and 73 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package net.mullvad.mullvadvpn.compose.screen

import android.content.Intent
import androidx.activity.ComponentActivity
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
Expand All @@ -19,14 +21,17 @@ import com.ramcosta.composedestinations.generated.destinations.NoDaemonDestinati
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
import com.ramcosta.composedestinations.rememberNavHostEngine
import com.ramcosta.composedestinations.utils.rememberDestinationsNavigator
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.filter
import net.mullvad.mullvadvpn.compose.util.CreateVpnProfile
import net.mullvad.mullvadvpn.lib.common.constant.KEY_REQUEST_VPN_PROFILE
import net.mullvad.mullvadvpn.lib.common.util.prepareVpnSafe
import net.mullvad.mullvadvpn.lib.model.PrepareError
import net.mullvad.mullvadvpn.lib.model.Prepared
import net.mullvad.mullvadvpn.util.getActivity
import net.mullvad.mullvadvpn.viewmodel.DaemonScreenEvent
import net.mullvad.mullvadvpn.viewmodel.NoDaemonViewModel
import net.mullvad.mullvadvpn.viewmodel.VpnProfileSideEffect
import net.mullvad.mullvadvpn.viewmodel.VpnProfileViewModel
import net.mullvad.mullvadvpn.viewmodel.MullvadAppViewModel
import org.koin.androidx.compose.koinViewModel

@OptIn(ExperimentalComposeUiApi::class)
Expand All @@ -36,12 +41,32 @@ fun MullvadApp() {
val navHostController: NavHostController = engine.rememberNavController()
val navigator: DestinationsNavigator = navHostController.rememberDestinationsNavigator()

val serviceVm = koinViewModel<NoDaemonViewModel>()
val permissionVm = koinViewModel<VpnProfileViewModel>()
val mullvadAppViewModel = koinViewModel<MullvadAppViewModel>()

DisposableEffect(Unit) {
navHostController.addOnDestinationChangedListener(serviceVm)
onDispose { navHostController.removeOnDestinationChangedListener(serviceVm) }
navHostController.addOnDestinationChangedListener(mullvadAppViewModel)
onDispose { navHostController.removeOnDestinationChangedListener(mullvadAppViewModel) }
}

// Get intents
val launchVpnPermission =
rememberLauncherForActivityResult(CreateVpnProfile()) { _ -> mullvadAppViewModel.connect() }
val activity = LocalContext.current.getActivity() as ComponentActivity
LaunchedEffect(navHostController) {
activity
.intents()
.filter { it.action == KEY_REQUEST_VPN_PROFILE }
.collect {
val prepareResult = activity.prepareVpnSafe().merge()
when (prepareResult) {
is PrepareError.NotPrepared ->
launchVpnPermission.launch(prepareResult.prepareIntent)
// If legacy or other always on connect at let daemon generate a error state
is PrepareError.OtherLegacyAlwaysOnVpn,
is PrepareError.OtherAlwaysOnApp,
Prepared -> mullvadAppViewModel.connect()
}
}
}

DestinationsNavHost(
Expand All @@ -56,7 +81,7 @@ fun MullvadApp() {

// Globally handle daemon dropped connection with NoDaemonScreen
LaunchedEffect(Unit) {
serviceVm.uiSideEffect.collect {
mullvadAppViewModel.uiSideEffect.collect {
Logger.i { "DaemonScreenEvent: $it" }
when (it) {
DaemonScreenEvent.Show ->
Expand All @@ -66,24 +91,15 @@ fun MullvadApp() {
}
}
}
}

// Ask for VPN Permission
val launchVpnPermission =
rememberLauncherForActivityResult(CreateVpnProfile()) { _ -> permissionVm.connect() }
val context = LocalContext.current
LaunchedEffect(Unit) {
permissionVm.uiSideEffect.collect {
if (it is VpnProfileSideEffect.RequestVpnProfile) {
val prepareResult = context.prepareVpnSafe().merge()
when (prepareResult) {
is PrepareError.NotPrepared ->
launchVpnPermission.launch(prepareResult.prepareIntent)
// If legacy or other always on connect at let daemon generate a error state
is PrepareError.OtherLegacyAlwaysOnVpn,
is PrepareError.OtherAlwaysOnApp,
Prepared -> permissionVm.connect()
}
}
}
private fun ComponentActivity.intents() =
callbackFlow<Intent> {
send(intent)

val listener: (Intent) -> Unit = { trySend(it) }

addOnNewIntentListener(listener)

awaitClose { removeOnNewIntentListener(listener) }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,8 @@ import net.mullvad.mullvadvpn.viewmodel.EditCustomListViewModel
import net.mullvad.mullvadvpn.viewmodel.FilterViewModel
import net.mullvad.mullvadvpn.viewmodel.LoginViewModel
import net.mullvad.mullvadvpn.viewmodel.MtuDialogViewModel
import net.mullvad.mullvadvpn.viewmodel.MullvadAppViewModel
import net.mullvad.mullvadvpn.viewmodel.MultihopViewModel
import net.mullvad.mullvadvpn.viewmodel.NoDaemonViewModel
import net.mullvad.mullvadvpn.viewmodel.OutOfTimeViewModel
import net.mullvad.mullvadvpn.viewmodel.PaymentViewModel
import net.mullvad.mullvadvpn.viewmodel.PrivacyDisclaimerViewModel
Expand All @@ -92,7 +92,6 @@ import net.mullvad.mullvadvpn.viewmodel.SplitTunnelingViewModel
import net.mullvad.mullvadvpn.viewmodel.Udp2TcpSettingsViewModel
import net.mullvad.mullvadvpn.viewmodel.ViewLogsViewModel
import net.mullvad.mullvadvpn.viewmodel.VoucherDialogViewModel
import net.mullvad.mullvadvpn.viewmodel.VpnProfileViewModel
import net.mullvad.mullvadvpn.viewmodel.VpnSettingsViewModel
import net.mullvad.mullvadvpn.viewmodel.WelcomeViewModel
import net.mullvad.mullvadvpn.viewmodel.WireguardCustomPortDialogViewModel
Expand Down Expand Up @@ -237,7 +236,6 @@ val uiModule = module {
viewModel { DeleteCustomListConfirmationViewModel(get(), get()) }
viewModel { ServerIpOverridesViewModel(get(), get()) }
viewModel { ResetServerIpOverridesConfirmationViewModel(get()) }
viewModel { VpnProfileViewModel(get(), get()) }
viewModel { ApiAccessListViewModel(get()) }
viewModel { EditApiAccessMethodViewModel(get(), get(), get()) }
viewModel { SaveApiAccessMethodViewModel(get(), get()) }
Expand Down Expand Up @@ -268,7 +266,7 @@ val uiModule = module {
viewModel { DaitaViewModel(get()) }

// This view model must be single so we correctly attach lifecycle and share it with activity
single { NoDaemonViewModel(get()) }
single { MullvadAppViewModel(get(), get()) }
}

const val SELF_PACKAGE_NAME = "SELF_PACKAGE_NAME"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import net.mullvad.mullvadvpn.lib.theme.AppTheme
import net.mullvad.mullvadvpn.repository.PrivacyDisclaimerRepository
import net.mullvad.mullvadvpn.repository.SplashCompleteRepository
import net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnectionManager
import net.mullvad.mullvadvpn.viewmodel.NoDaemonViewModel
import net.mullvad.mullvadvpn.viewmodel.MullvadAppViewModel
import org.koin.android.ext.android.inject
import org.koin.android.scope.AndroidScopeComponent
import org.koin.androidx.scope.activityScope
Expand All @@ -40,7 +40,7 @@ class MainActivity : ComponentActivity(), AndroidScopeComponent {
}

private val intentProvider by inject<IntentProvider>()
private val noDaemonViewModel by inject<NoDaemonViewModel>()
private val mullvadAppViewModel by inject<MullvadAppViewModel>()
private val privacyDisclaimerRepository by inject<PrivacyDisclaimerRepository>()
private val serviceConnectionManager by inject<ServiceConnectionManager>()
private val splashCompleteRepository by inject<SplashCompleteRepository>()
Expand All @@ -51,7 +51,7 @@ class MainActivity : ComponentActivity(), AndroidScopeComponent {
override fun onCreate(savedInstanceState: Bundle?) {
loadKoinModules(listOf(uiModule, paymentModule))

lifecycle.addObserver(noDaemonViewModel)
lifecycle.addObserver(mullvadAppViewModel)

installSplashScreen().setKeepOnScreenCondition {
val isReady = isReadyNextDraw
Expand Down Expand Up @@ -118,7 +118,7 @@ class MainActivity : ComponentActivity(), AndroidScopeComponent {
}

override fun onDestroy() {
lifecycle.removeObserver(noDaemonViewModel)
lifecycle.removeObserver(mullvadAppViewModel)
super.onDestroy()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,14 @@ import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.launch
import net.mullvad.mullvadvpn.lib.daemon.grpc.GrpcConnectivityState
import net.mullvad.mullvadvpn.lib.daemon.grpc.ManagementService
import net.mullvad.mullvadvpn.lib.shared.ConnectionProxy

private val noServiceDestinations = listOf(SplashDestination, PrivacyDisclaimerDestination)

class NoDaemonViewModel(managementService: ManagementService) :
ViewModel(), LifecycleEventObserver, NavController.OnDestinationChangedListener {
class MullvadAppViewModel(
private val connectionProxy: ConnectionProxy,
managementService: ManagementService,
) : ViewModel(), LifecycleEventObserver, NavController.OnDestinationChangedListener {

private val lifecycleFlow: MutableSharedFlow<Lifecycle.Event> = MutableSharedFlow()
private val destinationFlow: MutableSharedFlow<DestinationSpec> = MutableSharedFlow()
Expand Down Expand Up @@ -99,6 +102,10 @@ class NoDaemonViewModel(managementService: ManagementService) :
}
}

fun connect() {
viewModelScope.launch { connectionProxy.connectWithoutPermissionCheck() }
}

companion object {
private val SERVICE_DISCONNECT_DEBOUNCE = 2.seconds
}
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
package net.mullvad.mullvadvpn.lib.intent

import android.content.Intent
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow

class IntentProvider {
private val _intents = MutableStateFlow<Intent?>(null)
val intents: Flow<Intent?> = _intents

fun setStartIntent(intent: Intent?) {
_intents.tryEmit(intent)
Expand Down

0 comments on commit d312f47

Please sign in to comment.