summaryrefslogtreecommitdiffhomepage
path: root/android
diff options
context:
space:
mode:
Diffstat (limited to 'android')
-rw-r--r--android/app/src/main/AndroidManifest.xml9
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/VpnSettingsScreen.kt65
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/VpnSettingsUiState.kt3
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/di/UiModule.kt16
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/receiver/BootCompletedReceiver.kt30
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/repository/AutoStartAndConnectOnBootRepository.kt49
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/VpnSettingsViewModel.kt19
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/VpnSettingsViewModelState.kt3
-rw-r--r--android/lib/resource/src/main/res/values/strings.xml2
9 files changed, 168 insertions, 28 deletions
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 19e468783c..8dc3c4e385 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -7,6 +7,7 @@
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<!-- https://developer.android.com/guide/components/fg-service-types#system-exempted -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SYSTEM_EXEMPTED" />
+ <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-feature android:name="android.hardware.touchscreen"
android:required="false" />
<uses-feature android:name="android.hardware.faketouch"
@@ -111,5 +112,13 @@
<action android:name="android.intent.action.LOCALE_CHANGED" />
</intent-filter>
</receiver>
+ <receiver android:name=".receiver.BootCompletedReceiver"
+ android:enabled="false"
+ android:exported="false">
+ <intent-filter>
+ <action android:name="android.intent.action.QUICKBOOT_POWERON" />
+ <action android:name="android.intent.action.BOOT_COMPLETED" />
+ </intent-filter>
+ </receiver>
</application>
</manifest>
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/VpnSettingsScreen.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/VpnSettingsScreen.kt
index 75d12bd717..99de9066e7 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/VpnSettingsScreen.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/VpnSettingsScreen.kt
@@ -264,6 +264,7 @@ fun VpnSettings(
dropUnlessResumed { navigator.navigate(ShadowsocksSettingsDestination) },
navigateToUdp2TcpSettings =
dropUnlessResumed { navigator.navigate(Udp2TcpSettingsDestination) },
+ onToggleAutoStartAndConnectOnBoot = vm::onToggleAutoStartAndConnectOnBoot,
)
}
@@ -303,6 +304,7 @@ fun VpnSettingsScreen(
onWireguardPortSelected: (port: Constraint<Port>) -> Unit = {},
navigateToShadowSocksSettings: () -> Unit = {},
navigateToUdp2TcpSettings: () -> Unit = {},
+ onToggleAutoStartAndConnectOnBoot: (Boolean) -> Unit = {},
) {
var expandContentBlockersState by rememberSaveable { mutableStateOf(false) }
val biggerPadding = 54.dp
@@ -330,33 +332,50 @@ fun VpnSettingsScreen(
text = stringResource(id = R.string.auto_connect_and_lockdown_mode_footer)
)
}
- }
- item {
- Spacer(modifier = Modifier.height(Dimens.cellLabelVerticalPadding))
- HeaderSwitchComposeCell(
- title = stringResource(R.string.auto_connect_legacy),
- isToggled = state.isAutoConnectEnabled,
- isEnabled = true,
- onCellClicked = { newValue -> onToggleAutoConnect(newValue) },
- )
- }
- item {
- SwitchComposeSubtitleCell(
- text =
- HtmlCompat.fromHtml(
- if (state.systemVpnSettingsAvailable) {
+ item {
+ Spacer(modifier = Modifier.height(Dimens.cellLabelVerticalPadding))
+ HeaderSwitchComposeCell(
+ title = stringResource(R.string.auto_connect_legacy),
+ isToggled = state.isAutoConnectEnabled,
+ isEnabled = true,
+ onCellClicked = { newValue -> onToggleAutoConnect(newValue) },
+ )
+ }
+ item {
+ SwitchComposeSubtitleCell(
+ text =
+ HtmlCompat.fromHtml(
textResource(
R.string.auto_connect_footer_legacy,
textResource(R.string.auto_connect_and_lockdown_mode),
- )
- } else {
- textResource(R.string.auto_connect_footer_legacy_tv)
- },
- HtmlCompat.FROM_HTML_MODE_COMPACT,
- )
- .toAnnotatedString(boldFontWeight = FontWeight.ExtraBold)
- )
+ ),
+ HtmlCompat.FROM_HTML_MODE_COMPACT,
+ )
+ .toAnnotatedString(boldFontWeight = FontWeight.ExtraBold)
+ )
+ }
+ } else {
+ item {
+ Spacer(modifier = Modifier.height(Dimens.cellLabelVerticalPadding))
+ HeaderSwitchComposeCell(
+ title = stringResource(R.string.connect_on_start),
+ isToggled = state.autoStartAndConnectOnBoot,
+ onCellClicked = { newValue -> onToggleAutoStartAndConnectOnBoot(newValue) },
+ )
+ SwitchComposeSubtitleCell(
+ text =
+ HtmlCompat.fromHtml(
+ textResource(
+ R.string.connect_on_start_footer,
+ textResource(R.string.auto_connect_and_lockdown_mode),
+ ),
+ HtmlCompat.FROM_HTML_MODE_COMPACT,
+ )
+ .toAnnotatedString(boldFontWeight = FontWeight.ExtraBold)
+ )
+ }
}
+
item {
Spacer(modifier = Modifier.height(Dimens.cellLabelVerticalPadding))
HeaderSwitchComposeCell(
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/VpnSettingsUiState.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/VpnSettingsUiState.kt
index 7884f199f2..4c5f2c2ffb 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/VpnSettingsUiState.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/VpnSettingsUiState.kt
@@ -25,6 +25,7 @@ data class VpnSettingsUiState(
val customWireguardPort: Port?,
val availablePortRanges: List<PortRange>,
val systemVpnSettingsAvailable: Boolean,
+ val autoStartAndConnectOnBoot: Boolean,
) {
val isCustomWireguardPort =
selectedWireguardPort is Constraint.Only &&
@@ -47,6 +48,7 @@ data class VpnSettingsUiState(
customWireguardPort: Port? = null,
availablePortRanges: List<PortRange> = emptyList(),
systemVpnSettingsAvailable: Boolean = false,
+ autoStartAndConnectOnBoot: Boolean = false,
) =
VpnSettingsUiState(
mtu,
@@ -64,6 +66,7 @@ data class VpnSettingsUiState(
customWireguardPort,
availablePortRanges,
systemVpnSettingsAvailable,
+ autoStartAndConnectOnBoot,
)
}
}
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 b08708826d..61d491094a 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
@@ -1,5 +1,6 @@
package net.mullvad.mullvadvpn.di
+import android.content.ComponentName
import android.content.Context
import android.content.SharedPreferences
import android.content.pm.PackageManager
@@ -11,7 +12,9 @@ import net.mullvad.mullvadvpn.constant.IS_PLAY_BUILD
import net.mullvad.mullvadvpn.dataproxy.MullvadProblemReport
import net.mullvad.mullvadvpn.lib.payment.PaymentProvider
import net.mullvad.mullvadvpn.lib.shared.VoucherRepository
+import net.mullvad.mullvadvpn.receiver.BootCompletedReceiver
import net.mullvad.mullvadvpn.repository.ApiAccessRepository
+import net.mullvad.mullvadvpn.repository.AutoStartAndConnectOnBootRepository
import net.mullvad.mullvadvpn.repository.ChangelogRepository
import net.mullvad.mullvadvpn.repository.CustomListsRepository
import net.mullvad.mullvadvpn.repository.InAppNotificationController
@@ -102,6 +105,10 @@ val uiModule = module {
single<PackageManager> { androidContext().packageManager }
single<String>(named(SELF_PACKAGE_NAME)) { androidContext().packageName }
+ single<ComponentName>(named(BOOT_COMPLETED_RECEIVER_COMPONENT_NAME)) {
+ ComponentName(androidContext(), BootCompletedReceiver::class.java)
+ }
+
viewModel { SplitTunnelingViewModel(get(), get(), Dispatchers.Default) }
single { ApplicationsProvider(get(), get(named(SELF_PACKAGE_NAME))) }
@@ -127,6 +134,12 @@ val uiModule = module {
single { ApiAccessRepository(get()) }
single { NewDeviceRepository() }
single { SplashCompleteRepository() }
+ single {
+ AutoStartAndConnectOnBootRepository(
+ get(),
+ get(named(BOOT_COMPLETED_RECEIVER_COMPONENT_NAME)),
+ )
+ }
single { AccountExpiryNotificationUseCase(get()) }
single { TunnelStateNotificationUseCase(get()) }
@@ -195,7 +208,7 @@ val uiModule = module {
viewModel { SettingsViewModel(get(), get(), IS_PLAY_BUILD) }
viewModel { SplashViewModel(get(), get(), get(), get()) }
viewModel { VoucherDialogViewModel(get()) }
- viewModel { VpnSettingsViewModel(get(), get(), get()) }
+ viewModel { VpnSettingsViewModel(get(), get(), get(), get()) }
viewModel { WelcomeViewModel(get(), get(), get(), get(), isPlayBuild = IS_PLAY_BUILD) }
viewModel { ReportProblemViewModel(get(), get()) }
viewModel { ViewLogsViewModel(get()) }
@@ -226,3 +239,4 @@ val uiModule = module {
const val SELF_PACKAGE_NAME = "SELF_PACKAGE_NAME"
const val APP_PREFERENCES_NAME = "${BuildConfig.APPLICATION_ID}.app_preferences"
+const val BOOT_COMPLETED_RECEIVER_COMPONENT_NAME = "BOOT_COMPLETED_RECEIVER_COMPONENT_NAME"
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/receiver/BootCompletedReceiver.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/receiver/BootCompletedReceiver.kt
new file mode 100644
index 0000000000..9f153e724b
--- /dev/null
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/receiver/BootCompletedReceiver.kt
@@ -0,0 +1,30 @@
+package net.mullvad.mullvadvpn.receiver
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.net.VpnService
+import co.touchlab.kermit.Logger
+import net.mullvad.mullvadvpn.lib.common.constant.KEY_CONNECT_ACTION
+import net.mullvad.mullvadvpn.lib.common.constant.VPN_SERVICE_CLASS
+
+class BootCompletedReceiver : BroadcastReceiver() {
+ override fun onReceive(context: Context?, intent: Intent?) {
+ if (intent?.action == Intent.ACTION_BOOT_COMPLETED) {
+ context?.let { startAndConnectTunnel(context) }
+ }
+ }
+
+ private fun startAndConnectTunnel(context: Context) {
+ val hasVpnPermission = VpnService.prepare(context) == null
+ Logger.i("AutoStart on boot and connect, hasVpnPermission: $hasVpnPermission")
+ if (hasVpnPermission) {
+ val intent =
+ Intent().apply {
+ setClassName(context.packageName, VPN_SERVICE_CLASS)
+ action = KEY_CONNECT_ACTION
+ }
+ context.startForegroundService(intent)
+ }
+ }
+}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/repository/AutoStartAndConnectOnBootRepository.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/repository/AutoStartAndConnectOnBootRepository.kt
new file mode 100644
index 0000000000..3fc0f38c7c
--- /dev/null
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/repository/AutoStartAndConnectOnBootRepository.kt
@@ -0,0 +1,49 @@
+package net.mullvad.mullvadvpn.repository
+
+import android.content.ComponentName
+import android.content.pm.PackageManager
+import android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT
+import android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED
+import android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED
+import android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER
+import android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED
+import android.content.pm.PackageManager.DONT_KILL_APP
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+
+class AutoStartAndConnectOnBootRepository(
+ private val packageManager: PackageManager,
+ private val bootCompletedComponentName: ComponentName,
+) {
+ private val _autoStartAndConnectOnBoot = MutableStateFlow(isAutoStartAndConnectOnBoot())
+ val autoStartAndConnectOnBoot: StateFlow<Boolean> = _autoStartAndConnectOnBoot
+
+ fun setAutoStartAndConnectOnBoot(enabled: Boolean) {
+ packageManager.setComponentEnabledSetting(
+ bootCompletedComponentName,
+ if (enabled) {
+ COMPONENT_ENABLED_STATE_ENABLED
+ } else {
+ COMPONENT_ENABLED_STATE_DISABLED
+ },
+ DONT_KILL_APP,
+ )
+
+ _autoStartAndConnectOnBoot.value = isAutoStartAndConnectOnBoot()
+ }
+
+ private fun isAutoStartAndConnectOnBoot(): Boolean =
+ when (packageManager.getComponentEnabledSetting(bootCompletedComponentName)) {
+ COMPONENT_ENABLED_STATE_DEFAULT -> BOOT_COMPLETED_DEFAULT_STATE
+ COMPONENT_ENABLED_STATE_ENABLED -> true
+ COMPONENT_ENABLED_STATE_DISABLED -> false
+ COMPONENT_ENABLED_STATE_DISABLED_USER,
+ COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED ->
+ error("Enabled setting only applicable for application")
+ else -> error("Unknown component enabled setting")
+ }
+
+ companion object {
+ private const val BOOT_COMPLETED_DEFAULT_STATE = false
+ }
+}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/VpnSettingsViewModel.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/VpnSettingsViewModel.kt
index 3baeda244a..ec73067687 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/VpnSettingsViewModel.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/VpnSettingsViewModel.kt
@@ -28,6 +28,7 @@ import net.mullvad.mullvadvpn.lib.model.Port
import net.mullvad.mullvadvpn.lib.model.QuantumResistantState
import net.mullvad.mullvadvpn.lib.model.Settings
import net.mullvad.mullvadvpn.lib.model.WireguardConstraints
+import net.mullvad.mullvadvpn.repository.AutoStartAndConnectOnBootRepository
import net.mullvad.mullvadvpn.repository.RelayListRepository
import net.mullvad.mullvadvpn.repository.SettingsRepository
import net.mullvad.mullvadvpn.usecase.SystemVpnSettingsAvailableUseCase
@@ -46,6 +47,7 @@ class VpnSettingsViewModel(
private val repository: SettingsRepository,
private val relayListRepository: RelayListRepository,
private val systemVpnSettingsUseCase: SystemVpnSettingsAvailableUseCase,
+ private val autoStartAndConnectOnBootRepository: AutoStartAndConnectOnBootRepository,
private val dispatcher: CoroutineDispatcher = Dispatchers.IO,
) : ViewModel() {
@@ -55,10 +57,12 @@ class VpnSettingsViewModel(
private val customPort = MutableStateFlow<Port?>(null)
private val vmState =
- combine(repository.settingsUpdates, relayListRepository.portRanges, customPort) {
- settings,
- portRanges,
- customWgPort ->
+ combine(
+ repository.settingsUpdates,
+ relayListRepository.portRanges,
+ customPort,
+ autoStartAndConnectOnBootRepository.autoStartAndConnectOnBoot,
+ ) { settings, portRanges, customWgPort, autoStartAndConnectOnBoot ->
VpnSettingsViewModelState(
mtuValue = settings?.tunnelOptions?.wireguard?.mtu,
isAutoConnectEnabled = settings?.autoConnect ?: false,
@@ -78,6 +82,7 @@ class VpnSettingsViewModel(
customWireguardPort = customWgPort,
availablePortRanges = portRanges,
systemVpnSettingsAvailable = systemVpnSettingsUseCase(),
+ autoStartAndConnectOnBoot = autoStartAndConnectOnBoot,
)
}
.stateIn(
@@ -254,6 +259,12 @@ class VpnSettingsViewModel(
}
}
+ fun onToggleAutoStartAndConnectOnBoot(autoStartAndConnect: Boolean) {
+ viewModelScope.launch(dispatcher) {
+ autoStartAndConnectOnBootRepository.setAutoStartAndConnectOnBoot(autoStartAndConnect)
+ }
+ }
+
private fun updateDefaultDnsOptionsViaRepository(contentBlockersOption: DefaultDnsOptions) =
viewModelScope.launch(dispatcher) {
repository
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/VpnSettingsViewModelState.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/VpnSettingsViewModelState.kt
index 31d5515a3c..4798a8a91b 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/VpnSettingsViewModelState.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/VpnSettingsViewModelState.kt
@@ -25,6 +25,7 @@ data class VpnSettingsViewModelState(
val customWireguardPort: Port?,
val availablePortRanges: List<PortRange>,
val systemVpnSettingsAvailable: Boolean,
+ val autoStartAndConnectOnBoot: Boolean,
) {
val isCustomWireguardPort =
selectedWireguardPort is Constraint.Only &&
@@ -47,6 +48,7 @@ data class VpnSettingsViewModelState(
customWireguardPort,
availablePortRanges,
systemVpnSettingsAvailable,
+ autoStartAndConnectOnBoot,
)
companion object {
@@ -67,6 +69,7 @@ data class VpnSettingsViewModelState(
customWireguardPort = null,
availablePortRanges = emptyList(),
systemVpnSettingsAvailable = false,
+ autoStartAndConnectOnBoot = false,
)
}
}
diff --git a/android/lib/resource/src/main/res/values/strings.xml b/android/lib/resource/src/main/res/values/strings.xml
index 338aae32ee..fc02f8cc0d 100644
--- a/android/lib/resource/src/main/res/values/strings.xml
+++ b/android/lib/resource/src/main/res/values/strings.xml
@@ -403,4 +403,6 @@
<string name="connection_details_in">In</string>
<string name="feature_indicators_show_more">%1$s more...</string>
<string name="connect">Connect</string>
+ <string name="connect_on_start">Connect on device start-up</string>
+ <string name="connect_on_start_footer">Automatically connect on device start-up. Only works if the app has been granted the VPN permission.</string>
</resources>