fix: crashing and pin lock

Re-enabled pin lock after disablement from crashes.

Fixed crashing issues.

Closes #237

Fixed bug where pin lock will no longer initialize if never not enabled/in use.

Improved tunnel control tile performance.

Fix bad address crash when user enters bad addresses into allowedIps.
Closes #229

Disabled auto rotate
Closes #212

Add restart on boot toggle to make restart of services feature more obvious and configurable.
This commit is contained in:
Zane Schepke 2024-06-18 23:08:15 -04:00
parent 29616f8325
commit 79b5b039b0
25 changed files with 275 additions and 170 deletions

View File

@ -69,15 +69,16 @@ and on while on different networks. This app was created to offer a free solutio
Want updates faster? Want updates faster?
Check out my personal [fdroid repository](https://github.com/zaneschepke/fdroid) to get updates the moment they are released. Check out my personal [fdroid repository](https://github.com/zaneschepke/fdroid) to get updates the
moment they are released.
## Docs ## Docs
Information about features, behaviors, and answers to common questions can be found in the app [documentation](https://zaneschepke.com/wgtunnel-docs/overview.html). Information about features, behaviors, and answers to common questions can be found in the
app [documentation](https://zaneschepke.com/wgtunnel-docs/overview.html).
The repository for these docs can be found [here](https://github.com/zaneschepke/wgtunnel-docs). The repository for these docs can be found [here](https://github.com/zaneschepke/wgtunnel-docs).
## Translation ## Translation
This app is using [Weblate](https://weblate.org) to assist with translations. This app is using [Weblate](https://weblate.org) to assist with translations.

View File

@ -112,9 +112,6 @@ android {
} }
create("general") { create("general") {
dimension = Constants.TYPE dimension = Constants.TYPE
if (BuildHelper.isReleaseBuild(gradle) && BuildHelper.isGeneralFlavor(gradle)) {
//any plugins general specific
}
} }
} }
compileOptions { compileOptions {
@ -211,4 +208,7 @@ dependencies {
// shortcuts // shortcuts
implementation(libs.androidx.core) implementation(libs.androidx.core)
implementation(libs.androidx.core.google.shortcuts) implementation(libs.androidx.core.google.shortcuts)
// splash
implementation(libs.androidx.core.splashscreen)
} }

View File

@ -60,31 +60,36 @@
android:label="@string/app_name" android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round" android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/Theme.WireguardAutoTunnel" android:theme="@style/Theme.AppSplashScreen"
tools:targetApi="tiramisu"> tools:targetApi="tiramisu">
<activity <activity
android:name=".ui.MainActivity" android:name=".ui.SplashActivity"
android:exported="true" android:exported="true"
android:theme="@style/Theme.WireguardAutoTunnel"> android:theme="@style/Theme.AppSplashScreen">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
<category android:name="android.intent.category.LEANBACK_LAUNCHER" /> <category android:name="android.intent.category.LEANBACK_LAUNCHER" />
<action android:name="android.service.quicksettings.action.QS_TILE_PREFERENCES" />
</intent-filter> </intent-filter>
<meta-data <meta-data
android:name="android.app.shortcuts" android:name="android.app.shortcuts"
android:resource="@xml/shortcuts" /> android:resource="@xml/shortcuts" />
</activity> </activity>
<activity <activity
android:name=".ui.CaptureActivityPortrait" android:name=".ui.MainActivity"
android:screenOrientation="fullSensor" android:exported="true"
android:stateNotNeeded="true" android:screenOrientation="portrait"
android:theme="@style/zxing_CaptureTheme" android:theme="@style/Theme.WireguardAutoTunnel">
android:windowSoftInputMode="stateAlwaysHidden" <intent-filter>
tools:ignore="DiscouragedApi" /> <action android:name="android.service.quicksettings.action.QS_TILE_PREFERENCES" />
</intent-filter>
</activity>
<activity
android:name="com.journeyapps.barcodescanner.CaptureActivity"
android:screenOrientation="portrait"
tools:replace="screenOrientation" />
<activity <activity
android:name=".service.shortcut.ShortcutsActivity" android:name=".service.shortcut.ShortcutsActivity"
android:enabled="true" android:enabled="true"

View File

@ -6,34 +6,15 @@ import android.content.pm.PackageManager
import android.os.StrictMode import android.os.StrictMode
import android.os.StrictMode.ThreadPolicy import android.os.StrictMode.ThreadPolicy
import android.service.quicksettings.TileService import android.service.quicksettings.TileService
import com.zaneschepke.logcatter.LocalLogCollector
import com.zaneschepke.wireguardautotunnel.module.ApplicationScope
import com.zaneschepke.wireguardautotunnel.module.IoDispatcher
import com.zaneschepke.wireguardautotunnel.service.tile.AutoTunnelControlTile import com.zaneschepke.wireguardautotunnel.service.tile.AutoTunnelControlTile
import com.zaneschepke.wireguardautotunnel.service.tile.TunnelControlTile import com.zaneschepke.wireguardautotunnel.service.tile.TunnelControlTile
import com.zaneschepke.wireguardautotunnel.util.ReleaseTree import com.zaneschepke.wireguardautotunnel.util.ReleaseTree
import dagger.hilt.android.HiltAndroidApp import dagger.hilt.android.HiltAndroidApp
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import timber.log.Timber import timber.log.Timber
import xyz.teamgravity.pin_lock_compose.PinManager
import javax.inject.Inject
@HiltAndroidApp @HiltAndroidApp
class WireGuardAutoTunnel : Application() { class WireGuardAutoTunnel : Application() {
@Inject
lateinit var localLogCollector: LocalLogCollector
@Inject
@ApplicationScope
lateinit var applicationScope: CoroutineScope
@Inject
@IoDispatcher
lateinit var ioDispatcher: CoroutineDispatcher
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
instance = this instance = this
@ -48,11 +29,6 @@ class WireGuardAutoTunnel : Application() {
.build(), .build(),
) )
} else Timber.plant(ReleaseTree()) } else Timber.plant(ReleaseTree())
applicationScope.launch(ioDispatcher) {
//TODO disable pin lock for now
//PinManager.initialize(this@WireGuardAutoTunnel)
if (!isRunningOnAndroidTv()) localLogCollector.start()
}
} }
companion object { companion object {

View File

@ -28,6 +28,7 @@ class DataStoreManager(
booleanPreferencesKey("TUNNEL_RUNNING_FROM_MANUAL_START") booleanPreferencesKey("TUNNEL_RUNNING_FROM_MANUAL_START")
val ACTIVE_TUNNEL = intPreferencesKey("ACTIVE_TUNNEL") val ACTIVE_TUNNEL = intPreferencesKey("ACTIVE_TUNNEL")
val CURRENT_SSID = stringPreferencesKey("CURRENT_SSID") val CURRENT_SSID = stringPreferencesKey("CURRENT_SSID")
val IS_PIN_LOCK_ENABLED = booleanPreferencesKey("PIN_LOCK_ENABLED")
} }
// preferences // preferences

View File

@ -1,14 +1,16 @@
package com.zaneschepke.wireguardautotunnel.data.domain package com.zaneschepke.wireguardautotunnel.data.domain
data class GeneralState( data class GeneralState(
val locationDisclosureShown: Boolean = LOCATION_DISCLOSURE_SHOWN_DEFAULT, val isLocationDisclosureShown: Boolean = LOCATION_DISCLOSURE_SHOWN_DEFAULT,
val batteryOptimizationDisableShown: Boolean = BATTERY_OPTIMIZATION_DISABLE_SHOWN_DEFAULT, val isBatteryOptimizationDisableShown: Boolean = BATTERY_OPTIMIZATION_DISABLE_SHOWN_DEFAULT,
val tunnelRunningFromManualStart: Boolean = TUNNELING_RUNNING_FROM_MANUAL_START_DEFAULT, val isTunnelRunningFromManualStart: Boolean = TUNNELING_RUNNING_FROM_MANUAL_START_DEFAULT,
val isPinLockEnabled: Boolean = PIN_LOCK_ENABLED_DEFAULT,
val activeTunnelId: Int? = null val activeTunnelId: Int? = null
) { ) {
companion object { companion object {
const val LOCATION_DISCLOSURE_SHOWN_DEFAULT = false const val LOCATION_DISCLOSURE_SHOWN_DEFAULT = false
const val BATTERY_OPTIMIZATION_DISABLE_SHOWN_DEFAULT = false const val BATTERY_OPTIMIZATION_DISABLE_SHOWN_DEFAULT = false
const val TUNNELING_RUNNING_FROM_MANUAL_START_DEFAULT = false const val TUNNELING_RUNNING_FROM_MANUAL_START_DEFAULT = false
const val PIN_LOCK_ENABLED_DEFAULT = false
} }
} }

View File

@ -7,6 +7,9 @@ interface AppStateRepository {
suspend fun isLocationDisclosureShown(): Boolean suspend fun isLocationDisclosureShown(): Boolean
suspend fun setLocationDisclosureShown(shown: Boolean) suspend fun setLocationDisclosureShown(shown: Boolean)
suspend fun isPinLockEnabled(): Boolean
suspend fun setPinLockEnabled(enabled: Boolean)
suspend fun isBatteryOptimizationDisableShown(): Boolean suspend fun isBatteryOptimizationDisableShown(): Boolean
suspend fun setBatteryOptimizationDisableShown(shown: Boolean) suspend fun setBatteryOptimizationDisableShown(shown: Boolean)

View File

@ -17,6 +17,15 @@ class DataStoreAppStateRepository(private val dataStoreManager: DataStoreManager
dataStoreManager.saveToDataStore(DataStoreManager.LOCATION_DISCLOSURE_SHOWN, shown) dataStoreManager.saveToDataStore(DataStoreManager.LOCATION_DISCLOSURE_SHOWN, shown)
} }
override suspend fun isPinLockEnabled(): Boolean {
return dataStoreManager.getFromStore(DataStoreManager.IS_PIN_LOCK_ENABLED)
?: GeneralState.PIN_LOCK_ENABLED_DEFAULT
}
override suspend fun setPinLockEnabled(enabled: Boolean) {
dataStoreManager.saveToDataStore(DataStoreManager.IS_PIN_LOCK_ENABLED, enabled)
}
override suspend fun isBatteryOptimizationDisableShown(): Boolean { override suspend fun isBatteryOptimizationDisableShown(): Boolean {
return dataStoreManager.getFromStore(DataStoreManager.BATTERY_OPTIMIZE_DISABLE_SHOWN) return dataStoreManager.getFromStore(DataStoreManager.BATTERY_OPTIMIZE_DISABLE_SHOWN)
?: GeneralState.BATTERY_OPTIMIZATION_DISABLE_SHOWN_DEFAULT ?: GeneralState.BATTERY_OPTIMIZATION_DISABLE_SHOWN_DEFAULT
@ -65,11 +74,13 @@ class DataStoreAppStateRepository(private val dataStoreManager: DataStoreManager
prefs?.let { pref -> prefs?.let { pref ->
try { try {
GeneralState( GeneralState(
locationDisclosureShown = pref[DataStoreManager.LOCATION_DISCLOSURE_SHOWN] isLocationDisclosureShown = pref[DataStoreManager.LOCATION_DISCLOSURE_SHOWN]
?: GeneralState.LOCATION_DISCLOSURE_SHOWN_DEFAULT, ?: GeneralState.LOCATION_DISCLOSURE_SHOWN_DEFAULT,
batteryOptimizationDisableShown = pref[DataStoreManager.BATTERY_OPTIMIZE_DISABLE_SHOWN] isBatteryOptimizationDisableShown = pref[DataStoreManager.BATTERY_OPTIMIZE_DISABLE_SHOWN]
?: GeneralState.BATTERY_OPTIMIZATION_DISABLE_SHOWN_DEFAULT, ?: GeneralState.BATTERY_OPTIMIZATION_DISABLE_SHOWN_DEFAULT,
tunnelRunningFromManualStart = pref[DataStoreManager.TUNNEL_RUNNING_FROM_MANUAL_START] isTunnelRunningFromManualStart = pref[DataStoreManager.TUNNEL_RUNNING_FROM_MANUAL_START]
?: GeneralState.TUNNELING_RUNNING_FROM_MANUAL_START_DEFAULT,
isPinLockEnabled = pref[DataStoreManager.IS_PIN_LOCK_ENABLED]
?: GeneralState.TUNNELING_RUNNING_FROM_MANUAL_START_DEFAULT, ?: GeneralState.TUNNELING_RUNNING_FROM_MANUAL_START_DEFAULT,
) )
} catch (e: IllegalArgumentException) { } catch (e: IllegalArgumentException) {

View File

@ -4,9 +4,11 @@ import android.content.BroadcastReceiver
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
import com.zaneschepke.wireguardautotunnel.module.ApplicationScope
import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager
import com.zaneschepke.wireguardautotunnel.util.goAsync
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
@ -19,27 +21,36 @@ class BootReceiver : BroadcastReceiver() {
@Inject @Inject
lateinit var serviceManager: ServiceManager lateinit var serviceManager: ServiceManager
override fun onReceive(context: Context?, intent: Intent?) = goAsync { @Inject
if (Intent.ACTION_BOOT_COMPLETED != intent?.action) return@goAsync @ApplicationScope
lateinit var applicationScope: CoroutineScope
override fun onReceive(context: Context?, intent: Intent?) {
if (Intent.ACTION_BOOT_COMPLETED != intent?.action) return
context?.run { context?.run {
val settings = appDataRepository.settings.getSettings() applicationScope.launch {
if (settings.isAutoTunnelEnabled) { val settings = appDataRepository.settings.getSettings()
Timber.i("Starting watcher service from boot") if(settings.isRestoreOnBootEnabled) {
serviceManager.startWatcherServiceForeground(context) if (settings.isAutoTunnelEnabled) {
} Timber.i("Starting watcher service from boot")
if (appDataRepository.appState.isTunnelRunningFromManualStart()) { serviceManager.startWatcherServiceForeground(context)
appDataRepository.appState.getActiveTunnelId()?.let { }
Timber.i("Starting tunnel that was active before reboot") if (appDataRepository.appState.isTunnelRunningFromManualStart()) {
serviceManager.startVpnServiceForeground( appDataRepository.appState.getActiveTunnelId()?.let {
context, Timber.i("Starting tunnel that was active before reboot")
appDataRepository.tunnels.getById(it)?.id, serviceManager.startVpnServiceForeground(
) context,
appDataRepository.tunnels.getById(it)?.id,
)
return@launch
}
}
if (settings.isAlwaysOnVpnEnabled) {
Timber.i("Starting vpn service from boot AOVPN")
serviceManager.startVpnServiceForeground(context)
}
} }
} }
if (settings.isAlwaysOnVpnEnabled) {
Timber.i("Starting vpn service from boot AOVPN")
serviceManager.startVpnServiceForeground(context)
}
} }
} }
} }

View File

@ -4,12 +4,14 @@ import android.content.BroadcastReceiver
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import com.zaneschepke.wireguardautotunnel.data.repository.SettingsRepository import com.zaneschepke.wireguardautotunnel.data.repository.SettingsRepository
import com.zaneschepke.wireguardautotunnel.module.ApplicationScope
import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager
import com.zaneschepke.wireguardautotunnel.util.Constants import com.zaneschepke.wireguardautotunnel.util.Constants
import com.zaneschepke.wireguardautotunnel.util.goAsync
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.cancel import kotlinx.coroutines.cancel
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
@ -21,16 +23,22 @@ class NotificationActionReceiver : BroadcastReceiver() {
@Inject @Inject
lateinit var serviceManager: ServiceManager lateinit var serviceManager: ServiceManager
override fun onReceive(context: Context, intent: Intent?) = goAsync { @Inject
try { @ApplicationScope
//TODO fix for manual start changes when enabled lateinit var applicationScope: CoroutineScope
serviceManager.stopVpnServiceForeground(context)
delay(Constants.TOGGLE_TUNNEL_DELAY) override fun onReceive(context: Context, intent: Intent?) {
serviceManager.startVpnServiceForeground(context) applicationScope.launch {
} catch (e: Exception) { try {
Timber.e(e) //TODO fix for manual start changes when enabled
} finally { serviceManager.stopVpnServiceForeground(context)
cancel() delay(Constants.TOGGLE_TUNNEL_DELAY)
serviceManager.startVpnServiceForeground(context)
} catch (e: Exception) {
Timber.e(e)
} finally {
cancel()
}
} }
} }
} }

View File

@ -11,7 +11,6 @@ import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelState
import com.zaneschepke.wireguardautotunnel.service.tunnel.VpnService import com.zaneschepke.wireguardautotunnel.service.tunnel.VpnService
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.cancel import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import timber.log.Timber import timber.log.Timber
@ -35,32 +34,27 @@ class TunnelControlTile : TileService() {
private var manualStartConfig: TunnelConfig? = null private var manualStartConfig: TunnelConfig? = null
private var job: Job? = null;
override fun onStartListening() { override fun onStartListening() {
super.onStartListening() super.onStartListening()
Timber.d("On start listening called") Timber.d("On start listening called")
//TODO Fix this applicationScope.launch {
if (job == null || job?.isCancelled == true) job = applicationScope.launch { when (vpnService.getState()) {
vpnService.vpnState.collect { it -> TunnelState.UP -> {
when (it.status) { setActive()
TunnelState.UP -> { setTileDescription(vpnService.name)
setActive()
it.tunnelConfig?.name?.let { name -> setTileDescription(name) }
}
TunnelState.DOWN -> {
setInactive()
val config = appDataRepository.getStartTunnelConfig()?.also { config ->
manualStartConfig = config
} ?: appDataRepository.getPrimaryOrFirstTunnel()
config?.let {
setTileDescription(it.name)
} ?: setUnavailable()
}
else -> setInactive()
} }
TunnelState.DOWN -> {
setInactive()
val config = appDataRepository.getStartTunnelConfig()?.also { config ->
manualStartConfig = config
} ?: appDataRepository.getPrimaryOrFirstTunnel()
config?.let {
setTileDescription(it.name)
} ?: setUnavailable()
}
else -> setInactive()
} }
} }
} }

View File

@ -45,24 +45,19 @@ constructor(
private var statsJob: Job? = null private var statsJob: Job? = null
private lateinit var backend: Backend;
private var backendIsWgUserspace = true private var backendIsWgUserspace = true
private var backendIsAmneziaUserspace = false private var backendIsAmneziaUserspace = false
init { init {
applicationScope.launch(ioDispatcher) { applicationScope.launch(ioDispatcher) {
backend = userspaceBackend.get()
appDataRepository.settings.getSettingsFlow().collect { appDataRepository.settings.getSettingsFlow().collect {
if (it.isKernelEnabled && (backendIsWgUserspace || backendIsAmneziaUserspace)) { if (it.isKernelEnabled && (backendIsWgUserspace || backendIsAmneziaUserspace)) {
Timber.i("Setting kernel backend") Timber.i("Setting kernel backend")
backend = kernelBackend.get()
backendIsWgUserspace = false backendIsWgUserspace = false
backendIsAmneziaUserspace = false backendIsAmneziaUserspace = false
} else if (!it.isKernelEnabled && !it.isAmneziaEnabled && !backendIsWgUserspace) { } else if (!it.isKernelEnabled && !it.isAmneziaEnabled && !backendIsWgUserspace) {
Timber.i("Setting WireGuard userspace backend") Timber.i("Setting WireGuard userspace backend")
backend = userspaceBackend.get()
backendIsWgUserspace = true backendIsWgUserspace = true
backendIsAmneziaUserspace = false backendIsAmneziaUserspace = false
} else if (it.isAmneziaEnabled && !backendIsAmneziaUserspace) { } else if (it.isAmneziaEnabled && !backendIsAmneziaUserspace) {
@ -89,7 +84,7 @@ constructor(
} else { } else {
Timber.i("Using Wg backend") Timber.i("Using Wg backend")
val wgConfig = tunnelConfig?.let { TunnelConfig.configFromWgQuick(it.wgQuick) } val wgConfig = tunnelConfig?.let { TunnelConfig.configFromWgQuick(it.wgQuick) }
val state = backend.setState( val state = backend().setState(
this, this,
tunnelState.toWgState(), tunnelState.toWgState(),
wgConfig, wgConfig,
@ -102,6 +97,7 @@ constructor(
return withContext(ioDispatcher) { return withContext(ioDispatcher) {
try { try {
//TODO we need better error handling here //TODO we need better error handling here
// need to bubble up these errors to the UI
val config = tunnelConfig ?: appDataRepository.getPrimaryOrFirstTunnel() val config = tunnelConfig ?: appDataRepository.getPrimaryOrFirstTunnel()
if (config != null) { if (config != null) {
emitTunnelConfig(config) emitTunnelConfig(config)
@ -114,6 +110,22 @@ constructor(
} }
} }
private fun backend(): Backend {
return when {
backendIsWgUserspace -> {
userspaceBackend.get()
}
!backendIsWgUserspace && !backendIsAmneziaUserspace -> {
kernelBackend.get()
}
else -> {
userspaceBackend.get()
}
}
}
private fun emitTunnelState(state: TunnelState) { private fun emitTunnelState(state: TunnelState) {
_vpnState.tryEmit( _vpnState.tryEmit(
_vpnState.value.copy( _vpnState.value.copy(
@ -162,7 +174,7 @@ constructor(
return if (backendIsAmneziaUserspace) TunnelState.from( return if (backendIsAmneziaUserspace) TunnelState.from(
userspaceAmneziaBackend.get().getState(this), userspaceAmneziaBackend.get().getState(this),
) )
else TunnelState.from(backend.getState(this)) else TunnelState.from(backend().getState(this))
} }
override fun getName(): String { override fun getName(): String {
@ -198,7 +210,7 @@ constructor(
), ),
) )
} else { } else {
emitBackendStatistics(WireGuardStatistics(backend.getStatistics(this@WireGuardTunnel))) emitBackendStatistics(WireGuardStatistics(backend().getStatistics(this@WireGuardTunnel)))
} }
delay(Constants.VPN_STATISTIC_CHECK_INTERVAL) delay(Constants.VPN_STATISTIC_CHECK_INTERVAL)
} }

View File

@ -1,5 +0,0 @@
package com.zaneschepke.wireguardautotunnel.ui
import com.journeyapps.barcodescanner.CaptureActivity
class CaptureActivityPortrait : CaptureActivity()

View File

@ -43,7 +43,7 @@ import com.google.accompanist.permissions.isGranted
import com.google.accompanist.permissions.rememberPermissionState import com.google.accompanist.permissions.rememberPermissionState
import com.zaneschepke.wireguardautotunnel.R import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.WireGuardAutoTunnel import com.zaneschepke.wireguardautotunnel.WireGuardAutoTunnel
import com.zaneschepke.wireguardautotunnel.data.datastore.DataStoreManager import com.zaneschepke.wireguardautotunnel.data.repository.AppStateRepository
import com.zaneschepke.wireguardautotunnel.data.repository.SettingsRepository import com.zaneschepke.wireguardautotunnel.data.repository.SettingsRepository
import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager
import com.zaneschepke.wireguardautotunnel.ui.common.navigation.BottomNavBar import com.zaneschepke.wireguardautotunnel.ui.common.navigation.BottomNavBar
@ -61,14 +61,13 @@ import com.zaneschepke.wireguardautotunnel.util.StringValue
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import xyz.teamgravity.pin_lock_compose.PinManager
import javax.inject.Inject import javax.inject.Inject
@AndroidEntryPoint @AndroidEntryPoint
class MainActivity : AppCompatActivity() { class MainActivity : AppCompatActivity() {
@Inject @Inject
lateinit var dataStoreManager: DataStoreManager lateinit var appStateRepository: AppStateRepository
@Inject @Inject
lateinit var settingsRepository: SettingsRepository lateinit var settingsRepository: SettingsRepository
@ -82,17 +81,18 @@ class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
val isPinLockEnabled = intent.extras?.getBoolean(SplashActivity.IS_PIN_LOCK_ENABLED_KEY)
enableEdgeToEdge(navigationBarStyle = SystemBarStyle.dark(Color.Transparent.toArgb())) enableEdgeToEdge(navigationBarStyle = SystemBarStyle.dark(Color.Transparent.toArgb()))
// load preferences into memory and init data
lifecycleScope.launch { lifecycleScope.launch {
dataStoreManager.init()
WireGuardAutoTunnel.requestTunnelTileServiceStateUpdate() WireGuardAutoTunnel.requestTunnelTileServiceStateUpdate()
val settings = settingsRepository.getSettings() val settings = settingsRepository.getSettings()
if (settings.isAutoTunnelEnabled) { if (settings.isAutoTunnelEnabled) {
serviceManager.startWatcherService(application.applicationContext) serviceManager.startWatcherService(application.applicationContext)
} }
} }
setContent { setContent {
val appViewModel = hiltViewModel<AppViewModel>() val appViewModel = hiltViewModel<AppViewModel>()
val appUiState by appViewModel.appUiState.collectAsStateWithLifecycle() val appUiState by appViewModel.appUiState.collectAsStateWithLifecycle()
@ -201,12 +201,8 @@ class MainActivity : AppCompatActivity() {
) { padding -> ) { padding ->
NavHost( NavHost(
navController, navController,
startDestination = startDestination = (if (isPinLockEnabled == true) Screen.Lock.route else Screen.Main.route),
//TODO disable pin lock modifier = Modifier
//(if (PinManager.pinExists()) Screen.Lock.route else Screen.Main.route),
Screen.Main.route,
modifier =
Modifier
.padding(padding) .padding(padding)
.fillMaxSize(), .fillMaxSize(),
) { ) {

View File

@ -0,0 +1,68 @@
package com.zaneschepke.wireguardautotunnel.ui
import android.annotation.SuppressLint
import android.content.Intent
import android.os.Build
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import com.zaneschepke.logcatter.LocalLogCollector
import com.zaneschepke.wireguardautotunnel.WireGuardAutoTunnel
import com.zaneschepke.wireguardautotunnel.WireGuardAutoTunnel.Companion.isRunningOnAndroidTv
import com.zaneschepke.wireguardautotunnel.data.repository.AppStateRepository
import com.zaneschepke.wireguardautotunnel.module.ApplicationScope
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import xyz.teamgravity.pin_lock_compose.PinManager
import javax.inject.Inject
@SuppressLint("CustomSplashScreen")
@AndroidEntryPoint
class SplashActivity : ComponentActivity() {
@Inject
lateinit var appStateRepository: AppStateRepository
@Inject
lateinit var localLogCollector: LocalLogCollector
@Inject
@ApplicationScope
lateinit var applicationScope: CoroutineScope
override fun onCreate(savedInstanceState: Bundle?) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
val splashScreen = installSplashScreen()
splashScreen.setKeepOnScreenCondition { true }
}
super.onCreate(savedInstanceState)
applicationScope.launch {
if (!isRunningOnAndroidTv()) localLogCollector.start()
}
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.CREATED) {
val pinLockEnabled = appStateRepository.isPinLockEnabled()
if (pinLockEnabled) {
PinManager.initialize(WireGuardAutoTunnel.instance)
}
val intent = Intent(this@SplashActivity, MainActivity::class.java).apply {
putExtra(IS_PIN_LOCK_ENABLED_KEY, pinLockEnabled)
}
startActivity(intent)
finish()
}
}
}
companion object {
const val IS_PIN_LOCK_ENABLED_KEY = "is_pin_lock_enabled"
}
}

View File

@ -101,7 +101,6 @@ import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
import com.zaneschepke.wireguardautotunnel.service.tunnel.HandshakeStatus import com.zaneschepke.wireguardautotunnel.service.tunnel.HandshakeStatus
import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelState import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelState
import com.zaneschepke.wireguardautotunnel.ui.AppViewModel import com.zaneschepke.wireguardautotunnel.ui.AppViewModel
import com.zaneschepke.wireguardautotunnel.ui.CaptureActivityPortrait
import com.zaneschepke.wireguardautotunnel.ui.Screen import com.zaneschepke.wireguardautotunnel.ui.Screen
import com.zaneschepke.wireguardautotunnel.ui.common.RowListItem import com.zaneschepke.wireguardautotunnel.ui.common.RowListItem
import com.zaneschepke.wireguardautotunnel.ui.common.screen.LoadingScreen import com.zaneschepke.wireguardautotunnel.ui.common.screen.LoadingScreen
@ -262,8 +261,6 @@ fun MainScreen(
context.getString(R.string.scanning_qr), context.getString(R.string.scanning_qr),
) )
scanOptions.setBeepEnabled(false) scanOptions.setBeepEnabled(false)
scanOptions.captureActivity =
CaptureActivityPortrait::class.java
scanLauncher.launch(scanOptions) scanLauncher.launch(scanOptions)
} }

View File

@ -84,7 +84,6 @@ import com.zaneschepke.wireguardautotunnel.ui.common.text.SectionTitle
import com.zaneschepke.wireguardautotunnel.util.getMessage import com.zaneschepke.wireguardautotunnel.util.getMessage
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import timber.log.Timber import timber.log.Timber
import xyz.teamgravity.pin_lock_compose.PinManager
import java.io.File import java.io.File
@OptIn( @OptIn(
@ -652,21 +651,29 @@ fun SettingsScreen(
onCheckChanged = { viewModel.onToggleShortcutsEnabled() }, onCheckChanged = { viewModel.onToggleShortcutsEnabled() },
) )
} }
// TODO disable for now ConfigurationToggle(
// ConfigurationToggle( stringResource(R.string.restart_at_boot),
// stringResource(R.string.enable_app_lock), enabled = true,
// enabled = true, checked = uiState.settings.isRestoreOnBootEnabled,
// checked = pinExists.value, padding = screenPadding,
// padding = screenPadding, onCheckChanged = {
// onCheckChanged = { viewModel.onToggleRestartAtBoot()
// if (pinExists.value) { },
// PinManager.clearPin() )
// pinExists.value = PinManager.pinExists() ConfigurationToggle(
// } else { stringResource(R.string.enable_app_lock),
// navController.navigate(Screen.Lock.route) enabled = true,
// } checked = uiState.isPinLockEnabled,
// }, padding = screenPadding,
// ) onCheckChanged = {
if (uiState.isPinLockEnabled) {
viewModel.onPinLockDisabled()
} else {
viewModel.onPinLockEnabled()
navController.navigate(Screen.Lock.route)
}
},
)
if (!WireGuardAutoTunnel.isRunningOnAndroidTv()) { if (!WireGuardAutoTunnel.isRunningOnAndroidTv()) {
Row( Row(
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,

View File

@ -10,4 +10,5 @@ data class SettingsUiState(
val vpnState: VpnState = VpnState(), val vpnState: VpnState = VpnState(),
val isLocationDisclosureShown: Boolean = true, val isLocationDisclosureShown: Boolean = true,
val isBatteryOptimizeDisableShown: Boolean = false, val isBatteryOptimizeDisableShown: Boolean = false,
val isPinLockEnabled: Boolean = false
) )

View File

@ -27,6 +27,7 @@ import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import timber.log.Timber import timber.log.Timber
import xyz.teamgravity.pin_lock_compose.PinManager
import java.io.File import java.io.File
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Provider import javax.inject.Provider
@ -57,8 +58,9 @@ constructor(
settings, settings,
tunnels, tunnels,
tunnelState, tunnelState,
generalState.locationDisclosureShown, generalState.isLocationDisclosureShown,
generalState.batteryOptimizationDisableShown, generalState.isBatteryOptimizationDisableShown,
generalState.isPinLockEnabled,
) )
} }
.stateIn( .stateIn(
@ -234,4 +236,22 @@ constructor(
kernelSupport kernelSupport
} }
} }
fun onPinLockDisabled() = viewModelScope.launch {
PinManager.clearPin()
appDataRepository.appState.setPinLockEnabled(false)
}
fun onPinLockEnabled() = viewModelScope.launch {
PinManager.initialize(WireGuardAutoTunnel.instance)
appDataRepository.appState.setPinLockEnabled(true)
}
fun onToggleRestartAtBoot() = viewModelScope.launch {
saveSettings(
uiState.value.settings.copy(
isRestoreOnBootEnabled = !uiState.value.settings.isRestoreOnBootEnabled
)
)
}
} }

View File

@ -1,6 +1,5 @@
package com.zaneschepke.wireguardautotunnel.util package com.zaneschepke.wireguardautotunnel.util
import android.content.BroadcastReceiver
import android.content.Context import android.content.Context
import android.content.pm.PackageInfo import android.content.pm.PackageInfo
import com.zaneschepke.wireguardautotunnel.R import com.zaneschepke.wireguardautotunnel.R
@ -10,7 +9,6 @@ import com.zaneschepke.wireguardautotunnel.service.tunnel.statistics.TunnelStati
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.ObsoleteCoroutinesApi import kotlinx.coroutines.ObsoleteCoroutinesApi
import kotlinx.coroutines.channels.ClosedReceiveChannelException import kotlinx.coroutines.channels.ClosedReceiveChannelException
import kotlinx.coroutines.channels.ReceiveChannel import kotlinx.coroutines.channels.ReceiveChannel
@ -19,7 +17,6 @@ import kotlinx.coroutines.channels.ticker
import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.channelFlow import kotlinx.coroutines.flow.channelFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.selects.whileSelect import kotlinx.coroutines.selects.whileSelect
import org.amnezia.awg.config.Config import org.amnezia.awg.config.Config
import timber.log.Timber import timber.log.Timber
@ -27,25 +24,8 @@ import java.math.BigDecimal
import java.text.DecimalFormat import java.text.DecimalFormat
import java.time.Duration import java.time.Duration
import java.util.concurrent.ConcurrentLinkedQueue import java.util.concurrent.ConcurrentLinkedQueue
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
import kotlin.coroutines.cancellation.CancellationException import kotlin.coroutines.cancellation.CancellationException
fun BroadcastReceiver.goAsync(
context: CoroutineContext = EmptyCoroutineContext,
block: suspend CoroutineScope.() -> Unit
) {
val pendingResult = goAsync()
@OptIn(DelicateCoroutinesApi::class) // Must run globally; there's no teardown callback.
GlobalScope.launch(context) {
try {
block()
} finally {
pendingResult.finish()
}
}
}
fun BigDecimal.toThreeDecimalPlaceString(): String { fun BigDecimal.toThreeDecimalPlaceString(): String {
val df = DecimalFormat("#.###") val df = DecimalFormat("#.###")
return df.format(this) return df.format(this)

View File

@ -176,4 +176,5 @@
<string name="amnezia" translatable="false">Amnezia</string> <string name="amnezia" translatable="false">Amnezia</string>
<string name="wireguard" translatable="false">WireGuard</string> <string name="wireguard" translatable="false">WireGuard</string>
<string name="error_file_format">Invalid tunnel config format</string> <string name="error_file_format">Invalid tunnel config format</string>
<string name="restart_at_boot">Restart on boot</string>
</resources> </resources>

View File

@ -4,4 +4,12 @@
<style name="Theme.WireguardAutoTunnel" parent="@style/Theme.AppCompat.NoActionBar"> <style name="Theme.WireguardAutoTunnel" parent="@style/Theme.AppCompat.NoActionBar">
<item name="android:windowBackground">@color/black_background</item> <item name="android:windowBackground">@color/black_background</item>
</style> </style>
</resources>
<style name="Theme.AppSplashScreen" parent="Theme.SplashScreen">
<!--<item name="windowSplashScreenBackground">@color/white</item>-->
<!-- icon has to be a circle -->
<item name="windowSplashScreenAnimatedIcon">@mipmap/ic_launcher</item>
<item name="windowSplashScreenAnimationDuration">500</item>
<item name="postSplashScreenTheme">@style/Theme.WireguardAutoTunnel</item>
</style>
</resources>

View File

@ -1,7 +1,7 @@
object Constants { object Constants {
const val VERSION_NAME = "3.4.6" const val VERSION_NAME = "3.4.7"
const val JVM_TARGET = "17" const val JVM_TARGET = "17"
const val VERSION_CODE = 34600 const val VERSION_CODE = 34700
const val TARGET_SDK = 34 const val TARGET_SDK = 34
const val MIN_SDK = 26 const val MIN_SDK = 26
const val APP_ID = "com.zaneschepke.wireguardautotunnel" const val APP_ID = "com.zaneschepke.wireguardautotunnel"

View File

@ -0,0 +1,6 @@
What's new:
- Fix crashing issues
- Improve tile performance
- Re-enable pin lock
- Make restart on boot a setting
- Various performance and bug fixes

View File

@ -22,12 +22,13 @@ pinLockCompose = "1.0.3"
roomVersion = "2.6.1" roomVersion = "2.6.1"
timber = "5.0.1" timber = "5.0.1"
tunnel = "1.0.20230706" tunnel = "1.0.20230706"
androidGradlePlugin = "8.4.1" androidGradlePlugin = "8.5.0"
kotlin = "1.9.24" kotlin = "1.9.24"
ksp = "1.9.24-1.0.20" ksp = "1.9.24-1.0.20"
composeBom = "2024.05.00" composeBom = "2024.06.00"
compose = "1.6.7" compose = "1.6.8"
zxingAndroidEmbedded = "4.3.0" zxingAndroidEmbedded = "4.3.0"
coreSplashscreen = "1.0.1"
#plugins #plugins
gradlePlugins-kotlinxSerialization = "1.9.24" gradlePlugins-kotlinxSerialization = "1.9.24"
@ -74,6 +75,7 @@ androidx-core-ktx = { module = "androidx.core:core-ktx", version.ref = "coreKtx"
androidx-espresso-core = { module = "androidx.test.espresso:espresso-core", version.ref = "espressoCore" } androidx-espresso-core = { module = "androidx.test.espresso:espresso-core", version.ref = "espressoCore" }
androidx-hilt-navigation-compose = { module = "androidx.hilt:hilt-navigation-compose", version.ref = "hiltNavigationCompose" } androidx-hilt-navigation-compose = { module = "androidx.hilt:hilt-navigation-compose", version.ref = "hiltNavigationCompose" }
androidx-junit = { module = "androidx.test.ext:junit", version.ref = "androidx-junit" } androidx-junit = { module = "androidx.test.ext:junit", version.ref = "androidx-junit" }
androidx-core-splashscreen = { module = "androidx.core:core-splashscreen", version.ref = "coreSplashscreen" }
androidx-lifecycle-runtime-ktx = { module = "androidx.lifecycle:lifecycle-runtime-ktx", version.ref = "lifecycle-runtime-compose" } androidx-lifecycle-runtime-ktx = { module = "androidx.lifecycle:lifecycle-runtime-ktx", version.ref = "lifecycle-runtime-compose" }
androidx-material3 = { module = "androidx.compose.material3:material3", version.ref = "material3" } androidx-material3 = { module = "androidx.compose.material3:material3", version.ref = "material3" }
androidx-navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "navigationCompose" } androidx-navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "navigationCompose" }