feat: improved imports
Added a feature where you can now add a commented "# Name = " property to QR code configs to import them with a name. If there is no name configured, app will use the first peer's host address as a name. Improved imports so they no longer replace an existing config if that config has the same name. Instead, they will import with a (number) appended for config name duplicates. Closes #68 Fixed a bug where the initial state of auto tunneling may not be correct and cause unexpected behavior. Fixed a bug where Amnezia imports were not working when being imported as a zip. Improved/refactored error handling.
This commit is contained in:
parent
cb1b8ee7d6
commit
d44baa84a8
|
@ -2,7 +2,6 @@ package com.zaneschepke.wireguardautotunnel
|
||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.content.ComponentName
|
import android.content.ComponentName
|
||||||
import android.content.Context
|
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.service.quicksettings.TileService
|
import android.service.quicksettings.TileService
|
||||||
import com.zaneschepke.wireguardautotunnel.service.tile.AutoTunnelControlTile
|
import com.zaneschepke.wireguardautotunnel.service.tile.AutoTunnelControlTile
|
||||||
|
@ -40,16 +39,16 @@ class WireGuardAutoTunnel : Application() {
|
||||||
return instance.packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)
|
return instance.packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun requestTunnelTileServiceStateUpdate(context: Context) {
|
fun requestTunnelTileServiceStateUpdate() {
|
||||||
TileService.requestListeningState(
|
TileService.requestListeningState(
|
||||||
context,
|
instance,
|
||||||
ComponentName(instance, TunnelControlTile::class.java),
|
ComponentName(instance, TunnelControlTile::class.java),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun requestAutoTunnelTileServiceUpdate(context: Context) {
|
fun requestAutoTunnelTileServiceUpdate() {
|
||||||
TileService.requestListeningState(
|
TileService.requestListeningState(
|
||||||
context,
|
instance,
|
||||||
ComponentName(instance, AutoTunnelControlTile::class.java),
|
ComponentName(instance, AutoTunnelControlTile::class.java),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,9 @@ interface TunnelConfigDao {
|
||||||
@Query("SELECT * FROM TunnelConfig WHERE id=:id")
|
@Query("SELECT * FROM TunnelConfig WHERE id=:id")
|
||||||
suspend fun getById(id: Long): TunnelConfig?
|
suspend fun getById(id: Long): TunnelConfig?
|
||||||
|
|
||||||
|
@Query("SELECT * FROM TunnelConfig WHERE name=:name")
|
||||||
|
suspend fun getByName(name: String) : TunnelConfig?
|
||||||
|
|
||||||
@Query("SELECT * FROM TunnelConfig")
|
@Query("SELECT * FROM TunnelConfig")
|
||||||
suspend fun getAll(): TunnelConfigs
|
suspend fun getAll(): TunnelConfigs
|
||||||
|
|
||||||
|
|
|
@ -54,6 +54,10 @@ class RoomTunnelConfigRepository(private val tunnelConfigDao: TunnelConfigDao) :
|
||||||
return tunnelConfigDao.count().toInt()
|
return tunnelConfigDao.count().toInt()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun findByTunnelName(name: String): TunnelConfig? {
|
||||||
|
return tunnelConfigDao.getByName(name)
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun findByTunnelNetworksName(name: String): TunnelConfigs {
|
override suspend fun findByTunnelNetworksName(name: String): TunnelConfigs {
|
||||||
return tunnelConfigDao.findByTunnelNetworkName(name)
|
return tunnelConfigDao.findByTunnelNetworkName(name)
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,8 @@ interface TunnelConfigRepository {
|
||||||
|
|
||||||
suspend fun count(): Int
|
suspend fun count(): Int
|
||||||
|
|
||||||
|
suspend fun findByTunnelName(name : String) : TunnelConfig?
|
||||||
|
|
||||||
suspend fun findByTunnelNetworksName(name: String): TunnelConfigs
|
suspend fun findByTunnelNetworksName(name: String): TunnelConfigs
|
||||||
|
|
||||||
suspend fun findByMobileDataTunnel(): TunnelConfigs
|
suspend fun findByMobileDataTunnel(): TunnelConfigs
|
||||||
|
|
|
@ -1,11 +1,9 @@
|
||||||
package com.zaneschepke.wireguardautotunnel.service.foreground
|
package com.zaneschepke.wireguardautotunnel.service.foreground
|
||||||
|
|
||||||
import com.zaneschepke.wireguardautotunnel.data.domain.Settings
|
import com.zaneschepke.wireguardautotunnel.data.domain.Settings
|
||||||
import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
|
|
||||||
|
|
||||||
data class WatcherState(
|
data class WatcherState(
|
||||||
val isWifiConnected: Boolean = false,
|
val isWifiConnected: Boolean = false,
|
||||||
val config: TunnelConfig? = null,
|
|
||||||
val isEthernetConnected: Boolean = false,
|
val isEthernetConnected: Boolean = false,
|
||||||
val isMobileDataConnected: Boolean = false,
|
val isMobileDataConnected: Boolean = false,
|
||||||
val currentNetworkSSID: String = "",
|
val currentNetworkSSID: String = "",
|
||||||
|
@ -27,8 +25,7 @@ data class WatcherState(
|
||||||
return (!isEthernetConnected &&
|
return (!isEthernetConnected &&
|
||||||
settings.isTunnelOnMobileDataEnabled &&
|
settings.isTunnelOnMobileDataEnabled &&
|
||||||
!isWifiConnected &&
|
!isWifiConnected &&
|
||||||
isMobileDataConnected &&
|
isMobileDataConnected)
|
||||||
config?.isMobileDataTunnel == false)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isTunnelOffOnMobileDataConditionMet(): Boolean {
|
fun isTunnelOffOnMobileDataConditionMet(): Boolean {
|
||||||
|
@ -45,13 +42,6 @@ data class WatcherState(
|
||||||
settings.isTunnelOnWifiEnabled)
|
settings.isTunnelOnWifiEnabled)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isTunnelNotWifiNamePreferredMet(ssid: String): Boolean {
|
|
||||||
return (!isEthernetConnected &&
|
|
||||||
isWifiConnected &&
|
|
||||||
!settings.trustedNetworkSSIDs.contains(currentNetworkSSID) &&
|
|
||||||
settings.isTunnelOnWifiEnabled && config?.tunnelNetworks?.contains(ssid) == false)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun isTrustedWifiConditionMet(): Boolean {
|
fun isTrustedWifiConditionMet(): Boolean {
|
||||||
return (!isEthernetConnected &&
|
return (!isEthernetConnected &&
|
||||||
(isWifiConnected &&
|
(isWifiConnected &&
|
||||||
|
|
|
@ -24,6 +24,7 @@ import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.net.InetAddress
|
import java.net.InetAddress
|
||||||
|
@ -162,10 +163,6 @@ class WireGuardConnectivityWatcherService : ForegroundService() {
|
||||||
watchForEthernetConnectivityChanges()
|
watchForEthernetConnectivityChanges()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
launch {
|
|
||||||
Timber.i("Starting vpn state watcher")
|
|
||||||
watchForVpnConnectivityChanges()
|
|
||||||
}
|
|
||||||
launch {
|
launch {
|
||||||
Timber.i("Starting settings watcher")
|
Timber.i("Starting settings watcher")
|
||||||
watchForSettingsChanges()
|
watchForSettingsChanges()
|
||||||
|
@ -185,29 +182,32 @@ class WireGuardConnectivityWatcherService : ForegroundService() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun watchForMobileDataConnectivityChanges() {
|
private suspend fun watchForMobileDataConnectivityChanges() {
|
||||||
mobileDataService.networkStatus.collect {
|
mobileDataService.networkStatus.collect { status ->
|
||||||
when (it) {
|
when (status) {
|
||||||
is NetworkStatus.Available -> {
|
is NetworkStatus.Available -> {
|
||||||
Timber.i("Gained Mobile data connection")
|
Timber.i("Gained Mobile data connection")
|
||||||
networkEventsFlow.value =
|
networkEventsFlow.update {
|
||||||
networkEventsFlow.value.copy(
|
it.copy(
|
||||||
isMobileDataConnected = true,
|
isMobileDataConnected = true,
|
||||||
)
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
is NetworkStatus.CapabilitiesChanged -> {
|
is NetworkStatus.CapabilitiesChanged -> {
|
||||||
networkEventsFlow.value =
|
networkEventsFlow.update {
|
||||||
networkEventsFlow.value.copy(
|
it.copy(
|
||||||
isMobileDataConnected = true,
|
isMobileDataConnected = true,
|
||||||
)
|
)
|
||||||
|
}
|
||||||
Timber.i("Mobile data capabilities changed")
|
Timber.i("Mobile data capabilities changed")
|
||||||
}
|
}
|
||||||
|
|
||||||
is NetworkStatus.Unavailable -> {
|
is NetworkStatus.Unavailable -> {
|
||||||
networkEventsFlow.value =
|
networkEventsFlow.update {
|
||||||
networkEventsFlow.value.copy(
|
it.copy(
|
||||||
isMobileDataConnected = false,
|
isMobileDataConnected = false,
|
||||||
)
|
)
|
||||||
|
}
|
||||||
Timber.i("Lost mobile data connection")
|
Timber.i("Lost mobile data connection")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -249,53 +249,48 @@ class WireGuardConnectivityWatcherService : ForegroundService() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun watchForSettingsChanges() {
|
private suspend fun watchForSettingsChanges() {
|
||||||
appDataRepository.settings.getSettingsFlow().collect {
|
appDataRepository.settings.getSettingsFlow().collect { settings ->
|
||||||
if (networkEventsFlow.value.settings.isAutoTunnelPaused != it.isAutoTunnelPaused) {
|
if (networkEventsFlow.value.settings.isAutoTunnelPaused != settings.isAutoTunnelPaused) {
|
||||||
when (it.isAutoTunnelPaused) {
|
when (settings.isAutoTunnelPaused) {
|
||||||
true -> launchWatcherPausedNotification()
|
true -> launchWatcherPausedNotification()
|
||||||
false -> launchWatcherNotification()
|
false -> launchWatcherNotification()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
networkEventsFlow.value =
|
networkEventsFlow.update {
|
||||||
networkEventsFlow.value.copy(
|
it.copy(
|
||||||
settings = it,
|
settings = settings,
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun watchForVpnConnectivityChanges() {
|
|
||||||
vpnService.vpnState.collect {
|
|
||||||
networkEventsFlow.value =
|
|
||||||
networkEventsFlow.value.copy(
|
|
||||||
config = it.tunnelConfig,
|
|
||||||
)
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun watchForEthernetConnectivityChanges() {
|
private suspend fun watchForEthernetConnectivityChanges() {
|
||||||
ethernetService.networkStatus.collect {
|
ethernetService.networkStatus.collect { status ->
|
||||||
when (it) {
|
when (status) {
|
||||||
is NetworkStatus.Available -> {
|
is NetworkStatus.Available -> {
|
||||||
Timber.i("Gained Ethernet connection")
|
Timber.i("Gained Ethernet connection")
|
||||||
networkEventsFlow.value =
|
networkEventsFlow.update {
|
||||||
networkEventsFlow.value.copy(
|
it.copy(
|
||||||
isEthernetConnected = true,
|
isEthernetConnected = true,
|
||||||
)
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
is NetworkStatus.CapabilitiesChanged -> {
|
is NetworkStatus.CapabilitiesChanged -> {
|
||||||
Timber.i("Ethernet capabilities changed")
|
Timber.i("Ethernet capabilities changed")
|
||||||
networkEventsFlow.value =
|
networkEventsFlow.update {
|
||||||
networkEventsFlow.value.copy(
|
it.copy(
|
||||||
isEthernetConnected = true,
|
isEthernetConnected = true,
|
||||||
)
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
is NetworkStatus.Unavailable -> {
|
is NetworkStatus.Unavailable -> {
|
||||||
networkEventsFlow.value =
|
networkEventsFlow.update {
|
||||||
networkEventsFlow.value.copy(
|
it.copy(
|
||||||
isEthernetConnected = false,
|
isEthernetConnected = false,
|
||||||
)
|
)
|
||||||
|
}
|
||||||
Timber.i("Lost Ethernet connection")
|
Timber.i("Lost Ethernet connection")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -303,40 +298,43 @@ class WireGuardConnectivityWatcherService : ForegroundService() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun watchForWifiConnectivityChanges() {
|
private suspend fun watchForWifiConnectivityChanges() {
|
||||||
wifiService.networkStatus.collect {
|
wifiService.networkStatus.collect { status ->
|
||||||
when (it) {
|
when (status) {
|
||||||
is NetworkStatus.Available -> {
|
is NetworkStatus.Available -> {
|
||||||
Timber.i("Gained Wi-Fi connection")
|
Timber.i("Gained Wi-Fi connection")
|
||||||
networkEventsFlow.value =
|
networkEventsFlow.update {
|
||||||
networkEventsFlow.value.copy(
|
it.copy(
|
||||||
isWifiConnected = true,
|
isWifiConnected = true,
|
||||||
)
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
is NetworkStatus.CapabilitiesChanged -> {
|
is NetworkStatus.CapabilitiesChanged -> {
|
||||||
Timber.i("Wifi capabilities changed")
|
Timber.i("Wifi capabilities changed")
|
||||||
networkEventsFlow.value =
|
networkEventsFlow.update {
|
||||||
networkEventsFlow.value.copy(
|
it.copy(
|
||||||
isWifiConnected = true,
|
isWifiConnected = true,
|
||||||
)
|
)
|
||||||
val ssid = wifiService.getNetworkName(it.networkCapabilities)
|
}
|
||||||
|
val ssid = wifiService.getNetworkName(status.networkCapabilities)
|
||||||
ssid?.let { name ->
|
ssid?.let { name ->
|
||||||
if(name.contains(Constants.UNREADABLE_SSID)) {
|
if(name.contains(Constants.UNREADABLE_SSID)) {
|
||||||
Timber.w("SSID unreadable: missing permissions")
|
Timber.w("SSID unreadable: missing permissions")
|
||||||
} else Timber.i("Detected valid SSID")
|
} else Timber.i("Detected valid SSID")
|
||||||
appDataRepository.appState.setCurrentSsid(name)
|
appDataRepository.appState.setCurrentSsid(name)
|
||||||
networkEventsFlow.value =
|
networkEventsFlow.update {
|
||||||
networkEventsFlow.value.copy(
|
it.copy(
|
||||||
currentNetworkSSID = name,
|
currentNetworkSSID = name,
|
||||||
)
|
)
|
||||||
|
}
|
||||||
} ?: Timber.w("Failed to read ssid")
|
} ?: Timber.w("Failed to read ssid")
|
||||||
}
|
}
|
||||||
|
|
||||||
is NetworkStatus.Unavailable -> {
|
is NetworkStatus.Unavailable -> {
|
||||||
networkEventsFlow.value =
|
networkEventsFlow.update {
|
||||||
networkEventsFlow.value.copy(
|
it.copy(
|
||||||
isWifiConnected = false,
|
isWifiConnected = false,
|
||||||
)
|
)
|
||||||
|
}
|
||||||
Timber.i("Lost Wi-Fi connection")
|
Timber.i("Lost Wi-Fi connection")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -361,6 +359,7 @@ class WireGuardConnectivityWatcherService : ForegroundService() {
|
||||||
if (!watcherState.settings.isAutoTunnelPaused) {
|
if (!watcherState.settings.isAutoTunnelPaused) {
|
||||||
//delay for rapid network state changes and then collect latest
|
//delay for rapid network state changes and then collect latest
|
||||||
delay(Constants.WATCHER_COLLECTION_DELAY)
|
delay(Constants.WATCHER_COLLECTION_DELAY)
|
||||||
|
val tunnelConfig = vpnService.vpnState.value.tunnelConfig
|
||||||
when {
|
when {
|
||||||
watcherState.isEthernetConditionMet() -> {
|
watcherState.isEthernetConditionMet() -> {
|
||||||
Timber.i("$autoTunnel - tunnel on on ethernet condition met")
|
Timber.i("$autoTunnel - tunnel on on ethernet condition met")
|
||||||
|
@ -373,12 +372,14 @@ class WireGuardConnectivityWatcherService : ForegroundService() {
|
||||||
}
|
}
|
||||||
|
|
||||||
watcherState.isTunnelOnMobileDataPreferredConditionMet() -> {
|
watcherState.isTunnelOnMobileDataPreferredConditionMet() -> {
|
||||||
getMobileDataTunnel()?.let {
|
if(tunnelConfig?.isMobileDataTunnel == false) {
|
||||||
Timber.i("$autoTunnel - tunnel connected on mobile data is not preferred condition met, switching to preferred")
|
getMobileDataTunnel()?.let {
|
||||||
if(isTunnelDown()) serviceManager.startVpnServiceForeground(
|
Timber.i("$autoTunnel - tunnel connected on mobile data is not preferred condition met, switching to preferred")
|
||||||
this,
|
if(isTunnelDown()) serviceManager.startVpnServiceForeground(
|
||||||
getMobileDataTunnel()?.id,
|
this,
|
||||||
)
|
getMobileDataTunnel()?.id,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -387,25 +388,20 @@ class WireGuardConnectivityWatcherService : ForegroundService() {
|
||||||
if(!isTunnelDown()) serviceManager.stopVpnServiceForeground(this)
|
if(!isTunnelDown()) serviceManager.stopVpnServiceForeground(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
watcherState.isTunnelNotWifiNamePreferredMet(watcherState.currentNetworkSSID) -> {
|
|
||||||
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(this, it.id)
|
|
||||||
} ?: suspend {
|
|
||||||
Timber.i("No tunnel associated with this SSID, using defaults")
|
|
||||||
if (appDataRepository.getPrimaryOrFirstTunnel()?.name != vpnService.name) {
|
|
||||||
if(isTunnelDown()) serviceManager.startVpnServiceForeground(this)
|
|
||||||
}
|
|
||||||
}.invoke()
|
|
||||||
}
|
|
||||||
|
|
||||||
watcherState.isUntrustedWifiConditionMet() -> {
|
watcherState.isUntrustedWifiConditionMet() -> {
|
||||||
Timber.i("$autoTunnel - tunnel on untrusted wifi condition met")
|
if(tunnelConfig?.tunnelNetworks?.contains(watcherState.currentNetworkSSID) == false ||
|
||||||
if(isTunnelDown()) serviceManager.startVpnServiceForeground(
|
tunnelConfig == null) {
|
||||||
this,
|
Timber.i("$autoTunnel - tunnel on ssid not associated with current tunnel condition met")
|
||||||
getSsidTunnel(watcherState.currentNetworkSSID)?.id,
|
getSsidTunnel(watcherState.currentNetworkSSID)?.let {
|
||||||
)
|
Timber.i("Found tunnel associated with this SSID, bringing tunnel up")
|
||||||
|
if(isTunnelDown()) serviceManager.startVpnServiceForeground(this, it.id)
|
||||||
|
} ?: suspend {
|
||||||
|
Timber.i("No tunnel associated with this SSID, using defaults")
|
||||||
|
if (appDataRepository.getPrimaryOrFirstTunnel()?.name != vpnService.name) {
|
||||||
|
if(isTunnelDown()) serviceManager.startVpnServiceForeground(this)
|
||||||
|
}
|
||||||
|
}.invoke()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
watcherState.isTrustedWifiConditionMet() -> {
|
watcherState.isTrustedWifiConditionMet() -> {
|
||||||
|
|
|
@ -5,14 +5,12 @@ import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.core.app.ServiceCompat
|
import androidx.core.app.ServiceCompat
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import com.wireguard.android.backend.Tunnel
|
|
||||||
import com.zaneschepke.wireguardautotunnel.R
|
import com.zaneschepke.wireguardautotunnel.R
|
||||||
import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
|
import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
|
||||||
import com.zaneschepke.wireguardautotunnel.receiver.NotificationActionReceiver
|
import com.zaneschepke.wireguardautotunnel.receiver.NotificationActionReceiver
|
||||||
import com.zaneschepke.wireguardautotunnel.service.notification.NotificationService
|
import com.zaneschepke.wireguardautotunnel.service.notification.NotificationService
|
||||||
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.service.tunnel.statistics.TunnelStatistics
|
|
||||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.VpnService
|
import com.zaneschepke.wireguardautotunnel.service.tunnel.VpnService
|
||||||
import com.zaneschepke.wireguardautotunnel.util.Constants
|
import com.zaneschepke.wireguardautotunnel.util.Constants
|
||||||
import com.zaneschepke.wireguardautotunnel.util.handshakeStatus
|
import com.zaneschepke.wireguardautotunnel.util.handshakeStatus
|
||||||
|
|
|
@ -22,7 +22,6 @@ import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.amnezia.awg.backend.Tunnel
|
import org.amnezia.awg.backend.Tunnel
|
||||||
import org.amnezia.awg.config.Config
|
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@ -130,10 +129,15 @@ constructor(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun resetVpnState() {
|
||||||
|
_vpnState.tryEmit(VpnState())
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun stopTunnel() {
|
override suspend fun stopTunnel() {
|
||||||
try {
|
try {
|
||||||
if (getState() == TunnelState.UP) {
|
if (getState() == TunnelState.UP) {
|
||||||
val state = setState(null, TunnelState.DOWN)
|
val state = setState(null, TunnelState.DOWN)
|
||||||
|
resetVpnState()
|
||||||
emitTunnelState(state)
|
emitTunnelState(state)
|
||||||
}
|
}
|
||||||
} catch (e: BackendException) {
|
} catch (e: BackendException) {
|
||||||
|
@ -160,7 +164,7 @@ constructor(
|
||||||
private fun handleStateChange(state: TunnelState) {
|
private fun handleStateChange(state: TunnelState) {
|
||||||
val tunnel = this
|
val tunnel = this
|
||||||
emitTunnelState(state)
|
emitTunnelState(state)
|
||||||
WireGuardAutoTunnel.requestTunnelTileServiceStateUpdate(WireGuardAutoTunnel.instance)
|
WireGuardAutoTunnel.requestTunnelTileServiceStateUpdate()
|
||||||
if (state == TunnelState.UP) {
|
if (state == TunnelState.UP) {
|
||||||
statsJob =
|
statsJob =
|
||||||
scope.launch {
|
scope.launch {
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
package com.zaneschepke.wireguardautotunnel.ui
|
package com.zaneschepke.wireguardautotunnel.ui
|
||||||
|
|
||||||
import android.app.Application
|
|
||||||
import android.content.ActivityNotFoundException
|
import android.content.ActivityNotFoundException
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
@ -9,7 +8,6 @@ import android.widget.Toast
|
||||||
import androidx.compose.runtime.mutableStateListOf
|
import androidx.compose.runtime.mutableStateListOf
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.journeyapps.barcodescanner.BarcodeEncoder
|
|
||||||
import com.wireguard.android.backend.GoBackend
|
import com.wireguard.android.backend.GoBackend
|
||||||
import com.zaneschepke.logcatter.Logcatter
|
import com.zaneschepke.logcatter.Logcatter
|
||||||
import com.zaneschepke.logcatter.model.LogMessage
|
import com.zaneschepke.logcatter.model.LogMessage
|
||||||
|
|
|
@ -87,7 +87,7 @@ class MainActivity : AppCompatActivity() {
|
||||||
// load preferences into memory and init data
|
// load preferences into memory and init data
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
dataStoreManager.init()
|
dataStoreManager.init()
|
||||||
WireGuardAutoTunnel.requestTunnelTileServiceStateUpdate(this@MainActivity)
|
WireGuardAutoTunnel.requestTunnelTileServiceStateUpdate()
|
||||||
val settings = settingsRepository.getSettings()
|
val settings = settingsRepository.getSettings()
|
||||||
if (settings.isAutoTunnelEnabled) {
|
if (settings.isAutoTunnelEnabled) {
|
||||||
serviceManager.startWatcherService(application.applicationContext)
|
serviceManager.startWatcherService(application.applicationContext)
|
||||||
|
|
|
@ -4,11 +4,9 @@ import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.rounded.Home
|
import androidx.compose.material.icons.rounded.Home
|
||||||
import androidx.compose.material.icons.rounded.QuestionMark
|
import androidx.compose.material.icons.rounded.QuestionMark
|
||||||
import androidx.compose.material.icons.rounded.Settings
|
import androidx.compose.material.icons.rounded.Settings
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import com.zaneschepke.wireguardautotunnel.R
|
import com.zaneschepke.wireguardautotunnel.R
|
||||||
import com.zaneschepke.wireguardautotunnel.WireGuardAutoTunnel
|
import com.zaneschepke.wireguardautotunnel.WireGuardAutoTunnel
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.navigation.BottomNavItem
|
import com.zaneschepke.wireguardautotunnel.ui.common.navigation.BottomNavItem
|
||||||
import com.zaneschepke.wireguardautotunnel.util.StringValue
|
|
||||||
|
|
||||||
sealed class Screen(val route: String) {
|
sealed class Screen(val route: String) {
|
||||||
data object Main : Screen("main") {
|
data object Main : Screen("main") {
|
||||||
|
|
|
@ -81,8 +81,7 @@ import com.zaneschepke.wireguardautotunnel.ui.common.screen.LoadingScreen
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.text.SectionTitle
|
import com.zaneschepke.wireguardautotunnel.ui.common.text.SectionTitle
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.screens.main.ConfigType
|
import com.zaneschepke.wireguardautotunnel.ui.screens.main.ConfigType
|
||||||
import com.zaneschepke.wireguardautotunnel.util.Constants
|
import com.zaneschepke.wireguardautotunnel.util.Constants
|
||||||
import com.zaneschepke.wireguardautotunnel.util.Event
|
import com.zaneschepke.wireguardautotunnel.util.getMessage
|
||||||
import com.zaneschepke.wireguardautotunnel.util.Result
|
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
|
|
||||||
@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter")
|
@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter")
|
||||||
|
@ -150,11 +149,11 @@ fun ConfigScreen(
|
||||||
},
|
},
|
||||||
onError = {
|
onError = {
|
||||||
showAuthPrompt = false
|
showAuthPrompt = false
|
||||||
appViewModel.showSnackbarMessage(Event.Error.AuthenticationFailed.message)
|
appViewModel.showSnackbarMessage(context.getString(R.string.error_authentication_failed))
|
||||||
},
|
},
|
||||||
onFailure = {
|
onFailure = {
|
||||||
showAuthPrompt = false
|
showAuthPrompt = false
|
||||||
appViewModel.showSnackbarMessage(Event.Error.AuthorizationFailed.message)
|
appViewModel.showSnackbarMessage(context.getString(R.string.error_authorization_failed))
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -321,15 +320,11 @@ fun ConfigScreen(
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onClick = {
|
onClick = {
|
||||||
viewModel.onSaveAllChanges(configType).let {
|
viewModel.onSaveAllChanges(configType).onSuccess {
|
||||||
when (it) {
|
appViewModel.showSnackbarMessage(context.getString(R.string.config_changes_saved))
|
||||||
is Result.Success -> {
|
navController.navigate(Screen.Main.route)
|
||||||
appViewModel.showSnackbarMessage(it.data.message)
|
}.onFailure {
|
||||||
navController.navigate(Screen.Main.route)
|
appViewModel.showSnackbarMessage(it.getMessage(context))
|
||||||
}
|
|
||||||
|
|
||||||
is Result.Error -> appViewModel.showSnackbarMessage(it.error.message)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
containerColor = fobColor,
|
containerColor = fobColor,
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package com.zaneschepke.wireguardautotunnel.ui.screens.config
|
package com.zaneschepke.wireguardautotunnel.ui.screens.config
|
||||||
|
|
||||||
import android.Manifest
|
import android.Manifest
|
||||||
import android.app.Application
|
|
||||||
import android.content.pm.PackageInfo
|
import android.content.pm.PackageInfo
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
|
@ -12,6 +11,7 @@ import com.wireguard.config.Interface
|
||||||
import com.wireguard.config.Peer
|
import com.wireguard.config.Peer
|
||||||
import com.wireguard.crypto.Key
|
import com.wireguard.crypto.Key
|
||||||
import com.wireguard.crypto.KeyPair
|
import com.wireguard.crypto.KeyPair
|
||||||
|
import com.zaneschepke.wireguardautotunnel.R
|
||||||
import com.zaneschepke.wireguardautotunnel.WireGuardAutoTunnel
|
import com.zaneschepke.wireguardautotunnel.WireGuardAutoTunnel
|
||||||
import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
|
import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
|
||||||
import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
|
import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
|
||||||
|
@ -19,9 +19,9 @@ import com.zaneschepke.wireguardautotunnel.data.repository.SettingsRepository
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.screens.config.model.PeerProxy
|
import com.zaneschepke.wireguardautotunnel.ui.screens.config.model.PeerProxy
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.screens.main.ConfigType
|
import com.zaneschepke.wireguardautotunnel.ui.screens.main.ConfigType
|
||||||
import com.zaneschepke.wireguardautotunnel.util.Constants
|
import com.zaneschepke.wireguardautotunnel.util.Constants
|
||||||
import com.zaneschepke.wireguardautotunnel.util.Event
|
|
||||||
import com.zaneschepke.wireguardautotunnel.util.NumberUtils
|
import com.zaneschepke.wireguardautotunnel.util.NumberUtils
|
||||||
import com.zaneschepke.wireguardautotunnel.util.Result
|
import com.zaneschepke.wireguardautotunnel.util.StringValue
|
||||||
|
import com.zaneschepke.wireguardautotunnel.util.WgTunnelExceptions
|
||||||
import com.zaneschepke.wireguardautotunnel.util.removeAt
|
import com.zaneschepke.wireguardautotunnel.util.removeAt
|
||||||
import com.zaneschepke.wireguardautotunnel.util.update
|
import com.zaneschepke.wireguardautotunnel.util.update
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
@ -37,12 +37,11 @@ import javax.inject.Inject
|
||||||
class ConfigViewModel
|
class ConfigViewModel
|
||||||
@Inject
|
@Inject
|
||||||
constructor(
|
constructor(
|
||||||
private val application: Application,
|
|
||||||
private val settingsRepository: SettingsRepository,
|
private val settingsRepository: SettingsRepository,
|
||||||
private val appDataRepository: AppDataRepository
|
private val appDataRepository: AppDataRepository
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
|
|
||||||
private val packageManager = application.packageManager
|
private val packageManager = WireGuardAutoTunnel.instance.packageManager
|
||||||
|
|
||||||
private val _uiState = MutableStateFlow(ConfigUiState())
|
private val _uiState = MutableStateFlow(ConfigUiState())
|
||||||
val uiState = _uiState.asStateFlow()
|
val uiState = _uiState.asStateFlow()
|
||||||
|
@ -109,7 +108,7 @@ constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getPackageLabel(packageInfo: PackageInfo): String {
|
fun getPackageLabel(packageInfo: PackageInfo): String {
|
||||||
return packageInfo.applicationInfo.loadLabel(application.packageManager).toString()
|
return packageInfo.applicationInfo.loadLabel(packageManager).toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getAllInternetCapablePackages(): List<PackageInfo> {
|
private fun getAllInternetCapablePackages(): List<PackageInfo> {
|
||||||
|
@ -138,7 +137,7 @@ constructor(
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
if (tunnelConfig != null) {
|
if (tunnelConfig != null) {
|
||||||
saveConfig(tunnelConfig).join()
|
saveConfig(tunnelConfig).join()
|
||||||
WireGuardAutoTunnel.requestTunnelTileServiceStateUpdate(application)
|
WireGuardAutoTunnel.requestTunnelTileServiceStateUpdate()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -249,7 +248,7 @@ constructor(
|
||||||
return org.amnezia.awg.config.Config.Builder().addPeers(peerList).setInterface(amInterface).build()
|
return org.amnezia.awg.config.Config.Builder().addPeers(peerList).setInterface(amInterface).build()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onSaveAllChanges(configType: ConfigType): Result<Event> {
|
fun onSaveAllChanges(configType: ConfigType): Result<Unit> {
|
||||||
return try {
|
return try {
|
||||||
val wgQuick = buildConfig().toWgQuickString()
|
val wgQuick = buildConfig().toWgQuickString()
|
||||||
val amQuick = if(configType == ConfigType.AMNEZIA) {
|
val amQuick = if(configType == ConfigType.AMNEZIA) {
|
||||||
|
@ -268,11 +267,14 @@ constructor(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
updateTunnelConfig(tunnelConfig)
|
updateTunnelConfig(tunnelConfig)
|
||||||
Result.Success(Event.Message.ConfigSaved)
|
Result.success(Unit)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Timber.e(e)
|
Timber.e(e)
|
||||||
val message = e.message?.substringAfter(":", missingDelimiterValue = "")
|
val message = e.message?.substringAfter(":", missingDelimiterValue = "")
|
||||||
Result.Error(Event.Error.ConfigParseError(message ?: ""))
|
val stringValue = message?.let {
|
||||||
|
StringValue.DynamicString(message)
|
||||||
|
} ?: StringValue.StringResource(R.string.unknown_error)
|
||||||
|
Result.failure(WgTunnelExceptions.ConfigParseError(stringValue))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -108,8 +108,7 @@ import com.zaneschepke.wireguardautotunnel.ui.common.screen.LoadingScreen
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.theme.corn
|
import com.zaneschepke.wireguardautotunnel.ui.theme.corn
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.theme.mint
|
import com.zaneschepke.wireguardautotunnel.ui.theme.mint
|
||||||
import com.zaneschepke.wireguardautotunnel.util.Constants
|
import com.zaneschepke.wireguardautotunnel.util.Constants
|
||||||
import com.zaneschepke.wireguardautotunnel.util.Event
|
import com.zaneschepke.wireguardautotunnel.util.getMessage
|
||||||
import com.zaneschepke.wireguardautotunnel.util.Result
|
|
||||||
import com.zaneschepke.wireguardautotunnel.util.handshakeStatus
|
import com.zaneschepke.wireguardautotunnel.util.handshakeStatus
|
||||||
import com.zaneschepke.wireguardautotunnel.util.mapPeerStats
|
import com.zaneschepke.wireguardautotunnel.util.mapPeerStats
|
||||||
import com.zaneschepke.wireguardautotunnel.util.truncateWithEllipsis
|
import com.zaneschepke.wireguardautotunnel.util.truncateWithEllipsis
|
||||||
|
@ -194,7 +193,7 @@ fun MainScreen(
|
||||||
name.startsWith(Constants.ANDROID_TV_EXPLORER_STUB)
|
name.startsWith(Constants.ANDROID_TV_EXPLORER_STUB)
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
appViewModel.showSnackbarMessage(Event.Error.FileExplorerRequired.message)
|
appViewModel.showSnackbarMessage(context.getString(R.string.error_no_file_explorer))
|
||||||
}
|
}
|
||||||
return intent
|
return intent
|
||||||
}
|
}
|
||||||
|
@ -202,11 +201,8 @@ fun MainScreen(
|
||||||
) { data ->
|
) { data ->
|
||||||
if (data == null) return@rememberLauncherForActivityResult
|
if (data == null) return@rememberLauncherForActivityResult
|
||||||
scope.launch {
|
scope.launch {
|
||||||
viewModel.onTunnelFileSelected(data, configType).let {
|
viewModel.onTunnelFileSelected(data, configType, context).onFailure {
|
||||||
when (it) {
|
appViewModel.showSnackbarMessage(it.getMessage(context))
|
||||||
is Result.Error -> appViewModel.showSnackbarMessage(it.error.message)
|
|
||||||
is Result.Success -> {}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -216,11 +212,8 @@ fun MainScreen(
|
||||||
onResult = {
|
onResult = {
|
||||||
if (it.contents != null) {
|
if (it.contents != null) {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
viewModel.onTunnelQrResult(it.contents, configType).let { result ->
|
viewModel.onTunnelQrResult(it.contents, configType).onFailure {
|
||||||
when (result) {
|
appViewModel.showSnackbarMessage(it.getMessage(context))
|
||||||
is Result.Success -> {}
|
|
||||||
is Result.Error -> appViewModel.showSnackbarMessage(result.error.message)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -233,7 +226,7 @@ fun MainScreen(
|
||||||
confirmButton = {
|
confirmButton = {
|
||||||
TextButton(
|
TextButton(
|
||||||
onClick = {
|
onClick = {
|
||||||
selectedTunnel?.let { viewModel.onDelete(it) }
|
selectedTunnel?.let { viewModel.onDelete(it, context) }
|
||||||
showDeleteTunnelAlertDialog = false
|
showDeleteTunnelAlertDialog = false
|
||||||
selectedTunnel = null
|
selectedTunnel = null
|
||||||
},
|
},
|
||||||
|
@ -253,7 +246,7 @@ fun MainScreen(
|
||||||
|
|
||||||
fun onTunnelToggle(checked: Boolean, tunnel: TunnelConfig) {
|
fun onTunnelToggle(checked: Boolean, tunnel: TunnelConfig) {
|
||||||
if (appViewModel.isRequiredPermissionGranted()) {
|
if (appViewModel.isRequiredPermissionGranted()) {
|
||||||
if (checked) viewModel.onTunnelStart(tunnel) else viewModel.onTunnelStop()
|
if (checked) viewModel.onTunnelStart(tunnel, context) else viewModel.onTunnelStop(context)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -575,7 +568,7 @@ fun MainScreen(
|
||||||
(uiState.vpnState.status == TunnelState.UP) &&
|
(uiState.vpnState.status == TunnelState.UP) &&
|
||||||
(tunnel.name == uiState.vpnState.tunnelConfig?.name)
|
(tunnel.name == uiState.vpnState.tunnelConfig?.name)
|
||||||
) {
|
) {
|
||||||
appViewModel.showSnackbarMessage(Event.Message.TunnelOffAction.message)
|
appViewModel.showSnackbarMessage(context.getString(R.string.turn_off_tunnel))
|
||||||
return@RowListItem
|
return@RowListItem
|
||||||
}
|
}
|
||||||
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
|
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||||
|
@ -609,7 +602,7 @@ fun MainScreen(
|
||||||
!uiState.settings.isAutoTunnelPaused
|
!uiState.settings.isAutoTunnelPaused
|
||||||
) {
|
) {
|
||||||
appViewModel.showSnackbarMessage(
|
appViewModel.showSnackbarMessage(
|
||||||
Event.Message.AutoTunnelOffAction.message,
|
context.getString(R.string.turn_off_tunnel),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
navController.navigate(
|
navController.navigate(
|
||||||
|
@ -664,7 +657,7 @@ fun MainScreen(
|
||||||
onClick = {
|
onClick = {
|
||||||
if (uiState.settings.isAutoTunnelEnabled) {
|
if (uiState.settings.isAutoTunnelEnabled) {
|
||||||
appViewModel.showSnackbarMessage(
|
appViewModel.showSnackbarMessage(
|
||||||
Event.Message.AutoTunnelOffAction.message,
|
context.getString(R.string.turn_off_auto),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
selectedTunnel = tunnel
|
selectedTunnel = tunnel
|
||||||
|
@ -690,7 +683,7 @@ fun MainScreen(
|
||||||
expanded.value = !expanded.value
|
expanded.value = !expanded.value
|
||||||
} else {
|
} else {
|
||||||
appViewModel.showSnackbarMessage(
|
appViewModel.showSnackbarMessage(
|
||||||
Event.Message.TunnelOnAction.message,
|
context.getString(R.string.turn_on_tunnel),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -711,7 +704,7 @@ fun MainScreen(
|
||||||
tunnel.name == uiState.vpnState.tunnelConfig?.name
|
tunnel.name == uiState.vpnState.tunnelConfig?.name
|
||||||
) {
|
) {
|
||||||
appViewModel.showSnackbarMessage(
|
appViewModel.showSnackbarMessage(
|
||||||
Event.Message.TunnelOffAction.message,
|
context.getString(R.string.turn_off_tunnel),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
selectedTunnel = tunnel
|
selectedTunnel = tunnel
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
package com.zaneschepke.wireguardautotunnel.ui.screens.main
|
package com.zaneschepke.wireguardautotunnel.ui.screens.main
|
||||||
|
|
||||||
import android.app.Application
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.database.Cursor
|
import android.database.Cursor
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
@ -15,9 +14,8 @@ import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
|
||||||
import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager
|
import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager
|
||||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.VpnService
|
import com.zaneschepke.wireguardautotunnel.service.tunnel.VpnService
|
||||||
import com.zaneschepke.wireguardautotunnel.util.Constants
|
import com.zaneschepke.wireguardautotunnel.util.Constants
|
||||||
import com.zaneschepke.wireguardautotunnel.util.Event
|
|
||||||
import com.zaneschepke.wireguardautotunnel.util.NumberUtils
|
import com.zaneschepke.wireguardautotunnel.util.NumberUtils
|
||||||
import com.zaneschepke.wireguardautotunnel.util.Result
|
import com.zaneschepke.wireguardautotunnel.util.WgTunnelExceptions
|
||||||
import com.zaneschepke.wireguardautotunnel.util.toWgQuickString
|
import com.zaneschepke.wireguardautotunnel.util.toWgQuickString
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
@ -25,6 +23,7 @@ import kotlinx.coroutines.flow.SharingStarted
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
import kotlinx.coroutines.flow.stateIn
|
import kotlinx.coroutines.flow.stateIn
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import java.util.zip.ZipInputStream
|
import java.util.zip.ZipInputStream
|
||||||
|
@ -34,7 +33,6 @@ import javax.inject.Inject
|
||||||
class MainViewModel
|
class MainViewModel
|
||||||
@Inject
|
@Inject
|
||||||
constructor(
|
constructor(
|
||||||
private val application: Application,
|
|
||||||
private val appDataRepository: AppDataRepository,
|
private val appDataRepository: AppDataRepository,
|
||||||
private val serviceManager: ServiceManager,
|
private val serviceManager: ServiceManager,
|
||||||
val vpnService: VpnService
|
val vpnService: VpnService
|
||||||
|
@ -54,21 +52,21 @@ constructor(
|
||||||
MainUiState(),
|
MainUiState(),
|
||||||
)
|
)
|
||||||
|
|
||||||
private fun stopWatcherService() =
|
private fun stopWatcherService(context: Context) =
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
serviceManager.stopWatcherService(application.applicationContext)
|
serviceManager.stopWatcherService(context)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onDelete(tunnel: TunnelConfig) {
|
fun onDelete(tunnel: TunnelConfig, context: Context) {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
val settings = appDataRepository.settings.getSettings()
|
val settings = appDataRepository.settings.getSettings()
|
||||||
val isPrimary = tunnel.isPrimaryTunnel
|
val isPrimary = tunnel.isPrimaryTunnel
|
||||||
if (appDataRepository.tunnels.count() == 1 || isPrimary) {
|
if (appDataRepository.tunnels.count() == 1 || isPrimary) {
|
||||||
stopWatcherService()
|
stopWatcherService(context)
|
||||||
resetTunnelSetting(settings)
|
resetTunnelSetting(settings)
|
||||||
}
|
}
|
||||||
appDataRepository.tunnels.delete(tunnel)
|
appDataRepository.tunnels.delete(tunnel)
|
||||||
WireGuardAutoTunnel.requestTunnelTileServiceStateUpdate(application)
|
WireGuardAutoTunnel.requestTunnelTileServiceStateUpdate()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -81,21 +79,21 @@ constructor(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onTunnelStart(tunnelConfig: TunnelConfig) =
|
fun onTunnelStart(tunnelConfig: TunnelConfig, context: Context) =
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
Timber.d("On start called!")
|
Timber.d("On start called!")
|
||||||
serviceManager.startVpnService(
|
serviceManager.startVpnService(
|
||||||
application.applicationContext,
|
context,
|
||||||
tunnelConfig.id,
|
tunnelConfig.id,
|
||||||
isManualStart = true,
|
isManualStart = true,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fun onTunnelStop() =
|
fun onTunnelStop(context: Context) =
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
Timber.i("Stopping active tunnel")
|
Timber.i("Stopping active tunnel")
|
||||||
serviceManager.stopVpnService(application.applicationContext, isManualStop = true)
|
serviceManager.stopVpnService(context, isManualStop = true)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun validateConfigString(config: String, configType: ConfigType) {
|
private fun validateConfigString(config: String, configType: ConfigType) {
|
||||||
|
@ -105,24 +103,66 @@ constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun generateQrCodeDefaultName(config : String, configType: ConfigType) : String {
|
||||||
|
return try {
|
||||||
|
when(configType) {
|
||||||
|
ConfigType.AMNEZIA -> {
|
||||||
|
TunnelConfig.configFromAmQuick(config).peers[0].endpoint.get().host
|
||||||
|
}
|
||||||
|
ConfigType.WIREGUARD -> {
|
||||||
|
TunnelConfig.configFromWgQuick(config).peers[0].endpoint.get().host
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e : Exception) {
|
||||||
|
Timber.e(e)
|
||||||
|
NumberUtils.generateRandomTunnelName()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun generateQrCodeTunnelName(config : String, configType: ConfigType) : String {
|
||||||
|
var defaultName = generateQrCodeDefaultName(config, configType)
|
||||||
|
val lines = config.lines().toMutableList()
|
||||||
|
val linesIterator = lines.iterator()
|
||||||
|
while(linesIterator.hasNext()) {
|
||||||
|
val next = linesIterator.next()
|
||||||
|
if(next.contains(Constants.QR_CODE_NAME_PROPERTY)) {
|
||||||
|
defaultName = next.substringAfter(Constants.QR_CODE_NAME_PROPERTY).trim()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return defaultName
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun onTunnelQrResult(result: String, configType: ConfigType): Result<Unit> {
|
suspend fun onTunnelQrResult(result: String, configType: ConfigType): Result<Unit> {
|
||||||
return try {
|
return try {
|
||||||
validateConfigString(result, configType)
|
validateConfigString(result, configType)
|
||||||
|
val tunnelName = makeTunnelNameUnique(generateQrCodeTunnelName(result, configType))
|
||||||
val tunnelConfig = when(configType) {
|
val tunnelConfig = when(configType) {
|
||||||
ConfigType.AMNEZIA ->{
|
ConfigType.AMNEZIA ->{
|
||||||
TunnelConfig(name = NumberUtils.generateRandomTunnelName(), amQuick = result,
|
TunnelConfig(name = tunnelName, amQuick = result,
|
||||||
wgQuick = TunnelConfig.configFromAmQuick(result).toWgQuickString())
|
wgQuick = TunnelConfig.configFromAmQuick(result).toWgQuickString())
|
||||||
}
|
}
|
||||||
ConfigType.WIREGUARD -> TunnelConfig(name = NumberUtils.generateRandomTunnelName(), wgQuick = result)
|
ConfigType.WIREGUARD -> TunnelConfig(name = tunnelName, wgQuick = result)
|
||||||
}
|
}
|
||||||
addTunnel(tunnelConfig)
|
addTunnel(tunnelConfig)
|
||||||
Result.Success(Unit)
|
Result.success(Unit)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Timber.e(e)
|
Timber.e(e)
|
||||||
Result.Error(Event.Error.InvalidQrCode)
|
Result.failure(WgTunnelExceptions.InvalidQrCode())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private suspend fun makeTunnelNameUnique(name : String) : String {
|
||||||
|
val tunnels = appDataRepository.tunnels.getAll()
|
||||||
|
var tunnelName = name
|
||||||
|
var num = 1
|
||||||
|
while (tunnels.any { it.name == tunnelName }) {
|
||||||
|
tunnelName = name + "(${num})"
|
||||||
|
num++
|
||||||
|
}
|
||||||
|
return tunnelName
|
||||||
|
}
|
||||||
|
|
||||||
private suspend fun saveTunnelConfigFromStream(stream: InputStream, fileName: String, type: ConfigType) {
|
private suspend fun saveTunnelConfigFromStream(stream: InputStream, fileName: String, type: ConfigType) {
|
||||||
var amQuick : String? = null
|
var amQuick : String? = null
|
||||||
val wgQuick = stream.use {
|
val wgQuick = stream.use {
|
||||||
|
@ -137,42 +177,35 @@ constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val tunnelName = getNameFromFileName(fileName)
|
val tunnelName = makeTunnelNameUnique(getNameFromFileName(fileName))
|
||||||
addTunnel(TunnelConfig(name = tunnelName, wgQuick = wgQuick, amQuick = amQuick ?: TunnelConfig.AM_QUICK_DEFAULT))
|
addTunnel(TunnelConfig(name = tunnelName, wgQuick = wgQuick, amQuick = amQuick ?: TunnelConfig.AM_QUICK_DEFAULT))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getInputStreamFromUri(uri: Uri): InputStream? {
|
private fun getInputStreamFromUri(uri: Uri, context: Context): InputStream? {
|
||||||
return application.applicationContext.contentResolver.openInputStream(uri)
|
return context.applicationContext.contentResolver.openInputStream(uri)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun onTunnelFileSelected(uri: Uri, configType: ConfigType): Result<Unit> {
|
suspend fun onTunnelFileSelected(uri: Uri, configType: ConfigType, context: Context): Result<Unit> {
|
||||||
try {
|
return try {
|
||||||
if (isValidUriContentScheme(uri)) {
|
if (isValidUriContentScheme(uri)) {
|
||||||
val fileName = getFileName(application.applicationContext, uri)
|
val fileName = getFileName(context, uri)
|
||||||
when (getFileExtensionFromFileName(fileName)) {
|
return when (getFileExtensionFromFileName(fileName)) {
|
||||||
Constants.CONF_FILE_EXTENSION ->
|
Constants.CONF_FILE_EXTENSION ->
|
||||||
saveTunnelFromConfUri(fileName, uri, configType).let {
|
saveTunnelFromConfUri(fileName, uri, configType, context)
|
||||||
when (it) {
|
Constants.ZIP_FILE_EXTENSION -> saveTunnelsFromZipUri(uri, configType, context)
|
||||||
is Result.Error -> return Result.Error(Event.Error.FileReadFailed)
|
else -> Result.failure(WgTunnelExceptions.InvalidFileExtension())
|
||||||
is Result.Success -> return it
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Constants.ZIP_FILE_EXTENSION -> saveTunnelsFromZipUri(uri, configType)
|
|
||||||
else -> return Result.Error(Event.Error.InvalidFileExtension)
|
|
||||||
}
|
}
|
||||||
return Result.Success(Unit)
|
|
||||||
} else {
|
} else {
|
||||||
return Result.Error(Event.Error.InvalidFileExtension)
|
Result.failure(WgTunnelExceptions.InvalidFileExtension())
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Timber.e(e)
|
Timber.e(e)
|
||||||
return Result.Error(Event.Error.FileReadFailed)
|
Result.failure(WgTunnelExceptions.FileReadFailed())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun saveTunnelsFromZipUri(uri: Uri, configType: ConfigType) {
|
private suspend fun saveTunnelsFromZipUri(uri: Uri, configType: ConfigType, context: Context) : Result<Unit> {
|
||||||
ZipInputStream(getInputStreamFromUri(uri)).use { zip ->
|
return ZipInputStream(getInputStreamFromUri(uri, context)).use { zip ->
|
||||||
generateSequence { zip.nextEntry }
|
generateSequence { zip.nextEntry }
|
||||||
.filterNot {
|
.filterNot {
|
||||||
it.isDirectory ||
|
it.isDirectory ||
|
||||||
|
@ -180,51 +213,57 @@ constructor(
|
||||||
}
|
}
|
||||||
.forEach {
|
.forEach {
|
||||||
val name = getNameFromFileName(it.name)
|
val name = getNameFromFileName(it.name)
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
withContext(viewModelScope.coroutineContext + Dispatchers.IO) {
|
||||||
var amQuick : String? = null
|
try {
|
||||||
val wgQuick =
|
var amQuick : String? = null
|
||||||
when(configType) {
|
val wgQuick =
|
||||||
ConfigType.AMNEZIA -> {
|
when(configType) {
|
||||||
val config = org.amnezia.awg.config.Config.parse(zip)
|
ConfigType.AMNEZIA -> {
|
||||||
amQuick = config.toAwgQuickString()
|
val config = org.amnezia.awg.config.Config.parse(zip)
|
||||||
config.toWgQuickString()
|
amQuick = config.toAwgQuickString()
|
||||||
|
config.toWgQuickString()
|
||||||
|
}
|
||||||
|
ConfigType.WIREGUARD -> {
|
||||||
|
Config.parse(zip).toWgQuickString()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
ConfigType.WIREGUARD -> {
|
addTunnel(TunnelConfig(name = makeTunnelNameUnique(name), wgQuick = wgQuick, amQuick = amQuick ?: TunnelConfig.AM_QUICK_DEFAULT))
|
||||||
Config.parse(zip).toWgQuickString()
|
Result.success(Unit)
|
||||||
}
|
} catch (e : Exception) {
|
||||||
}
|
Result.failure(WgTunnelExceptions.FileReadFailed())
|
||||||
addTunnel(TunnelConfig(name = name, wgQuick = wgQuick, amQuick = amQuick ?: TunnelConfig.AM_QUICK_DEFAULT))
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Result.success(Unit)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun saveTunnelFromConfUri(name: String, uri: Uri, configType: ConfigType): Result<Unit> {
|
private suspend fun saveTunnelFromConfUri(name: String, uri: Uri, configType: ConfigType, context: Context): Result<Unit> {
|
||||||
val stream = getInputStreamFromUri(uri)
|
val stream = getInputStreamFromUri(uri, context)
|
||||||
return if (stream != null) {
|
return if (stream != null) {
|
||||||
saveTunnelConfigFromStream(stream, name, configType)
|
saveTunnelConfigFromStream(stream, name, configType)
|
||||||
Result.Success(Unit)
|
Result.success(Unit)
|
||||||
} else {
|
} else {
|
||||||
Result.Error(Event.Error.FileReadFailed)
|
Result.failure(WgTunnelExceptions.FileReadFailed())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun addTunnel(tunnelConfig: TunnelConfig) {
|
private suspend fun addTunnel(tunnelConfig: TunnelConfig) {
|
||||||
val firstTunnel = appDataRepository.tunnels.count() == 0
|
val firstTunnel = appDataRepository.tunnels.count() == 0
|
||||||
saveTunnel(tunnelConfig)
|
saveTunnel(tunnelConfig)
|
||||||
if (firstTunnel) WireGuardAutoTunnel.requestTunnelTileServiceStateUpdate(application)
|
if (firstTunnel) WireGuardAutoTunnel.requestTunnelTileServiceStateUpdate()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun pauseAutoTunneling() =
|
fun pauseAutoTunneling() =
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
appDataRepository.settings.save(uiState.value.settings.copy(isAutoTunnelPaused = true))
|
appDataRepository.settings.save(uiState.value.settings.copy(isAutoTunnelPaused = true))
|
||||||
WireGuardAutoTunnel.requestAutoTunnelTileServiceUpdate(application)
|
WireGuardAutoTunnel.requestAutoTunnelTileServiceUpdate()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun resumeAutoTunneling() =
|
fun resumeAutoTunneling() =
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
appDataRepository.settings.save(uiState.value.settings.copy(isAutoTunnelPaused = false))
|
appDataRepository.settings.save(uiState.value.settings.copy(isAutoTunnelPaused = false))
|
||||||
WireGuardAutoTunnel.requestAutoTunnelTileServiceUpdate(application)
|
WireGuardAutoTunnel.requestAutoTunnelTileServiceUpdate()
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun saveTunnel(tunnelConfig: TunnelConfig) {
|
private suspend fun saveTunnel(tunnelConfig: TunnelConfig) {
|
||||||
|
@ -268,12 +307,12 @@ constructor(
|
||||||
return fileName.substring(0, fileName.lastIndexOf('.'))
|
return fileName.substring(0, fileName.lastIndexOf('.'))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getFileExtensionFromFileName(fileName: String): String {
|
private fun getFileExtensionFromFileName(fileName: String): String? {
|
||||||
return try {
|
return try {
|
||||||
fileName.substring(fileName.lastIndexOf('.'))
|
fileName.substring(fileName.lastIndexOf('.'))
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Timber.e(e)
|
Timber.e(e)
|
||||||
""
|
null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,6 @@ import androidx.compose.material3.OutlinedTextField
|
||||||
import androidx.compose.material3.Scaffold
|
import androidx.compose.material3.Scaffold
|
||||||
import androidx.compose.material3.Surface
|
import androidx.compose.material3.Surface
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TextButton
|
|
||||||
import androidx.compose.material3.surfaceColorAtElevation
|
import androidx.compose.material3.surfaceColorAtElevation
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
@ -42,6 +41,7 @@ import androidx.compose.ui.focus.FocusRequester
|
||||||
import androidx.compose.ui.focus.focusRequester
|
import androidx.compose.ui.focus.focusRequester
|
||||||
import androidx.compose.ui.focus.onFocusChanged
|
import androidx.compose.ui.focus.onFocusChanged
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.platform.LocalFocusManager
|
import androidx.compose.ui.platform.LocalFocusManager
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.font.FontStyle
|
import androidx.compose.ui.text.font.FontStyle
|
||||||
|
@ -65,7 +65,7 @@ import com.zaneschepke.wireguardautotunnel.ui.common.config.ConfigurationToggle
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.text.SectionTitle
|
import com.zaneschepke.wireguardautotunnel.ui.common.text.SectionTitle
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.screens.main.ConfigType
|
import com.zaneschepke.wireguardautotunnel.ui.screens.main.ConfigType
|
||||||
import com.zaneschepke.wireguardautotunnel.util.Constants
|
import com.zaneschepke.wireguardautotunnel.util.Constants
|
||||||
import com.zaneschepke.wireguardautotunnel.util.Result
|
import com.zaneschepke.wireguardautotunnel.util.getMessage
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
@ -81,6 +81,8 @@ fun OptionsScreen(
|
||||||
) {
|
) {
|
||||||
val scrollState = rememberScrollState()
|
val scrollState = rememberScrollState()
|
||||||
val uiState by optionsViewModel.uiState.collectAsStateWithLifecycle()
|
val uiState by optionsViewModel.uiState.collectAsStateWithLifecycle()
|
||||||
|
val context = LocalContext.current
|
||||||
|
|
||||||
val interactionSource = remember { MutableInteractionSource() }
|
val interactionSource = remember { MutableInteractionSource() }
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
val focusManager = LocalFocusManager.current
|
val focusManager = LocalFocusManager.current
|
||||||
|
@ -100,11 +102,10 @@ fun OptionsScreen(
|
||||||
fun saveTrustedSSID() {
|
fun saveTrustedSSID() {
|
||||||
if (currentText.isNotEmpty()) {
|
if (currentText.isNotEmpty()) {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
optionsViewModel.onSaveRunSSID(currentText).let {
|
optionsViewModel.onSaveRunSSID(currentText).onSuccess {
|
||||||
when (it) {
|
currentText = ""
|
||||||
is Result.Success -> currentText = ""
|
}.onFailure {
|
||||||
is Result.Error -> appViewModel.showSnackbarMessage(it.error.message)
|
appViewModel.showSnackbarMessage(it.getMessage(context))
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,8 +7,7 @@ import com.zaneschepke.wireguardautotunnel.WireGuardAutoTunnel
|
||||||
import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
|
import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
|
||||||
import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
|
import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
|
||||||
import com.zaneschepke.wireguardautotunnel.util.Constants
|
import com.zaneschepke.wireguardautotunnel.util.Constants
|
||||||
import com.zaneschepke.wireguardautotunnel.util.Event
|
import com.zaneschepke.wireguardautotunnel.util.WgTunnelExceptions
|
||||||
import com.zaneschepke.wireguardautotunnel.util.Result
|
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
@ -76,9 +75,9 @@ constructor(
|
||||||
tunnelsWithName.isEmpty()) {
|
tunnelsWithName.isEmpty()) {
|
||||||
uiState.value.tunnel?.tunnelNetworks?.add(trimmed)
|
uiState.value.tunnel?.tunnelNetworks?.add(trimmed)
|
||||||
saveTunnel(uiState.value.tunnel)
|
saveTunnel(uiState.value.tunnel)
|
||||||
Result.Success(Unit)
|
Result.success(Unit)
|
||||||
} else {
|
} else {
|
||||||
Result.Error(Event.Error.SsidConflict)
|
Result.failure(WgTunnelExceptions.SsidConflict())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -98,7 +97,7 @@ constructor(
|
||||||
false -> uiState.value.tunnel
|
false -> uiState.value.tunnel
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
WireGuardAutoTunnel.requestTunnelTileServiceStateUpdate(WireGuardAutoTunnel.instance)
|
WireGuardAutoTunnel.requestTunnelTileServiceStateUpdate()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -70,7 +70,6 @@ import androidx.navigation.NavController
|
||||||
import com.google.accompanist.permissions.ExperimentalPermissionsApi
|
import com.google.accompanist.permissions.ExperimentalPermissionsApi
|
||||||
import com.google.accompanist.permissions.isGranted
|
import com.google.accompanist.permissions.isGranted
|
||||||
import com.google.accompanist.permissions.rememberPermissionState
|
import com.google.accompanist.permissions.rememberPermissionState
|
||||||
import com.wireguard.android.backend.Tunnel
|
|
||||||
import com.wireguard.android.backend.WgQuickBackend
|
import com.wireguard.android.backend.WgQuickBackend
|
||||||
import com.zaneschepke.wireguardautotunnel.R
|
import com.zaneschepke.wireguardautotunnel.R
|
||||||
import com.zaneschepke.wireguardautotunnel.WireGuardAutoTunnel
|
import com.zaneschepke.wireguardautotunnel.WireGuardAutoTunnel
|
||||||
|
@ -82,9 +81,8 @@ import com.zaneschepke.wireguardautotunnel.ui.common.ClickableIconButton
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.config.ConfigurationToggle
|
import com.zaneschepke.wireguardautotunnel.ui.common.config.ConfigurationToggle
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.prompt.AuthorizationPrompt
|
import com.zaneschepke.wireguardautotunnel.ui.common.prompt.AuthorizationPrompt
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.text.SectionTitle
|
import com.zaneschepke.wireguardautotunnel.ui.common.text.SectionTitle
|
||||||
import com.zaneschepke.wireguardautotunnel.util.Event
|
|
||||||
import com.zaneschepke.wireguardautotunnel.util.FileUtils
|
import com.zaneschepke.wireguardautotunnel.util.FileUtils
|
||||||
import com.zaneschepke.wireguardautotunnel.util.Result
|
import com.zaneschepke.wireguardautotunnel.util.getMessage
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
@ -146,12 +144,14 @@ fun SettingsScreen(
|
||||||
}
|
}
|
||||||
file
|
file
|
||||||
} else null }
|
} else null }
|
||||||
FileUtils.saveFilesToZip(context, wgFiles + amFiles)
|
FileUtils.saveFilesToZip(context, wgFiles + amFiles).onFailure {
|
||||||
didExportFiles = true
|
appViewModel.showSnackbarMessage(it.getMessage(context))
|
||||||
appViewModel.showSnackbarMessage(Event.Message.ConfigsExported.message)
|
}.onSuccess {
|
||||||
|
didExportFiles = true
|
||||||
|
appViewModel.showSnackbarMessage(context.getString(R.string.exported_configs_message))
|
||||||
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Timber.e(e)
|
Timber.e(e)
|
||||||
appViewModel.showSnackbarMessage(Event.Error.Exception(e).message)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -172,7 +172,7 @@ fun SettingsScreen(
|
||||||
fun handleAutoTunnelToggle() {
|
fun handleAutoTunnelToggle() {
|
||||||
if (uiState.isBatteryOptimizeDisableShown || isBatteryOptimizationsDisabled()) {
|
if (uiState.isBatteryOptimizeDisableShown || isBatteryOptimizationsDisabled()) {
|
||||||
if (appViewModel.isRequiredPermissionGranted()) {
|
if (appViewModel.isRequiredPermissionGranted()) {
|
||||||
viewModel.onToggleAutoTunnel()
|
viewModel.onToggleAutoTunnel(context)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
requestBatteryOptimizationsDisabled()
|
requestBatteryOptimizationsDisabled()
|
||||||
|
@ -181,11 +181,10 @@ fun SettingsScreen(
|
||||||
|
|
||||||
fun saveTrustedSSID() {
|
fun saveTrustedSSID() {
|
||||||
if (currentText.isNotEmpty()) {
|
if (currentText.isNotEmpty()) {
|
||||||
viewModel.onSaveTrustedSSID(currentText).let {
|
viewModel.onSaveTrustedSSID(currentText).onSuccess {
|
||||||
when (it) {
|
currentText = ""
|
||||||
is Result.Success -> currentText = ""
|
}.onFailure {
|
||||||
is Result.Error -> appViewModel.showSnackbarMessage(it.error.message)
|
appViewModel.showSnackbarMessage(it.getMessage(context))
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -319,11 +318,11 @@ fun SettingsScreen(
|
||||||
},
|
},
|
||||||
onError = { _ ->
|
onError = { _ ->
|
||||||
showAuthPrompt = false
|
showAuthPrompt = false
|
||||||
appViewModel.showSnackbarMessage(Event.Error.AuthenticationFailed.message)
|
appViewModel.showSnackbarMessage(context.getString(R.string.error_authentication_failed))
|
||||||
},
|
},
|
||||||
onFailure = {
|
onFailure = {
|
||||||
showAuthPrompt = false
|
showAuthPrompt = false
|
||||||
appViewModel.showSnackbarMessage(Event.Error.AuthorizationFailed.message)
|
appViewModel.showSnackbarMessage(context.getString(R.string.error_authorization_failed))
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -531,12 +530,12 @@ fun SettingsScreen(
|
||||||
when (false) {
|
when (false) {
|
||||||
isBackgroundLocationGranted ->
|
isBackgroundLocationGranted ->
|
||||||
appViewModel.showSnackbarMessage(
|
appViewModel.showSnackbarMessage(
|
||||||
Event.Error.BackgroundLocationRequired.message,
|
context.getString(R.string.background_location_required),
|
||||||
)
|
)
|
||||||
|
|
||||||
fineLocationState.status.isGranted ->
|
fineLocationState.status.isGranted ->
|
||||||
appViewModel.showSnackbarMessage(
|
appViewModel.showSnackbarMessage(
|
||||||
Event.Error.PreciseLocationRequired.message,
|
context.getString(R.string.precise_location_required),
|
||||||
)
|
)
|
||||||
|
|
||||||
viewModel.isLocationEnabled(context) ->
|
viewModel.isLocationEnabled(context) ->
|
||||||
|
@ -602,11 +601,8 @@ fun SettingsScreen(
|
||||||
checked = uiState.settings.isKernelEnabled,
|
checked = uiState.settings.isKernelEnabled,
|
||||||
padding = screenPadding,
|
padding = screenPadding,
|
||||||
onCheckChanged = {
|
onCheckChanged = {
|
||||||
viewModel.onToggleKernelMode().let {
|
viewModel.onToggleKernelMode().onFailure {
|
||||||
when (it) {
|
appViewModel.showSnackbarMessage(it.getMessage(context))
|
||||||
is Result.Error -> appViewModel.showSnackbarMessage(it.error.message)
|
|
||||||
is Result.Success -> {}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
package com.zaneschepke.wireguardautotunnel.ui.screens.settings
|
package com.zaneschepke.wireguardautotunnel.ui.screens.settings
|
||||||
|
|
||||||
import android.app.Application
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.location.LocationManager
|
import android.location.LocationManager
|
||||||
import androidx.core.location.LocationManagerCompat
|
import androidx.core.location.LocationManagerCompat
|
||||||
|
@ -13,8 +12,7 @@ import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
|
||||||
import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager
|
import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager
|
||||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.VpnService
|
import com.zaneschepke.wireguardautotunnel.service.tunnel.VpnService
|
||||||
import com.zaneschepke.wireguardautotunnel.util.Constants
|
import com.zaneschepke.wireguardautotunnel.util.Constants
|
||||||
import com.zaneschepke.wireguardautotunnel.util.Event
|
import com.zaneschepke.wireguardautotunnel.util.WgTunnelExceptions
|
||||||
import com.zaneschepke.wireguardautotunnel.util.Result
|
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import kotlinx.coroutines.flow.SharingStarted
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
|
@ -27,7 +25,6 @@ import javax.inject.Inject
|
||||||
class SettingsViewModel
|
class SettingsViewModel
|
||||||
@Inject
|
@Inject
|
||||||
constructor(
|
constructor(
|
||||||
private val application: Application,
|
|
||||||
private val appDataRepository: AppDataRepository,
|
private val appDataRepository: AppDataRepository,
|
||||||
private val serviceManager: ServiceManager,
|
private val serviceManager: ServiceManager,
|
||||||
private val rootShell: RootShell,
|
private val rootShell: RootShell,
|
||||||
|
@ -60,9 +57,9 @@ constructor(
|
||||||
return if (!uiState.value.settings.trustedNetworkSSIDs.contains(trimmed)) {
|
return if (!uiState.value.settings.trustedNetworkSSIDs.contains(trimmed)) {
|
||||||
uiState.value.settings.trustedNetworkSSIDs.add(trimmed)
|
uiState.value.settings.trustedNetworkSSIDs.add(trimmed)
|
||||||
saveSettings(uiState.value.settings)
|
saveSettings(uiState.value.settings)
|
||||||
Result.Success(Unit)
|
Result.success(Unit)
|
||||||
} else {
|
} else {
|
||||||
Result.Error(Event.Error.SsidConflict)
|
Result.failure(WgTunnelExceptions.SsidConflict())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -93,15 +90,15 @@ constructor(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onToggleAutoTunnel() =
|
fun onToggleAutoTunnel(context: Context) =
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
val isAutoTunnelEnabled = uiState.value.settings.isAutoTunnelEnabled
|
val isAutoTunnelEnabled = uiState.value.settings.isAutoTunnelEnabled
|
||||||
var isAutoTunnelPaused = uiState.value.settings.isAutoTunnelPaused
|
var isAutoTunnelPaused = uiState.value.settings.isAutoTunnelPaused
|
||||||
|
|
||||||
if (isAutoTunnelEnabled) {
|
if (isAutoTunnelEnabled) {
|
||||||
serviceManager.stopWatcherService(application)
|
serviceManager.stopWatcherService(context)
|
||||||
} else {
|
} else {
|
||||||
serviceManager.startWatcherService(application)
|
serviceManager.startWatcherService(context)
|
||||||
isAutoTunnelPaused = false
|
isAutoTunnelPaused = false
|
||||||
}
|
}
|
||||||
saveSettings(
|
saveSettings(
|
||||||
|
@ -110,7 +107,7 @@ constructor(
|
||||||
isAutoTunnelPaused = isAutoTunnelPaused,
|
isAutoTunnelPaused = isAutoTunnelPaused,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
WireGuardAutoTunnel.requestAutoTunnelTileServiceUpdate(application)
|
WireGuardAutoTunnel.requestAutoTunnelTileServiceUpdate()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onToggleAlwaysOnVPN() =
|
fun onToggleAlwaysOnVPN() =
|
||||||
|
@ -192,12 +189,12 @@ constructor(
|
||||||
} catch (e: RootShell.RootShellException) {
|
} catch (e: RootShell.RootShellException) {
|
||||||
Timber.e(e)
|
Timber.e(e)
|
||||||
saveKernelMode(on = false)
|
saveKernelMode(on = false)
|
||||||
return Result.Error(Event.Error.RootDenied)
|
return Result.failure(WgTunnelExceptions.RootDenied())
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
saveKernelMode(on = false)
|
saveKernelMode(on = false)
|
||||||
}
|
}
|
||||||
return Result.Success(Unit)
|
return Result.success(Unit)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onToggleRestartOnPing() = viewModelScope.launch {
|
fun onToggleRestartOnPing() = viewModelScope.launch {
|
||||||
|
|
|
@ -38,5 +38,6 @@ object Constants {
|
||||||
const val UNREADABLE_SSID = "<unknown ssid>"
|
const val UNREADABLE_SSID = "<unknown ssid>"
|
||||||
|
|
||||||
val amneziaProperties = listOf("Jc", "Jmin", "Jmax", "S1", "S2", "H1", "H2", "H3", "H4")
|
val amneziaProperties = listOf("Jc", "Jmin", "Jmax", "S1", "S2", "H1", "H2", "H3", "H4")
|
||||||
|
const val QR_CODE_NAME_PROPERTY = "# Name ="
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,117 +0,0 @@
|
||||||
package com.zaneschepke.wireguardautotunnel.util
|
|
||||||
|
|
||||||
import com.zaneschepke.wireguardautotunnel.R
|
|
||||||
import com.zaneschepke.wireguardautotunnel.WireGuardAutoTunnel
|
|
||||||
|
|
||||||
sealed class Event {
|
|
||||||
|
|
||||||
abstract val message: String
|
|
||||||
|
|
||||||
sealed class Error : Event() {
|
|
||||||
data object None : Error() {
|
|
||||||
override val message: String
|
|
||||||
get() = WireGuardAutoTunnel.instance.getString(R.string.error_none)
|
|
||||||
}
|
|
||||||
|
|
||||||
data object SsidConflict : Error() {
|
|
||||||
override val message: String
|
|
||||||
get() = WireGuardAutoTunnel.instance.getString(R.string.error_ssid_exists)
|
|
||||||
}
|
|
||||||
|
|
||||||
data class ConfigParseError(val appendedMessage: String) : Error() {
|
|
||||||
override val message: String =
|
|
||||||
WireGuardAutoTunnel.instance.getString(R.string.config_parse_error) + (
|
|
||||||
if (appendedMessage != "") ": ${appendedMessage.trim()}" else "")
|
|
||||||
}
|
|
||||||
|
|
||||||
data object RootDenied : Error() {
|
|
||||||
override val message: String
|
|
||||||
get() = WireGuardAutoTunnel.instance.getString(R.string.error_root_denied)
|
|
||||||
}
|
|
||||||
|
|
||||||
data class General(val customMessage: String) : Error() {
|
|
||||||
override val message: String
|
|
||||||
get() = customMessage
|
|
||||||
}
|
|
||||||
|
|
||||||
data class Exception(val exception: kotlin.Exception) : Error() {
|
|
||||||
override val message: String
|
|
||||||
get() =
|
|
||||||
exception.message
|
|
||||||
?: WireGuardAutoTunnel.instance.getString(R.string.unknown_error)
|
|
||||||
}
|
|
||||||
|
|
||||||
data object InvalidQrCode : Error() {
|
|
||||||
override val message: String
|
|
||||||
get() = WireGuardAutoTunnel.instance.getString(R.string.error_invalid_code)
|
|
||||||
}
|
|
||||||
|
|
||||||
data object InvalidFileExtension : Error() {
|
|
||||||
override val message: String
|
|
||||||
get() = WireGuardAutoTunnel.instance.getString(R.string.error_file_extension)
|
|
||||||
}
|
|
||||||
|
|
||||||
data object FileReadFailed : Error() {
|
|
||||||
override val message: String
|
|
||||||
get() = WireGuardAutoTunnel.instance.getString(R.string.error_file_format)
|
|
||||||
}
|
|
||||||
|
|
||||||
data object AuthenticationFailed : Error() {
|
|
||||||
override val message: String
|
|
||||||
get() = WireGuardAutoTunnel.instance.getString(R.string.error_authentication_failed)
|
|
||||||
}
|
|
||||||
|
|
||||||
data object AuthorizationFailed : Error() {
|
|
||||||
override val message: String
|
|
||||||
get() = WireGuardAutoTunnel.instance.getString(R.string.error_authorization_failed)
|
|
||||||
}
|
|
||||||
|
|
||||||
data object BackgroundLocationRequired : Error() {
|
|
||||||
override val message: String
|
|
||||||
get() =
|
|
||||||
WireGuardAutoTunnel.instance.getString(R.string.background_location_required)
|
|
||||||
}
|
|
||||||
|
|
||||||
data object LocationServicesRequired : Error() {
|
|
||||||
override val message: String
|
|
||||||
get() = WireGuardAutoTunnel.instance.getString(R.string.location_services_required)
|
|
||||||
}
|
|
||||||
|
|
||||||
data object PreciseLocationRequired : Error() {
|
|
||||||
override val message: String
|
|
||||||
get() = WireGuardAutoTunnel.instance.getString(R.string.precise_location_required)
|
|
||||||
}
|
|
||||||
|
|
||||||
data object FileExplorerRequired : Error() {
|
|
||||||
override val message: String
|
|
||||||
get() = WireGuardAutoTunnel.instance.getString(R.string.error_no_file_explorer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sealed class Message : Event() {
|
|
||||||
data object ConfigSaved : Message() {
|
|
||||||
override val message: String
|
|
||||||
get() = WireGuardAutoTunnel.instance.getString(R.string.config_changes_saved)
|
|
||||||
}
|
|
||||||
|
|
||||||
data object ConfigsExported : Message() {
|
|
||||||
override val message: String
|
|
||||||
get() = WireGuardAutoTunnel.instance.getString(R.string.exported_configs_message)
|
|
||||||
}
|
|
||||||
|
|
||||||
data object TunnelOffAction : Message() {
|
|
||||||
override val message: String
|
|
||||||
get() = WireGuardAutoTunnel.instance.getString(R.string.turn_off_tunnel)
|
|
||||||
}
|
|
||||||
|
|
||||||
data object TunnelOnAction : Message() {
|
|
||||||
override val message: String
|
|
||||||
get() = WireGuardAutoTunnel.instance.getString(R.string.turn_on_tunnel)
|
|
||||||
}
|
|
||||||
|
|
||||||
data object AutoTunnelOffAction : Message() {
|
|
||||||
override val message: String
|
|
||||||
get() = WireGuardAutoTunnel.instance.getString(R.string.turn_off_auto)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,8 +1,9 @@
|
||||||
package com.zaneschepke.wireguardautotunnel.util
|
package com.zaneschepke.wireguardautotunnel.util
|
||||||
|
|
||||||
import android.content.BroadcastReceiver
|
import android.content.BroadcastReceiver
|
||||||
|
import android.content.Context
|
||||||
import android.content.pm.PackageInfo
|
import android.content.pm.PackageInfo
|
||||||
import com.wireguard.config.Peer
|
import com.zaneschepke.wireguardautotunnel.R
|
||||||
import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
|
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.statistics.TunnelStatistics
|
import com.zaneschepke.wireguardautotunnel.service.tunnel.statistics.TunnelStatistics
|
||||||
|
@ -11,7 +12,6 @@ import kotlinx.coroutines.DelicateCoroutinesApi
|
||||||
import kotlinx.coroutines.GlobalScope
|
import kotlinx.coroutines.GlobalScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.amnezia.awg.config.Config
|
import org.amnezia.awg.config.Config
|
||||||
import timber.log.Timber
|
|
||||||
import java.math.BigDecimal
|
import java.math.BigDecimal
|
||||||
import java.text.DecimalFormat
|
import java.text.DecimalFormat
|
||||||
import kotlin.coroutines.CoroutineContext
|
import kotlin.coroutines.CoroutineContext
|
||||||
|
@ -87,3 +87,10 @@ fun Config.toWgQuickString() : String {
|
||||||
}
|
}
|
||||||
return lines.joinToString(System.lineSeparator())
|
return lines.joinToString(System.lineSeparator())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun Throwable.getMessage(context: Context) : String {
|
||||||
|
return when(this) {
|
||||||
|
is WgTunnelExceptions -> this.getMessage(context)
|
||||||
|
else -> this.message ?: StringValue.StringResource(R.string.unknown_error).asString(context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ import android.os.Build
|
||||||
import android.os.Environment
|
import android.os.Environment
|
||||||
import android.provider.MediaStore
|
import android.provider.MediaStore
|
||||||
import android.provider.MediaStore.MediaColumns
|
import android.provider.MediaStore.MediaColumns
|
||||||
|
import timber.log.Timber
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileOutputStream
|
import java.io.FileOutputStream
|
||||||
import java.io.OutputStream
|
import java.io.OutputStream
|
||||||
|
@ -70,21 +71,27 @@ object FileUtils {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun saveFilesToZip(context: Context, files: List<File>) {
|
fun saveFilesToZip(context: Context, files: List<File>) : Result<Unit> {
|
||||||
val zipOutputStream =
|
return try {
|
||||||
createDownloadsFileOutputStream(
|
val zipOutputStream =
|
||||||
context,
|
createDownloadsFileOutputStream(
|
||||||
"wg-export_${Instant.now().epochSecond}.zip",
|
context,
|
||||||
ZIP_FILE_MIME_TYPE,
|
"wg-export_${Instant.now().epochSecond}.zip",
|
||||||
)
|
ZIP_FILE_MIME_TYPE,
|
||||||
ZipOutputStream(zipOutputStream).use { zos ->
|
)
|
||||||
files.forEach { file ->
|
ZipOutputStream(zipOutputStream).use { zos ->
|
||||||
val entry = ZipEntry(file.name)
|
files.forEach { file ->
|
||||||
zos.putNextEntry(entry)
|
val entry = ZipEntry(file.name)
|
||||||
if (file.isFile) {
|
zos.putNextEntry(entry)
|
||||||
file.inputStream().use { fis -> fis.copyTo(zos) }
|
if (file.isFile) {
|
||||||
|
file.inputStream().use { fis -> fis.copyTo(zos) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return Result.success(Unit)
|
||||||
}
|
}
|
||||||
|
} catch (e : Exception) {
|
||||||
|
Timber.e(e)
|
||||||
|
Result.failure(WgTunnelExceptions.ConfigExportFailed())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,16 +0,0 @@
|
||||||
package com.zaneschepke.wireguardautotunnel.util
|
|
||||||
|
|
||||||
import timber.log.Timber
|
|
||||||
|
|
||||||
sealed class Result<T> {
|
|
||||||
class Success<T>(val data: T) : Result<T>()
|
|
||||||
|
|
||||||
class Error<T>(val error: Event.Error) : Result<T>() {
|
|
||||||
init {
|
|
||||||
when (this.error) {
|
|
||||||
is Event.Error.Exception -> Timber.e(this.error.exception)
|
|
||||||
else -> Timber.e(this.error.message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,124 @@
|
||||||
|
package com.zaneschepke.wireguardautotunnel.util
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import com.zaneschepke.wireguardautotunnel.R
|
||||||
|
|
||||||
|
sealed class WgTunnelExceptions : Exception() {
|
||||||
|
abstract fun getMessage(context: Context) : String
|
||||||
|
data class General(private val userMessage : StringValue) : WgTunnelExceptions() {
|
||||||
|
override fun getMessage(context: Context) : String {
|
||||||
|
return userMessage.asString(context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class SsidConflict(private val userMessage : StringValue = StringValue.StringResource(R.string.error_ssid_exists)) : WgTunnelExceptions() {
|
||||||
|
override fun getMessage(context: Context) : String {
|
||||||
|
return userMessage.asString(context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class ConfigExportFailed(private val userMessage : StringValue = StringValue.StringResource(R.string.export_configs_failed)) : WgTunnelExceptions() {
|
||||||
|
override fun getMessage(context: Context) : String {
|
||||||
|
return userMessage.asString(context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class ConfigParseError(private val appendMessage : StringValue = StringValue.Empty) : WgTunnelExceptions() {
|
||||||
|
override fun getMessage(context: Context) : String {
|
||||||
|
return StringValue.StringResource(R.string.config_parse_error).asString(context) + (
|
||||||
|
if (appendMessage != StringValue.Empty) ": ${appendMessage.asString(context)}" else "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class RootDenied(private val userMessage : StringValue = StringValue.StringResource(R.string.error_root_denied)) : WgTunnelExceptions() {
|
||||||
|
override fun getMessage(context: Context) : String {
|
||||||
|
return userMessage.asString(context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class InvalidQrCode(private val userMessage : StringValue = StringValue.StringResource(R.string.error_invalid_code)) : WgTunnelExceptions() {
|
||||||
|
override fun getMessage(context: Context) : String {
|
||||||
|
return userMessage.asString(context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class InvalidFileExtension(private val userMessage : StringValue = StringValue.StringResource(R.string.error_file_extension)) : WgTunnelExceptions() {
|
||||||
|
override fun getMessage(context: Context) : String {
|
||||||
|
return userMessage.asString(context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class FileReadFailed(private val userMessage : StringValue = StringValue.StringResource(R.string.error_file_format)) : WgTunnelExceptions() {
|
||||||
|
override fun getMessage(context: Context) : String {
|
||||||
|
return userMessage.asString(context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class AuthenticationFailed(private val userMessage : StringValue = StringValue.StringResource(R.string.error_authentication_failed)) : WgTunnelExceptions() {
|
||||||
|
override fun getMessage(context: Context) : String {
|
||||||
|
return userMessage.asString(context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class AuthorizationFailed(private val userMessage : StringValue = StringValue.StringResource(R.string.error_authorization_failed)) : WgTunnelExceptions() {
|
||||||
|
override fun getMessage(context: Context) : String {
|
||||||
|
return userMessage.asString(context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class BackgroundLocationRequired(private val userMessage : StringValue = StringValue.StringResource(R.string.background_location_required)) : WgTunnelExceptions() {
|
||||||
|
override fun getMessage(context: Context) : String {
|
||||||
|
return userMessage.asString(context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class LocationServicesRequired(private val userMessage : StringValue = StringValue.StringResource(R.string.location_services_required)) : WgTunnelExceptions() {
|
||||||
|
override fun getMessage(context: Context) : String {
|
||||||
|
return userMessage.asString(context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class PreciseLocationRequired(private val userMessage : StringValue = StringValue.StringResource(R.string.precise_location_required)) : WgTunnelExceptions() {
|
||||||
|
override fun getMessage(context: Context) : String {
|
||||||
|
return userMessage.asString(context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class FileExplorerRequired (private val userMessage : StringValue = StringValue.StringResource(R.string.error_no_file_explorer)) : WgTunnelExceptions() {
|
||||||
|
override fun getMessage(context: Context) : String {
|
||||||
|
return userMessage.asString(context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// sealed class Message : Event() {
|
||||||
|
// data object ConfigSaved : Message() {
|
||||||
|
// override val message: String
|
||||||
|
// get() = WireGuardAutoTunnel.instance.getString(R.string.config_changes_saved)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// data object ConfigsExported : Message() {
|
||||||
|
// override val message: String
|
||||||
|
// get() = WireGuardAutoTunnel.instance.getString(R.string.exported_configs_message)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// data object TunnelOffAction : Message() {
|
||||||
|
// override val message: String
|
||||||
|
// get() = WireGuardAutoTunnel.instance.getString(R.string.turn_off_tunnel)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// data object TunnelOnAction : Message() {
|
||||||
|
// override val message: String
|
||||||
|
// get() = WireGuardAutoTunnel.instance.getString(R.string.turn_on_tunnel)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// data object AutoTunnelOffAction : Message() {
|
||||||
|
// override val message: String
|
||||||
|
// get() = WireGuardAutoTunnel.instance.getString(R.string.turn_off_auto)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
|
@ -94,6 +94,7 @@
|
||||||
<string name="error_authorization_failed">Failed to authorize</string>
|
<string name="error_authorization_failed">Failed to authorize</string>
|
||||||
<string name="enabled_app_shortcuts">Enable app shortcuts</string>
|
<string name="enabled_app_shortcuts">Enable app shortcuts</string>
|
||||||
<string name="export_configs">Export configs</string>
|
<string name="export_configs">Export configs</string>
|
||||||
|
<string name="export_configs_failed">Failed to export configs</string>
|
||||||
<string name="location_services_required">Location services required</string>
|
<string name="location_services_required">Location services required</string>
|
||||||
<string name="background_location_required">Background location required</string>
|
<string name="background_location_required">Background location required</string>
|
||||||
<string name="precise_location_required">Precise location required</string>
|
<string name="precise_location_required">Precise location required</string>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
object Constants {
|
object Constants {
|
||||||
const val VERSION_NAME = "3.4.3"
|
const val VERSION_NAME = "3.4.4"
|
||||||
const val JVM_TARGET = "17"
|
const val JVM_TARGET = "17"
|
||||||
const val VERSION_CODE = 34300
|
const val VERSION_CODE = 34400
|
||||||
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"
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
What's new:
|
||||||
|
- Improve tunnel import naming
|
||||||
|
- Fix auto tunneling init state bug
|
||||||
|
- Improved error handling
|
||||||
|
- Fix Amnezia zip import bug
|
Loading…
Reference in New Issue