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 e27223245ea3..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,19 +1,14 @@ 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 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 @@ -21,15 +16,6 @@ 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.MullvadAppViewModel import org.koin.androidx.compose.koinViewModel @@ -48,27 +34,6 @@ fun MullvadApp() { 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( modifier = Modifier.semantics { testTagsAsResourceId = true }.fillMaxSize(), engine = engine, @@ -92,14 +57,3 @@ fun MullvadApp() { } } } - -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/ui/MainActivity.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/MainActivity.kt index ab30b26a2ce6..3c0f82991642 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 @@ -10,16 +10,24 @@ import androidx.core.view.WindowCompat 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 @@ -38,6 +46,8 @@ 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 mullvadAppViewModel by inject() @@ -65,15 +75,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 @@ -100,11 +109,6 @@ class MainActivity : ComponentActivity(), AndroidScopeComponent { } } - override fun onNewIntent(intent: Intent) { - super.onNewIntent(intent) - intentProvider.setStartIntent(intent) - } - fun bindService() { requestNotificationPermissionIfMissing(requestNotificationPermissionLauncher) serviceConnectionManager.bind() @@ -121,4 +125,38 @@ class MainActivity : ComponentActivity(), AndroidScopeComponent { 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) } + } }