From 271ab4a7347e6d6319b4741cc827896d72462e4b Mon Sep 17 00:00:00 2001 From: Jonatan Rhodin Date: Tue, 19 Nov 2024 08:16:53 +0100 Subject: [PATCH 1/2] Remove the use of intent provider for request vpn permission --- .../mullvadvpn/compose/screen/MullvadApp.kt | 40 ++---------- .../net/mullvad/mullvadvpn/di/UiModule.kt | 6 +- .../net/mullvad/mullvadvpn/ui/MainActivity.kt | 64 +++++++++++++++---- ...monViewModel.kt => MullvadAppViewModel.kt} | 11 +++- .../viewmodel/VpnProfileViewModel.kt | 34 ---------- .../mullvadvpn/lib/intent/IntentProvider.kt | 2 - 6 files changed, 67 insertions(+), 90 deletions(-) rename android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/{NoDaemonViewModel.kt => MullvadAppViewModel.kt} (92%) delete mode 100644 android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/VpnProfileViewModel.kt diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/MullvadApp.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/MullvadApp.kt index f52a4e887907..220cb50471fc 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/MullvadApp.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/MullvadApp.kt @@ -1,17 +1,14 @@ package net.mullvad.mullvadvpn.compose.screen -import androidx.activity.compose.rememberLauncherForActivityResult import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.semantics.semantics import androidx.compose.ui.semantics.testTagsAsResourceId import androidx.navigation.NavHostController -import arrow.core.merge import co.touchlab.kermit.Logger import com.ramcosta.composedestinations.DestinationsNavHost import com.ramcosta.composedestinations.generated.NavGraphs @@ -19,14 +16,8 @@ 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 net.mullvad.mullvadvpn.compose.util.CreateVpnProfile -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.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) @@ -36,12 +27,11 @@ fun MullvadApp() { val navHostController: NavHostController = engine.rememberNavController() val navigator: DestinationsNavigator = navHostController.rememberDestinationsNavigator() - val serviceVm = koinViewModel() - val permissionVm = koinViewModel() + val mullvadAppViewModel = koinViewModel() DisposableEffect(Unit) { - navHostController.addOnDestinationChangedListener(serviceVm) - onDispose { navHostController.removeOnDestinationChangedListener(serviceVm) } + navHostController.addOnDestinationChangedListener(mullvadAppViewModel) + onDispose { navHostController.removeOnDestinationChangedListener(mullvadAppViewModel) } } DestinationsNavHost( @@ -56,7 +46,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 -> @@ -66,24 +56,4 @@ 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() - } - } - } - } } 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 df35e54006d7..b111db10b207 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 @@ -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 @@ -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 @@ -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()) } @@ -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" 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 baf2da3b7065..134816417f64 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 @@ -12,21 +12,29 @@ import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle +import arrow.core.merge +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch import net.mullvad.mullvadvpn.compose.screen.MullvadApp +import net.mullvad.mullvadvpn.compose.util.CreateVpnProfile import net.mullvad.mullvadvpn.di.paymentModule import net.mullvad.mullvadvpn.di.uiModule +import net.mullvad.mullvadvpn.lib.common.constant.KEY_REQUEST_VPN_PROFILE import net.mullvad.mullvadvpn.lib.common.util.SdkUtils.requestNotificationPermissionIfMissing +import net.mullvad.mullvadvpn.lib.common.util.prepareVpnSafe import net.mullvad.mullvadvpn.lib.daemon.grpc.GrpcConnectivityState import net.mullvad.mullvadvpn.lib.daemon.grpc.ManagementService import net.mullvad.mullvadvpn.lib.intent.IntentProvider +import net.mullvad.mullvadvpn.lib.model.PrepareError +import net.mullvad.mullvadvpn.lib.model.Prepared 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 @@ -40,9 +48,11 @@ class MainActivity : ComponentActivity(), AndroidScopeComponent { // NotificationManager.areNotificationsEnabled is used to check the state rather than // handling the callback value. } + private val launchVpnPermission = + registerForActivityResult(CreateVpnProfile()) { _ -> mullvadAppViewModel.connect() } private val intentProvider by inject() - private val noDaemonViewModel by inject() + private val mullvadAppViewModel by inject() private val privacyDisclaimerRepository by inject() private val serviceConnectionManager by inject() private val splashCompleteRepository by inject() @@ -53,7 +63,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 @@ -68,15 +78,14 @@ class MainActivity : ComponentActivity(), AndroidScopeComponent { super.onCreate(savedInstanceState) - // Needs to be before set content since we want to access the intent in compose - if (savedInstanceState == null) { - intentProvider.setStartIntent(intent) - } setContent { AppTheme { MullvadApp() } } // This is to protect against tapjacking attacks window.decorView.filterTouchesWhenObscured = true + // Needs to be before we start the service, since we need to access the intent there + setUpIntentListener() + // We use lifecycleScope here to get less start service in background exceptions // Se this article for more information: // https://medium.com/@lepicekmichal/android-background-service-without-hiccup-501e4479110f @@ -103,11 +112,6 @@ class MainActivity : ComponentActivity(), AndroidScopeComponent { } } - override fun onNewIntent(intent: Intent) { - super.onNewIntent(intent) - intentProvider.setStartIntent(intent) - } - fun bindService() { requestNotificationPermissionIfMissing(requestNotificationPermissionLauncher) serviceConnectionManager.bind() @@ -121,7 +125,41 @@ class MainActivity : ComponentActivity(), AndroidScopeComponent { } override fun onDestroy() { - lifecycle.removeObserver(noDaemonViewModel) + lifecycle.removeObserver(mullvadAppViewModel) super.onDestroy() } + + private fun setUpIntentListener() { + lifecycleScope.launch { + intents().collect { + if (it.action == KEY_REQUEST_VPN_PROFILE) { + handleRequestVpnProfileIntent() + } else { + intentProvider.setStartIntent(it) + } + } + } + } + + private fun handleRequestVpnProfileIntent() { + val prepareResult = 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() + } + } + + private fun ComponentActivity.intents() = + callbackFlow { + send(intent) + + val listener: (Intent) -> Unit = { trySend(it) } + + addOnNewIntentListener(listener) + + awaitClose { removeOnNewIntentListener(listener) } + } } diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/NoDaemonViewModel.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/MullvadAppViewModel.kt similarity index 92% rename from android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/NoDaemonViewModel.kt rename to android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/MullvadAppViewModel.kt index a3f21f8ccf5c..91325402bd47 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/NoDaemonViewModel.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/MullvadAppViewModel.kt @@ -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 = MutableSharedFlow() private val destinationFlow: MutableSharedFlow = MutableSharedFlow() @@ -99,6 +102,10 @@ class NoDaemonViewModel(managementService: ManagementService) : } } + fun connect() { + viewModelScope.launch { connectionProxy.connectWithoutPermissionCheck() } + } + companion object { private val SERVICE_DISCONNECT_DEBOUNCE = 2.seconds } diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/VpnProfileViewModel.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/VpnProfileViewModel.kt deleted file mode 100644 index cb1a2862bf68..000000000000 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/VpnProfileViewModel.kt +++ /dev/null @@ -1,34 +0,0 @@ -package net.mullvad.mullvadvpn.viewmodel - -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.SharingStarted -import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.filter -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.shareIn -import kotlinx.coroutines.launch -import net.mullvad.mullvadvpn.lib.common.constant.KEY_REQUEST_VPN_PROFILE -import net.mullvad.mullvadvpn.lib.intent.IntentProvider -import net.mullvad.mullvadvpn.lib.shared.ConnectionProxy - -class VpnProfileViewModel( - intentProvider: IntentProvider, - private val connectionProxy: ConnectionProxy, -) : ViewModel() { - val uiSideEffect: Flow = - intentProvider.intents - .filter { it?.action == KEY_REQUEST_VPN_PROFILE } - .distinctUntilChanged() - .map { VpnProfileSideEffect.RequestVpnProfile } - .shareIn(viewModelScope, SharingStarted.WhileSubscribed()) - - fun connect() { - viewModelScope.launch { connectionProxy.connectWithoutPermissionCheck() } - } -} - -sealed interface VpnProfileSideEffect { - data object RequestVpnProfile : VpnProfileSideEffect -} diff --git a/android/lib/intent-provider/src/main/kotlin/net/mullvad/mullvadvpn/lib/intent/IntentProvider.kt b/android/lib/intent-provider/src/main/kotlin/net/mullvad/mullvadvpn/lib/intent/IntentProvider.kt index 86ad970b5df6..bd6e716d8dbd 100644 --- a/android/lib/intent-provider/src/main/kotlin/net/mullvad/mullvadvpn/lib/intent/IntentProvider.kt +++ b/android/lib/intent-provider/src/main/kotlin/net/mullvad/mullvadvpn/lib/intent/IntentProvider.kt @@ -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(null) - val intents: Flow = _intents fun setStartIntent(intent: Intent?) { _intents.tryEmit(intent) From b95e54a41dfa3e5932e9385adbfe1209eafb45bc Mon Sep 17 00:00:00 2001 From: Jonatan Rhodin Date: Tue, 10 Dec 2024 22:57:56 +0100 Subject: [PATCH 2/2] Replace intentholder with api override intent holder --- android/app/build.gradle.kts | 1 - .../net/mullvad/mullvadvpn/di/AppModule.kt | 4 +-- .../net/mullvad/mullvadvpn/ui/MainActivity.kt | 25 ++++++------- .../endpoint/ApiEndpointFromIntentHolder.kt | 10 ++++++ .../endpoint/ApiEndpointFromIntentHolder.kt | 11 ++++++ android/lib/intent-provider/build.gradle.kts | 35 ------------------- .../src/main/AndroidManifest.xml | 1 - .../mullvadvpn/lib/intent/IntentProvider.kt | 14 -------- android/service/build.gradle.kts | 1 - .../mullvadvpn/service/MullvadVpnService.kt | 10 +++--- android/settings.gradle.kts | 1 - 11 files changed, 40 insertions(+), 73 deletions(-) create mode 100644 android/lib/endpoint/src/debug/kotlin/net/mullvad/mullvadvpn/lib/endpoint/ApiEndpointFromIntentHolder.kt create mode 100644 android/lib/endpoint/src/release/kotlin/net/mullvad/mullvadvpn/lib/endpoint/ApiEndpointFromIntentHolder.kt delete mode 100644 android/lib/intent-provider/build.gradle.kts delete mode 100644 android/lib/intent-provider/src/main/AndroidManifest.xml delete mode 100644 android/lib/intent-provider/src/main/kotlin/net/mullvad/mullvadvpn/lib/intent/IntentProvider.kt diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index d62df346450f..8e252e1fc6a5 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -328,7 +328,6 @@ dependencies { implementation(projects.lib.common) implementation(projects.lib.daemonGrpc) implementation(projects.lib.endpoint) - implementation(projects.lib.intentProvider) implementation(projects.lib.map) implementation(projects.lib.model) implementation(projects.lib.payment) diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/di/AppModule.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/di/AppModule.kt index 9c52677dce8d..d7a1bfc8cb56 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/di/AppModule.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/di/AppModule.kt @@ -6,7 +6,7 @@ import net.mullvad.mullvadvpn.BuildConfig import net.mullvad.mullvadvpn.lib.common.constant.GRPC_SOCKET_FILE_NAME import net.mullvad.mullvadvpn.lib.common.constant.GRPC_SOCKET_FILE_NAMED_ARGUMENT import net.mullvad.mullvadvpn.lib.daemon.grpc.ManagementService -import net.mullvad.mullvadvpn.lib.intent.IntentProvider +import net.mullvad.mullvadvpn.lib.endpoint.ApiEndpointFromIntentHolder import net.mullvad.mullvadvpn.lib.model.BuildVersion import net.mullvad.mullvadvpn.lib.shared.AccountRepository import net.mullvad.mullvadvpn.lib.shared.ConnectionProxy @@ -33,7 +33,7 @@ val appModule = module { single { PrepareVpnUseCase(androidContext()) } single { BuildVersion(BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE) } - single { IntentProvider() } + single { ApiEndpointFromIntentHolder() } single { AccountRepository(get(), get(), MainScope()) } single { DeviceRepository(get()) } single { ConnectionProxy(get(), get(), get()) } 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 134816417f64..4007b09ecd1f 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 @@ -13,6 +13,7 @@ import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle import arrow.core.merge +import co.touchlab.kermit.Logger import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.flow.filter @@ -27,7 +28,8 @@ import net.mullvad.mullvadvpn.lib.common.util.SdkUtils.requestNotificationPermis import net.mullvad.mullvadvpn.lib.common.util.prepareVpnSafe import net.mullvad.mullvadvpn.lib.daemon.grpc.GrpcConnectivityState import net.mullvad.mullvadvpn.lib.daemon.grpc.ManagementService -import net.mullvad.mullvadvpn.lib.intent.IntentProvider +import net.mullvad.mullvadvpn.lib.endpoint.ApiEndpointFromIntentHolder +import net.mullvad.mullvadvpn.lib.endpoint.getApiEndpointConfigurationExtras import net.mullvad.mullvadvpn.lib.model.PrepareError import net.mullvad.mullvadvpn.lib.model.Prepared import net.mullvad.mullvadvpn.lib.theme.AppTheme @@ -51,7 +53,7 @@ class MainActivity : ComponentActivity(), AndroidScopeComponent { private val launchVpnPermission = registerForActivityResult(CreateVpnProfile()) { _ -> mullvadAppViewModel.connect() } - private val intentProvider by inject() + private val apiEndpointFromIntentHolder by inject() private val mullvadAppViewModel by inject() private val privacyDisclaimerRepository by inject() private val serviceConnectionManager by inject() @@ -84,7 +86,7 @@ class MainActivity : ComponentActivity(), AndroidScopeComponent { window.decorView.filterTouchesWhenObscured = true // Needs to be before we start the service, since we need to access the intent there - setUpIntentListener() + lifecycleScope.launch { intents().collect(::handleIntent) } // We use lifecycleScope here to get less start service in background exceptions // Se this article for more information: @@ -129,15 +131,14 @@ class MainActivity : ComponentActivity(), AndroidScopeComponent { super.onDestroy() } - private fun setUpIntentListener() { - lifecycleScope.launch { - intents().collect { - if (it.action == KEY_REQUEST_VPN_PROFILE) { - handleRequestVpnProfileIntent() - } else { - intentProvider.setStartIntent(it) - } - } + private fun handleIntent(intent: Intent) { + when (val action = intent.action) { + Intent.ACTION_MAIN -> + apiEndpointFromIntentHolder.setApiEndpointOverride( + intent.getApiEndpointConfigurationExtras() + ) + KEY_REQUEST_VPN_PROFILE -> handleRequestVpnProfileIntent() + else -> Logger.w("Unhandled intent action: $action") } } diff --git a/android/lib/endpoint/src/debug/kotlin/net/mullvad/mullvadvpn/lib/endpoint/ApiEndpointFromIntentHolder.kt b/android/lib/endpoint/src/debug/kotlin/net/mullvad/mullvadvpn/lib/endpoint/ApiEndpointFromIntentHolder.kt new file mode 100644 index 000000000000..2de7e829dda6 --- /dev/null +++ b/android/lib/endpoint/src/debug/kotlin/net/mullvad/mullvadvpn/lib/endpoint/ApiEndpointFromIntentHolder.kt @@ -0,0 +1,10 @@ +package net.mullvad.mullvadvpn.lib.endpoint + +class ApiEndpointFromIntentHolder { + var apiEndpointOverride: ApiEndpointOverride? = null + private set + + fun setApiEndpointOverride(apiEndpointOverride: ApiEndpointOverride?) { + this.apiEndpointOverride = apiEndpointOverride + } +} diff --git a/android/lib/endpoint/src/release/kotlin/net/mullvad/mullvadvpn/lib/endpoint/ApiEndpointFromIntentHolder.kt b/android/lib/endpoint/src/release/kotlin/net/mullvad/mullvadvpn/lib/endpoint/ApiEndpointFromIntentHolder.kt new file mode 100644 index 000000000000..9fa617276b5d --- /dev/null +++ b/android/lib/endpoint/src/release/kotlin/net/mullvad/mullvadvpn/lib/endpoint/ApiEndpointFromIntentHolder.kt @@ -0,0 +1,11 @@ +package net.mullvad.mullvadvpn.lib.endpoint + +// Overridding the API endpoint is not supported in release builds +class ApiEndpointFromIntentHolder { + val apiEndpointOverride: ApiEndpointOverride? = null + + @Suppress("UnusedParameter") + fun setApiEndpointOverride(apiEndpointOverride: ApiEndpointOverride?) { + // No-op + } +} diff --git a/android/lib/intent-provider/build.gradle.kts b/android/lib/intent-provider/build.gradle.kts deleted file mode 100644 index 66c9b6ff7490..000000000000 --- a/android/lib/intent-provider/build.gradle.kts +++ /dev/null @@ -1,35 +0,0 @@ -plugins { - alias(libs.plugins.android.library) - alias(libs.plugins.kotlin.android) - alias(libs.plugins.kotlin.parcelize) -} - -android { - namespace = "net.mullvad.mullvadvpn.lib.intent" - compileSdk = Versions.compileSdkVersion - buildToolsVersion = Versions.buildToolsVersion - - defaultConfig { minSdk = Versions.minSdkVersion } - - compileOptions { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 - } - - kotlinOptions { - jvmTarget = Versions.jvmTarget - allWarningsAsErrors = true - } - - lint { - lintConfig = file("${rootProject.projectDir}/config/lint.xml") - abortOnError = true - warningsAsErrors = true - } - buildFeatures { buildConfig = true } -} - -dependencies { - implementation(libs.kotlin.stdlib) - implementation(libs.kotlinx.coroutines.android) -} diff --git a/android/lib/intent-provider/src/main/AndroidManifest.xml b/android/lib/intent-provider/src/main/AndroidManifest.xml deleted file mode 100644 index cc947c567995..000000000000 --- a/android/lib/intent-provider/src/main/AndroidManifest.xml +++ /dev/null @@ -1 +0,0 @@ - diff --git a/android/lib/intent-provider/src/main/kotlin/net/mullvad/mullvadvpn/lib/intent/IntentProvider.kt b/android/lib/intent-provider/src/main/kotlin/net/mullvad/mullvadvpn/lib/intent/IntentProvider.kt deleted file mode 100644 index bd6e716d8dbd..000000000000 --- a/android/lib/intent-provider/src/main/kotlin/net/mullvad/mullvadvpn/lib/intent/IntentProvider.kt +++ /dev/null @@ -1,14 +0,0 @@ -package net.mullvad.mullvadvpn.lib.intent - -import android.content.Intent -import kotlinx.coroutines.flow.MutableStateFlow - -class IntentProvider { - private val _intents = MutableStateFlow(null) - - fun setStartIntent(intent: Intent?) { - _intents.tryEmit(intent) - } - - fun getLatestIntent(): Intent? = _intents.value -} diff --git a/android/service/build.gradle.kts b/android/service/build.gradle.kts index 8d8ffd22194b..717e2c6ef781 100644 --- a/android/service/build.gradle.kts +++ b/android/service/build.gradle.kts @@ -56,7 +56,6 @@ dependencies { implementation(projects.lib.common) implementation(projects.lib.daemonGrpc) implementation(projects.lib.endpoint) - implementation(projects.lib.intentProvider) implementation(projects.lib.model) implementation(projects.lib.shared) implementation(projects.lib.talpid) diff --git a/android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/MullvadVpnService.kt b/android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/MullvadVpnService.kt index 458f592f1b85..e4c8eba3c60c 100644 --- a/android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/MullvadVpnService.kt +++ b/android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/MullvadVpnService.kt @@ -17,8 +17,7 @@ import kotlinx.coroutines.runBlocking import net.mullvad.mullvadvpn.lib.common.constant.KEY_CONNECT_ACTION import net.mullvad.mullvadvpn.lib.common.constant.KEY_DISCONNECT_ACTION import net.mullvad.mullvadvpn.lib.daemon.grpc.ManagementService -import net.mullvad.mullvadvpn.lib.endpoint.getApiEndpointConfigurationExtras -import net.mullvad.mullvadvpn.lib.intent.IntentProvider +import net.mullvad.mullvadvpn.lib.endpoint.ApiEndpointFromIntentHolder import net.mullvad.mullvadvpn.lib.model.TunnelState import net.mullvad.mullvadvpn.lib.shared.ConnectionProxy import net.mullvad.mullvadvpn.service.di.vpnServiceModule @@ -39,7 +38,7 @@ class MullvadVpnService : TalpidVpnService() { private lateinit var managementService: ManagementService private lateinit var migrateSplitTunneling: MigrateSplitTunneling - private lateinit var intentProvider: IntentProvider + private lateinit var apiEndpointFromIntentHolder: ApiEndpointFromIntentHolder private lateinit var connectionProxy: ConnectionProxy private lateinit var daemonConfig: DaemonConfig @@ -66,7 +65,7 @@ class MullvadVpnService : TalpidVpnService() { daemonConfig = get() migrateSplitTunneling = get() - intentProvider = get() + apiEndpointFromIntentHolder = get() connectionProxy = get() } @@ -77,8 +76,7 @@ class MullvadVpnService : TalpidVpnService() { // If it is a debug build and we have an api override in the intent, use it // This is for injecting hostname and port for our mock api tests - val intentApiOverride = - intentProvider.getLatestIntent()?.getApiEndpointConfigurationExtras() + val intentApiOverride = apiEndpointFromIntentHolder.apiEndpointOverride val updatedConfig = if (BuildConfig.DEBUG && intentApiOverride != null) { daemonConfig.copy(apiEndpointOverride = intentApiOverride) diff --git a/android/settings.gradle.kts b/android/settings.gradle.kts index 7aa90a373f49..7e49d4cb9294 100644 --- a/android/settings.gradle.kts +++ b/android/settings.gradle.kts @@ -21,7 +21,6 @@ include( ":lib:common-test", ":lib:daemon-grpc", ":lib:endpoint", - ":lib:intent-provider", ":lib:map", ":lib:model", ":lib:payment",