diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/foreground/AutoTunnelService.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/foreground/AutoTunnelService.kt index 2d5926a..cfbc88d 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/foreground/AutoTunnelService.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/foreground/AutoTunnelService.kt @@ -2,11 +2,13 @@ package com.zaneschepke.wireguardautotunnel.service.foreground import android.content.Context import android.content.Intent +import android.net.NetworkCapabilities import android.os.IBinder import android.os.PowerManager import androidx.core.app.ServiceCompat import androidx.lifecycle.LifecycleService import androidx.lifecycle.lifecycleScope +import com.wireguard.android.util.RootShell import com.zaneschepke.wireguardautotunnel.R import com.zaneschepke.wireguardautotunnel.data.domain.Settings 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.util.Constants 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.isReachable import com.zaneschepke.wireguardautotunnel.util.extensions.onNotRunning @@ -46,6 +49,9 @@ import javax.inject.Provider class AutoTunnelService : LifecycleService() { private val foregroundId = 122 + @Inject + lateinit var rootShell: Provider + @Inject lateinit var wifiService: NetworkService @@ -397,6 +403,14 @@ class AutoTunnelService : LifecycleService() { } } + private fun updateWifi(connected: Boolean) { + autoTunnelStateFlow.update { + it.copy( + isWifiConnected = connected, + ) + } + } + private suspend fun watchForEthernetConnectivityChanges() { withContext(ioDispatcher) { Timber.i("Starting ethernet data watcher") @@ -428,21 +442,13 @@ class AutoTunnelService : LifecycleService() { when (status) { is NetworkStatus.Available -> { Timber.i("Gained Wi-Fi connection") - autoTunnelStateFlow.update { - it.copy( - isWifiConnected = true, - ) - } + updateWifi(true) } is NetworkStatus.CapabilitiesChanged -> { Timber.i("Wifi capabilities changed") - autoTunnelStateFlow.update { - it.copy( - isWifiConnected = true, - ) - } - val ssid = wifiService.getNetworkName(status.networkCapabilities) + updateWifi(true) + val ssid = getWifiSSID(status.networkCapabilities) ssid?.let { name -> if (name.contains(Constants.UNREADABLE_SSID)) { Timber.w("SSID unreadable: missing permissions") @@ -459,11 +465,7 @@ class AutoTunnelService : LifecycleService() { } is NetworkStatus.Unavailable -> { - autoTunnelStateFlow.update { - it.copy( - isWifiConnected = false, - ) - } + updateWifi(false) 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? { return appDataRepository.tunnels.findByMobileDataTunnel().firstOrNull() } diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/SettingsScreen.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/SettingsScreen.kt index 4f4e5ae..6e0d5a9 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/SettingsScreen.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/SettingsScreen.kt @@ -1,7 +1,6 @@ package com.zaneschepke.wireguardautotunnel.ui.screens.settings import android.Manifest -import android.app.Activity import android.content.Context.POWER_SERVICE import android.content.Intent import android.net.Uri @@ -101,7 +100,7 @@ fun SettingsScreen(viewModel: SettingsViewModel = hiltViewModel(), appViewModel: val interactionSource = remember { MutableInteractionSource() } val isRunningOnTv = context.isRunningOnTv() - val kernelSupport by viewModel.kernelSupport.collectAsStateWithLifecycle() + val settingsUiState by viewModel.uiState.collectAsStateWithLifecycle() val fineLocationState = rememberPermissionState(Manifest.permission.ACCESS_FINE_LOCATION) var currentText by remember { mutableStateOf("") } @@ -115,10 +114,6 @@ fun SettingsScreen(viewModel: SettingsViewModel = hiltViewModel(), appViewModel: val screenPadding = 5.dp val fillMaxWidth = .85f - LaunchedEffect(Unit) { - viewModel.checkKernelSupport() - } - LaunchedEffect(uiState.settings.trustedNetworkSSIDs) { currentText = "" } @@ -127,7 +122,7 @@ fun SettingsScreen(viewModel: SettingsViewModel = hiltViewModel(), appViewModel: rememberLauncherForActivityResult( ActivityResultContracts.StartActivityForResult(), ) { result: ActivityResult -> - if (result.resultCode == Activity.RESULT_OK) { + if (result.resultCode == RESULT_OK) { result.data // 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( horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Top, @@ -311,18 +318,8 @@ fun SettingsScreen(viewModel: SettingsViewModel = hiltViewModel(), appViewModel: checked = uiState.settings.isTunnelOnWifiEnabled, padding = screenPadding, onCheckChanged = { checked -> - if (!checked) viewModel.onToggleTunnelOnWifi() - if (checked) { - when (false) { - isBackgroundLocationGranted -> showLocationDialog = true - fineLocationState.status.isGranted -> showLocationDialog = true - viewModel.isLocationEnabled(context) -> - showLocationServicesAlertDialog = true - else -> { - viewModel.onToggleTunnelOnWifi() - } - } - } + if (!checked || settingsUiState.isRooted) viewModel.onToggleTunnelOnWifi().also { return@ConfigurationToggle } + onAutoTunnelWifiChecked() }, modifier = if (uiState.settings.isAutoTunnelEnabled) { @@ -491,7 +488,7 @@ fun SettingsScreen(viewModel: SettingsViewModel = hiltViewModel(), appViewModel: uiState.settings.isAutoTunnelEnabled || uiState.settings.isAlwaysOnVpnEnabled || (uiState.vpnState.status == TunnelState.UP) || - !kernelSupport + !settingsUiState.isKernelAvailable ), checked = uiState.settings.isKernelEnabled, padding = screenPadding, diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/SettingsUiState.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/SettingsUiState.kt new file mode 100644 index 0000000..f2a3a68 --- /dev/null +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/SettingsUiState.kt @@ -0,0 +1,6 @@ +package com.zaneschepke.wireguardautotunnel.ui.screens.settings + +data class SettingsUiState( + val isRooted: Boolean = false, + val isKernelAvailable: Boolean = false, +) diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/SettingsViewModel.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/SettingsViewModel.kt index 7eaf6dd..6460095 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/SettingsViewModel.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/SettingsViewModel.kt @@ -13,13 +13,14 @@ import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository import com.zaneschepke.wireguardautotunnel.module.IoDispatcher import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager 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.StringValue import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted -import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch @@ -38,8 +39,16 @@ constructor( @IoDispatcher private val ioDispatcher: CoroutineDispatcher, ) : ViewModel() { - private val _kernelSupport = MutableStateFlow(false) - val kernelSupport = _kernelSupport.asStateFlow() + private val _uiState = MutableStateFlow(SettingsUiState()) + 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() .stateIn(viewModelScope, SharingStarted.Eagerly, Settings()) @@ -211,13 +220,9 @@ constructor( } } - fun checkKernelSupport() = viewModelScope.launch { - val kernelSupport = - withContext(ioDispatcher) { - WgQuickBackend.hasKernelSupport() - } - _kernelSupport.update { - kernelSupport + private suspend fun isKernelSupported(): Boolean { + return withContext(ioDispatcher) { + WgQuickBackend.hasKernelSupport() } } @@ -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 { return withContext(ioDispatcher) { kotlin.runCatching { diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/support/logs/LogsViewModel.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/support/logs/LogsViewModel.kt index 17df5fe..bac7515 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/support/logs/LogsViewModel.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/support/logs/LogsViewModel.kt @@ -7,6 +7,7 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.zaneschepke.logcatter.LogReader import com.zaneschepke.logcatter.model.LogMessage +import com.zaneschepke.wireguardautotunnel.R import com.zaneschepke.wireguardautotunnel.module.IoDispatcher import com.zaneschepke.wireguardautotunnel.module.MainDispatcher import com.zaneschepke.wireguardautotunnel.util.Constants @@ -15,7 +16,6 @@ import com.zaneschepke.wireguardautotunnel.util.extensions.launchShareFile import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.Job -import com.zaneschepke.wireguardautotunnel.R import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import timber.log.Timber diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/util/extensions/TunnelExtensions.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/util/extensions/TunnelExtensions.kt index c3513d5..8764bc4 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/util/extensions/TunnelExtensions.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/util/extensions/TunnelExtensions.kt @@ -1,6 +1,7 @@ package com.zaneschepke.wireguardautotunnel.util.extensions import androidx.compose.ui.graphics.Color +import com.wireguard.android.util.RootShell import com.wireguard.config.Peer import com.zaneschepke.wireguardautotunnel.service.tunnel.HandshakeStatus import com.zaneschepke.wireguardautotunnel.service.tunnel.statistics.TunnelStatistics @@ -78,3 +79,9 @@ fun Config.toWgQuickString(): String { } return lines.joinToString(System.lineSeparator()) } + +fun RootShell.getCurrentWifiName(): String? { + val response = mutableListOf() + this.run(response, "dumpsys wifi | grep -o \"SSID: [^,]*\" | cut -d ' ' -f2- | tr -d '\"'") + return response.lastOrNull() +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 1d0c734..fd18574 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -21,7 +21,7 @@ pinLockCompose = "1.0.4" roomVersion = "2.6.1" timber = "5.0.1" tunnel = "1.2.1" -androidGradlePlugin = "8.7.0" +androidGradlePlugin = "8.7.1" kotlin = "2.0.21" ksp = "2.0.21-1.0.25" composeBom = "2024.09.03"