diff options
| author | Albin <albin@mullvad.net> | 2022-12-13 10:37:16 +0100 |
|---|---|---|
| committer | Albin <albin@mullvad.net> | 2023-01-10 15:32:33 +0100 |
| commit | 3cf1466817025198bc3138774a724d49bb857914 (patch) | |
| tree | 38018dca3ea02ac5af85c946edde976430d5c5e1 /android | |
| parent | ca8878aa238a5c8e3be1f326ce98e238ffe87388 (diff) | |
| download | mullvadvpn-3cf1466817025198bc3138774a724d49bb857914.tar.xz mullvadvpn-3cf1466817025198bc3138774a724d49bb857914.zip | |
Add option to use custom api endpoint in debug builds
Diffstat (limited to 'android')
10 files changed, 111 insertions, 44 deletions
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/DaemonInstance.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/DaemonInstance.kt index 0d1750f625..67ec721a52 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/DaemonInstance.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/DaemonInstance.kt @@ -2,6 +2,8 @@ package net.mullvad.mullvadvpn.service import java.io.File import kotlin.properties.Delegates.observable +import kotlin.reflect.KClass +import kotlin.reflect.safeCast import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.channels.Channel @@ -9,14 +11,17 @@ import kotlinx.coroutines.channels.ClosedReceiveChannelException import kotlinx.coroutines.channels.ReceiveChannel import kotlinx.coroutines.channels.actor import kotlinx.coroutines.channels.trySendBlocking +import net.mullvad.mullvadvpn.lib.endpoint.ApiEndpointConfiguration import net.mullvad.mullvadvpn.util.Intermittent private const val RELAYS_FILE = "relays.json" -class DaemonInstance(val vpnService: MullvadVpnService) { - private enum class Command { - START, - STOP, +class DaemonInstance( + val vpnService: MullvadVpnService +) { + sealed class Command { + data class Start(val apiEndpointConfiguration: ApiEndpointConfiguration) : Command() + object Stop : Command() } private val commandChannel = spawnActor() @@ -27,12 +32,12 @@ class DaemonInstance(val vpnService: MullvadVpnService) { val intermittentDaemon = Intermittent<MullvadDaemon>() - fun start() { - commandChannel.trySendBlocking(Command.START) + fun start(apiEndpointConfiguration: ApiEndpointConfiguration) { + commandChannel.trySendBlocking(Command.Start(apiEndpointConfiguration)) } fun stop() { - commandChannel.trySendBlocking(Command.STOP) + commandChannel.trySendBlocking(Command.Stop) } fun onDestroy() { @@ -46,30 +51,25 @@ class DaemonInstance(val vpnService: MullvadVpnService) { prepareFiles() while (isRunning) { - if (!waitForCommand(channel, Command.START)) { - break - } - - startDaemon() - - isRunning = waitForCommand(channel, Command.STOP) - + val startCommand = waitForCommand(channel, Command.Start::class) ?: break + startDaemon(startCommand.apiEndpointConfiguration) + isRunning = waitForCommand(channel, Command.Stop::class) is Command.Stop stopDaemon() } } - private suspend fun waitForCommand( + private suspend fun <T : Command> waitForCommand( channel: ReceiveChannel<Command>, - command: Command - ): Boolean { - try { - while (channel.receive() != command) { - // Wait for command - } - - return true + command: KClass<T> + ): T? { + return try { + var receivedCommand: T? + do { + receivedCommand = command.safeCast(channel.receive()) + } while (receivedCommand == null) + receivedCommand } catch (exception: ClosedReceiveChannelException) { - return false + null } } @@ -91,8 +91,10 @@ class DaemonInstance(val vpnService: MullvadVpnService) { } } - private suspend fun startDaemon() { - val newDaemon = MullvadDaemon(vpnService).apply { + private suspend fun startDaemon( + apiEndpointConfiguration: ApiEndpointConfiguration + ) { + val newDaemon = MullvadDaemon(vpnService, apiEndpointConfiguration).apply { onDaemonStopped = { intermittentDaemon.spawnUpdate(null) daemon = null diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/MullvadDaemon.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/MullvadDaemon.kt index cd92a27472..6d82ee617c 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/MullvadDaemon.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/MullvadDaemon.kt @@ -3,6 +3,7 @@ package net.mullvad.mullvadvpn.service import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.asSharedFlow import net.mullvad.mullvadvpn.lib.endpoint.ApiEndpoint +import net.mullvad.mullvadvpn.lib.endpoint.ApiEndpointConfiguration import net.mullvad.mullvadvpn.model.AppVersionInfo import net.mullvad.mullvadvpn.model.Device import net.mullvad.mullvadvpn.model.DeviceEvent @@ -21,7 +22,10 @@ import net.mullvad.mullvadvpn.model.TunnelState import net.mullvad.mullvadvpn.model.VoucherSubmissionResult import net.mullvad.talpid.util.EventNotifier -class MullvadDaemon(vpnService: MullvadVpnService) { +class MullvadDaemon( + vpnService: MullvadVpnService, + apiEndpointConfiguration: ApiEndpointConfiguration +) { protected var daemonInterfaceAddress = 0L val onSettingsChange = EventNotifier<Settings?>(null) @@ -39,8 +43,12 @@ class MullvadDaemon(vpnService: MullvadVpnService) { init { System.loadLibrary("mullvad_jni") + initialize( - vpnService, vpnService.cacheDir.absolutePath, vpnService.filesDir.absolutePath, null + vpnService = vpnService, + cacheDirectory = vpnService.cacheDir.absolutePath, + resourceDirectory = vpnService.filesDir.absolutePath, + apiEndpoint = apiEndpointConfiguration.apiEndpoint() ) onSettingsChange.notify(getSettings()) diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/MullvadVpnService.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/MullvadVpnService.kt index 4753294376..c35125c326 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/MullvadVpnService.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/MullvadVpnService.kt @@ -12,7 +12,11 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.Job import kotlinx.coroutines.launch +import net.mullvad.mullvadvpn.BuildConfig import net.mullvad.mullvadvpn.di.vpnServiceModule +import net.mullvad.mullvadvpn.lib.endpoint.ApiEndpointConfiguration +import net.mullvad.mullvadvpn.lib.endpoint.DefaultApiEndpointConfiguration +import net.mullvad.mullvadvpn.lib.endpoint.getApiEndpointConfigurationExtras import net.mullvad.mullvadvpn.model.Settings import net.mullvad.mullvadvpn.model.TunnelState import net.mullvad.mullvadvpn.service.endpoint.ServiceEndpoint @@ -64,11 +68,15 @@ class MullvadVpnService : TalpidVpnService() { } } + private var apiEndpointConfiguration: ApiEndpointConfiguration = + DefaultApiEndpointConfiguration() + override fun onCreate() { super.onCreate() Log.d(TAG, "Initializing service") loadKoinModules(vpnServiceModule) + daemonInstance = DaemonInstance(this) keyguardManager = getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager @@ -97,14 +105,6 @@ class MullvadVpnService : TalpidVpnService() { endpoint.accountCache ) - daemonInstance.apply { - intermittentDaemon.registerListener(this@MullvadVpnService) { daemon -> - handleDaemonInstance(daemon) - } - - start() - } - // Remove any leftover tunnel state persistence data getSharedPreferences("tunnel_state", MODE_PRIVATE) .edit() @@ -114,6 +114,21 @@ class MullvadVpnService : TalpidVpnService() { override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { Log.d(TAG, "Starting service") + + if (BuildConfig.DEBUG) { + intent?.getApiEndpointConfigurationExtras()?.let { + apiEndpointConfiguration = it + } + } + + daemonInstance.apply { + intermittentDaemon.registerListener(this@MullvadVpnService) { daemon -> + handleDaemonInstance(daemon) + } + + start(apiEndpointConfiguration) + } + val startResult = super.onStartCommand(intent, flags, startId) var quitCommand = false @@ -231,7 +246,7 @@ class MullvadVpnService : TalpidVpnService() { daemonInstance.apply { stop() - start() + start(apiEndpointConfiguration) } } else { Log.d(TAG, "Ignoring restart because onDestroy has executed") 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 becf82a69e..fc53d94aaf 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 @@ -22,7 +22,6 @@ import androidx.lifecycle.Lifecycle import androidx.lifecycle.flowWithLifecycle import androidx.lifecycle.lifecycleScope import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.first @@ -34,6 +33,7 @@ import net.mullvad.mullvadvpn.R import net.mullvad.mullvadvpn.compose.component.ChangelogDialog import net.mullvad.mullvadvpn.dataproxy.MullvadProblemReport import net.mullvad.mullvadvpn.di.uiModule +import net.mullvad.mullvadvpn.lib.endpoint.getApiEndpointConfigurationExtras import net.mullvad.mullvadvpn.model.AccountExpiry import net.mullvad.mullvadvpn.model.DeviceState import net.mullvad.mullvadvpn.ui.fragments.DeviceRevokedFragment @@ -103,7 +103,11 @@ open class MainActivity : FragmentActivity() { override fun onStart() { Log.d("mullvad", "Starting main activity") super.onStart() - serviceConnectionManager.bind(vpnPermissionRequestHandler = ::requestVpnPermission) + + serviceConnectionManager.bind( + vpnPermissionRequestHandler = ::requestVpnPermission, + apiEndpointConfiguration = intent?.getApiEndpointConfigurationExtras() + ) } override fun onActivityResult(requestCode: Int, resultCode: Int, resultData: Intent?) { 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 index e1654476b8..f912f765a8 100644 --- 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 @@ -8,6 +8,9 @@ import android.os.Messenger import android.util.Log import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow +import net.mullvad.mullvadvpn.lib.endpoint.ApiEndpointConfiguration +import net.mullvad.mullvadvpn.lib.endpoint.BuildConfig +import net.mullvad.mullvadvpn.lib.endpoint.putApiEndpointConfigurationExtra import net.mullvad.mullvadvpn.service.MullvadVpnService import net.mullvad.talpid.util.EventNotifier @@ -47,9 +50,17 @@ class ServiceConnectionManager( } } - fun bind(vpnPermissionRequestHandler: () -> Unit) { + fun bind( + vpnPermissionRequestHandler: () -> Unit, + apiEndpointConfiguration: ApiEndpointConfiguration? + ) { this.vpnPermissionRequestHandler = vpnPermissionRequestHandler val intent = Intent(context, MullvadVpnService::class.java) + + if (BuildConfig.DEBUG && apiEndpointConfiguration != null) { + intent.putApiEndpointConfigurationExtra(apiEndpointConfiguration) + } + context.startService(intent) context.bindService(intent, serviceConnection, 0) } diff --git a/android/lib/endpoint/src/main/kotlin/net/mullvad/mullvadvpn/lib/endpoint/ApiEndpointIntentExtensions.kt b/android/lib/endpoint/src/main/kotlin/net/mullvad/mullvadvpn/lib/endpoint/ApiEndpointIntentExtensions.kt new file mode 100644 index 0000000000..cf2f2fb0dd --- /dev/null +++ b/android/lib/endpoint/src/main/kotlin/net/mullvad/mullvadvpn/lib/endpoint/ApiEndpointIntentExtensions.kt @@ -0,0 +1,18 @@ +package net.mullvad.mullvadvpn.lib.endpoint + +import android.content.Intent +import android.os.Build + +private const val OVERRIDE_API_EXTRA_NAME = "override_api" + +fun Intent.putApiEndpointConfigurationExtra(apiEndpointConfiguration: ApiEndpointConfiguration) { + putExtra(OVERRIDE_API_EXTRA_NAME, apiEndpointConfiguration) +} + +fun Intent.getApiEndpointConfigurationExtras(): ApiEndpointConfiguration? { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + getParcelableExtra(OVERRIDE_API_EXTRA_NAME, ApiEndpointConfiguration::class.java) + } else { + getParcelableExtra(OVERRIDE_API_EXTRA_NAME) + } +} diff --git a/android/test/common/src/main/kotlin/net/mullvad/mullvadvpn/test/common/interactor/AppInteractor.kt b/android/test/common/src/main/kotlin/net/mullvad/mullvadvpn/test/common/interactor/AppInteractor.kt index e225997534..83d217236f 100644 --- a/android/test/common/src/main/kotlin/net/mullvad/mullvadvpn/test/common/interactor/AppInteractor.kt +++ b/android/test/common/src/main/kotlin/net/mullvad/mullvadvpn/test/common/interactor/AppInteractor.kt @@ -6,6 +6,8 @@ import android.widget.ImageButton import androidx.test.uiautomator.By import androidx.test.uiautomator.UiDevice import androidx.test.uiautomator.Until +import net.mullvad.mullvadvpn.lib.endpoint.CustomApiEndpointConfiguration +import net.mullvad.mullvadvpn.lib.endpoint.putApiEndpointConfigurationExtra import net.mullvad.mullvadvpn.test.common.constant.APP_LAUNCH_TIMEOUT import net.mullvad.mullvadvpn.test.common.constant.CONNECTION_TIMEOUT import net.mullvad.mullvadvpn.test.common.constant.LOGIN_TIMEOUT @@ -19,17 +21,21 @@ class AppInteractor( private val device: UiDevice, private val targetContext: Context ) { - fun launch() { + fun launch(customApiEndpointConfiguration: CustomApiEndpointConfiguration? = null) { device.pressHome() // Wait for launcher device.wait( Until.hasObject(By.pkg(device.launcherPackageName).depth(0)), APP_LAUNCH_TIMEOUT ) + val intent = targetContext.packageManager.getLaunchIntentForPackage(MULLVAD_PACKAGE)?.apply { // Clear out any previous instances addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK) + if (customApiEndpointConfiguration != null) { + putApiEndpointConfigurationExtra(customApiEndpointConfiguration) + } } targetContext.startActivity(intent) device.wait( diff --git a/android/test/common/src/main/kotlin/net/mullvad/mullvadvpn/test/common/rule/CaptureScreenshotOnFailedTestRule.kt b/android/test/common/src/main/kotlin/net/mullvad/mullvadvpn/test/common/rule/CaptureScreenshotOnFailedTestRule.kt index 7f70d98ca7..73d515c501 100644 --- a/android/test/common/src/main/kotlin/net/mullvad/mullvadvpn/test/common/rule/CaptureScreenshotOnFailedTestRule.kt +++ b/android/test/common/src/main/kotlin/net/mullvad/mullvadvpn/test/common/rule/CaptureScreenshotOnFailedTestRule.kt @@ -4,10 +4,10 @@ import android.util.Log import androidx.test.runner.screenshot.BasicScreenCaptureProcessor import androidx.test.runner.screenshot.ScreenCaptureProcessor import androidx.test.runner.screenshot.Screenshot -import org.junit.rules.TestWatcher -import org.junit.runner.Description import java.time.LocalDateTime import java.time.format.DateTimeFormatter +import org.junit.rules.TestWatcher +import org.junit.runner.Description class CaptureScreenshotOnFailedTestRule(private val logTag: String) : TestWatcher() { override fun failed(e: Throwable?, description: Description?) { diff --git a/android/test/e2e/build.gradle.kts b/android/test/e2e/build.gradle.kts index 8aaf83742c..a8f0714782 100644 --- a/android/test/e2e/build.gradle.kts +++ b/android/test/e2e/build.gradle.kts @@ -106,6 +106,8 @@ configure<org.owasp.dependencycheck.gradle.extension.DependencyCheckExtension> { dependencies { implementation(project(Projects.testCommon)) + implementation(project(Dependencies.Mullvad.endpointLib)) + implementation(Dependencies.AndroidX.testCore) // Fixes: https://github.com/android/android-test/issues/1589 implementation(Dependencies.AndroidX.testMonitor) diff --git a/android/test/mockapi/build.gradle.kts b/android/test/mockapi/build.gradle.kts index 84410e168f..d464e2dc5a 100644 --- a/android/test/mockapi/build.gradle.kts +++ b/android/test/mockapi/build.gradle.kts @@ -47,6 +47,7 @@ configure<org.owasp.dependencycheck.gradle.extension.DependencyCheckExtension> { dependencies { implementation(project(Projects.testCommon)) + implementation(project(Dependencies.Mullvad.endpointLib)) implementation(Dependencies.AndroidX.testCore) // Fixes: https://github.com/android/android-test/issues/1589 |
