diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 04618c5..f9ad73d 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -51,7 +51,7 @@ , + @Userspace userspaceBackend: Provider, + @Kernel kernelBackend: Provider, appDataRepository: AppDataRepository, @ApplicationScope applicationScope: CoroutineScope, @IoDispatcher ioDispatcher: CoroutineDispatcher diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/foreground/WireGuardConnectivityWatcherService.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/foreground/WireGuardConnectivityWatcherService.kt index 36ea1f0..bc9c493 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/foreground/WireGuardConnectivityWatcherService.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/foreground/WireGuardConnectivityWatcherService.kt @@ -390,19 +390,14 @@ class WireGuardConnectivityWatcherService : ForegroundService() { } watcherState.isMobileDataConditionMet() -> { - Timber.i("$autoTunnel - tunnel on on mobile data condition met") + Timber.i("$autoTunnel - tunnel on mobile data condition met") val mobileDataTunnel = getMobileDataTunnel() val tunnel = mobileDataTunnel ?: appDataRepository.getPrimaryOrFirstTunnel() - if (isTunnelDown()) return@collectLatest serviceManager.startVpnServiceForeground( - context, - tunnel?.id, - ) - if (tunnelConfig?.isMobileDataTunnel == false && mobileDataTunnel != null) { - Timber.i("$autoTunnel - tunnel connected on mobile data is not preferred condition met, switching to preferred") + if (isTunnelDown() || tunnelConfig?.isMobileDataTunnel == false) { serviceManager.startVpnServiceForeground( context, - mobileDataTunnel.id, + tunnel?.id, ) } } @@ -417,8 +412,8 @@ class WireGuardConnectivityWatcherService : ForegroundService() { tunnelConfig == null) { Timber.i("$autoTunnel - tunnel on ssid not associated with current tunnel condition met") getSsidTunnel(watcherState.currentNetworkSSID)?.let { - Timber.i("Found tunnel associated with this SSID, bringing tunnel up") - if (isTunnelDown()) serviceManager.startVpnServiceForeground( + Timber.i("Found tunnel associated with this SSID, bringing tunnel up: ${it.name}") + if (isTunnelDown() || tunnelConfig?.id != it.id) serviceManager.startVpnServiceForeground( context, it.id, ) diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/WireGuardTunnel.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/WireGuardTunnel.kt index f6704cb..c8edafe 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/WireGuardTunnel.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/WireGuardTunnel.kt @@ -27,13 +27,14 @@ import kotlinx.coroutines.withContext import org.amnezia.awg.backend.Tunnel import timber.log.Timber import javax.inject.Inject +import javax.inject.Provider class WireGuardTunnel @Inject constructor( - private val userspaceAmneziaBackend: org.amnezia.awg.backend.Backend, - @Userspace private val userspaceBackend: Backend, - @Kernel private val kernelBackend: Backend, + private val userspaceAmneziaBackend: Provider, + @Userspace private val userspaceBackend: Provider, + @Kernel private val kernelBackend: Provider, private val appDataRepository: AppDataRepository, @ApplicationScope private val applicationScope: CoroutineScope, @IoDispatcher private val ioDispatcher: CoroutineDispatcher @@ -44,7 +45,7 @@ constructor( private var statsJob: Job? = null - private var backend: Backend = userspaceBackend + private lateinit var backend: Backend; private var backendIsWgUserspace = true @@ -52,15 +53,16 @@ constructor( init { applicationScope.launch(ioDispatcher) { + backend = userspaceBackend.get() appDataRepository.settings.getSettingsFlow().collect { if (it.isKernelEnabled && (backendIsWgUserspace || backendIsAmneziaUserspace)) { Timber.i("Setting kernel backend") - backend = kernelBackend + backend = kernelBackend.get() backendIsWgUserspace = false backendIsAmneziaUserspace = false } else if (!it.isKernelEnabled && !it.isAmneziaEnabled && !backendIsWgUserspace) { Timber.i("Setting WireGuard userspace backend") - backend = userspaceBackend + backend = userspaceBackend.get() backendIsWgUserspace = true backendIsAmneziaUserspace = false } else if (it.isAmneziaEnabled && !backendIsAmneziaUserspace) { @@ -81,7 +83,8 @@ constructor( TunnelConfig.configFromAmQuick(it.wgQuick) } } - val state = userspaceAmneziaBackend.setState(this, tunnelState.toAmState(), config) + val state = + userspaceAmneziaBackend.get().setState(this, tunnelState.toAmState(), config) TunnelState.from(state) } else { Timber.i("Using Wg backend") @@ -156,7 +159,9 @@ constructor( } override fun getState(): TunnelState { - return if (backendIsAmneziaUserspace) TunnelState.from(userspaceAmneziaBackend.getState(this)) + return if (backendIsAmneziaUserspace) TunnelState.from( + userspaceAmneziaBackend.get().getState(this), + ) else TunnelState.from(backend.getState(this)) } @@ -187,7 +192,11 @@ constructor( private fun startTunnelStatisticsJob() = applicationScope.launch(ioDispatcher) { while (true) { if (backendIsAmneziaUserspace) { - emitBackendStatistics(AmneziaStatistics(userspaceAmneziaBackend.getStatistics(this@WireGuardTunnel))) + emitBackendStatistics( + AmneziaStatistics( + userspaceAmneziaBackend.get().getStatistics(this@WireGuardTunnel), + ), + ) } else { emitBackendStatistics(WireGuardStatistics(backend.getStatistics(this@WireGuardTunnel))) } 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 e9e60f3..fbfa9c8 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 @@ -44,6 +44,7 @@ import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.SideEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -70,7 +71,6 @@ import androidx.navigation.NavController import com.google.accompanist.permissions.ExperimentalPermissionsApi import com.google.accompanist.permissions.isGranted import com.google.accompanist.permissions.rememberPermissionState -import com.wireguard.android.backend.WgQuickBackend import com.zaneschepke.wireguardautotunnel.R import com.zaneschepke.wireguardautotunnel.WireGuardAutoTunnel import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig @@ -106,6 +106,7 @@ fun SettingsScreen( val pinExists = remember { mutableStateOf(PinManager.pinExists()) } val uiState by viewModel.uiState.collectAsStateWithLifecycle() + val kernelSupport by viewModel.kernelSupport.collectAsStateWithLifecycle() val fineLocationState = rememberPermissionState(Manifest.permission.ACCESS_FINE_LOCATION) var currentText by remember { mutableStateOf("") } @@ -117,6 +118,10 @@ fun SettingsScreen( val screenPadding = 5.dp val fillMaxWidth = .85f + LaunchedEffect(Unit) { + viewModel.checkKernelSupport() + } + val startForResult = rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) { result: ActivityResult -> if (result.resultCode == Activity.RESULT_OK) { @@ -591,7 +596,7 @@ fun SettingsScreen( viewModel.onToggleAmnezia() }, ) - if (WgQuickBackend.hasKernelSupport()) { + if (kernelSupport) { ConfigurationToggle( stringResource(R.string.use_kernel), enabled = @@ -601,8 +606,10 @@ fun SettingsScreen( checked = uiState.settings.isKernelEnabled, padding = screenPadding, onCheckChanged = { - viewModel.onToggleKernelMode().onFailure { - appViewModel.showSnackbarMessage(it.getMessage(context)) + scope.launch { + viewModel.onToggleKernelMode().onFailure { + appViewModel.showSnackbarMessage(it.getMessage(context)) + } } }, ) 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 index f231c86..39b5db3 100644 --- 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 @@ -9,5 +9,5 @@ data class SettingsUiState( val tunnels: List = emptyList(), val vpnState: VpnState = VpnState(), val isLocationDisclosureShown: Boolean = true, - val isBatteryOptimizeDisableShown: Boolean = false + val isBatteryOptimizeDisableShown: 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 7b1d56a..9be2d67 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 @@ -5,23 +5,31 @@ import android.location.LocationManager import androidx.core.location.LocationManagerCompat import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.wireguard.android.backend.WgQuickBackend import com.wireguard.android.util.RootShell import com.zaneschepke.wireguardautotunnel.WireGuardAutoTunnel import com.zaneschepke.wireguardautotunnel.data.domain.Settings import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository +import com.zaneschepke.wireguardautotunnel.module.IoDispatcher import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager import com.zaneschepke.wireguardautotunnel.service.tunnel.VpnService import com.zaneschepke.wireguardautotunnel.util.Constants import com.zaneschepke.wireguardautotunnel.util.FileUtils import com.zaneschepke.wireguardautotunnel.util.WgTunnelExceptions 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.combine import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import timber.log.Timber import java.io.File import javax.inject.Inject +import javax.inject.Provider @HiltViewModel class SettingsViewModel @@ -29,11 +37,15 @@ class SettingsViewModel constructor( private val appDataRepository: AppDataRepository, private val serviceManager: ServiceManager, - private val rootShell: RootShell, + private val rootShell: Provider, private val fileUtils: FileUtils, + @IoDispatcher private val ioDispatcher: CoroutineDispatcher, vpnService: VpnService ) : ViewModel() { + private val _kernelSupport = MutableStateFlow(false) + val kernelSupport = _kernelSupport.asStateFlow() + val uiState = combine( appDataRepository.settings.getSettingsFlow(), @@ -181,27 +193,29 @@ constructor( ) } - fun onToggleKernelMode(): Result { - if (!uiState.value.settings.isKernelEnabled) { - try { - rootShell.start() - Timber.i("Root shell accepted!") - saveSettings( - uiState.value.settings.copy( - isKernelEnabled = true, - isAmneziaEnabled = false, - ), - ) + suspend fun onToggleKernelMode(): Result { + return withContext(ioDispatcher) { + if (!uiState.value.settings.isKernelEnabled) { + try { + rootShell.get().start() + Timber.i("Root shell accepted!") + saveSettings( + uiState.value.settings.copy( + isKernelEnabled = true, + isAmneziaEnabled = false, + ), + ) - } catch (e: RootShell.RootShellException) { - Timber.e(e) + } catch (e: RootShell.RootShellException) { + Timber.e(e) + saveKernelMode(on = false) + return@withContext Result.failure(WgTunnelExceptions.RootDenied()) + } + } else { saveKernelMode(on = false) - return Result.failure(WgTunnelExceptions.RootDenied()) } - } else { - saveKernelMode(on = false) + Result.success(Unit) } - return Result.success(Unit) } fun onToggleRestartOnPing() = viewModelScope.launch { @@ -211,4 +225,13 @@ constructor( ), ) } + + fun checkKernelSupport() = viewModelScope.launch { + val kernelSupport = withContext(ioDispatcher) { + WgQuickBackend.hasKernelSupport() + } + _kernelSupport.update { + kernelSupport + } + } } diff --git a/buildSrc/src/main/kotlin/Constants.kt b/buildSrc/src/main/kotlin/Constants.kt index aa2dcf0..8484b92 100644 --- a/buildSrc/src/main/kotlin/Constants.kt +++ b/buildSrc/src/main/kotlin/Constants.kt @@ -1,12 +1,12 @@ object Constants { - const val VERSION_NAME = "3.4.5" + const val VERSION_NAME = "3.4.6" const val JVM_TARGET = "17" - const val VERSION_CODE = 34500 + const val VERSION_CODE = 34600 const val TARGET_SDK = 34 const val MIN_SDK = 26 const val APP_ID = "com.zaneschepke.wireguardautotunnel" const val APP_NAME = "wgtunnel" - const val COMPOSE_COMPILER_EXTENSION_VERSION = "1.5.11" + const val COMPOSE_COMPILER_EXTENSION_VERSION = "1.5.14" const val STORE_PASS_VAR = "SIGNING_STORE_PASSWORD" diff --git a/fastlane/metadata/android/en-US/changelogs/34600.txt b/fastlane/metadata/android/en-US/changelogs/34600.txt new file mode 100644 index 0000000..6666ca4 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/34600.txt @@ -0,0 +1,4 @@ +What's new: +- Fixes auto tunneling bugs +- Fixes android backup bug +- Bump versions \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 93d8ceb..2128c20 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -3,7 +3,7 @@ accompanist = "0.34.0" activityCompose = "1.9.0" amneziawgAndroid = "1.2.0" androidx-junit = "1.1.5" -appcompat = "1.6.1" +appcompat = "1.7.0" biometricKtx = "1.2.0-alpha05" coreGoogleShortcuts = "1.1.0" coreKtx = "1.13.1" @@ -23,14 +23,14 @@ roomVersion = "2.6.1" timber = "5.0.1" tunnel = "1.0.20230706" androidGradlePlugin = "8.4.1" -kotlin = "1.9.23" -ksp = "1.9.23-1.0.19" +kotlin = "1.9.24" +ksp = "1.9.24-1.0.20" composeBom = "2024.05.00" compose = "1.6.7" zxingAndroidEmbedded = "4.3.0" #plugins -gradlePlugins-kotlinxSerialization = "1.9.23" +gradlePlugins-kotlinxSerialization = "1.9.24" material = "1.12.0"