diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 044109e..dce8bc1 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -14,8 +14,8 @@ android {
applicationId = "com.zaneschepke.wireguardautotunnel"
minSdk = 26
targetSdk = 34
- versionCode = 30000
- versionName = "3.0.0"
+ versionCode = 30001
+ versionName = "3.0.1"
multiDexEnabled = true
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 3d9332b..be059cb 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -56,6 +56,7 @@
+
-
\ No newline at end of file
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/Constants.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/Constants.kt
index 2cf4afe..d4283bc 100644
--- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/Constants.kt
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/Constants.kt
@@ -4,8 +4,12 @@ object Constants {
const val VPN_CONNECTIVITY_CHECK_INTERVAL = 3000L
const val VPN_STATISTIC_CHECK_INTERVAL = 10000L
const val SNACKBAR_DELAY = 3000L
- const val TOGGLE_TUNNEL_DELAY = 1000L
+ const val TOGGLE_TUNNEL_DELAY = 500L
const val FADE_IN_ANIMATION_DURATION = 1000
const val SLIDE_IN_ANIMATION_DURATION = 500
const val SLIDE_IN_TRANSITION_OFFSET = 1000
+ const val VALID_FILE_EXTENSION = ".conf"
+ const val URI_CONTENT_SCHEME = "content"
+ const val URI_PACKAGE_SCHEME = "package"
+ const val ALLOWED_FILE_TYPES = "*/*"
}
\ No newline at end of file
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/module/ServiceModule.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/module/ServiceModule.kt
index 59f6644..addf983 100644
--- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/module/ServiceModule.kt
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/module/ServiceModule.kt
@@ -1,5 +1,6 @@
package com.zaneschepke.wireguardautotunnel.module
+import com.zaneschepke.wireguardautotunnel.service.network.EthernetService
import com.zaneschepke.wireguardautotunnel.service.network.MobileDataService
import com.zaneschepke.wireguardautotunnel.service.network.NetworkService
import com.zaneschepke.wireguardautotunnel.service.network.WifiService
@@ -26,4 +27,8 @@ abstract class ServiceModule {
@Binds
@ServiceScoped
abstract fun provideMobileDataService(mobileDataService : MobileDataService) : NetworkService
+
+ @Binds
+ @ServiceScoped
+ abstract fun provideEthernetService(ethernetService: EthernetService) : NetworkService
}
\ No newline at end of file
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/receiver/BootReceiver.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/receiver/BootReceiver.kt
index ae743f0..8d85636 100644
--- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/receiver/BootReceiver.kt
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/receiver/BootReceiver.kt
@@ -23,7 +23,7 @@ class BootReceiver : BroadcastReceiver() {
CoroutineScope(Dispatchers.IO).launch {
try {
val settings = settingsRepo.getAll()
- if (!settings.isNullOrEmpty()) {
+ if (settings.isNotEmpty()) {
val setting = settings.first()
if (setting.isAutoTunnelEnabled && setting.defaultTunnel != null) {
ServiceManager.startWatcherService(context, setting.defaultTunnel!!)
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/repository/model/Settings.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/repository/model/Settings.kt
index dd67942..07879dc 100644
--- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/repository/model/Settings.kt
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/repository/model/Settings.kt
@@ -12,4 +12,5 @@ data class Settings(
@ColumnInfo(name = "trusted_network_ssids") var trustedNetworkSSIDs : MutableList = mutableListOf(),
@ColumnInfo(name = "default_tunnel") var defaultTunnel : String? = null,
@ColumnInfo(name = "is_always_on_vpn_enabled") var isAlwaysOnVpnEnabled : Boolean = false,
+ @ColumnInfo(name = "is_tunnel_on_ethernet_enabled") var isTunnelOnEthernetEnabled : Boolean = false,
)
\ No newline at end of file
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 37e24d4..e6dcf4e 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
@@ -12,6 +12,7 @@ import com.zaneschepke.wireguardautotunnel.Constants
import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.repository.SettingsDoa
import com.zaneschepke.wireguardautotunnel.repository.model.Settings
+import com.zaneschepke.wireguardautotunnel.service.network.EthernetService
import com.zaneschepke.wireguardautotunnel.service.network.MobileDataService
import com.zaneschepke.wireguardautotunnel.service.network.NetworkService
import com.zaneschepke.wireguardautotunnel.service.network.NetworkStatus
@@ -38,6 +39,9 @@ class WireGuardConnectivityWatcherService : ForegroundService() {
@Inject
lateinit var mobileDataService : NetworkService
+ @Inject
+ lateinit var ethernetService: NetworkService
+
@Inject
lateinit var settingsRepo: SettingsDoa
@@ -48,6 +52,7 @@ class WireGuardConnectivityWatcherService : ForegroundService() {
lateinit var vpnService : VpnService
private var isWifiConnected = false;
+ private var isEthernetConnected = false;
private var isMobileDataConnected = false;
private var currentNetworkSSID = "";
@@ -142,6 +147,11 @@ class WireGuardConnectivityWatcherService : ForegroundService() {
watchForMobileDataConnectivityChanges()
}
}
+ if(setting.isTunnelOnEthernetEnabled) {
+ launch {
+ watchForEthernetConnectivityChanges()
+ }
+ }
launch {
manageVpn()
}
@@ -167,6 +177,25 @@ class WireGuardConnectivityWatcherService : ForegroundService() {
}
}
+ private suspend fun watchForEthernetConnectivityChanges() {
+ ethernetService.networkStatus.collect {
+ when (it) {
+ is NetworkStatus.Available -> {
+ Timber.d("Gained Ethernet connection")
+ isEthernetConnected = true
+ }
+ is NetworkStatus.CapabilitiesChanged -> {
+ Timber.d("Ethernet capabilities changed")
+ isEthernetConnected = true
+ }
+ is NetworkStatus.Unavailable -> {
+ isEthernetConnected = false
+ Timber.d("Lost Ethernet connection")
+ }
+ }
+ }
+ }
+
private suspend fun watchForWifiConnectivityChanges() {
wifiService.networkStatus.collect {
when (it) {
@@ -189,20 +218,23 @@ class WireGuardConnectivityWatcherService : ForegroundService() {
private suspend fun manageVpn() {
while(true) {
- if(setting.isTunnelOnMobileDataEnabled &&
+ if(isEthernetConnected && setting.isTunnelOnEthernetEnabled && vpnService.getState() == Tunnel.State.DOWN) {
+ ServiceManager.startVpnService(this, tunnelConfig)
+ }
+ if(!isEthernetConnected && setting.isTunnelOnMobileDataEnabled &&
!isWifiConnected &&
isMobileDataConnected
&& vpnService.getState() == Tunnel.State.DOWN) {
ServiceManager.startVpnService(this, tunnelConfig)
- } else if(!setting.isTunnelOnMobileDataEnabled &&
+ } else if(!isEthernetConnected && !setting.isTunnelOnMobileDataEnabled &&
!isWifiConnected &&
vpnService.getState() == Tunnel.State.UP) {
ServiceManager.stopVpnService(this)
- } else if(isWifiConnected &&
+ } else if(!isEthernetConnected && isWifiConnected &&
!setting.trustedNetworkSSIDs.contains(currentNetworkSSID) &&
(vpnService.getState() != Tunnel.State.UP)) {
ServiceManager.startVpnService(this, tunnelConfig)
- } else if((isWifiConnected &&
+ } else if(!isEthernetConnected && (isWifiConnected &&
setting.trustedNetworkSSIDs.contains(currentNetworkSSID)) &&
(vpnService.getState() == Tunnel.State.UP)) {
ServiceManager.stopVpnService(this)
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/network/EthernetService.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/network/EthernetService.kt
new file mode 100644
index 0000000..5450ca3
--- /dev/null
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/network/EthernetService.kt
@@ -0,0 +1,10 @@
+package com.zaneschepke.wireguardautotunnel.service.network
+
+import android.content.Context
+import android.net.NetworkCapabilities
+import dagger.hilt.android.qualifiers.ApplicationContext
+import javax.inject.Inject
+
+class EthernetService @Inject constructor(@ApplicationContext context: Context) :
+ BaseNetworkService(context, NetworkCapabilities.TRANSPORT_ETHERNET) {
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/shortcut/ShortcutsManager.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/shortcut/ShortcutsManager.kt
index 8798319..3b02d19 100644
--- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/shortcut/ShortcutsManager.kt
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/shortcut/ShortcutsManager.kt
@@ -46,9 +46,11 @@ object ShortcutsManager {
)
}
- fun removeTunnelShortcuts(context : Context, tunnelConfig : TunnelConfig) {
- ShortcutManagerCompat.removeDynamicShortcuts(context, listOf(tunnelConfig.id.toString() + APPEND_ON,
- tunnelConfig.id.toString() + APPEND_OFF ))
+ fun removeTunnelShortcuts(context : Context, tunnelConfig : TunnelConfig?) {
+ if(tunnelConfig != null) {
+ ShortcutManagerCompat.removeDynamicShortcuts(context, listOf(tunnelConfig.id.toString() + APPEND_ON,
+ tunnelConfig.id.toString() + APPEND_OFF ))
+ }
}
private fun createTunnelOnIntent(context: Context, extras : Map) : Intent {
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/CaptureActivityPortrait.java b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/CaptureActivityPortrait.java
deleted file mode 100644
index f9770eb..0000000
--- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/CaptureActivityPortrait.java
+++ /dev/null
@@ -1,6 +0,0 @@
-package com.zaneschepke.wireguardautotunnel.ui;
-
-import com.journeyapps.barcodescanner.CaptureActivity;
-
-public class CaptureActivityPortrait extends CaptureActivity {
-}
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/CaptureActivityPortrait.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/CaptureActivityPortrait.kt
new file mode 100644
index 0000000..9972857
--- /dev/null
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/CaptureActivityPortrait.kt
@@ -0,0 +1,5 @@
+package com.zaneschepke.wireguardautotunnel.ui
+
+import com.journeyapps.barcodescanner.CaptureActivity
+
+class CaptureActivityPortrait : CaptureActivity()
\ No newline at end of file
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/MainActivity.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/MainActivity.kt
index b72f801..548355a 100644
--- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/MainActivity.kt
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/MainActivity.kt
@@ -44,6 +44,7 @@ import com.zaneschepke.wireguardautotunnel.ui.screens.settings.SettingsScreen
import com.zaneschepke.wireguardautotunnel.ui.screens.support.SupportScreen
import com.zaneschepke.wireguardautotunnel.ui.theme.TransparentSystemBars
import com.zaneschepke.wireguardautotunnel.ui.theme.WireguardAutoTunnelTheme
+import com.zaneschepke.wireguardautotunnel.util.WgTunnelException
import dagger.hilt.android.AndroidEntryPoint
import timber.log.Timber
import java.lang.IllegalStateException
@@ -101,7 +102,7 @@ class MainActivity : AppCompatActivity() {
}
false
} else -> {
- false;
+ false
}
}
} else {
@@ -131,8 +132,8 @@ class MainActivity : AppCompatActivity() {
val intentSettings =
Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
intentSettings.data =
- Uri.fromParts("package", this.packageName, null)
- startActivity(intentSettings);
+ Uri.fromParts(Constants.URI_PACKAGE_SCHEME, this.packageName, null)
+ startActivity(intentSettings)
},
message = getString(R.string.notification_permission_required),
getString(R.string.open_settings)
@@ -190,10 +191,19 @@ class MainActivity : AppCompatActivity() {
}) { SupportScreen(padding = padding, focusRequester) }
composable("${Routes.Config.name}/{id}", enterTransition = {
fadeIn(animationSpec = tween(Constants.FADE_IN_ANIMATION_DURATION))
- }) { ConfigScreen(padding = padding, navController = navController, id = it.arguments?.getString("id"), focusRequester = focusRequester)}
+ }) {
+ val id = it.arguments?.getString("id")
+ if(!id.isNullOrBlank()) {
+ ConfigScreen(padding = padding, navController = navController, id = id, focusRequester = focusRequester)}
+ }
composable("${Routes.Detail.name}/{id}", enterTransition = {
fadeIn(animationSpec = tween(Constants.FADE_IN_ANIMATION_DURATION))
- }) { DetailScreen(padding = padding, id = it.arguments?.getString("id")) }
+ }) {
+ val id = it.arguments?.getString("id")
+ if(!id.isNullOrBlank()) {
+ DetailScreen(padding = padding, id = id)
+ }
+ }
}
}
}
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/config/ConfigScreen.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/config/ConfigScreen.kt
index bc38ed4..ccb75b6 100644
--- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/config/ConfigScreen.kt
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/config/ConfigScreen.kt
@@ -24,6 +24,7 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
@@ -53,7 +54,7 @@ fun ConfigScreen(
padding: PaddingValues,
focusRequester: FocusRequester,
navController: NavController,
- id : String?
+ id : String
) {
val context = LocalContext.current
@@ -67,11 +68,12 @@ fun ConfigScreen(
val checkedPackages by viewModel.checkedPackages.collectAsStateWithLifecycle()
val include by viewModel.include.collectAsStateWithLifecycle()
val allApplications by viewModel.allApplications.collectAsStateWithLifecycle()
+ val sortedPackages = remember(packages) {
+ packages.sortedBy { viewModel.getPackageLabel(it) }
+ }
LaunchedEffect(Unit) {
- viewModel.getTunnelById(id)
- viewModel.emitQueriedPackages("")
- viewModel.emitCurrentPackageConfigurations(id)
+ viewModel.emitScreenData(id)
}
if(tunnel != null) {
@@ -174,7 +176,7 @@ fun ConfigScreen(
SearchBar(viewModel::emitQueriedPackages);
}
}
- items(packages) { pack ->
+ items(sortedPackages, key = { it.packageName }) { pack ->
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
@@ -200,8 +202,7 @@ fun ConfigScreen(
)
}
Text(
- pack.applicationInfo.loadLabel(context.packageManager)
- .toString(), modifier = Modifier.padding(5.dp)
+ viewModel.getPackageLabel(pack), modifier = Modifier.padding(5.dp)
)
}
Checkbox(
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/config/ConfigViewModel.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/config/ConfigViewModel.kt
index 6afe299..770b47e 100644
--- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/config/ConfigViewModel.kt
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/config/ConfigViewModel.kt
@@ -9,11 +9,14 @@ import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.toMutableStateList
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
+import com.wireguard.config.Config
import com.zaneschepke.wireguardautotunnel.repository.SettingsDoa
import com.zaneschepke.wireguardautotunnel.repository.TunnelConfigDao
import com.zaneschepke.wireguardautotunnel.service.shortcut.ShortcutsManager
import com.zaneschepke.wireguardautotunnel.repository.model.TunnelConfig
+import com.zaneschepke.wireguardautotunnel.util.WgTunnelException
import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
@@ -41,24 +44,37 @@ class ConfigViewModel @Inject constructor(private val application : Application,
private val _allApplications = MutableStateFlow(true)
val allApplications get() = _allApplications.asStateFlow()
- suspend fun getTunnelById(id : String?) : TunnelConfig? {
- return try {
- if(id != null) {
- val config = tunnelRepo.getById(id.toLong())
- if (config != null) {
- _tunnel.emit(config)
- _tunnelName.emit(config.name)
+ fun emitScreenData(id : String) {
+ viewModelScope.launch(Dispatchers.IO) {
+ val tunnelConfig = getTunnelConfigById(id);
+ emitTunnelConfig(tunnelConfig);
+ emitTunnelConfigName(tunnelConfig?.name)
+ emitQueriedPackages("")
+ emitCurrentPackageConfigurations(id)
+ }
+ }
- }
- return config
- }
- return null
+ private suspend fun getTunnelConfigById(id : String) : TunnelConfig? {
+ return try {
+ tunnelRepo.getById(id.toLong())
} catch (e : Exception) {
Timber.e(e.message)
null
}
}
+ private suspend fun emitTunnelConfig(tunnelConfig: TunnelConfig?) {
+ if(tunnelConfig != null) {
+ _tunnel.emit(tunnelConfig)
+ }
+ }
+
+ private suspend fun emitTunnelConfigName(name : String?) {
+ if(name != null) {
+ _tunnelName.emit(name)
+ }
+ }
+
fun onTunnelNameChange(name : String) {
_tunnelName.value = name
}
@@ -78,35 +94,71 @@ class ConfigViewModel @Inject constructor(private val application : Application,
_checkedPackages.value.remove(packageName)
}
- suspend fun emitCurrentPackageConfigurations(id : String?) {
- val tunnelConfig = getTunnelById(id)
- if(tunnelConfig != null) {
- val config = TunnelConfig.configFromQuick(tunnelConfig.wgQuick)
- val excludedApps = config.`interface`.excludedApplications
- val includedApps = config.`interface`.includedApplications
- if(excludedApps.isNullOrEmpty() && includedApps.isNullOrEmpty()) {
- _allApplications.emit(true)
- return
+ private suspend fun emitSplitTunnelConfiguration(config : Config) {
+ val excludedApps = config.`interface`.excludedApplications
+ val includedApps = config.`interface`.includedApplications
+ if (excludedApps.isNotEmpty() || includedApps.isNotEmpty()) {
+ emitTunnelAllApplicationsDisabled()
+ determineAppInclusionState(excludedApps, includedApps)
+ } else {
+ emitTunnelAllApplicationsEnabled()
+ }
+ }
+
+ private suspend fun determineAppInclusionState(excludedApps : Set, includedApps : Set) {
+ if (excludedApps.isEmpty()) {
+ emitIncludedAppsExist()
+ emitCheckedApps(includedApps)
+ } else {
+ emitExcludedAppsExist()
+ emitCheckedApps(excludedApps)
+ }
+ }
+
+ private suspend fun emitIncludedAppsExist() {
+ _include.emit(true)
+ }
+
+ private suspend fun emitExcludedAppsExist() {
+ _include.emit(false)
+ }
+
+ private suspend fun emitCheckedApps(apps : Set) {
+ _checkedPackages.emit(apps.toMutableStateList())
+ }
+
+ private suspend fun emitTunnelAllApplicationsEnabled() {
+ _allApplications.emit(true)
+ }
+
+ private suspend fun emitTunnelAllApplicationsDisabled() {
+ _allApplications.emit(false)
+ }
+
+ private fun emitCurrentPackageConfigurations(id : String) {
+ viewModelScope.launch(Dispatchers.IO) {
+ val tunnelConfig = getTunnelConfigById(id)
+ if (tunnelConfig != null) {
+ val config = TunnelConfig.configFromQuick(tunnelConfig.wgQuick)
+ emitSplitTunnelConfiguration(config)
}
- if(excludedApps.isEmpty()) {
- _include.emit(true)
- _checkedPackages.emit(includedApps.toMutableStateList())
- } else {
- _include.emit(false)
- _checkedPackages.emit(excludedApps.toMutableStateList())
- }
- _allApplications.emit(false)
}
}
fun emitQueriedPackages(query : String) {
- viewModelScope.launch {
- _packages.emit(getAllInternetCapablePackages().filter {
- it.packageName.contains(query)
- })
+ viewModelScope.launch(Dispatchers.IO) {
+ val packages = getAllInternetCapablePackages().filter {
+ getPackageLabel(it).lowercase().contains(query.lowercase())
+ }
+ _packages.emit(packages)
}
}
+ fun getPackageLabel(packageInfo : PackageInfo) : String {
+ return packageInfo.applicationInfo.loadLabel(application.packageManager).toString()
+ }
+
+
private fun getAllInternetCapablePackages() : List {
return getPackagesHoldingPermissions(arrayOf(Manifest.permission.INTERNET))
}
@@ -119,39 +171,77 @@ class ConfigViewModel @Inject constructor(private val application : Application,
}
}
- suspend fun onSaveAllChanges() {
- if(_tunnel.value != null) {
- ShortcutsManager.removeTunnelShortcuts(application, _tunnel.value!!)
+ private fun removeTunnelShortcuts(tunnelConfig: TunnelConfig?) {
+ if(tunnelConfig != null) {
+ ShortcutsManager.removeTunnelShortcuts(application, tunnelConfig)
}
+
+ }
+
+ private fun isAllApplicationsEnabled() : Boolean {
+ return _allApplications.value
+ }
+
+ private fun isIncludeApplicationsEnabled() : Boolean {
+ return _include.value
+ }
+
+ private fun updateQuickStringWithSelectedPackages() : String {
var wgQuick = _tunnel.value?.wgQuick
if(wgQuick != null) {
- wgQuick = if(_include.value) {
+ wgQuick = if(isAllApplicationsEnabled()) {
+ TunnelConfig.clearAllApplicationsFromConfig(wgQuick)
+ } else if(isIncludeApplicationsEnabled()) {
TunnelConfig.setIncludedApplicationsOnQuick(_checkedPackages.value, wgQuick)
} else {
TunnelConfig.setExcludedApplicationsOnQuick(_checkedPackages.value, wgQuick)
}
- if(_allApplications.value) {
- wgQuick = TunnelConfig.clearAllApplicationsFromConfig(wgQuick)
- }
- _tunnel.value?.copy(
- name = _tunnelName.value,
- wgQuick = wgQuick
- )?.let {
- tunnelRepo.save(it)
- ShortcutsManager.createTunnelShortcuts(application, it)
- val settings = settingsRepo.getAll()
- if(settings.isEmpty()) {
- return
- }
- val setting = settings[0]
- if(setting.defaultTunnel != null) {
- if(it.id == TunnelConfig.from(setting.defaultTunnel!!).id) {
- settingsRepo.save(setting.copy(
- defaultTunnel = it.toString()
- ))
- }
+ } else {
+ throw WgTunnelException("Wg quick string is null")
+ }
+ return wgQuick;
+ }
+
+ private suspend fun saveConfig(tunnelConfig: TunnelConfig) {
+ tunnelRepo.save(tunnelConfig)
+ }
+ private suspend fun updateTunnelConfig(tunnelConfig: TunnelConfig?) {
+ if(tunnelConfig != null) {
+ saveConfig(tunnelConfig)
+ addTunnelShortcuts(tunnelConfig)
+ updateSettingsDefaultTunnel(tunnelConfig)
+ }
+ }
+
+ private suspend fun updateSettingsDefaultTunnel(tunnelConfig: TunnelConfig) {
+ val settings = settingsRepo.getAll()
+ if(settings.isNotEmpty()) {
+ val setting = settings[0]
+ if(setting.defaultTunnel != null) {
+ if(tunnelConfig.id == TunnelConfig.from(setting.defaultTunnel!!).id) {
+ settingsRepo.save(setting.copy(
+ defaultTunnel = tunnelConfig.toString()
+ ))
}
}
}
}
+
+ private fun addTunnelShortcuts(tunnelConfig: TunnelConfig) {
+ ShortcutsManager.createTunnelShortcuts(application, tunnelConfig)
+ }
+
+ suspend fun onSaveAllChanges() {
+ try {
+ removeTunnelShortcuts(_tunnel.value)
+ val wgQuick = updateQuickStringWithSelectedPackages()
+ val tunnelConfig = _tunnel.value?.copy(
+ name = _tunnelName.value,
+ wgQuick = wgQuick
+ )
+ updateTunnelConfig(tunnelConfig)
+ } catch (e : Exception) {
+ Timber.e(e.message)
+ }
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/detail/DetailScreen.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/detail/DetailScreen.kt
index c663ebd..bf19e36 100644
--- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/detail/DetailScreen.kt
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/detail/DetailScreen.kt
@@ -36,7 +36,7 @@ import java.time.Instant
fun DetailScreen(
viewModel: DetailViewModel = hiltViewModel(),
padding: PaddingValues,
- id : String?
+ id : String
) {
val clipboardManager: ClipboardManager = LocalClipboardManager.current
@@ -47,15 +47,17 @@ fun DetailScreen(
LaunchedEffect(Unit) {
- viewModel.getTunnelById(id)
+ viewModel.emitConfig(id)
}
- if(tunnel != null) {
+ if(null != tunnel) {
val interfaceKey = tunnel?.`interface`?.keyPair?.publicKey?.toBase64().toString()
val addresses = tunnel?.`interface`?.addresses!!.joinToString()
val dnsServers = tunnel?.`interface`?.dnsServers!!.joinToString()
val optionalMtu = tunnel?.`interface`?.mtu
- val mtu = if(optionalMtu?.isPresent == true) optionalMtu.get().toString() else "None"
+ val mtu = if(optionalMtu?.isPresent == true) optionalMtu.get().toString() else stringResource(
+ id = R.string.none
+ )
Column(
horizontalAlignment = Alignment.Start,
verticalArrangement = Arrangement.Top,
@@ -97,7 +99,9 @@ fun DetailScreen(
tunnel?.peers?.forEach{
val peerKey = it.publicKey.toBase64().toString()
val allowedIps = it.allowedIps.joinToString()
- val endpoint = if(it.endpoint.isPresent) it.endpoint.get().toString() else "None"
+ val endpoint = if(it.endpoint.isPresent) it.endpoint.get().toString() else stringResource(
+ id = R.string.none
+ )
Text(stringResource(R.string.peer), fontWeight = FontWeight.Bold, fontSize = 20.sp)
Text(stringResource(R.string.public_key), fontStyle = FontStyle.Italic)
Text(text = peerKey, modifier = Modifier.clickable {
@@ -123,7 +127,7 @@ fun DetailScreen(
val handshakeEpoch = lastHandshake[it.publicKey]
if(handshakeEpoch != null) {
if(handshakeEpoch == 0L) {
- Text("Never")
+ Text(stringResource(id = R.string.never))
} else {
val time = Instant.ofEpochMilli(handshakeEpoch)
Text("${Duration.between(time, Instant.now()).seconds} seconds ago")
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/detail/DetailViewModel.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/detail/DetailViewModel.kt
index c6f39ac..ec48f74 100644
--- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/detail/DetailViewModel.kt
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/detail/DetailViewModel.kt
@@ -1,45 +1,45 @@
package com.zaneschepke.wireguardautotunnel.ui.screens.detail
import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
import com.wireguard.config.Config
import com.zaneschepke.wireguardautotunnel.repository.TunnelConfigDao
import com.zaneschepke.wireguardautotunnel.repository.model.TunnelConfig
import com.zaneschepke.wireguardautotunnel.service.tunnel.VpnService
import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.launch
import timber.log.Timber
import javax.inject.Inject
@HiltViewModel
class DetailViewModel @Inject constructor(private val tunnelRepo : TunnelConfigDao, private val vpnService : VpnService
-
) : ViewModel() {
private val _tunnel = MutableStateFlow(null)
val tunnel get() = _tunnel.asStateFlow()
- private val _tunnelName = MutableStateFlow("")
+ private val _tunnelName = MutableStateFlow("")
val tunnelName = _tunnelName.asStateFlow()
val tunnelStats get() = vpnService.statistics
val lastHandshake get() = vpnService.lastHandshake
- private var config : TunnelConfig? = null
-
- suspend fun getTunnelById(id : String?) : TunnelConfig? {
+ private suspend fun getTunnelConfigById(id: String): TunnelConfig? {
return try {
- if(id != null) {
- config = tunnelRepo.getById(id.toLong())
- if (config != null) {
- _tunnel.emit(TunnelConfig.configFromQuick(config!!.wgQuick))
- _tunnelName.emit(config!!.name)
- }
- return config
- }
- return null
- } catch (e : Exception) {
+ tunnelRepo.getById(id.toLong())
+ } catch (e: Exception) {
Timber.e(e.message)
null
}
}
+ fun emitConfig(id: String) {
+ viewModelScope.launch(Dispatchers.IO) {
+ val tunnelConfig = getTunnelConfigById(id)
+ if(tunnelConfig != null) {
+ _tunnel.emit(TunnelConfig.configFromQuick(tunnelConfig.wgQuick))
+ }
+ }
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/main/MainScreen.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/main/MainScreen.kt
index 58a8ae4..3a8195f 100644
--- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/main/MainScreen.kt
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/main/MainScreen.kt
@@ -73,11 +73,12 @@ import androidx.navigation.NavController
import com.journeyapps.barcodescanner.ScanContract
import com.journeyapps.barcodescanner.ScanOptions
import com.wireguard.android.backend.Tunnel
-import com.zaneschepke.wireguardautotunnel.ui.CaptureActivityPortrait
+import com.zaneschepke.wireguardautotunnel.Constants
import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.WireGuardAutoTunnel
import com.zaneschepke.wireguardautotunnel.repository.model.TunnelConfig
import com.zaneschepke.wireguardautotunnel.service.tunnel.HandshakeStatus
+import com.zaneschepke.wireguardautotunnel.ui.CaptureActivityPortrait
import com.zaneschepke.wireguardautotunnel.ui.Routes
import com.zaneschepke.wireguardautotunnel.ui.common.RowListItem
import com.zaneschepke.wireguardautotunnel.ui.theme.brickRed
@@ -146,7 +147,13 @@ fun MainScreen(
val scanLauncher = rememberLauncherForActivityResult(
contract = ScanContract(),
- onResult = { result -> viewModel.onTunnelQrResult(result.contents) }
+ onResult = {
+ try {
+ viewModel.onTunnelQrResult(it.contents)
+ } catch (e : Exception) {
+ viewModel.showSnackBarMessage(context.getString(R.string.qr_result_failed))
+ }
+ }
)
Scaffold(
@@ -205,7 +212,7 @@ fun MainScreen(
showBottomSheet = false
val fileSelectionIntent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
- type = "*/*"
+ type = Constants.ALLOWED_FILE_TYPES
}
pickFileLauncher.launch(fileSelectionIntent)
}
@@ -232,7 +239,7 @@ fun MainScreen(
scanOptions.setOrientationLocked(true)
scanOptions.setPrompt(context.getString(R.string.scanning_qr))
scanOptions.setBeepEnabled(false)
- scanOptions.captureActivity = CaptureActivityPortrait().javaClass
+ scanOptions.captureActivity = CaptureActivityPortrait::class.java
scanLauncher.launch(scanOptions)
}
}
@@ -264,7 +271,7 @@ fun MainScreen(
.nestedScroll(nestedScrollConnection),
) {
items(tunnels, key = { tunnel -> tunnel.id }) {tunnel ->
- val focusRequester = FocusRequester();
+ val focusRequester = FocusRequester()
RowListItem(leadingIcon = Icons.Rounded.Circle,
leadingIconColor = if (tunnelName == tunnel.name) when (handshakeStatus) {
HandshakeStatus.HEALTHY -> mint
@@ -281,7 +288,7 @@ fun MainScreen(
return@RowListItem
}
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
- selectedTunnel = tunnel;
+ selectedTunnel = tunnel
},
onClick = {
if (!WireGuardAutoTunnel.isRunningOnAndroidTv(context)) {
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/main/MainViewModel.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/main/MainViewModel.kt
index aab12fe..31625af 100644
--- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/main/MainViewModel.kt
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/main/MainViewModel.kt
@@ -1,8 +1,8 @@
package com.zaneschepke.wireguardautotunnel.ui.screens.main
-import android.annotation.SuppressLint
import android.app.Application
import android.content.Context
+import android.database.Cursor
import android.net.Uri
import android.provider.OpenableColumns
import androidx.lifecycle.ViewModel
@@ -18,18 +18,21 @@ import com.zaneschepke.wireguardautotunnel.repository.model.TunnelConfig
import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager
import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceState
import com.zaneschepke.wireguardautotunnel.service.foreground.WireGuardConnectivityWatcherService
+import com.zaneschepke.wireguardautotunnel.service.foreground.WireGuardTunnelService
import com.zaneschepke.wireguardautotunnel.service.shortcut.ShortcutsManager
import com.zaneschepke.wireguardautotunnel.service.tunnel.VpnService
import com.zaneschepke.wireguardautotunnel.ui.ViewState
import com.zaneschepke.wireguardautotunnel.util.NumberUtils
+import com.zaneschepke.wireguardautotunnel.util.WgTunnelException
import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.launch
-import timber.log.Timber
+import java.io.InputStream
import javax.inject.Inject
@@ -86,88 +89,164 @@ class MainViewModel @Inject constructor(private val application : Application,
}
}
- fun onTunnelStart(tunnelConfig : TunnelConfig) = viewModelScope.launch {
+ fun onTunnelStart(tunnelConfig : TunnelConfig) {
+ viewModelScope.launch {
+ stopActiveTunnel()
+ startTunnel(tunnelConfig)
+ }
+ }
+
+ private fun startTunnel(tunnelConfig: TunnelConfig) {
ServiceManager.startVpnService(application.applicationContext, tunnelConfig.toString())
}
+ private suspend fun stopActiveTunnel() {
+ if(ServiceManager.getServiceState(application.applicationContext,
+ WireGuardTunnelService::class.java, ) == ServiceState.STARTED) {
+ onTunnelStop()
+ delay(Constants.TOGGLE_TUNNEL_DELAY)
+ }
+ }
+
fun onTunnelStop() {
ServiceManager.stopVpnService(application.applicationContext)
}
+ private fun validateConfigString(config : String) {
+ if(!config.contains(application.getString(R.string.config_validation))) {
+ throw WgTunnelException(application.getString(R.string.config_validation))
+ }
+ }
+
fun onTunnelQrResult(result : String) {
viewModelScope.launch(Dispatchers.IO) {
- if(result.contains(application.resources.getString(R.string.config_validation))) {
+ try {
+ validateConfigString(result)
val tunnelConfig = TunnelConfig(name = NumberUtils.generateRandomTunnelName(), wgQuick = result)
- saveTunnel(tunnelConfig)
- } else {
- showSnackBarMessage(application.resources.getString(R.string.barcode_error))
+ addTunnel(tunnelConfig)
+ } catch (e : WgTunnelException) {
+ showSnackBarMessage(e.message ?: application.getString(R.string.unknown_error_message))
}
}
}
- fun onTunnelFileSelected(uri : Uri) {
- viewModelScope.launch(Dispatchers.IO) {
- try {
- val fileName = getFileName(application.applicationContext, uri)
- val extension = getFileExtensionFromFileName(fileName)
- if (extension != ".conf") {
- launch {
- showSnackBarMessage(application.resources.getString(R.string.file_extension_message))
- }
- return@launch
- }
- val stream = application.applicationContext.contentResolver.openInputStream(uri)
- stream ?: return@launch
- val bufferReader = stream.bufferedReader(charset = Charsets.UTF_8)
- val config = Config.parse(bufferReader)
- val tunnelName = getNameFromFileName(fileName)
- saveTunnel(TunnelConfig(name = tunnelName, wgQuick = config.toWgQuickString()))
- stream.close()
- } catch (_: BadConfigException) {
- launch {
- showSnackBarMessage(application.applicationContext.getString(R.string.bad_config))
- }
- }
+ private fun validateFileExtension(fileName : String) {
+ val extension = getFileExtensionFromFileName(fileName)
+ if(extension != Constants.VALID_FILE_EXTENSION) {
+ throw WgTunnelException(application.getString(R.string.file_extension_message))
}
}
+ private fun saveTunnelConfigFromStream(stream : InputStream, fileName : String) {
+ viewModelScope.launch(Dispatchers.IO) {
+ val bufferReader = stream.bufferedReader(charset = Charsets.UTF_8)
+ val config = Config.parse(bufferReader)
+ val tunnelName = getNameFromFileName(fileName)
+ addTunnel(TunnelConfig(name = tunnelName, wgQuick = config.toWgQuickString()))
+ stream.close()
+ }
+ }
+
+ private fun getInputStreamFromUri(uri: Uri): InputStream {
+ return application.applicationContext.contentResolver.openInputStream(uri)
+ ?: throw WgTunnelException(application.getString(R.string.stream_failed))
+ }
+
+ fun onTunnelFileSelected(uri : Uri) {
+ try {
+ val fileName = getFileName(application.applicationContext, uri)
+ validateFileExtension(fileName)
+ val stream = getInputStreamFromUri(uri)
+ saveTunnelConfigFromStream(stream, fileName)
+ } catch (e : Exception) {
+ showExceptionMessage(e)
+ }
+ }
+
+ private fun showExceptionMessage(e : Exception) {
+ when(e) {
+ is BadConfigException -> {
+ showSnackBarMessage(application.getString(R.string.bad_config))
+ }
+ is WgTunnelException -> {
+ showSnackBarMessage(e.message ?: application.getString(R.string.unknown_error_message))
+ }
+ else -> showSnackBarMessage(application.getString(R.string.unknown_error_message))
+ }
+ }
+
+ private suspend fun addTunnel(tunnelConfig: TunnelConfig) {
+ saveTunnel(tunnelConfig)
+ createTunnelAppShortcuts(tunnelConfig)
+ }
+
private suspend fun saveTunnel(tunnelConfig : TunnelConfig) {
tunnelRepo.save(tunnelConfig)
+ }
+
+ private fun createTunnelAppShortcuts(tunnelConfig: TunnelConfig) {
ShortcutsManager.createTunnelShortcuts(application.applicationContext, tunnelConfig)
}
- @SuppressLint("Range")
- private fun getFileName(context: Context, uri: Uri): String {
- if (uri.scheme == "content") {
- val cursor = try {
- context.contentResolver.query(uri, null, null, null, null)
- } catch (e : Exception) {
- Timber.d("Exception getting config name")
- null
- }
- cursor ?: return NumberUtils.generateRandomTunnelName()
+ private fun getFileNameByCursor(context: Context, uri: Uri) : String {
+ val cursor = context.contentResolver.query(uri, null, null, null, null)
+ if(cursor != null) {
cursor.use {
- if(cursor.moveToFirst()) {
- return cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME))
- }
+ return getDisplayNameByCursor(it)
}
+ } else {
+ throw WgTunnelException("Failed to initialize cursor")
}
- return NumberUtils.generateRandomTunnelName()
}
- suspend fun showSnackBarMessage(message : String) {
- _viewState.emit(_viewState.value.copy(
- showSnackbarMessage = true,
- snackbarMessage = message,
- snackbarActionText = "Okay",
- onSnackbarActionClick = {
- viewModelScope.launch {
- dismissSnackBar()
+ private fun getDisplayNameColumnIndex(cursor: Cursor) : Int {
+ val columnIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
+ if(columnIndex == -1) {
+ throw WgTunnelException("Cursor out of bounds")
+ }
+ return columnIndex
+ }
+
+ private fun getDisplayNameByCursor(cursor: Cursor) : String {
+ if(cursor.moveToFirst()) {
+ val index = getDisplayNameColumnIndex(cursor)
+ return cursor.getString(index)
+ } else {
+ throw WgTunnelException("Cursor failed to move to first")
+ }
+ }
+
+ private fun validateUriContentScheme(uri : Uri) {
+ if (uri.scheme != Constants.URI_CONTENT_SCHEME) {
+ throw WgTunnelException(application.getString(R.string.file_extension_message))
+ }
+ }
+
+
+ private fun getFileName(context: Context, uri: Uri): String {
+ validateUriContentScheme(uri)
+ return try {
+ getFileNameByCursor(context, uri)
+ } catch (_: Exception) {
+ NumberUtils.generateRandomTunnelName()
+ }
+ }
+
+ fun showSnackBarMessage(message : String) {
+ CoroutineScope(Dispatchers.IO).launch {
+ _viewState.emit(_viewState.value.copy(
+ showSnackbarMessage = true,
+ snackbarMessage = message,
+ snackbarActionText = application.getString(R.string.okay),
+ onSnackbarActionClick = {
+ viewModelScope.launch {
+ dismissSnackBar()
+ }
}
- }
- ))
- delay(Constants.SNACKBAR_DELAY)
- dismissSnackBar()
+ ))
+ delay(Constants.SNACKBAR_DELAY)
+ dismissSnackBar()
+ }
}
private suspend fun dismissSnackBar() {
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 3275815..d187479 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
@@ -392,6 +392,24 @@ fun SettingsScreen(
}
)
}
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(screenPadding),
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.SpaceBetween
+ ) {
+ Text("Tunnel on Ethernet")
+ Switch(
+ enabled = !settings.isAutoTunnelEnabled,
+ checked = settings.isTunnelOnEthernetEnabled,
+ onCheckedChange = {
+ scope.launch {
+ viewModel.onToggleTunnelOnEthernet()
+ }
+ }
+ )
+ }
Row(
modifier = Modifier
.fillMaxWidth()
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 0a4ecde..4d9bd35 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
@@ -122,6 +122,18 @@ class SettingsViewModel @Inject constructor(private val application : Applicatio
showSnackBarMessage(application.getString(R.string.select_tunnel_message))
}
}
+
+ suspend fun onToggleTunnelOnEthernet() {
+ if(_settings.value.defaultTunnel != null) {
+ _settings.emit(
+ _settings.value.copy(isTunnelOnEthernetEnabled = !_settings.value.isTunnelOnEthernetEnabled)
+ )
+ settingsRepo.save(_settings.value)
+ } else {
+ showSnackBarMessage(application.getString(R.string.select_tunnel_message))
+ }
+ }
+
fun checkLocationServicesEnabled() : Boolean {
val locationManager =
application.getSystemService(Context.LOCATION_SERVICE) as LocationManager
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/util/WgTunnelException.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/util/WgTunnelException.kt
new file mode 100644
index 0000000..8a34abe
--- /dev/null
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/util/WgTunnelException.kt
@@ -0,0 +1,3 @@
+package com.zaneschepke.wireguardautotunnel.util
+
+class WgTunnelException(message: String) : Exception(message)
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index da5c9e0..cfbec0b 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -38,7 +38,6 @@
Enter SSID
Submit SSID
[Interface]
- Invalid QR code.
Add tunnel from files
File Open
Add tunnel from QR code
@@ -62,7 +61,6 @@
Public key
Waiting for the Barcode UI module to be downloaded.
Barcode module downloading. Try again.
- Invalid QR code. Try again.
Addresses
DNS servers
MTU
@@ -92,5 +90,10 @@
Attempting connection..
VPN Starting
wg-tunnel-db
- Reading QR code
+ Scanning for QR
+ QR scan failed
+ None
+ Never
+ Failed to open file stream.
+ An unknown error occurred.
\ No newline at end of file