parent
09b669f54b
commit
bc811f74ef
|
@ -2,11 +2,13 @@ package com.zaneschepke.wireguardautotunnel.service.foreground
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.net.NetworkCapabilities
|
||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
import android.os.PowerManager
|
import android.os.PowerManager
|
||||||
import androidx.core.app.ServiceCompat
|
import androidx.core.app.ServiceCompat
|
||||||
import androidx.lifecycle.LifecycleService
|
import androidx.lifecycle.LifecycleService
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import com.wireguard.android.util.RootShell
|
||||||
import com.zaneschepke.wireguardautotunnel.R
|
import com.zaneschepke.wireguardautotunnel.R
|
||||||
import com.zaneschepke.wireguardautotunnel.data.domain.Settings
|
import com.zaneschepke.wireguardautotunnel.data.domain.Settings
|
||||||
import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
|
import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
|
||||||
|
@ -23,6 +25,7 @@ import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelService
|
||||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelState
|
import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelState
|
||||||
import com.zaneschepke.wireguardautotunnel.util.Constants
|
import com.zaneschepke.wireguardautotunnel.util.Constants
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.cancelWithMessage
|
import com.zaneschepke.wireguardautotunnel.util.extensions.cancelWithMessage
|
||||||
|
import com.zaneschepke.wireguardautotunnel.util.extensions.getCurrentWifiName
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.isMatchingToWildcardList
|
import com.zaneschepke.wireguardautotunnel.util.extensions.isMatchingToWildcardList
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.isReachable
|
import com.zaneschepke.wireguardautotunnel.util.extensions.isReachable
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.onNotRunning
|
import com.zaneschepke.wireguardautotunnel.util.extensions.onNotRunning
|
||||||
|
@ -46,6 +49,9 @@ import javax.inject.Provider
|
||||||
class AutoTunnelService : LifecycleService() {
|
class AutoTunnelService : LifecycleService() {
|
||||||
private val foregroundId = 122
|
private val foregroundId = 122
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var rootShell: Provider<RootShell>
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var wifiService: NetworkService<WifiService>
|
lateinit var wifiService: NetworkService<WifiService>
|
||||||
|
|
||||||
|
@ -397,6 +403,14 @@ class AutoTunnelService : LifecycleService() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun updateWifi(connected: Boolean) {
|
||||||
|
autoTunnelStateFlow.update {
|
||||||
|
it.copy(
|
||||||
|
isWifiConnected = connected,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private suspend fun watchForEthernetConnectivityChanges() {
|
private suspend fun watchForEthernetConnectivityChanges() {
|
||||||
withContext(ioDispatcher) {
|
withContext(ioDispatcher) {
|
||||||
Timber.i("Starting ethernet data watcher")
|
Timber.i("Starting ethernet data watcher")
|
||||||
|
@ -428,21 +442,13 @@ class AutoTunnelService : LifecycleService() {
|
||||||
when (status) {
|
when (status) {
|
||||||
is NetworkStatus.Available -> {
|
is NetworkStatus.Available -> {
|
||||||
Timber.i("Gained Wi-Fi connection")
|
Timber.i("Gained Wi-Fi connection")
|
||||||
autoTunnelStateFlow.update {
|
updateWifi(true)
|
||||||
it.copy(
|
|
||||||
isWifiConnected = true,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
is NetworkStatus.CapabilitiesChanged -> {
|
is NetworkStatus.CapabilitiesChanged -> {
|
||||||
Timber.i("Wifi capabilities changed")
|
Timber.i("Wifi capabilities changed")
|
||||||
autoTunnelStateFlow.update {
|
updateWifi(true)
|
||||||
it.copy(
|
val ssid = getWifiSSID(status.networkCapabilities)
|
||||||
isWifiConnected = true,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
val ssid = wifiService.getNetworkName(status.networkCapabilities)
|
|
||||||
ssid?.let { name ->
|
ssid?.let { name ->
|
||||||
if (name.contains(Constants.UNREADABLE_SSID)) {
|
if (name.contains(Constants.UNREADABLE_SSID)) {
|
||||||
Timber.w("SSID unreadable: missing permissions")
|
Timber.w("SSID unreadable: missing permissions")
|
||||||
|
@ -459,11 +465,7 @@ class AutoTunnelService : LifecycleService() {
|
||||||
}
|
}
|
||||||
|
|
||||||
is NetworkStatus.Unavailable -> {
|
is NetworkStatus.Unavailable -> {
|
||||||
autoTunnelStateFlow.update {
|
updateWifi(false)
|
||||||
it.copy(
|
|
||||||
isWifiConnected = false,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Timber.i("Lost Wi-Fi connection")
|
Timber.i("Lost Wi-Fi connection")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -471,6 +473,16 @@ class AutoTunnelService : LifecycleService() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private suspend fun getWifiSSID(networkCapabilities: NetworkCapabilities): String? {
|
||||||
|
return withContext(ioDispatcher) {
|
||||||
|
try {
|
||||||
|
rootShell.get().getCurrentWifiName()
|
||||||
|
} catch (_: Exception) {
|
||||||
|
wifiService.getNetworkName(networkCapabilities)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private suspend fun getMobileDataTunnel(): TunnelConfig? {
|
private suspend fun getMobileDataTunnel(): TunnelConfig? {
|
||||||
return appDataRepository.tunnels.findByMobileDataTunnel().firstOrNull()
|
return appDataRepository.tunnels.findByMobileDataTunnel().firstOrNull()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package com.zaneschepke.wireguardautotunnel.ui.screens.settings
|
package com.zaneschepke.wireguardautotunnel.ui.screens.settings
|
||||||
|
|
||||||
import android.Manifest
|
import android.Manifest
|
||||||
import android.app.Activity
|
|
||||||
import android.content.Context.POWER_SERVICE
|
import android.content.Context.POWER_SERVICE
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
@ -101,7 +100,7 @@ fun SettingsScreen(viewModel: SettingsViewModel = hiltViewModel(), appViewModel:
|
||||||
val interactionSource = remember { MutableInteractionSource() }
|
val interactionSource = remember { MutableInteractionSource() }
|
||||||
val isRunningOnTv = context.isRunningOnTv()
|
val isRunningOnTv = context.isRunningOnTv()
|
||||||
|
|
||||||
val kernelSupport by viewModel.kernelSupport.collectAsStateWithLifecycle()
|
val settingsUiState by viewModel.uiState.collectAsStateWithLifecycle()
|
||||||
|
|
||||||
val fineLocationState = rememberPermissionState(Manifest.permission.ACCESS_FINE_LOCATION)
|
val fineLocationState = rememberPermissionState(Manifest.permission.ACCESS_FINE_LOCATION)
|
||||||
var currentText by remember { mutableStateOf("") }
|
var currentText by remember { mutableStateOf("") }
|
||||||
|
@ -115,10 +114,6 @@ fun SettingsScreen(viewModel: SettingsViewModel = hiltViewModel(), appViewModel:
|
||||||
val screenPadding = 5.dp
|
val screenPadding = 5.dp
|
||||||
val fillMaxWidth = .85f
|
val fillMaxWidth = .85f
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
|
||||||
viewModel.checkKernelSupport()
|
|
||||||
}
|
|
||||||
|
|
||||||
LaunchedEffect(uiState.settings.trustedNetworkSSIDs) {
|
LaunchedEffect(uiState.settings.trustedNetworkSSIDs) {
|
||||||
currentText = ""
|
currentText = ""
|
||||||
}
|
}
|
||||||
|
@ -127,7 +122,7 @@ fun SettingsScreen(viewModel: SettingsViewModel = hiltViewModel(), appViewModel:
|
||||||
rememberLauncherForActivityResult(
|
rememberLauncherForActivityResult(
|
||||||
ActivityResultContracts.StartActivityForResult(),
|
ActivityResultContracts.StartActivityForResult(),
|
||||||
) { result: ActivityResult ->
|
) { result: ActivityResult ->
|
||||||
if (result.resultCode == Activity.RESULT_OK) {
|
if (result.resultCode == RESULT_OK) {
|
||||||
result.data
|
result.data
|
||||||
// Handle the Intent
|
// Handle the Intent
|
||||||
}
|
}
|
||||||
|
@ -262,6 +257,18 @@ fun SettingsScreen(viewModel: SettingsViewModel = hiltViewModel(), appViewModel:
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun onAutoTunnelWifiChecked() {
|
||||||
|
when (false) {
|
||||||
|
isBackgroundLocationGranted -> showLocationDialog = true
|
||||||
|
fineLocationState.status.isGranted -> showLocationDialog = true
|
||||||
|
viewModel.isLocationEnabled(context) ->
|
||||||
|
showLocationServicesAlertDialog = true
|
||||||
|
else -> {
|
||||||
|
viewModel.onToggleTunnelOnWifi()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
verticalArrangement = Arrangement.Top,
|
verticalArrangement = Arrangement.Top,
|
||||||
|
@ -311,18 +318,8 @@ fun SettingsScreen(viewModel: SettingsViewModel = hiltViewModel(), appViewModel:
|
||||||
checked = uiState.settings.isTunnelOnWifiEnabled,
|
checked = uiState.settings.isTunnelOnWifiEnabled,
|
||||||
padding = screenPadding,
|
padding = screenPadding,
|
||||||
onCheckChanged = { checked ->
|
onCheckChanged = { checked ->
|
||||||
if (!checked) viewModel.onToggleTunnelOnWifi()
|
if (!checked || settingsUiState.isRooted) viewModel.onToggleTunnelOnWifi().also { return@ConfigurationToggle }
|
||||||
if (checked) {
|
onAutoTunnelWifiChecked()
|
||||||
when (false) {
|
|
||||||
isBackgroundLocationGranted -> showLocationDialog = true
|
|
||||||
fineLocationState.status.isGranted -> showLocationDialog = true
|
|
||||||
viewModel.isLocationEnabled(context) ->
|
|
||||||
showLocationServicesAlertDialog = true
|
|
||||||
else -> {
|
|
||||||
viewModel.onToggleTunnelOnWifi()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
modifier =
|
modifier =
|
||||||
if (uiState.settings.isAutoTunnelEnabled) {
|
if (uiState.settings.isAutoTunnelEnabled) {
|
||||||
|
@ -491,7 +488,7 @@ fun SettingsScreen(viewModel: SettingsViewModel = hiltViewModel(), appViewModel:
|
||||||
uiState.settings.isAutoTunnelEnabled ||
|
uiState.settings.isAutoTunnelEnabled ||
|
||||||
uiState.settings.isAlwaysOnVpnEnabled ||
|
uiState.settings.isAlwaysOnVpnEnabled ||
|
||||||
(uiState.vpnState.status == TunnelState.UP) ||
|
(uiState.vpnState.status == TunnelState.UP) ||
|
||||||
!kernelSupport
|
!settingsUiState.isKernelAvailable
|
||||||
),
|
),
|
||||||
checked = uiState.settings.isKernelEnabled,
|
checked = uiState.settings.isKernelEnabled,
|
||||||
padding = screenPadding,
|
padding = screenPadding,
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
package com.zaneschepke.wireguardautotunnel.ui.screens.settings
|
||||||
|
|
||||||
|
data class SettingsUiState(
|
||||||
|
val isRooted: Boolean = false,
|
||||||
|
val isKernelAvailable: Boolean = false,
|
||||||
|
)
|
|
@ -13,13 +13,14 @@ import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
|
||||||
import com.zaneschepke.wireguardautotunnel.module.IoDispatcher
|
import com.zaneschepke.wireguardautotunnel.module.IoDispatcher
|
||||||
import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager
|
import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.snackbar.SnackbarController
|
import com.zaneschepke.wireguardautotunnel.ui.common.snackbar.SnackbarController
|
||||||
|
import com.zaneschepke.wireguardautotunnel.util.Constants
|
||||||
import com.zaneschepke.wireguardautotunnel.util.FileUtils
|
import com.zaneschepke.wireguardautotunnel.util.FileUtils
|
||||||
import com.zaneschepke.wireguardautotunnel.util.StringValue
|
import com.zaneschepke.wireguardautotunnel.util.StringValue
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import kotlinx.coroutines.CoroutineDispatcher
|
import kotlinx.coroutines.CoroutineDispatcher
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.SharingStarted
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.onStart
|
||||||
import kotlinx.coroutines.flow.stateIn
|
import kotlinx.coroutines.flow.stateIn
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
@ -38,8 +39,16 @@ constructor(
|
||||||
@IoDispatcher private val ioDispatcher: CoroutineDispatcher,
|
@IoDispatcher private val ioDispatcher: CoroutineDispatcher,
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
|
|
||||||
private val _kernelSupport = MutableStateFlow(false)
|
private val _uiState = MutableStateFlow(SettingsUiState())
|
||||||
val kernelSupport = _kernelSupport.asStateFlow()
|
val uiState = _uiState.onStart {
|
||||||
|
_uiState.update {
|
||||||
|
it.copy(isKernelAvailable = isKernelSupported(), isRooted = isRooted())
|
||||||
|
}
|
||||||
|
}.stateIn(
|
||||||
|
viewModelScope,
|
||||||
|
SharingStarted.WhileSubscribed(Constants.SUBSCRIPTION_TIMEOUT),
|
||||||
|
SettingsUiState(),
|
||||||
|
)
|
||||||
private val settings = appDataRepository.settings.getSettingsFlow()
|
private val settings = appDataRepository.settings.getSettingsFlow()
|
||||||
.stateIn(viewModelScope, SharingStarted.Eagerly, Settings())
|
.stateIn(viewModelScope, SharingStarted.Eagerly, Settings())
|
||||||
|
|
||||||
|
@ -211,14 +220,10 @@ constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun checkKernelSupport() = viewModelScope.launch {
|
private suspend fun isKernelSupported(): Boolean {
|
||||||
val kernelSupport =
|
return withContext(ioDispatcher) {
|
||||||
withContext(ioDispatcher) {
|
|
||||||
WgQuickBackend.hasKernelSupport()
|
WgQuickBackend.hasKernelSupport()
|
||||||
}
|
}
|
||||||
_kernelSupport.update {
|
|
||||||
kernelSupport
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onToggleRestartAtBoot() = viewModelScope.launch {
|
fun onToggleRestartAtBoot() = viewModelScope.launch {
|
||||||
|
@ -231,6 +236,17 @@ constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private suspend fun isRooted(): Boolean {
|
||||||
|
return try {
|
||||||
|
withContext(ioDispatcher) {
|
||||||
|
rootShell.get().start()
|
||||||
|
}
|
||||||
|
true
|
||||||
|
} catch (_: Exception) {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private suspend fun requestRoot(): Result<Unit> {
|
private suspend fun requestRoot(): Result<Unit> {
|
||||||
return withContext(ioDispatcher) {
|
return withContext(ioDispatcher) {
|
||||||
kotlin.runCatching {
|
kotlin.runCatching {
|
||||||
|
|
|
@ -7,6 +7,7 @@ import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.zaneschepke.logcatter.LogReader
|
import com.zaneschepke.logcatter.LogReader
|
||||||
import com.zaneschepke.logcatter.model.LogMessage
|
import com.zaneschepke.logcatter.model.LogMessage
|
||||||
|
import com.zaneschepke.wireguardautotunnel.R
|
||||||
import com.zaneschepke.wireguardautotunnel.module.IoDispatcher
|
import com.zaneschepke.wireguardautotunnel.module.IoDispatcher
|
||||||
import com.zaneschepke.wireguardautotunnel.module.MainDispatcher
|
import com.zaneschepke.wireguardautotunnel.module.MainDispatcher
|
||||||
import com.zaneschepke.wireguardautotunnel.util.Constants
|
import com.zaneschepke.wireguardautotunnel.util.Constants
|
||||||
|
@ -15,7 +16,6 @@ import com.zaneschepke.wireguardautotunnel.util.extensions.launchShareFile
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import kotlinx.coroutines.CoroutineDispatcher
|
import kotlinx.coroutines.CoroutineDispatcher
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import com.zaneschepke.wireguardautotunnel.R
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package com.zaneschepke.wireguardautotunnel.util.extensions
|
package com.zaneschepke.wireguardautotunnel.util.extensions
|
||||||
|
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import com.wireguard.android.util.RootShell
|
||||||
import com.wireguard.config.Peer
|
import com.wireguard.config.Peer
|
||||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.HandshakeStatus
|
import com.zaneschepke.wireguardautotunnel.service.tunnel.HandshakeStatus
|
||||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.statistics.TunnelStatistics
|
import com.zaneschepke.wireguardautotunnel.service.tunnel.statistics.TunnelStatistics
|
||||||
|
@ -78,3 +79,9 @@ fun Config.toWgQuickString(): String {
|
||||||
}
|
}
|
||||||
return lines.joinToString(System.lineSeparator())
|
return lines.joinToString(System.lineSeparator())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun RootShell.getCurrentWifiName(): String? {
|
||||||
|
val response = mutableListOf<String>()
|
||||||
|
this.run(response, "dumpsys wifi | grep -o \"SSID: [^,]*\" | cut -d ' ' -f2- | tr -d '\"'")
|
||||||
|
return response.lastOrNull()
|
||||||
|
}
|
||||||
|
|
|
@ -21,7 +21,7 @@ pinLockCompose = "1.0.4"
|
||||||
roomVersion = "2.6.1"
|
roomVersion = "2.6.1"
|
||||||
timber = "5.0.1"
|
timber = "5.0.1"
|
||||||
tunnel = "1.2.1"
|
tunnel = "1.2.1"
|
||||||
androidGradlePlugin = "8.7.0"
|
androidGradlePlugin = "8.7.1"
|
||||||
kotlin = "2.0.21"
|
kotlin = "2.0.21"
|
||||||
ksp = "2.0.21-1.0.25"
|
ksp = "2.0.21-1.0.25"
|
||||||
composeBom = "2024.09.03"
|
composeBom = "2024.09.03"
|
||||||
|
|
Loading…
Reference in New Issue