mirror of
https://github.com/wgtunnel/android.git
synced 2026-07-03 14:07:49 +02:00
Compare commits
41 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6387ff9e8b | |||
| 757669ddbe | |||
| c71c4e5b29 | |||
| 7f0fea3766 | |||
| 53c19762ef | |||
| c98fa04f73 | |||
| aba0f7d4d3 | |||
| fa517b2124 | |||
| d7e2648393 | |||
| 53ff3bb1e5 | |||
| 97ede3d5b4 | |||
| dcd15f7bd8 | |||
| 6031d85edd | |||
| a71f8f86b1 | |||
| 007c9f4c5d | |||
| e32a99db77 | |||
| 6670a62e2f | |||
| 34b20bd7f7 | |||
| 01e15099ca | |||
| 8f2fd93e77 | |||
| 94197c9943 | |||
| 39fc9014d8 | |||
| 8860b45230 | |||
| e220b26d88 | |||
| 2302b473b4 | |||
| b01473073f | |||
| 3ae9a24ca4 | |||
| dc3f7fa736 | |||
| 93d6f8aa45 | |||
| 3ea4aea5cf | |||
| 68b41c8925 | |||
| 06de1f24c2 | |||
| a39feeeea6 | |||
| c1619ff012 | |||
| 2534b86005 | |||
| 15c550737c | |||
| e1e7e27bb5 | |||
| 8021c133a5 | |||
| 6009445a15 | |||
| f80af9dd5e | |||
| 3f912ed532 |
@@ -4,7 +4,7 @@ WG Tunnel
|
||||
|
||||
<div align="center">
|
||||
|
||||
An alternative Android client app for [WireGuard®](https://www.wireguard.com/)
|
||||
An alternative Android client app for [WireGuard](https://www.wireguard.com/)
|
||||
and [AmneziaWG](https://docs.amnezia.org/documentation/amnezia-wg/)
|
||||
<br />
|
||||
<br />
|
||||
@@ -23,14 +23,14 @@ and [AmneziaWG](https://docs.amnezia.org/documentation/amnezia-wg/)
|
||||
[](https://play.google.com/store/apps/details?id=com.zaneschepke.wireguardautotunnel)
|
||||
[](https://f-droid.org/packages/com.zaneschepke.wireguardautotunnel/)
|
||||
[](https://github.com/zaneschepke/fdroid)
|
||||
[](https://apps.obtainium.imranr.dev/redirect?r=obtainium://app/%7B%22id%22%3A%22com.zaneschepke.wireguardautotunnel%22%2C%22url%22%3A%22https%3A%2F%2Fgithub.com%2Fzaneschepke%2Fwgtunnel%22%2C%22author%22%3A%22zaneschepke%22%2C%22name%22%3A%22WG%20Tunnel%22%2C%22preferredApkIndex%22%3A0%2C%22additionalSettings%22%3A%22%7B%5C%22includePrereleases%5C%22%3Afalse%2C%5C%22fallbackToOlderReleases%5C%22%3Atrue%2C%5C%22filterReleaseTitlesByRegEx%5C%22%3A%5C%22%5C%22%2C%5C%22filterReleaseNotesByRegEx%5C%22%3A%5C%22%5C%22%2C%5C%22verifyLatestTag%5C%22%3Atrue%2C%5C%22sortMethodChoice%5C%22%3A%5C%22date%5C%22%2C%5C%22useLatestAssetDateAsReleaseDate%5C%22%3Afalse%2C%5C%22releaseTitleAsVersion%5C%22%3Afalse%2C%5C%22trackOnly%5C%22%3Afalse%2C%5C%22versionExtractionRegEx%5C%22%3A%5C%22%5C%22%2C%5C%22matchGroupToUse%5C%22%3A%5C%22%5C%22%2C%5C%22versionDetection%5C%22%3Atrue%2C%5C%22releaseDateAsVersion%5C%22%3Afalse%2C%5C%22useVersionCodeAsOSVersion%5C%22%3Afalse%2C%5C%22apkFilterRegEx%5C%22%3A%5C%22%5C%22%2C%5C%22invertAPKFilter%5C%22%3Afalse%2C%5C%22autoApkFilterByArch%5C%22%3Atrue%2C%5C%22appName%5C%22%3A%5C%22WG%20Tunnel%5C%22%2C%5C%22appAuthor%5C%22%3A%5C%22Zane%20Schepke%5C%22%2C%5C%22shizukuPretendToBeGooglePlay%5C%22%3Afalse%2C%5C%22allowInsecure%5C%22%3Afalse%2C%5C%22exemptFromBackgroundUpdates%5C%22%3Afalse%2C%5C%22skipUpdateNotifications%5C%22%3Afalse%2C%5C%22about%5C%22%3A%5C%22%5C%22%2C%5C%22refreshBeforeDownload%5C%22%3Afalse%7D%22%2C%22overrideSource%22%3Anull%7D)
|
||||
|
||||
</div>
|
||||
|
||||
<div align="center">
|
||||
|
||||
[](https://discord.gg/rbRRNh6H7V)
|
||||
[](https://t.me/wgtunnel)
|
||||
|
||||
[<img src="https://img.shields.io/badge/Telegram-26A5E4.svg?style=for-the-badge&logo=Telegram&logoColor=white">](https://t.me/wgtunnel)
|
||||
[<img src="https://img.shields.io/badge/Matrix-000000.svg?style=for-the-badge&logo=Matrix&logoColor=white">](https://matrix.to/#/#wg-tunnel-space:matrix.org)
|
||||
</div>
|
||||
|
||||
<details open="open">
|
||||
@@ -49,7 +49,7 @@ and [AmneziaWG](https://docs.amnezia.org/documentation/amnezia-wg/)
|
||||
<div style="text-align: left;">
|
||||
|
||||
## About
|
||||
Inspired by the official [wireguard-android](https://github.com/WireGuard/wireguard-android) app, WG Tunnel was created to address features and support missing from the official app. This app combines support for both [WireGuard®](https://www.wireguard.com/)
|
||||
Inspired by the official [wireguard-android](https://github.com/WireGuard/wireguard-android) app, WG Tunnel was created to address features and support missing from the official app. This app combines support for both [WireGuard](https://www.wireguard.com/)
|
||||
and [AmneziaWG](https://docs.amnezia.org/documentation/amnezia-wg/), with its primary feature of auto-tunneling (on-demand tunneling).
|
||||
|
||||
</div>
|
||||
@@ -61,14 +61,14 @@ and [AmneziaWG](https://docs.amnezia.org/documentation/amnezia-wg/), with its pr
|
||||
Thank you to the following:
|
||||
|
||||
- All of the users that have helped contribute to the project with ideas, translations, feedback, bug reports, testing, and donations.
|
||||
- [WireGuard®](https://www.wireguard.com/) - © Jason A. Donenfeld (https://github.com/WireGuard/wireguard-android)
|
||||
- [WireGuard](https://www.wireguard.com/) - Jason A. Donenfeld (https://github.com/WireGuard/wireguard-android)
|
||||
|
||||
- [AmneziaWG](https://docs.amnezia.org/documentation/amnezia-wg/) - Amnezia Team (https://github.com/amnezia-vpn/amneziawg-android)
|
||||
|
||||
## Screenshots
|
||||
|
||||
</div>
|
||||
<div style="display: flex; flex-wrap: wrap; justify-content: center; gap: 10px;">
|
||||
<div style="display: flex; flex-wrap: wrap; justify-content: left; gap: 10px;">
|
||||
<img label="Main" src="fastlane/metadata/android/en-US/images/phoneScreenshots/main_screen.png" width="200" />
|
||||
<img label="Settings" src="fastlane/metadata/android/en-US/images/phoneScreenshots/settings_screen.png" width="200" />
|
||||
<img label="Auto" src="fastlane/metadata/android/en-US/images/phoneScreenshots/auto_screen.png" width="200" />
|
||||
|
||||
@@ -144,8 +144,8 @@ android {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
implementation(project(":logcatter"))
|
||||
implementation(project(":networkmonitor"))
|
||||
|
||||
implementation(libs.androidx.core.ktx)
|
||||
implementation(libs.androidx.lifecycle.runtime.ktx)
|
||||
|
||||
+38
-1
@@ -2,4 +2,41 @@
|
||||
|
||||
-keepclassmembers class * extends androidx.datastore.preferences.protobuf.GeneratedMessageLite {
|
||||
<fields>;
|
||||
}
|
||||
}
|
||||
|
||||
# Keep all classes in the org.xbill.DNS package and subpackages
|
||||
-keep class org.xbill.DNS.** { *; }
|
||||
-dontwarn org.xbill.DNS.**
|
||||
|
||||
# Preserve JNA classes if used (e.g., for IPHlpAPI on Windows)
|
||||
-keep class com.sun.jna.** { *; }
|
||||
-dontwarn com.sun.jna.**
|
||||
|
||||
# Keep DNS resolver configuration classes that might be loaded dynamically
|
||||
-keep class org.xbill.DNS.config.** { *; }
|
||||
-dontwarn org.xbill.DNS.config.**
|
||||
|
||||
-keep class org.xbill.DNS.** { *; }
|
||||
|
||||
# Prevent optimization issues with native or reflection-based calls
|
||||
-dontoptimize
|
||||
-dontshrink
|
||||
# Uncomment the above if errors persist, but use sparingly as they’re broad
|
||||
|
||||
# Suppress warnings about missing classes if not all features are used
|
||||
-dontwarn java.lang.management.**
|
||||
-dontwarn sun.nio.ch.**
|
||||
|
||||
-dontwarn com.google.api.client.http.GenericUrl
|
||||
-dontwarn com.google.api.client.http.HttpHeaders
|
||||
-dontwarn com.google.api.client.http.HttpRequest
|
||||
-dontwarn com.google.api.client.http.HttpRequestFactory
|
||||
-dontwarn com.google.api.client.http.HttpResponse
|
||||
-dontwarn com.google.api.client.http.HttpTransport
|
||||
-dontwarn com.google.api.client.http.javanet.NetHttpTransport$Builder
|
||||
-dontwarn com.google.api.client.http.javanet.NetHttpTransport
|
||||
-dontwarn javax.lang.model.element.Modifier
|
||||
-dontwarn org.joda.time.Instant
|
||||
-dontwarn org.slf4j.impl.StaticLoggerBinder
|
||||
-dontwarn org.slf4j.impl.StaticMDCBinder
|
||||
-dontwarn org.slf4j.impl.StaticMarkerBinder
|
||||
Vendored
+38
-1
@@ -21,4 +21,41 @@
|
||||
#-renamesourcefileattribute SourceFile
|
||||
-keepclassmembers class * extends androidx.datastore.preferences.protobuf.GeneratedMessageLite {
|
||||
<fields>;
|
||||
}
|
||||
}
|
||||
|
||||
# Keep all classes in the org.xbill.DNS package and subpackages
|
||||
-keep class org.xbill.DNS.** { *; }
|
||||
-dontwarn org.xbill.DNS.**
|
||||
|
||||
# Preserve JNA classes if used (e.g., for IPHlpAPI on Windows)
|
||||
-keep class com.sun.jna.** { *; }
|
||||
-dontwarn com.sun.jna.**
|
||||
|
||||
# Keep DNS resolver configuration classes that might be loaded dynamically
|
||||
-keep class org.xbill.DNS.config.** { *; }
|
||||
-dontwarn org.xbill.DNS.config.**
|
||||
|
||||
-keep class org.xbill.DNS.** { *; }
|
||||
|
||||
# Prevent optimization issues with native or reflection-based calls
|
||||
-dontoptimize
|
||||
-dontshrink
|
||||
# Uncomment the above if errors persist, but use sparingly as they’re broad
|
||||
|
||||
# Suppress warnings about missing classes if not all features are used
|
||||
-dontwarn java.lang.management.**
|
||||
-dontwarn sun.nio.ch.**
|
||||
|
||||
-dontwarn com.google.api.client.http.GenericUrl
|
||||
-dontwarn com.google.api.client.http.HttpHeaders
|
||||
-dontwarn com.google.api.client.http.HttpRequest
|
||||
-dontwarn com.google.api.client.http.HttpRequestFactory
|
||||
-dontwarn com.google.api.client.http.HttpResponse
|
||||
-dontwarn com.google.api.client.http.HttpTransport
|
||||
-dontwarn com.google.api.client.http.javanet.NetHttpTransport$Builder
|
||||
-dontwarn com.google.api.client.http.javanet.NetHttpTransport
|
||||
-dontwarn javax.lang.model.element.Modifier
|
||||
-dontwarn org.joda.time.Instant
|
||||
-dontwarn org.slf4j.impl.StaticLoggerBinder
|
||||
-dontwarn org.slf4j.impl.StaticMDCBinder
|
||||
-dontwarn org.slf4j.impl.StaticMarkerBinder
|
||||
@@ -3,13 +3,8 @@
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
|
||||
<!--foreground service exempt android 14-->
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SYSTEM_EXEMPTED" />
|
||||
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM"
|
||||
@@ -168,25 +163,17 @@
|
||||
</service>
|
||||
|
||||
<receiver
|
||||
android:name=".core.broadcast.BootReceiver"
|
||||
android:name=".core.broadcast.RestartReceiver"
|
||||
android:enabled="true"
|
||||
android:exported="false">
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
|
||||
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||
<action android:name="android.intent.action.ACTION_BOOT_COMPLETED" />
|
||||
<action android:name="android.intent.action.QUICKBOOT_POWERON" />
|
||||
<action android:name="com.htc.intent.action.QUICKBOOT_POWERON" />
|
||||
<action android:name="android.intent.action.MY_PACKAGE_REPLACED" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
<receiver
|
||||
android:name=".core.broadcast.AppUpdateReceiver"
|
||||
android:exported="false">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MY_PACKAGE_REPLACED" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
<receiver
|
||||
android:name=".core.broadcast.KernelReceiver"
|
||||
android:exported="false"
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package com.zaneschepke.wireguardautotunnel
|
||||
|
||||
import android.Manifest
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.graphics.Color
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
@@ -32,15 +34,16 @@ import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.navigation.compose.NavHost
|
||||
import androidx.navigation.compose.composable
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import androidx.navigation.toRoute
|
||||
import com.zaneschepke.networkmonitor.NetworkMonitor
|
||||
import com.zaneschepke.wireguardautotunnel.core.shortcut.ShortcutManager
|
||||
import com.zaneschepke.wireguardautotunnel.core.tunnel.TunnelManager
|
||||
import com.zaneschepke.wireguardautotunnel.core.worker.ServiceWorker
|
||||
import com.zaneschepke.wireguardautotunnel.domain.repository.AppStateRepository
|
||||
import com.zaneschepke.wireguardautotunnel.ui.Route
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.navigation.BottomNavBar
|
||||
@@ -67,9 +70,9 @@ import com.zaneschepke.wireguardautotunnel.ui.screens.support.LogsScreen
|
||||
import com.zaneschepke.wireguardautotunnel.ui.screens.support.SupportScreen
|
||||
import com.zaneschepke.wireguardautotunnel.ui.theme.WireguardAutoTunnelTheme
|
||||
import com.zaneschepke.wireguardautotunnel.util.Constants
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.requestAutoTunnelTileServiceUpdate
|
||||
import com.zaneschepke.wireguardautotunnel.viewmodel.AppViewModel
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
@@ -85,6 +88,11 @@ class MainActivity : AppCompatActivity() {
|
||||
@Inject
|
||||
lateinit var shortcutManager: ShortcutManager
|
||||
|
||||
@Inject
|
||||
lateinit var networkMonitor: NetworkMonitor
|
||||
|
||||
private var lastLocationPermissionState: Boolean? = null
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
enableEdgeToEdge(
|
||||
statusBarStyle = SystemBarStyle.Companion.auto(Color.TRANSPARENT, Color.TRANSPARENT),
|
||||
@@ -121,22 +129,13 @@ class MainActivity : AppCompatActivity() {
|
||||
viewModel.getEmitSplitTunnelApps(this@MainActivity)
|
||||
}
|
||||
|
||||
LaunchedEffect(appUiState.autoTunnelActive) {
|
||||
requestAutoTunnelTileServiceUpdate()
|
||||
}
|
||||
|
||||
with(appUiState.appSettings) {
|
||||
LaunchedEffect(isAutoTunnelEnabled) {
|
||||
this@MainActivity.requestAutoTunnelTileServiceUpdate()
|
||||
}
|
||||
LaunchedEffect(isShortcutsEnabled) {
|
||||
if (!isShortcutsEnabled) return@LaunchedEffect shortcutManager.removeShortcuts()
|
||||
shortcutManager.addShortcuts()
|
||||
}
|
||||
}
|
||||
|
||||
ServiceWorker.start(this)
|
||||
|
||||
CompositionLocalProvider(LocalNavController provides navController) {
|
||||
SnackbarControllerProvider { host ->
|
||||
WireguardAutoTunnelTheme(theme = appUiState.generalState.theme) {
|
||||
@@ -221,16 +220,17 @@ class MainActivity : AppCompatActivity() {
|
||||
composable<Route.Logs> {
|
||||
LogsScreen()
|
||||
}
|
||||
composable<Route.Config> {
|
||||
val args = it.toRoute<Route.Config>()
|
||||
composable<Route.Config> { backStack ->
|
||||
val args = backStack.toRoute<Route.Config>()
|
||||
val config =
|
||||
appUiState.tunnels.firstOrNull { it.id == args.id }
|
||||
ConfigScreen(config, viewModel)
|
||||
}
|
||||
composable<Route.TunnelOptions> {
|
||||
val args = it.toRoute<Route.TunnelOptions>()
|
||||
val config = appUiState.tunnels.first { it.id == args.id }
|
||||
OptionsScreen(config)
|
||||
composable<Route.TunnelOptions> { backStack ->
|
||||
val args = backStack.toRoute<Route.TunnelOptions>()
|
||||
appUiState.tunnels.firstOrNull { it.id == args.id }?.let { config ->
|
||||
OptionsScreen(config, appUiState)
|
||||
}
|
||||
}
|
||||
composable<Route.Lock> {
|
||||
PinLockScreen(viewModel)
|
||||
@@ -241,15 +241,17 @@ class MainActivity : AppCompatActivity() {
|
||||
composable<Route.KillSwitch> {
|
||||
KillSwitchScreen(appUiState, viewModel)
|
||||
}
|
||||
composable<Route.SplitTunnel> {
|
||||
val args = it.toRoute<Route.SplitTunnel>()
|
||||
val config = appUiState.tunnels.first { it.id == args.id }
|
||||
SplitTunnelScreen(config, viewModel)
|
||||
composable<Route.SplitTunnel> { backStack ->
|
||||
val args = backStack.toRoute<Route.SplitTunnel>()
|
||||
appUiState.tunnels.firstOrNull { it.id == args.id }?.let {
|
||||
SplitTunnelScreen(it, viewModel)
|
||||
}
|
||||
}
|
||||
composable<Route.TunnelAutoTunnel> {
|
||||
val args = it.toRoute<Route.TunnelOptions>()
|
||||
val config = appUiState.tunnels.first { it.id == args.id }
|
||||
TunnelAutoTunnelScreen(config, appUiState.appSettings)
|
||||
composable<Route.TunnelAutoTunnel> { backStack ->
|
||||
val args = backStack.toRoute<Route.TunnelOptions>()
|
||||
appUiState.tunnels.firstOrNull { it.id == args.id }?.let {
|
||||
TunnelAutoTunnelScreen(it, appUiState.appSettings)
|
||||
}
|
||||
}
|
||||
}
|
||||
BackHandler {
|
||||
@@ -264,4 +266,22 @@ class MainActivity : AppCompatActivity() {
|
||||
}
|
||||
}
|
||||
}
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
checkPermissionAndNotify()
|
||||
}
|
||||
|
||||
private fun checkPermissionAndNotify() {
|
||||
val hasLocation = ContextCompat.checkSelfPermission(
|
||||
this,
|
||||
Manifest.permission.ACCESS_FINE_LOCATION,
|
||||
) == PackageManager.PERMISSION_GRANTED
|
||||
if (lastLocationPermissionState != hasLocation) {
|
||||
Timber.d("Location permission changed to: $hasLocation")
|
||||
if (hasLocation) {
|
||||
networkMonitor.sendLocationPermissionsGrantedBroadcast()
|
||||
}
|
||||
lastLocationPermissionState = hasLocation
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import androidx.work.Configuration
|
||||
import com.wireguard.android.backend.GoBackend
|
||||
import com.zaneschepke.logcatter.LogReader
|
||||
import com.zaneschepke.wireguardautotunnel.core.tunnel.TunnelManager
|
||||
import com.zaneschepke.wireguardautotunnel.core.worker.ServiceWorker
|
||||
import com.zaneschepke.wireguardautotunnel.di.ApplicationScope
|
||||
import com.zaneschepke.wireguardautotunnel.di.IoDispatcher
|
||||
import com.zaneschepke.wireguardautotunnel.di.MainDispatcher
|
||||
@@ -91,9 +92,11 @@ class WireGuardAutoTunnel : Application(), Configuration.Provider {
|
||||
}
|
||||
}
|
||||
|
||||
ServiceWorker.start(this)
|
||||
|
||||
applicationScope.launch {
|
||||
withContext(mainDispatcher) {
|
||||
if (appDataRepository.appState.isLocalLogsEnabled() && !isRunningOnTv()) logReader.initialize()
|
||||
if (appDataRepository.appState.isLocalLogsEnabled() && !isRunningOnTv()) logReader.start()
|
||||
}
|
||||
if (!appDataRepository.settings.get().isKernelEnabled) {
|
||||
tunnelManager.setBackendState(BackendState.SERVICE_ACTIVE, emptyList())
|
||||
|
||||
-45
@@ -1,45 +0,0 @@
|
||||
package com.zaneschepke.wireguardautotunnel.core.broadcast
|
||||
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import com.zaneschepke.wireguardautotunnel.domain.repository.AppDataRepository
|
||||
import com.zaneschepke.wireguardautotunnel.di.ApplicationScope
|
||||
import com.zaneschepke.wireguardautotunnel.core.service.ServiceManager
|
||||
import com.zaneschepke.wireguardautotunnel.core.tunnel.TunnelManager
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
class AppUpdateReceiver : BroadcastReceiver() {
|
||||
|
||||
@Inject
|
||||
@ApplicationScope
|
||||
lateinit var applicationScope: CoroutineScope
|
||||
|
||||
@Inject
|
||||
lateinit var appDataRepository: AppDataRepository
|
||||
|
||||
@Inject
|
||||
lateinit var serviceManager: ServiceManager
|
||||
|
||||
@Inject
|
||||
lateinit var tunnelManager: TunnelManager
|
||||
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
if (intent.action != Intent.ACTION_MY_PACKAGE_REPLACED) return
|
||||
serviceManager.updateTunnelTile()
|
||||
serviceManager.updateAutoTunnelTile()
|
||||
applicationScope.launch {
|
||||
with(appDataRepository.settings.get()) {
|
||||
if (isRestoreOnBootEnabled) {
|
||||
// If auto tunnel is enabled, just start it and let auto tunnel start appropriate tun
|
||||
if (isAutoTunnelEnabled && !serviceManager.autoTunnelActive.value) return@launch serviceManager.startAutoTunnel(true)
|
||||
tunnelManager.restorePreviousState()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
package com.zaneschepke.wireguardautotunnel.core.broadcast
|
||||
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import com.zaneschepke.wireguardautotunnel.domain.repository.AppDataRepository
|
||||
import com.zaneschepke.wireguardautotunnel.di.ApplicationScope
|
||||
import com.zaneschepke.wireguardautotunnel.core.service.ServiceManager
|
||||
import com.zaneschepke.wireguardautotunnel.core.tunnel.TunnelManager
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
class BootReceiver : BroadcastReceiver() {
|
||||
@Inject
|
||||
lateinit var appDataRepository: AppDataRepository
|
||||
|
||||
@Inject
|
||||
@ApplicationScope
|
||||
lateinit var applicationScope: CoroutineScope
|
||||
|
||||
@Inject
|
||||
lateinit var serviceManager: ServiceManager
|
||||
|
||||
@Inject
|
||||
lateinit var tunnelManager: TunnelManager
|
||||
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
if (Intent.ACTION_BOOT_COMPLETED != intent.action) return
|
||||
serviceManager.updateTunnelTile()
|
||||
serviceManager.updateAutoTunnelTile()
|
||||
applicationScope.launch {
|
||||
with(appDataRepository.settings.get()) {
|
||||
if (isRestoreOnBootEnabled) {
|
||||
// If auto tunnel is enabled, just start it and let auto tunnel start appropriate tun
|
||||
if (isAutoTunnelEnabled && !serviceManager.autoTunnelActive.value) return@launch serviceManager.startAutoTunnel(true)
|
||||
tunnelManager.restorePreviousState()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -32,8 +32,8 @@ class KernelReceiver : BroadcastReceiver() {
|
||||
val action = intent.action ?: return
|
||||
applicationScope.launch {
|
||||
if (action == REFRESH_TUNNELS_ACTION) {
|
||||
tunnelManager.runningTunnelNames().forEach {
|
||||
val tunnel = tunnelRepository.findByTunnelName(it)
|
||||
tunnelManager.runningTunnelNames().forEach { name ->
|
||||
val tunnel = tunnelRepository.findByTunnelName(name)
|
||||
tunnel?.let {
|
||||
tunnelRepository.save(it.copy(isActive = true))
|
||||
}
|
||||
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
package com.zaneschepke.wireguardautotunnel.core.broadcast
|
||||
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import com.zaneschepke.wireguardautotunnel.domain.repository.AppDataRepository
|
||||
import com.zaneschepke.wireguardautotunnel.di.ApplicationScope
|
||||
import com.zaneschepke.wireguardautotunnel.core.service.ServiceManager
|
||||
import com.zaneschepke.wireguardautotunnel.core.tunnel.TunnelManager
|
||||
import com.zaneschepke.wireguardautotunnel.di.IoDispatcher
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
class RestartReceiver : BroadcastReceiver() {
|
||||
@Inject
|
||||
lateinit var appDataRepository: AppDataRepository
|
||||
|
||||
@Inject
|
||||
@ApplicationScope
|
||||
lateinit var applicationScope: CoroutineScope
|
||||
|
||||
@Inject
|
||||
lateinit var serviceManager: ServiceManager
|
||||
|
||||
@Inject
|
||||
lateinit var tunnelManager: TunnelManager
|
||||
|
||||
@Inject
|
||||
@IoDispatcher
|
||||
lateinit var ioDispatcher: CoroutineDispatcher
|
||||
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
val action = intent.action ?: return
|
||||
if (action != Intent.ACTION_BOOT_COMPLETED &&
|
||||
action != Intent.ACTION_MY_PACKAGE_REPLACED &&
|
||||
action != "com.htc.intent.action.QUICKBOOT_POWERON"
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
Timber.d("RestartReceiver triggered with action: ${intent.action}")
|
||||
applicationScope.launch(ioDispatcher) {
|
||||
serviceManager.updateTunnelTile()
|
||||
serviceManager.updateAutoTunnelTile()
|
||||
val settings = appDataRepository.settings.get()
|
||||
if (settings.isRestoreOnBootEnabled) {
|
||||
if (settings.isAutoTunnelEnabled && !serviceManager.autoTunnelActive.value) {
|
||||
Timber.d("Starting auto-tunnel on boot/update")
|
||||
serviceManager.startAutoTunnel(true)
|
||||
} else {
|
||||
Timber.d("Restoring previous tunnel state")
|
||||
tunnelManager.restorePreviousState()
|
||||
}
|
||||
} else {
|
||||
Timber.d("Restore on boot disabled, skipping")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
-161
@@ -1,161 +0,0 @@
|
||||
package com.zaneschepke.wireguardautotunnel.core.network
|
||||
|
||||
import android.content.Context
|
||||
import android.net.ConnectivityManager
|
||||
import android.net.Network
|
||||
import android.net.NetworkCapabilities
|
||||
import android.net.NetworkRequest
|
||||
import android.net.wifi.SupplicantState
|
||||
import android.net.wifi.WifiManager
|
||||
import android.os.Build
|
||||
import com.zaneschepke.wireguardautotunnel.domain.state.ConnectivityState
|
||||
import com.zaneschepke.wireguardautotunnel.di.IoDispatcher
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.channels.awaitClose
|
||||
import kotlinx.coroutines.flow.callbackFlow
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import javax.inject.Inject
|
||||
|
||||
class InternetConnectivityMonitor
|
||||
@Inject
|
||||
constructor(
|
||||
@ApplicationContext private val context: Context,
|
||||
@IoDispatcher private val ioDispatcher: CoroutineDispatcher,
|
||||
) : NetworkMonitor {
|
||||
|
||||
private val connectivityManager =
|
||||
context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
|
||||
|
||||
@get:Synchronized @set:Synchronized
|
||||
private var wifiCapabilities: NetworkCapabilities? = null
|
||||
|
||||
@get:Synchronized @set:Synchronized
|
||||
private var wifiNetworkChanged: Boolean = false
|
||||
|
||||
override val didWifiChangeSinceLastCapabilitiesQuery: Boolean
|
||||
get() = wifiNetworkChanged
|
||||
|
||||
override val status = callbackFlow {
|
||||
|
||||
var wifiState: Boolean = false
|
||||
var ethernetState: Boolean = false
|
||||
var cellularState: Boolean = false
|
||||
|
||||
fun emitState() {
|
||||
trySend(ConnectivityState(wifiState, ethernetState, cellularState))
|
||||
}
|
||||
|
||||
val currentNetwork = connectivityManager.activeNetwork
|
||||
if (currentNetwork == null) {
|
||||
emitState()
|
||||
}
|
||||
|
||||
fun updateCapabilityState(up: Boolean, network: Network) {
|
||||
with(connectivityManager.getNetworkCapabilities(network)) {
|
||||
when {
|
||||
this == null -> return
|
||||
hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> wifiState = up
|
||||
hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) ->
|
||||
cellularState = up
|
||||
|
||||
hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) ->
|
||||
ethernetState = up
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun onWifiChange(network: Network, callback: () -> Unit) {
|
||||
if (connectivityManager.getNetworkCapabilities(network)?.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) == true) {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
|
||||
fun onAvailable(network: Network) {
|
||||
onWifiChange(network) {
|
||||
wifiNetworkChanged = true
|
||||
}
|
||||
}
|
||||
|
||||
fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) {
|
||||
onWifiChange(network) {
|
||||
wifiCapabilities = networkCapabilities
|
||||
}
|
||||
updateCapabilityState(true, network)
|
||||
emitState()
|
||||
}
|
||||
|
||||
val networkStatusCallback =
|
||||
when (Build.VERSION.SDK_INT) {
|
||||
in Build.VERSION_CODES.S..Int.MAX_VALUE -> {
|
||||
object :
|
||||
ConnectivityManager.NetworkCallback(
|
||||
FLAG_INCLUDE_LOCATION_INFO,
|
||||
) {
|
||||
override fun onAvailable(network: Network) {
|
||||
onAvailable(network)
|
||||
}
|
||||
|
||||
override fun onLost(network: Network) {
|
||||
updateCapabilityState(false, network)
|
||||
emitState()
|
||||
}
|
||||
|
||||
override fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) {
|
||||
onCapabilitiesChanged(network, networkCapabilities)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
else -> {
|
||||
object : ConnectivityManager.NetworkCallback() {
|
||||
override fun onAvailable(network: Network) {
|
||||
onAvailable(network)
|
||||
}
|
||||
|
||||
override fun onLost(network: Network) {
|
||||
updateCapabilityState(false, network)
|
||||
emitState()
|
||||
}
|
||||
|
||||
override fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) {
|
||||
onCapabilitiesChanged(network, networkCapabilities)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
val request =
|
||||
NetworkRequest.Builder()
|
||||
.addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
|
||||
.addTransportType(NetworkCapabilities.TRANSPORT_ETHERNET)
|
||||
.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
|
||||
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
|
||||
.addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
|
||||
.build()
|
||||
connectivityManager.registerNetworkCallback(request, networkStatusCallback)
|
||||
|
||||
awaitClose { connectivityManager.unregisterNetworkCallback(networkStatusCallback) }
|
||||
}.flowOn(ioDispatcher)
|
||||
|
||||
override fun getWifiCapabilities(): NetworkCapabilities? {
|
||||
wifiNetworkChanged = false
|
||||
return wifiCapabilities
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun getNetworkName(networkCapabilities: NetworkCapabilities, context: Context): String? {
|
||||
var ssid = networkCapabilities.getWifiName()
|
||||
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.S) {
|
||||
val wifiManager =
|
||||
context.applicationContext.getSystemService(Context.WIFI_SERVICE) as WifiManager
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
val info = wifiManager.connectionInfo
|
||||
if (info.supplicantState === SupplicantState.COMPLETED) {
|
||||
ssid = info.ssid
|
||||
}
|
||||
}
|
||||
return ssid?.trim('"')
|
||||
}
|
||||
}
|
||||
}
|
||||
-16
@@ -1,16 +0,0 @@
|
||||
package com.zaneschepke.wireguardautotunnel.core.network
|
||||
|
||||
import android.net.NetworkCapabilities
|
||||
import android.net.wifi.WifiInfo
|
||||
import android.os.Build
|
||||
|
||||
fun NetworkCapabilities.getWifiName(): String? {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
val info: WifiInfo
|
||||
if (transportInfo is WifiInfo) {
|
||||
info = transportInfo as WifiInfo
|
||||
return info.ssid
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
package com.zaneschepke.wireguardautotunnel.core.network
|
||||
|
||||
import android.net.NetworkCapabilities
|
||||
import com.zaneschepke.wireguardautotunnel.domain.state.ConnectivityState
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface NetworkMonitor {
|
||||
val status: Flow<ConnectivityState>
|
||||
|
||||
// util to help limit location queries
|
||||
val didWifiChangeSinceLastCapabilitiesQuery: Boolean
|
||||
fun getWifiCapabilities(): NetworkCapabilities?
|
||||
}
|
||||
+78
-55
@@ -3,35 +3,36 @@ package com.zaneschepke.wireguardautotunnel.core.service
|
||||
import android.app.Service
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import com.zaneschepke.wireguardautotunnel.domain.repository.AppDataRepository
|
||||
import com.zaneschepke.wireguardautotunnel.core.service.autotunnel.AutoTunnelService
|
||||
import com.zaneschepke.wireguardautotunnel.core.service.tile.AutoTunnelControlTile
|
||||
import com.zaneschepke.wireguardautotunnel.core.service.tile.TunnelControlTile
|
||||
import com.zaneschepke.wireguardautotunnel.di.ApplicationScope
|
||||
import com.zaneschepke.wireguardautotunnel.di.IoDispatcher
|
||||
import com.zaneschepke.wireguardautotunnel.domain.entity.TunnelConf
|
||||
import com.zaneschepke.wireguardautotunnel.domain.repository.AppDataRepository
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.requestAutoTunnelTileServiceUpdate
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.requestTunnelTileServiceStateUpdate
|
||||
import jakarta.inject.Inject
|
||||
import kotlinx.coroutines.CompletableDeferred
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withTimeoutOrNull
|
||||
import timber.log.Timber
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
class ServiceManager
|
||||
@Inject constructor(private val context: Context, private val ioDispatcher: CoroutineDispatcher, private val appDataRepository: AppDataRepository) {
|
||||
class ServiceManager @Inject constructor(
|
||||
private val context: Context,
|
||||
@IoDispatcher private val ioDispatcher: CoroutineDispatcher,
|
||||
@ApplicationScope private val applicationScope: CoroutineScope,
|
||||
private val appDataRepository: AppDataRepository,
|
||||
) {
|
||||
|
||||
private val _autoTunnelActive = MutableStateFlow(false)
|
||||
|
||||
val autoTunnelActive = _autoTunnelActive.asStateFlow()
|
||||
|
||||
var autoTunnelService = CompletableDeferred<AutoTunnelService>()
|
||||
var backgroundService = CompletableDeferred<TunnelForegroundService>()
|
||||
var autoTunnelTile = CompletableDeferred<AutoTunnelControlTile>()
|
||||
var tunnelControlTile = CompletableDeferred<TunnelControlTile>()
|
||||
|
||||
private fun <T : Service> startService(cls: Class<T>, background: Boolean) {
|
||||
runCatching {
|
||||
@@ -44,76 +45,98 @@ class ServiceManager
|
||||
}.onFailure { Timber.e(it) }
|
||||
}
|
||||
|
||||
suspend fun startAutoTunnel(background: Boolean) {
|
||||
val settings = appDataRepository.settings.get()
|
||||
appDataRepository.settings.save(settings.copy(isAutoTunnelEnabled = true))
|
||||
if (autoTunnelService.isCompleted) return _autoTunnelActive.update { true }
|
||||
runCatching {
|
||||
startService(AutoTunnelService::class.java, background)
|
||||
autoTunnelService.await()
|
||||
autoTunnelService.getCompleted().start()
|
||||
_autoTunnelActive.update { true }
|
||||
updateAutoTunnelTile()
|
||||
}.onFailure {
|
||||
Timber.e(it)
|
||||
fun startAutoTunnel(background: Boolean) {
|
||||
applicationScope.launch(ioDispatcher) {
|
||||
val settings = appDataRepository.settings.get()
|
||||
appDataRepository.settings.save(settings.copy(isAutoTunnelEnabled = true))
|
||||
if (autoTunnelService.isCompleted) {
|
||||
_autoTunnelActive.update { true }
|
||||
return@launch
|
||||
}
|
||||
runCatching {
|
||||
autoTunnelService = CompletableDeferred()
|
||||
startService(AutoTunnelService::class.java, background)
|
||||
_autoTunnelActive.update { true }
|
||||
}.onFailure {
|
||||
Timber.e(it)
|
||||
_autoTunnelActive.update { false }
|
||||
}
|
||||
}
|
||||
updateAutoTunnelTile()
|
||||
}
|
||||
|
||||
fun startTunnelForegroundService(tunnelConf: TunnelConf) {
|
||||
applicationScope.launch(ioDispatcher) {
|
||||
if (backgroundService.isCompleted) return@launch
|
||||
runCatching {
|
||||
backgroundService = CompletableDeferred()
|
||||
startService(TunnelForegroundService::class.java, true)
|
||||
val service = withTimeoutOrNull(SERVICE_START_TIMEOUT) { backgroundService.await() }
|
||||
?: throw IllegalStateException("Background service start timed out")
|
||||
service.start(tunnelConf)
|
||||
}.onFailure {
|
||||
Timber.e(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun startBackgroundService(tunnelConf: TunnelConf) {
|
||||
if (backgroundService.isCompleted) return
|
||||
runCatching {
|
||||
startService(TunnelForegroundService::class.java, true)
|
||||
backgroundService.await()
|
||||
backgroundService.getCompleted().start(tunnelConf)
|
||||
}.onFailure {
|
||||
Timber.e(it)
|
||||
fun updateTunnelForegroundServiceNotification(tunnelConf: TunnelConf) {
|
||||
applicationScope.launch(ioDispatcher) {
|
||||
if (!backgroundService.isCompleted) return@launch
|
||||
runCatching {
|
||||
val service = backgroundService.await()
|
||||
service.start(tunnelConf)
|
||||
}.onFailure {
|
||||
Timber.e(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun stopBackgroundService() {
|
||||
if (!backgroundService.isCompleted) return
|
||||
runCatching {
|
||||
backgroundService.getCompleted().stop()
|
||||
}.onFailure {
|
||||
Timber.e(it)
|
||||
fun stopTunnelForegroundService() {
|
||||
applicationScope.launch(ioDispatcher) {
|
||||
if (!backgroundService.isCompleted) return@launch
|
||||
runCatching {
|
||||
val service = backgroundService.await()
|
||||
service.stop()
|
||||
backgroundService = CompletableDeferred()
|
||||
}.onFailure {
|
||||
Timber.e(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun toggleAutoTunnel(background: Boolean) {
|
||||
withContext(ioDispatcher) {
|
||||
if (_autoTunnelActive.value) return@withContext stopAutoTunnel()
|
||||
startAutoTunnel(background)
|
||||
fun toggleAutoTunnel(background: Boolean) {
|
||||
applicationScope.launch(ioDispatcher) {
|
||||
if (_autoTunnelActive.value) stopAutoTunnel() else startAutoTunnel(background)
|
||||
}
|
||||
}
|
||||
|
||||
fun updateAutoTunnelTile() {
|
||||
if (autoTunnelTile.isCompleted) {
|
||||
autoTunnelTile.getCompleted().updateTileState()
|
||||
} else {
|
||||
context.requestAutoTunnelTileServiceUpdate()
|
||||
}
|
||||
context.requestAutoTunnelTileServiceUpdate()
|
||||
}
|
||||
|
||||
fun updateTunnelTile() {
|
||||
if (tunnelControlTile.isCompleted) {
|
||||
tunnelControlTile.getCompleted().updateTileState()
|
||||
} else {
|
||||
context.requestTunnelTileServiceStateUpdate()
|
||||
}
|
||||
context.requestTunnelTileServiceStateUpdate()
|
||||
}
|
||||
|
||||
suspend fun stopAutoTunnel() {
|
||||
withContext(ioDispatcher) {
|
||||
fun stopAutoTunnel() {
|
||||
applicationScope.launch(ioDispatcher) {
|
||||
val settings = appDataRepository.settings.get()
|
||||
appDataRepository.settings.save(settings.copy(isAutoTunnelEnabled = false))
|
||||
if (!autoTunnelService.isCompleted) return@withContext
|
||||
if (!autoTunnelService.isCompleted) return@launch
|
||||
runCatching {
|
||||
autoTunnelService.getCompleted().stop()
|
||||
val service = autoTunnelService.await()
|
||||
service.stop()
|
||||
_autoTunnelActive.update { false }
|
||||
autoTunnelService = CompletableDeferred()
|
||||
updateAutoTunnelTile()
|
||||
}.onFailure {
|
||||
Timber.e(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val SERVICE_START_TIMEOUT = 5_000L
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -49,12 +49,12 @@ class TunnelForegroundService : LifecycleService() {
|
||||
}
|
||||
|
||||
fun stop() {
|
||||
stopForeground(STOP_FOREGROUND_REMOVE)
|
||||
stopSelf()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
serviceManager.backgroundService = CompletableDeferred()
|
||||
ServiceCompat.stopForeground(this, ServiceCompat.STOP_FOREGROUND_REMOVE)
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
|
||||
+45
-44
@@ -1,44 +1,42 @@
|
||||
package com.zaneschepke.wireguardautotunnel.core.service.autotunnel
|
||||
|
||||
import android.content.Intent
|
||||
import android.net.NetworkCapabilities
|
||||
import android.os.IBinder
|
||||
import android.os.PowerManager
|
||||
import androidx.core.app.ServiceCompat
|
||||
import androidx.lifecycle.LifecycleService
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.wireguard.android.util.RootShell
|
||||
import com.zaneschepke.networkmonitor.NetworkMonitor
|
||||
import com.zaneschepke.networkmonitor.NetworkStatus
|
||||
import com.zaneschepke.wireguardautotunnel.R
|
||||
import com.zaneschepke.wireguardautotunnel.domain.repository.AppDataRepository
|
||||
import com.zaneschepke.wireguardautotunnel.di.AppShell
|
||||
import com.zaneschepke.wireguardautotunnel.di.IoDispatcher
|
||||
import com.zaneschepke.wireguardautotunnel.di.MainImmediateDispatcher
|
||||
import com.zaneschepke.wireguardautotunnel.core.service.ServiceManager
|
||||
import com.zaneschepke.wireguardautotunnel.domain.state.AutoTunnelState
|
||||
import com.zaneschepke.wireguardautotunnel.domain.state.NetworkState
|
||||
import com.zaneschepke.wireguardautotunnel.core.network.InternetConnectivityMonitor
|
||||
import com.zaneschepke.wireguardautotunnel.core.network.NetworkMonitor
|
||||
import com.zaneschepke.wireguardautotunnel.domain.state.ConnectivityState
|
||||
import com.zaneschepke.wireguardautotunnel.domain.enums.NotificationAction
|
||||
import com.zaneschepke.wireguardautotunnel.core.notification.NotificationManager
|
||||
import com.zaneschepke.wireguardautotunnel.core.notification.WireGuardNotification
|
||||
import com.zaneschepke.wireguardautotunnel.core.service.ServiceManager
|
||||
import com.zaneschepke.wireguardautotunnel.core.tunnel.TunnelManager
|
||||
import com.zaneschepke.wireguardautotunnel.di.IoDispatcher
|
||||
import com.zaneschepke.wireguardautotunnel.di.MainImmediateDispatcher
|
||||
import com.zaneschepke.wireguardautotunnel.domain.entity.AppSettings
|
||||
import com.zaneschepke.wireguardautotunnel.domain.enums.BackendState
|
||||
import com.zaneschepke.wireguardautotunnel.domain.enums.NotificationAction
|
||||
import com.zaneschepke.wireguardautotunnel.domain.events.AutoTunnelEvent
|
||||
import com.zaneschepke.wireguardautotunnel.domain.events.KillSwitchEvent
|
||||
import com.zaneschepke.wireguardautotunnel.domain.repository.AppDataRepository
|
||||
import com.zaneschepke.wireguardautotunnel.domain.state.AutoTunnelState
|
||||
import com.zaneschepke.wireguardautotunnel.domain.state.NetworkState
|
||||
import com.zaneschepke.wireguardautotunnel.util.Constants
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.Tunnels
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.getCurrentWifiName
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.CompletableDeferred
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.FlowPreview
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.debounce
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
@@ -49,10 +47,6 @@ import javax.inject.Provider
|
||||
@AndroidEntryPoint
|
||||
class AutoTunnelService : LifecycleService() {
|
||||
|
||||
@Inject
|
||||
@AppShell
|
||||
lateinit var rootShell: Provider<RootShell>
|
||||
|
||||
@Inject
|
||||
lateinit var networkMonitor: NetworkMonitor
|
||||
|
||||
@@ -100,9 +94,11 @@ class AutoTunnelService : LifecycleService() {
|
||||
}
|
||||
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
super.onStartCommand(intent, flags, startId)
|
||||
Timber.d("onStartCommand executed with startId: $startId")
|
||||
serviceManager.autoTunnelService.complete(this)
|
||||
return super.onStartCommand(intent, flags, startId)
|
||||
start()
|
||||
return START_STICKY
|
||||
}
|
||||
|
||||
fun start() {
|
||||
@@ -161,49 +157,54 @@ class AutoTunnelService : LifecycleService() {
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun buildNetworkState(connectivityState: ConnectivityState): NetworkState {
|
||||
private fun buildNetworkState(networkStatus: NetworkStatus): NetworkState {
|
||||
return with(autoTunnelStateFlow.value.networkState) {
|
||||
val wifiName = when {
|
||||
connectivityState.wifiAvailable &&
|
||||
(wifiName == null || wifiName == Constants.UNREADABLE_SSID || networkMonitor.didWifiChangeSinceLastCapabilitiesQuery) -> {
|
||||
networkMonitor.getWifiCapabilities()?.let { getWifiName(it) } ?: wifiName
|
||||
val wifiName = when (networkStatus) {
|
||||
is NetworkStatus.Connected -> {
|
||||
networkStatus.wifiSsid
|
||||
}
|
||||
!connectivityState.wifiAvailable -> null
|
||||
else -> wifiName
|
||||
else -> null
|
||||
}
|
||||
copy(
|
||||
isWifiConnected = connectivityState.wifiAvailable,
|
||||
isMobileDataConnected = connectivityState.cellularAvailable,
|
||||
isEthernetConnected = isEthernetConnected,
|
||||
isWifiConnected = networkStatus.wifiConnected,
|
||||
isMobileDataConnected = networkStatus.cellularConnected,
|
||||
isEthernetConnected = networkStatus.ethernetConnected,
|
||||
wifiName = wifiName,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
private fun startAutoTunnelStateJob() = lifecycleScope.launch(ioDispatcher) {
|
||||
combine(
|
||||
combineSettings(),
|
||||
networkMonitor.status.map {
|
||||
buildNetworkState(it)
|
||||
}.distinctUntilChanged(),
|
||||
appDataRepository.get().settings.flow
|
||||
.distinctUntilChanged { old, new -> old.isKernelEnabled == new.isKernelEnabled } // Only emit when isKernelEnabled changes
|
||||
.flatMapLatest {
|
||||
networkMonitor.networkStatusFlow
|
||||
.flowOn(ioDispatcher)
|
||||
.map { buildNetworkState(it) }
|
||||
}
|
||||
.distinctUntilChanged(),
|
||||
) { double, networkState ->
|
||||
AutoTunnelState(tunnelManager.activeTunnels().value, networkState, double.first, double.second)
|
||||
AutoTunnelState(
|
||||
tunnelManager.activeTunnels.value,
|
||||
networkState,
|
||||
double.first,
|
||||
double.second,
|
||||
)
|
||||
}.collect { state ->
|
||||
autoTunnelStateFlow.update {
|
||||
it.copy(activeTunnels = state.activeTunnels, networkState = state.networkState, settings = state.settings, tunnels = state.tunnels)
|
||||
it.copy(
|
||||
activeTunnels = state.activeTunnels,
|
||||
networkState = state.networkState,
|
||||
settings = state.settings,
|
||||
tunnels = state.tunnels,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getWifiName(wifiCapabilities: NetworkCapabilities): String? {
|
||||
val setting = appDataRepository.get().settings.get()
|
||||
return if (setting.isWifiNameByShellEnabled) {
|
||||
rootShell.get().getCurrentWifiName()
|
||||
} else {
|
||||
InternetConnectivityMonitor.getNetworkName(wifiCapabilities, this@AutoTunnelService)
|
||||
}
|
||||
}
|
||||
|
||||
private fun combineSettings(): Flow<Pair<AppSettings, Tunnels>> {
|
||||
return combine(
|
||||
appDataRepository.get().settings.flow,
|
||||
@@ -240,7 +241,7 @@ class AutoTunnelService : LifecycleService() {
|
||||
Timber.d("Starting with debounce delay of: ${settings.debounceDelaySeconds} seconds")
|
||||
autoTunnelStateFlow.debounce(settings.debounceDelayMillis()).collect { watcherState ->
|
||||
if (watcherState == defaultState) return@collect
|
||||
Timber.d("New auto tunnel state emitted")
|
||||
Timber.d("New auto tunnel state emitted ${watcherState.networkState}")
|
||||
when (val event = watcherState.asAutoTunnelEvent()) {
|
||||
is AutoTunnelEvent.Start -> (event.tunnelConf ?: appDataRepository.get().getPrimaryOrFirstTunnel())?.let {
|
||||
tunnelManager.startTunnel(it)
|
||||
|
||||
+26
-19
@@ -4,57 +4,61 @@ import android.content.Intent
|
||||
import android.os.IBinder
|
||||
import android.service.quicksettings.Tile
|
||||
import android.service.quicksettings.TileService
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.LifecycleRegistry
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.zaneschepke.wireguardautotunnel.domain.repository.AppDataRepository
|
||||
import com.zaneschepke.wireguardautotunnel.di.ApplicationScope
|
||||
import com.zaneschepke.wireguardautotunnel.core.service.ServiceManager
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.CompletableDeferred
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
class AutoTunnelControlTile : TileService() {
|
||||
class AutoTunnelControlTile : TileService(), LifecycleOwner {
|
||||
@Inject
|
||||
lateinit var appDataRepository: AppDataRepository
|
||||
|
||||
@Inject
|
||||
lateinit var serviceManager: ServiceManager
|
||||
|
||||
@Inject
|
||||
@ApplicationScope
|
||||
lateinit var applicationScope: CoroutineScope
|
||||
private val lifecycleRegistry: LifecycleRegistry = LifecycleRegistry(this)
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
serviceManager.autoTunnelTile.complete(this)
|
||||
lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE)
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
serviceManager.autoTunnelTile = CompletableDeferred()
|
||||
lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY)
|
||||
}
|
||||
|
||||
override fun onStartListening() {
|
||||
super.onStartListening()
|
||||
serviceManager.autoTunnelTile.complete(this)
|
||||
applicationScope.launch {
|
||||
if (appDataRepository.tunnels.getAll().isEmpty()) return@launch setUnavailable()
|
||||
updateTileState()
|
||||
Timber.d("Start listening called for auto tunnel tile")
|
||||
lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START)
|
||||
lifecycleScope.launch {
|
||||
serviceManager.autoTunnelActive.collect {
|
||||
if (it) setActive() else setInactive()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun updateTileState() {
|
||||
serviceManager.autoTunnelActive.value.let {
|
||||
if (it) setActive() else setInactive()
|
||||
lifecycleScope.launch {
|
||||
appDataRepository.tunnels.flow.collect {
|
||||
if (it.isEmpty()) {
|
||||
setUnavailable()
|
||||
} else {
|
||||
if (qsTile.state == Tile.STATE_ACTIVE) setInactive()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onClick() {
|
||||
super.onClick()
|
||||
unlockAndRun {
|
||||
applicationScope.launch {
|
||||
lifecycleScope.launch {
|
||||
if (serviceManager.autoTunnelActive.value) {
|
||||
serviceManager.stopAutoTunnel()
|
||||
setInactive()
|
||||
@@ -97,4 +101,7 @@ class AutoTunnelControlTile : TileService() {
|
||||
qsTile.updateTile()
|
||||
}
|
||||
}
|
||||
|
||||
override val lifecycle: Lifecycle
|
||||
get() = lifecycleRegistry
|
||||
}
|
||||
|
||||
+32
-20
@@ -5,56 +5,66 @@ import android.os.Build
|
||||
import android.os.IBinder
|
||||
import android.service.quicksettings.Tile
|
||||
import android.service.quicksettings.TileService
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.LifecycleRegistry
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.zaneschepke.wireguardautotunnel.R
|
||||
import com.zaneschepke.wireguardautotunnel.core.service.ServiceManager
|
||||
import com.zaneschepke.wireguardautotunnel.core.tunnel.TunnelManager
|
||||
import com.zaneschepke.wireguardautotunnel.di.ApplicationScope
|
||||
import com.zaneschepke.wireguardautotunnel.domain.repository.AppDataRepository
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.CompletableDeferred
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
class TunnelControlTile : TileService() {
|
||||
class TunnelControlTile : TileService(), LifecycleOwner {
|
||||
@Inject
|
||||
lateinit var appDataRepository: AppDataRepository
|
||||
|
||||
@Inject
|
||||
@ApplicationScope
|
||||
lateinit var applicationScope: CoroutineScope
|
||||
|
||||
@Inject
|
||||
lateinit var serviceManager: ServiceManager
|
||||
|
||||
@Inject
|
||||
lateinit var tunnelManager: TunnelManager
|
||||
|
||||
private val lifecycleRegistry: LifecycleRegistry = LifecycleRegistry(this)
|
||||
|
||||
private var isCollecting = false
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
serviceManager.tunnelControlTile.complete(this)
|
||||
lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE)
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
serviceManager.tunnelControlTile = CompletableDeferred()
|
||||
lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY)
|
||||
}
|
||||
|
||||
override fun onStartListening() {
|
||||
super.onStartListening()
|
||||
Timber.d("Start listening called")
|
||||
serviceManager.tunnelControlTile.complete(this)
|
||||
applicationScope.launch {
|
||||
updateTileState()
|
||||
lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START)
|
||||
Timber.d("Start listening called for tunnel tile")
|
||||
if (isCollecting) return
|
||||
isCollecting = true
|
||||
lifecycleScope.launch {
|
||||
tunnelManager.activeTunnels.collect {
|
||||
updateTileState()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun updateTileState() = applicationScope.launch {
|
||||
if (appDataRepository.tunnels.getAll().isEmpty()) return@launch setUnavailable()
|
||||
with(tunnelManager.activeTunnels().value) {
|
||||
if (isNotEmpty()) return@launch updateTile(if (size == 1) first().tunName else getString(R.string.multiple), true)
|
||||
private fun updateTileState() = lifecycleScope.launch {
|
||||
val tunnels = appDataRepository.tunnels.getAll()
|
||||
if (tunnels.isEmpty()) return@launch setUnavailable()
|
||||
with(tunnelManager.activeTunnels.value) {
|
||||
if (isNotEmpty()) if (size == 1) {
|
||||
tunnels.firstOrNull { it.id == keys.first().id }?.let { return@launch updateTile(it.tunName, true) }
|
||||
} else {
|
||||
return@launch updateTile(getString(R.string.multiple), true)
|
||||
}
|
||||
}
|
||||
appDataRepository.getStartTunnelConfig()?.let {
|
||||
updateTile(it.tunName, false)
|
||||
@@ -64,8 +74,8 @@ class TunnelControlTile : TileService() {
|
||||
override fun onClick() {
|
||||
super.onClick()
|
||||
unlockAndRun {
|
||||
applicationScope.launch {
|
||||
if (tunnelManager.activeTunnels().value.isNotEmpty()) return@launch tunnelManager.stopTunnel()
|
||||
lifecycleScope.launch {
|
||||
if (tunnelManager.activeTunnels.value.isNotEmpty()) return@launch tunnelManager.stopTunnel()
|
||||
appDataRepository.getStartTunnelConfig()?.let {
|
||||
tunnelManager.startTunnel(it)
|
||||
}
|
||||
@@ -127,4 +137,6 @@ class TunnelControlTile : TileService() {
|
||||
Timber.e(it)
|
||||
}
|
||||
}
|
||||
override val lifecycle: Lifecycle
|
||||
get() = lifecycleRegistry
|
||||
}
|
||||
|
||||
+11
-7
@@ -6,6 +6,7 @@ import com.zaneschepke.wireguardautotunnel.domain.repository.AppDataRepository
|
||||
import com.zaneschepke.wireguardautotunnel.di.ApplicationScope
|
||||
import com.zaneschepke.wireguardautotunnel.core.service.ServiceManager
|
||||
import com.zaneschepke.wireguardautotunnel.core.service.autotunnel.AutoTunnelService
|
||||
import com.zaneschepke.wireguardautotunnel.core.tunnel.TunnelManager
|
||||
import com.zaneschepke.wireguardautotunnel.core.tunnel.TunnelProvider
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
@@ -21,6 +22,9 @@ class ShortcutsActivity : ComponentActivity() {
|
||||
@Inject
|
||||
lateinit var serviceManager: ServiceManager
|
||||
|
||||
@Inject
|
||||
lateinit var tunnelManager: TunnelManager
|
||||
|
||||
@Inject
|
||||
@ApplicationScope
|
||||
lateinit var applicationScope: CoroutineScope
|
||||
@@ -39,13 +43,13 @@ class ShortcutsActivity : ComponentActivity() {
|
||||
.firstOrNull { it.tunName == tunnelName }
|
||||
} ?: appDataRepository.getStartTunnelConfig()
|
||||
Timber.d("Shortcut action on name: ${tunnelConfig?.tunName}")
|
||||
// tunnelConfig?.let {
|
||||
// when (intent.action) {
|
||||
// Action.START.name -> tunnelService.get().startTunnel(it)
|
||||
// Action.STOP.name -> tunnelService.get().stopTunnel()
|
||||
// else -> Unit
|
||||
// }
|
||||
// }
|
||||
tunnelConfig?.let {
|
||||
when (intent.action) {
|
||||
Action.START.name -> tunnelManager.startTunnel(it)
|
||||
Action.STOP.name -> tunnelManager.stopTunnel()
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
}
|
||||
AutoTunnelService::class.java.simpleName, LEGACY_AUTO_TUNNEL_SERVICE_NAME -> {
|
||||
when (intent.action) {
|
||||
|
||||
@@ -1,40 +1,44 @@
|
||||
package com.zaneschepke.wireguardautotunnel.core.tunnel
|
||||
|
||||
import com.wireguard.android.backend.BackendException
|
||||
import com.wireguard.android.backend.Tunnel
|
||||
import com.zaneschepke.networkmonitor.NetworkMonitor
|
||||
import com.zaneschepke.networkmonitor.NetworkStatus
|
||||
import com.zaneschepke.wireguardautotunnel.R
|
||||
import com.zaneschepke.wireguardautotunnel.WireGuardAutoTunnel
|
||||
import com.zaneschepke.wireguardautotunnel.core.network.NetworkMonitor
|
||||
import com.zaneschepke.wireguardautotunnel.core.notification.NotificationManager
|
||||
import com.zaneschepke.wireguardautotunnel.core.notification.WireGuardNotification
|
||||
import com.zaneschepke.wireguardautotunnel.core.service.ServiceManager
|
||||
import com.zaneschepke.wireguardautotunnel.core.tunnel.TunnelProvider.Companion.CHECK_INTERVAL
|
||||
import com.zaneschepke.wireguardautotunnel.di.ApplicationScope
|
||||
import com.zaneschepke.wireguardautotunnel.di.IoDispatcher
|
||||
import com.zaneschepke.wireguardautotunnel.domain.entity.TunnelConf
|
||||
import com.zaneschepke.wireguardautotunnel.domain.enums.BackendError
|
||||
import com.zaneschepke.wireguardautotunnel.domain.enums.BackendState
|
||||
import com.zaneschepke.wireguardautotunnel.domain.enums.TunnelStatus
|
||||
import com.zaneschepke.wireguardautotunnel.domain.repository.AppDataRepository
|
||||
import com.zaneschepke.wireguardautotunnel.domain.state.TunnelState
|
||||
import com.zaneschepke.wireguardautotunnel.domain.state.TunnelStatistics
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.snackbar.SnackbarController
|
||||
import com.zaneschepke.wireguardautotunnel.util.Constants
|
||||
import com.zaneschepke.wireguardautotunnel.util.StringValue
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.asTunnelState
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.cancelWithMessage
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.toBackendError
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import timber.log.Timber
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
open class BaseTunnel(
|
||||
abstract class BaseTunnel(
|
||||
@IoDispatcher private val ioDispatcher: CoroutineDispatcher,
|
||||
@ApplicationScope private val applicationScope: CoroutineScope,
|
||||
private val networkMonitor: NetworkMonitor,
|
||||
@@ -43,145 +47,71 @@ open class BaseTunnel(
|
||||
private val notificationManager: NotificationManager,
|
||||
) : TunnelProvider {
|
||||
|
||||
internal val tunnels = MutableStateFlow<List<TunnelConf>>(emptyList())
|
||||
companion object {
|
||||
const val CHECK_INTERVAL = 1000L
|
||||
}
|
||||
|
||||
private val tunnelJobs = mutableMapOf<TunnelConf, Job>()
|
||||
protected val activeTuns = MutableStateFlow<Map<TunnelConf, TunnelState>>(emptyMap())
|
||||
override val activeTunnels = activeTuns.asStateFlow()
|
||||
|
||||
private val isNetworkAvailable = AtomicBoolean(false)
|
||||
private val tunnelJobs = ConcurrentHashMap<Int, MutableList<Job>>()
|
||||
|
||||
protected val mutex = Mutex()
|
||||
private val isNetworkConnected = MutableStateFlow(true)
|
||||
|
||||
init {
|
||||
applicationScope.launch(ioDispatcher) {
|
||||
launch {
|
||||
startNetworkJob()
|
||||
}
|
||||
tunnels.collect { tuns ->
|
||||
val previousTuns = tunnelJobs.keys.toSet()
|
||||
val newTuns = tuns - previousTuns
|
||||
val removedItems = previousTuns - tuns.toSet()
|
||||
|
||||
newTuns.forEach { tun ->
|
||||
Timber.d("Starting tunnel jobs for tun ${tun.name}")
|
||||
tunnelJobs[tun] = startTunnelJobs(tun)
|
||||
}
|
||||
|
||||
removedItems.forEach { tun ->
|
||||
tunnelJobs[tun]?.cancelWithMessage("Canceling tunnel jobs for tunnel: ${tun.name}")
|
||||
tunnelJobs.remove(tun)
|
||||
}
|
||||
serviceManager.updateTunnelTile()
|
||||
}
|
||||
launch { monitorNetworkStatus() }
|
||||
launch { monitorTunnelConfigChanges() }
|
||||
}
|
||||
}
|
||||
|
||||
private fun startTunnelJobs(tunnel: TunnelConf) = applicationScope.launch(ioDispatcher) {
|
||||
launch {
|
||||
startTunnelStatisticsJob(tunnel)
|
||||
}
|
||||
launch {
|
||||
startPingJob(tunnel)
|
||||
}
|
||||
launch {
|
||||
startTunnelConfigChangeJob(tunnel)
|
||||
private fun startTunnelJobs(tunnel: TunnelConf): Job {
|
||||
return applicationScope.launch(ioDispatcher) {
|
||||
val jobs = mutableListOf<Job>()
|
||||
jobs += launch { updateTunnelStatistics(tunnel) }
|
||||
if (tunnel.isPingEnabled) jobs += launch { monitorTunnelPing(tunnel) }
|
||||
jobs.forEach { it.join() }
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun bounceTunnel(tunnelConf: TunnelConf) {
|
||||
if (tunnels.value.any { it.id == tunnelConf.id }) {
|
||||
toggleTunnel(tunnelConf, TunnelStatus.DOWN)
|
||||
toggleTunnel(tunnelConf, TunnelStatus.UP)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun setBackendState(backendState: BackendState, allowedIps: Collection<String>) {
|
||||
}
|
||||
|
||||
override suspend fun runningTunnelNames(): Set<String> {
|
||||
return emptySet()
|
||||
}
|
||||
|
||||
override suspend fun activeTunnels(): StateFlow<List<TunnelConf>> {
|
||||
return tunnels.asStateFlow()
|
||||
}
|
||||
|
||||
override suspend fun startTunnel(tunnelConf: TunnelConf) {
|
||||
if (tunnels.value.any { it.id == tunnelConf.id }) return Timber.w("Tunnel already running")
|
||||
serviceManager.startBackgroundService(tunnelConf)
|
||||
appDataRepository.tunnels.save(tunnelConf.copy(isActive = true))
|
||||
}
|
||||
|
||||
override suspend fun stopTunnel(tunnelConf: TunnelConf?) {
|
||||
}
|
||||
|
||||
open suspend fun toggleTunnel(tunnelConf: TunnelConf, state: TunnelStatus) {
|
||||
}
|
||||
|
||||
open suspend fun getStatistics(tunnelConf: TunnelConf): TunnelStatistics {
|
||||
throw NotImplementedError("Get statistics not implemented in base class")
|
||||
}
|
||||
|
||||
internal suspend fun onTunnelStop(tunnelConf: TunnelConf) {
|
||||
appDataRepository.tunnels.save(tunnelConf.copy(isActive = false))
|
||||
removeFromActiveTunnels(tunnelConf)
|
||||
if (tunnels.value.isEmpty()) serviceManager.stopBackgroundService()
|
||||
}
|
||||
|
||||
internal suspend fun stopAllTunnels() {
|
||||
tunnels.value.forEach {
|
||||
stopTunnel(it)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun addToActiveTunnels(conf: TunnelConf) {
|
||||
tunnels.update {
|
||||
it.toMutableList().apply {
|
||||
add(conf)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun removeFromActiveTunnels(conf: TunnelConf) {
|
||||
tunnels.update {
|
||||
it.toMutableList().apply {
|
||||
remove(conf)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun startNetworkJob() = coroutineScope {
|
||||
networkMonitor.status.distinctUntilChanged().collect {
|
||||
isNetworkAvailable.set(!it.allOffline)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun startPingJob(tunnel: TunnelConf) = coroutineScope {
|
||||
while (isActive) {
|
||||
if (isNetworkAvailable.get() && tunnel.isActive) {
|
||||
val pingResult = tunnel.pingTunnel(ioDispatcher)
|
||||
handlePingResult(tunnel, pingResult)
|
||||
private suspend fun updateTunnelStatistics(tunnel: TunnelConf) {
|
||||
while (true) {
|
||||
runCatching {
|
||||
val stats = getStatistics(tunnel)
|
||||
updateTunnelState(tunnel, stats = stats)
|
||||
}.onFailure { e ->
|
||||
Timber.e(e, "Failed to update stats for ${tunnel.tunName}")
|
||||
}
|
||||
delay(CHECK_INTERVAL)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun handlePingResult(tunnel: TunnelConf, pingResult: List<Boolean>) {
|
||||
if (pingResult.contains(false)) {
|
||||
if (isNetworkAvailable.get()) {
|
||||
Timber.i("Ping result: target was not reachable, bouncing the tunnel")
|
||||
bounceTunnel(tunnel)
|
||||
delay(tunnel.pingCooldown ?: Constants.PING_COOLDOWN)
|
||||
} else {
|
||||
Timber.i("Ping result: target was not reachable, but no network available")
|
||||
private suspend fun monitorTunnelPing(tunnel: TunnelConf) {
|
||||
while (true) {
|
||||
runCatching {
|
||||
if (isNetworkConnected.value && tunnel.isActive) {
|
||||
val pingSuccess = tunnel.isTunnelPingable(ioDispatcher)
|
||||
if (!pingSuccess) bounceTunnel(tunnel)
|
||||
}
|
||||
}.onFailure { e ->
|
||||
Timber.e(e, "Ping failed for ${tunnel.tunName}")
|
||||
}
|
||||
} else {
|
||||
Timber.i("Ping result: all ping targets were reached successfully")
|
||||
delay(tunnel.pingInterval ?: Constants.PING_INTERVAL)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun handleBackendThrowable(backendError: BackendError) {
|
||||
protected fun handleBackendThrowable(throwable: Throwable) {
|
||||
val backendError = when (throwable) {
|
||||
is BackendException -> throwable.toBackendError()
|
||||
is org.amnezia.awg.backend.BackendException -> throwable.toBackendError()
|
||||
else -> BackendError.Unknown
|
||||
}
|
||||
val message = when (backendError) {
|
||||
BackendError.Config -> StringValue.StringResource(R.string.start_failed_config)
|
||||
BackendError.DNS -> StringValue.StringResource(R.string.dns_error)
|
||||
BackendError.Unauthorized -> StringValue.StringResource(R.string.unauthorized)
|
||||
BackendError.Unknown -> StringValue.StringResource(R.string.unknown_error)
|
||||
}
|
||||
if (WireGuardAutoTunnel.isForeground()) {
|
||||
SnackbarController.showMessage(message)
|
||||
@@ -197,23 +127,133 @@ open class BaseTunnel(
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun startTunnelConfigChangeJob(tunnel: TunnelConf) = coroutineScope {
|
||||
appDataRepository.tunnels.flow.collect { storageTuns ->
|
||||
storageTuns.firstOrNull { it.id == tunnel.id }?.let { storageTun ->
|
||||
if (tunnel.isQuickConfigChanged(storageTun) || tunnel.isPingConfigMatching(storageTun)) {
|
||||
bounceTunnel(tunnel)
|
||||
protected fun updateTunnelState(tunnelConf: TunnelConf, state: TunnelStatus? = null, stats: TunnelStatistics? = null) {
|
||||
applicationScope.launch(ioDispatcher) {
|
||||
mutex.withLock {
|
||||
activeTuns.update { current ->
|
||||
val originalConf = current.getKeyById(tunnelConf.id) ?: tunnelConf
|
||||
val existingState = current.getValueById(tunnelConf.id) ?: TunnelState()
|
||||
val newState = state ?: existingState.state
|
||||
if (newState == TunnelStatus.DOWN) {
|
||||
// Remove tunnel from activeTunnels when it goes DOWN
|
||||
Timber.d("Removing tunnel ${tunnelConf.id} from activeTunnels as state is DOWN")
|
||||
current - originalConf
|
||||
} else if (existingState.state == newState && stats == null) {
|
||||
Timber.d("Skipping redundant state update for ${tunnelConf.id}: $newState")
|
||||
current
|
||||
} else {
|
||||
val updated = existingState.copy(
|
||||
state = newState,
|
||||
statistics = stats ?: existingState.statistics,
|
||||
)
|
||||
current + (originalConf to updated)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun startTunnelStatisticsJob(tunnel: TunnelConf) = coroutineScope {
|
||||
while (isActive) {
|
||||
val stats = getStatistics(tunnel)
|
||||
tunnel.state.update {
|
||||
it.copy(statistics = stats)
|
||||
protected suspend fun configureTunnel(tunnelConf: TunnelConf) {
|
||||
// setup state change callback
|
||||
tunnelConf.setStateChangeCallback { state ->
|
||||
Timber.d("State change callback triggered for tunnel ${tunnelConf.id}: ${tunnelConf.tunName} with state $state at ${System.currentTimeMillis()}")
|
||||
when (state) {
|
||||
is Tunnel.State -> updateTunnelState(tunnelConf, state.asTunnelState())
|
||||
is org.amnezia.awg.backend.Tunnel.State -> updateTunnelState(tunnelConf, state.asTunnelState())
|
||||
}
|
||||
delay(CHECK_INTERVAL)
|
||||
applicationScope.launch(ioDispatcher) { serviceManager.updateTunnelTile() }
|
||||
}
|
||||
|
||||
activeTuns.update { current ->
|
||||
current.filter { it.key != tunnelConf } + (tunnelConf to TunnelState())
|
||||
}
|
||||
}
|
||||
|
||||
protected suspend fun onStartSuccess(tunnelConf: TunnelConf) {
|
||||
val tunnelCopy = tunnelConf.copyWithCallback(isActive = true)
|
||||
|
||||
// start service
|
||||
if (activeTuns.value.isEmpty()) {
|
||||
serviceManager.startTunnelForegroundService(tunnelCopy)
|
||||
} else {
|
||||
serviceManager.updateTunnelForegroundServiceNotification(tunnelCopy)
|
||||
}
|
||||
// save active
|
||||
appDataRepository.tunnels.save(tunnelCopy)
|
||||
// start tunnel jobs
|
||||
tunnelJobs[tunnelCopy.id] = mutableListOf(startTunnelJobs(tunnelConf))
|
||||
}
|
||||
|
||||
override fun startTunnel(tunnelConf: TunnelConf) {
|
||||
throw NotImplementedError("Must be implemented by subclass")
|
||||
}
|
||||
|
||||
override fun stopTunnel(tunnelConf: TunnelConf?) {
|
||||
tunnelConf?.let {
|
||||
applicationScope.launch(ioDispatcher) {
|
||||
mutex.withLock {
|
||||
removeActiveTunnel(tunnelConf)
|
||||
tunnelJobs[tunnelConf.id]?.forEach { it.cancelWithMessage("Cancel tunnel job") }
|
||||
tunnelJobs.remove(tunnelConf.id)
|
||||
val lockedConf = it.copyWithCallback(isActive = false)
|
||||
appDataRepository.tunnels.save(lockedConf)
|
||||
|
||||
// TODO improve to handle multiple tunnels
|
||||
if (activeTuns.value.isEmpty()) {
|
||||
Timber.d("No tunnels active, stopping background service")
|
||||
serviceManager.stopTunnelForegroundService()
|
||||
} else {
|
||||
Timber.d("Other tunnels still active, updating service notification")
|
||||
val nextActive = activeTuns.value.keys.firstOrNull()
|
||||
if (nextActive != null) {
|
||||
Timber.d("Next active tunnel: ${nextActive.id}")
|
||||
serviceManager.updateTunnelForegroundServiceNotification(nextActive)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun removeActiveTunnel(tunnelConf: TunnelConf) {
|
||||
activeTuns.update { current ->
|
||||
current.toMutableMap().apply { remove(tunnelConf) }
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun bounceTunnel(tunnelConf: TunnelConf) {
|
||||
stopTunnel(tunnelConf)
|
||||
delay(1000)
|
||||
startTunnel(tunnelConf)
|
||||
}
|
||||
|
||||
private suspend fun monitorNetworkStatus() {
|
||||
networkMonitor.networkStatusFlow
|
||||
.flowOn(ioDispatcher)
|
||||
.collectLatest { status ->
|
||||
val isAvailable = status !is NetworkStatus.Disconnected
|
||||
isNetworkConnected.value = isAvailable
|
||||
Timber.d("Network status: $isAvailable")
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun monitorTunnelConfigChanges() {
|
||||
appDataRepository.tunnels.flow.collectLatest { storedTunnels ->
|
||||
mutex.withLock {
|
||||
storedTunnels.forEach { stored ->
|
||||
val current = activeTuns.value.keys.find { it.id == stored.id }
|
||||
if (current != null && !current.isQuickConfigMatching(stored)) {
|
||||
Timber.d("Config changed for ${stored.id}, bouncing")
|
||||
bounceTunnel(stored)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getStatistics(tunnelConf: TunnelConf): TunnelStatistics? {
|
||||
throw NotImplementedError("Must be implemented by subclass")
|
||||
}
|
||||
|
||||
override suspend fun runningTunnelNames(): Set<String> = activeTuns.value.keys.map { it.tunName }.toSet()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.zaneschepke.wireguardautotunnel.core.tunnel
|
||||
|
||||
import com.zaneschepke.wireguardautotunnel.domain.entity.TunnelConf
|
||||
import com.zaneschepke.wireguardautotunnel.domain.state.TunnelState
|
||||
|
||||
fun Map<TunnelConf, TunnelState>.allDown(): Boolean {
|
||||
return this.all { it.value.state.isDown() }
|
||||
}
|
||||
|
||||
fun Map<TunnelConf, TunnelState>.hasActive(): Boolean {
|
||||
return this.any { it.value.state.isUp() }
|
||||
}
|
||||
|
||||
fun Map<TunnelConf, TunnelState>.getValueById(id: Int): TunnelState? {
|
||||
val key = this.keys.find { it.id == id }
|
||||
return key?.let { this@getValueById[it] }
|
||||
}
|
||||
|
||||
fun Map<TunnelConf, TunnelState>.getKeyById(id: Int): TunnelConf? {
|
||||
return this.keys.find { it.id == id }
|
||||
}
|
||||
|
||||
fun Map<TunnelConf, TunnelState>.isUp(tunnelConf: TunnelConf): Boolean {
|
||||
return this.getValueById(tunnelConf.id)?.state?.isUp() ?: false
|
||||
}
|
||||
@@ -1,9 +1,8 @@
|
||||
package com.zaneschepke.wireguardautotunnel.core.tunnel
|
||||
|
||||
import com.wireguard.android.backend.Backend
|
||||
import com.wireguard.android.backend.BackendException
|
||||
import com.wireguard.android.backend.Tunnel
|
||||
import com.zaneschepke.wireguardautotunnel.core.network.NetworkMonitor
|
||||
import com.zaneschepke.networkmonitor.NetworkMonitor
|
||||
import com.zaneschepke.wireguardautotunnel.core.notification.NotificationManager
|
||||
import com.zaneschepke.wireguardautotunnel.core.service.ServiceManager
|
||||
import com.zaneschepke.wireguardautotunnel.di.ApplicationScope
|
||||
@@ -14,10 +13,11 @@ import com.zaneschepke.wireguardautotunnel.domain.enums.TunnelStatus
|
||||
import com.zaneschepke.wireguardautotunnel.domain.repository.AppDataRepository
|
||||
import com.zaneschepke.wireguardautotunnel.domain.state.TunnelStatistics
|
||||
import com.zaneschepke.wireguardautotunnel.domain.state.WireGuardStatistics
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.toBackendError
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.asTunnelState
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
@@ -31,48 +31,65 @@ class KernelTunnel @Inject constructor(
|
||||
networkMonitor: NetworkMonitor,
|
||||
) : BaseTunnel(ioDispatcher, applicationScope, networkMonitor, appDataRepository, serviceManager, notificationManager) {
|
||||
|
||||
override suspend fun startTunnel(tunnelConf: TunnelConf) {
|
||||
withContext(ioDispatcher) {
|
||||
super.startTunnel(tunnelConf)
|
||||
override fun startTunnel(tunnelConf: TunnelConf) {
|
||||
Timber.i("Starting tunnel ${tunnelConf.id} kernel")
|
||||
applicationScope.launch(ioDispatcher) {
|
||||
runCatching {
|
||||
backend.setState(tunnelConf, Tunnel.State.UP, tunnelConf.toWgConfig())
|
||||
addToActiveTunnels(tunnelConf)
|
||||
}.onFailure {
|
||||
onTunnelStop(tunnelConf)
|
||||
if (it is BackendException) {
|
||||
handleBackendThrowable(it.toBackendError())
|
||||
} else {
|
||||
Timber.e(it)
|
||||
// tunnel already active
|
||||
if (activeTuns.value.any { it.key.id == tunnelConf.id }) return@launch
|
||||
|
||||
mutex.withLock {
|
||||
updateTunnelState(tunnelConf, TunnelStatus.STARTING)
|
||||
|
||||
// configure state callback and add to tunnels
|
||||
configureTunnel(tunnelConf)
|
||||
|
||||
updateTunnelState(tunnelConf, backend.setState(tunnelConf, Tunnel.State.UP, tunnelConf.toWgConfig()).asTunnelState())
|
||||
|
||||
// run some actions after start success
|
||||
onStartSuccess(tunnelConf)
|
||||
}
|
||||
}.onFailure { exception ->
|
||||
Timber.e(exception, "Failed to start tunnel ${tunnelConf.id} kernel")
|
||||
stopTunnel(tunnelConf)
|
||||
handleBackendThrowable(exception)
|
||||
}.onSuccess {
|
||||
Timber.i("Tunnel ${tunnelConf.id} started successfully")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getStatistics(tunnelConf: TunnelConf): TunnelStatistics {
|
||||
return WireGuardStatistics(backend.getStatistics(tunnelConf))
|
||||
override fun getStatistics(tunnelConf: TunnelConf): TunnelStatistics? {
|
||||
return try {
|
||||
WireGuardStatistics(backend.getStatistics(tunnelConf))
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun stopTunnel(tunnelConf: TunnelConf?) {
|
||||
withContext(ioDispatcher) {
|
||||
val tunnel = tunnels.value.firstOrNull { it.id == tunnelConf?.id }
|
||||
override fun stopTunnel(tunnelConf: TunnelConf?) {
|
||||
applicationScope.launch(ioDispatcher) {
|
||||
runCatching {
|
||||
tunnel?.let {
|
||||
backend.setState(it, Tunnel.State.DOWN, it.toWgConfig())
|
||||
onTunnelStop(it)
|
||||
} ?: stopAllTunnels()
|
||||
}.onFailure {
|
||||
Timber.e(it)
|
||||
val originalTunnel = activeTuns.value.keys.find { it.id == tunnelConf?.id }
|
||||
if (originalTunnel != null) {
|
||||
Timber.i("Stopping tunnel ${originalTunnel.id} kernel")
|
||||
mutex.withLock {
|
||||
updateTunnelState(originalTunnel, backend.setState(originalTunnel, Tunnel.State.DOWN, originalTunnel.toWgConfig()).asTunnelState())
|
||||
super.stopTunnel(originalTunnel)
|
||||
}
|
||||
} else {
|
||||
Timber.w("Tunnel not found in startedTunnels, stopping all tunnels")
|
||||
activeTuns.value.keys.forEach { config ->
|
||||
stopTunnel(config)
|
||||
}
|
||||
}
|
||||
}.onFailure { e ->
|
||||
Timber.e(e, "Failed to stop tunnel ${tunnelConf?.id}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun toggleTunnel(tunnelConf: TunnelConf, status: TunnelStatus) {
|
||||
when (status) {
|
||||
TunnelStatus.UP -> backend.setState(tunnelConf, Tunnel.State.UP, tunnelConf.toWgConfig())
|
||||
TunnelStatus.DOWN -> backend.setState(tunnelConf, Tunnel.State.DOWN, tunnelConf.toWgConfig())
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun setBackendState(backendState: BackendState, allowedIps: Collection<String>) {
|
||||
Timber.w("Not yet implemented for kernel")
|
||||
}
|
||||
|
||||
+51
-53
@@ -4,20 +4,20 @@ import com.zaneschepke.wireguardautotunnel.di.ApplicationScope
|
||||
import com.zaneschepke.wireguardautotunnel.di.IoDispatcher
|
||||
import com.zaneschepke.wireguardautotunnel.di.Kernel
|
||||
import com.zaneschepke.wireguardautotunnel.di.Userspace
|
||||
import com.zaneschepke.wireguardautotunnel.domain.entity.AppSettings
|
||||
import com.zaneschepke.wireguardautotunnel.domain.entity.TunnelConf
|
||||
import com.zaneschepke.wireguardautotunnel.domain.enums.BackendState
|
||||
import com.zaneschepke.wireguardautotunnel.domain.repository.AppDataRepository
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.withData
|
||||
import com.zaneschepke.wireguardautotunnel.domain.state.TunnelStatistics
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.filterNotNull
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.plus
|
||||
import kotlinx.coroutines.withContext
|
||||
import javax.inject.Inject
|
||||
|
||||
class TunnelManager @Inject constructor(
|
||||
@@ -28,71 +28,69 @@ class TunnelManager @Inject constructor(
|
||||
@IoDispatcher private val ioDispatcher: CoroutineDispatcher,
|
||||
) : TunnelProvider {
|
||||
|
||||
val appSettings: StateFlow<AppSettings?> = appDataRepository.settings.flow.stateIn(
|
||||
scope = applicationScope.plus(ioDispatcher),
|
||||
started = SharingStarted.Eagerly,
|
||||
initialValue = null,
|
||||
)
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
private val tunnelProviderFlow = appDataRepository.settings.flow
|
||||
.filterNotNull()
|
||||
.flatMapLatest { settings ->
|
||||
MutableStateFlow(if (settings.isKernelEnabled) kernelTunnel else userspaceTunnel)
|
||||
}
|
||||
.stateIn(
|
||||
scope = applicationScope.plus(ioDispatcher),
|
||||
started = SharingStarted.Eagerly,
|
||||
initialValue = userspaceTunnel,
|
||||
)
|
||||
|
||||
override suspend fun activeTunnels(): StateFlow<List<TunnelConf>> {
|
||||
return withContext(ioDispatcher) {
|
||||
appSettings.filterNotNull().first().let {
|
||||
if (it.isKernelEnabled) return@withContext kernelTunnel.activeTunnels()
|
||||
userspaceTunnel.activeTunnels()
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
override val activeTunnels = appDataRepository.settings.flow
|
||||
.filterNotNull()
|
||||
.flatMapLatest { settings ->
|
||||
if (settings.isKernelEnabled) {
|
||||
kernelTunnel.activeTunnels
|
||||
} else {
|
||||
userspaceTunnel.activeTunnels
|
||||
}
|
||||
}
|
||||
.stateIn(
|
||||
scope = applicationScope.plus(ioDispatcher),
|
||||
started = SharingStarted.Eagerly,
|
||||
initialValue = emptyMap(),
|
||||
)
|
||||
|
||||
override fun startTunnel(tunnelConf: TunnelConf) {
|
||||
tunnelProviderFlow.value.startTunnel(tunnelConf)
|
||||
}
|
||||
|
||||
override suspend fun startTunnel(tunnelConf: TunnelConf) {
|
||||
appSettings.withData {
|
||||
if (it.isKernelEnabled) return@withData kernelTunnel.startTunnel(tunnelConf)
|
||||
userspaceTunnel.startTunnel(tunnelConf)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun stopTunnel(tunnelConf: TunnelConf?) {
|
||||
appSettings.withData {
|
||||
if (it.isKernelEnabled) return@withData kernelTunnel.stopTunnel(tunnelConf)
|
||||
userspaceTunnel.stopTunnel(tunnelConf)
|
||||
}
|
||||
override fun stopTunnel(tunnelConf: TunnelConf?) {
|
||||
tunnelProviderFlow.value.stopTunnel(tunnelConf)
|
||||
}
|
||||
|
||||
override suspend fun bounceTunnel(tunnelConf: TunnelConf) {
|
||||
appSettings.withData {
|
||||
if (it.isKernelEnabled) return@withData kernelTunnel.stopTunnel(tunnelConf)
|
||||
userspaceTunnel.stopTunnel(tunnelConf)
|
||||
}
|
||||
tunnelProviderFlow.value.bounceTunnel(tunnelConf)
|
||||
}
|
||||
|
||||
override suspend fun setBackendState(backendState: BackendState, allowedIps: Collection<String>) {
|
||||
appSettings.withData {
|
||||
if (it.isKernelEnabled) return@withData kernelTunnel.setBackendState(backendState, allowedIps)
|
||||
userspaceTunnel.setBackendState(backendState, allowedIps)
|
||||
}
|
||||
tunnelProviderFlow.value.setBackendState(backendState, allowedIps)
|
||||
}
|
||||
|
||||
override suspend fun runningTunnelNames(): Set<String> {
|
||||
appSettings.filterNotNull().first().let {
|
||||
if (it.isKernelEnabled) return kernelTunnel.runningTunnelNames()
|
||||
return userspaceTunnel.runningTunnelNames()
|
||||
}
|
||||
return tunnelProviderFlow.value.runningTunnelNames()
|
||||
}
|
||||
|
||||
suspend fun restorePreviousState() {
|
||||
withContext(ioDispatcher) {
|
||||
with(appDataRepository.settings.get()) {
|
||||
if (isRestoreOnBootEnabled) {
|
||||
val previouslyActiveTuns = appDataRepository.tunnels.getActive()
|
||||
// handle kernel mode
|
||||
val tunsToStart = previouslyActiveTuns.filterNot { tun -> activeTunnels().value.any { tun.id == it.id } }
|
||||
if (isKernelEnabled) {
|
||||
return@withContext tunsToStart.forEach {
|
||||
startTunnel(it)
|
||||
}
|
||||
}
|
||||
// handle userspace
|
||||
if (activeTunnels().value.isEmpty()) tunsToStart.firstOrNull()?.let { startTunnel(it) }
|
||||
override fun getStatistics(tunnelConf: TunnelConf): TunnelStatistics? {
|
||||
return tunnelProviderFlow.value.getStatistics(tunnelConf)
|
||||
}
|
||||
|
||||
fun restorePreviousState() = applicationScope.launch(ioDispatcher) {
|
||||
val settings = appDataRepository.settings.get()
|
||||
if (settings.isRestoreOnBootEnabled) {
|
||||
val previouslyActiveTuns = appDataRepository.tunnels.getActive()
|
||||
val tunsToStart = previouslyActiveTuns.filterNot { tun -> activeTunnels.value.any { tun.id == it.key.id } }
|
||||
if (settings.isKernelEnabled) {
|
||||
return@launch tunsToStart.forEach {
|
||||
startTunnel(it)
|
||||
}
|
||||
} else {
|
||||
tunsToStart.firstOrNull()?.let { startTunnel(it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+6
-13
@@ -2,23 +2,16 @@ package com.zaneschepke.wireguardautotunnel.core.tunnel
|
||||
|
||||
import com.zaneschepke.wireguardautotunnel.domain.entity.TunnelConf
|
||||
import com.zaneschepke.wireguardautotunnel.domain.enums.BackendState
|
||||
import com.zaneschepke.wireguardautotunnel.domain.state.TunnelState
|
||||
import com.zaneschepke.wireguardautotunnel.domain.state.TunnelStatistics
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
interface TunnelProvider {
|
||||
|
||||
suspend fun activeTunnels(): StateFlow<List<TunnelConf>>
|
||||
|
||||
suspend fun startTunnel(tunnelConf: TunnelConf)
|
||||
|
||||
suspend fun stopTunnel(tunnelConf: TunnelConf? = null)
|
||||
|
||||
fun startTunnel(tunnelConf: TunnelConf)
|
||||
fun stopTunnel(tunnelConf: TunnelConf? = null)
|
||||
suspend fun bounceTunnel(tunnelConf: TunnelConf)
|
||||
|
||||
suspend fun setBackendState(backendState: BackendState, allowedIps: Collection<String>)
|
||||
|
||||
suspend fun runningTunnelNames(): Set<String>
|
||||
|
||||
companion object {
|
||||
const val CHECK_INTERVAL = 1_000L
|
||||
}
|
||||
fun getStatistics(tunnelConf: TunnelConf): TunnelStatistics?
|
||||
val activeTunnels: StateFlow<Map<TunnelConf, TunnelState>>
|
||||
}
|
||||
|
||||
+64
-32
@@ -1,7 +1,6 @@
|
||||
package com.zaneschepke.wireguardautotunnel.core.tunnel
|
||||
|
||||
import com.wireguard.android.backend.BackendException
|
||||
import com.zaneschepke.wireguardautotunnel.core.network.NetworkMonitor
|
||||
import com.zaneschepke.networkmonitor.NetworkMonitor
|
||||
import com.zaneschepke.wireguardautotunnel.core.notification.NotificationManager
|
||||
import com.zaneschepke.wireguardautotunnel.core.service.ServiceManager
|
||||
import com.zaneschepke.wireguardautotunnel.di.ApplicationScope
|
||||
@@ -13,10 +12,12 @@ import com.zaneschepke.wireguardautotunnel.domain.repository.AppDataRepository
|
||||
import com.zaneschepke.wireguardautotunnel.domain.state.AmneziaStatistics
|
||||
import com.zaneschepke.wireguardautotunnel.domain.state.TunnelStatistics
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.asAmBackendState
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.toBackendError
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.asTunnelState
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import org.amnezia.awg.backend.Backend
|
||||
import org.amnezia.awg.backend.Tunnel
|
||||
import timber.log.Timber
|
||||
@@ -30,54 +31,85 @@ class UserspaceTunnel @Inject constructor(
|
||||
notificationManager: NotificationManager,
|
||||
private val backend: Backend,
|
||||
networkMonitor: NetworkMonitor,
|
||||
) : TunnelProvider, BaseTunnel(ioDispatcher, applicationScope, networkMonitor, appDataRepository, serviceManager, notificationManager) {
|
||||
) : BaseTunnel(ioDispatcher, applicationScope, networkMonitor, appDataRepository, serviceManager, notificationManager) {
|
||||
|
||||
override suspend fun startTunnel(tunnelConf: TunnelConf) {
|
||||
withContext(ioDispatcher) {
|
||||
super.startTunnel(tunnelConf)
|
||||
override fun startTunnel(tunnelConf: TunnelConf) {
|
||||
Timber.i("Starting tunnel ${tunnelConf.id} userspace")
|
||||
applicationScope.launch(ioDispatcher) {
|
||||
runCatching {
|
||||
backend.setState(tunnelConf, Tunnel.State.UP, tunnelConf.toAmConfig())
|
||||
addToActiveTunnels(tunnelConf)
|
||||
}.onFailure {
|
||||
onTunnelStop(tunnelConf)
|
||||
if (it is BackendException) {
|
||||
handleBackendThrowable(it.toBackendError())
|
||||
} else {
|
||||
Timber.e(it)
|
||||
// tunnel already active
|
||||
if (activeTuns.value.any { it.key.id == tunnelConf.id }) return@launch
|
||||
|
||||
// stop any active tunnels that aren't this one, userspace only
|
||||
stopActiveTunnels()
|
||||
|
||||
mutex.withLock {
|
||||
updateTunnelState(tunnelConf, TunnelStatus.STARTING)
|
||||
|
||||
// configure state callback and add to tunnels
|
||||
configureTunnel(tunnelConf)
|
||||
|
||||
updateTunnelState(tunnelConf, backend.setState(tunnelConf, Tunnel.State.UP, tunnelConf.toAmConfig()).asTunnelState())
|
||||
|
||||
// run some actions after start success
|
||||
onStartSuccess(tunnelConf)
|
||||
}
|
||||
}.onFailure { exception ->
|
||||
Timber.e(exception, "Failed to start tunnel ${tunnelConf.id} userspace")
|
||||
stopTunnel(tunnelConf)
|
||||
handleBackendThrowable(exception)
|
||||
}.onSuccess {
|
||||
Timber.i("Tunnel ${tunnelConf.id} started successfully")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getStatistics(tunnelConf: TunnelConf): TunnelStatistics {
|
||||
return AmneziaStatistics(backend.getStatistics(tunnelConf))
|
||||
}
|
||||
|
||||
override suspend fun toggleTunnel(tunnelConf: TunnelConf, status: TunnelStatus) {
|
||||
when (status) {
|
||||
TunnelStatus.UP -> backend.setState(tunnelConf, Tunnel.State.UP, tunnelConf.toAmConfig())
|
||||
TunnelStatus.DOWN -> backend.setState(tunnelConf, Tunnel.State.DOWN, tunnelConf.toAmConfig())
|
||||
private suspend fun stopActiveTunnels() {
|
||||
activeTunnels.value.forEach { (config, state) ->
|
||||
if (state.state.isUp()) {
|
||||
stopTunnel(config)
|
||||
delay(300)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun stopTunnel(tunnelConf: TunnelConf?) {
|
||||
withContext(ioDispatcher) {
|
||||
override fun stopTunnel(tunnelConf: TunnelConf?) {
|
||||
applicationScope.launch(ioDispatcher) {
|
||||
runCatching {
|
||||
tunnels.value.firstOrNull { it.id == tunnelConf?.id }?.let {
|
||||
backend.setState(it, Tunnel.State.DOWN, it.toAmConfig())
|
||||
onTunnelStop(it)
|
||||
} ?: stopAllTunnels()
|
||||
}.onFailure {
|
||||
Timber.e(it)
|
||||
val originalTunnel = activeTuns.value.keys.find { it.id == tunnelConf?.id }
|
||||
if (originalTunnel != null) {
|
||||
Timber.i("Stopping tunnel ${originalTunnel.id} userspace")
|
||||
mutex.withLock {
|
||||
updateTunnelState(originalTunnel, backend.setState(originalTunnel, Tunnel.State.DOWN, originalTunnel.toAmConfig()).asTunnelState())
|
||||
super.stopTunnel(originalTunnel)
|
||||
}
|
||||
} else {
|
||||
Timber.w("Tunnel not found in startedTunnels, stopping all tunnels")
|
||||
activeTuns.value.keys.forEach { config ->
|
||||
stopTunnel(config)
|
||||
}
|
||||
}
|
||||
}.onFailure { e ->
|
||||
Timber.e(e, "Failed to stop tunnel ${tunnelConf?.id}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun setBackendState(backendState: BackendState, allowedIps: Collection<String>) {
|
||||
Timber.d("Setting backend state: $backendState with allowedIps: $allowedIps")
|
||||
backend.setBackendState(backendState.asAmBackendState(), allowedIps)
|
||||
}
|
||||
|
||||
override suspend fun runningTunnelNames(): Set<String> {
|
||||
return backend.runningTunnelNames
|
||||
}
|
||||
|
||||
override fun getStatistics(tunnelConf: TunnelConf): TunnelStatistics? {
|
||||
return try {
|
||||
AmneziaStatistics(backend.getStatistics(tunnelConf))
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "Failed to get stats for ${tunnelConf.tunName}")
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ class ServiceWorker @AssistedInject constructor(
|
||||
Timber.i("Service worker started")
|
||||
with(appDataRepository.settings.get()) {
|
||||
if (isAutoTunnelEnabled && !serviceManager.autoTunnelActive.value) return@with serviceManager.startAutoTunnel(true)
|
||||
if (tunnelManager.activeTunnels().value.isEmpty()) tunnelManager.restorePreviousState()
|
||||
if (tunnelManager.activeTunnels.value.isEmpty()) tunnelManager.restorePreviousState()
|
||||
}
|
||||
Result.success()
|
||||
}
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
package com.zaneschepke.wireguardautotunnel.di
|
||||
|
||||
import com.zaneschepke.wireguardautotunnel.core.network.InternetConnectivityMonitor
|
||||
import com.zaneschepke.wireguardautotunnel.core.network.NetworkMonitor
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
abstract class ServiceModule {
|
||||
|
||||
@Binds
|
||||
abstract fun provideInternetConnectivityService(wifiService: InternetConnectivityMonitor): NetworkMonitor
|
||||
}
|
||||
@@ -4,7 +4,8 @@ import android.content.Context
|
||||
import com.wireguard.android.backend.WgQuickBackend
|
||||
import com.wireguard.android.util.RootShell
|
||||
import com.wireguard.android.util.ToolsInstaller
|
||||
import com.zaneschepke.wireguardautotunnel.core.network.NetworkMonitor
|
||||
import com.zaneschepke.networkmonitor.AndroidNetworkMonitor
|
||||
import com.zaneschepke.networkmonitor.NetworkMonitor
|
||||
import com.zaneschepke.wireguardautotunnel.core.notification.NotificationManager
|
||||
import com.zaneschepke.wireguardautotunnel.core.service.ServiceManager
|
||||
import com.zaneschepke.wireguardautotunnel.core.tunnel.KernelTunnel
|
||||
@@ -12,6 +13,7 @@ import com.zaneschepke.wireguardautotunnel.core.tunnel.TunnelManager
|
||||
import com.zaneschepke.wireguardautotunnel.core.tunnel.TunnelProvider
|
||||
import com.zaneschepke.wireguardautotunnel.core.tunnel.UserspaceTunnel
|
||||
import com.zaneschepke.wireguardautotunnel.domain.repository.AppDataRepository
|
||||
import com.zaneschepke.wireguardautotunnel.domain.repository.AppSettingRepository
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
@@ -19,6 +21,7 @@ import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.amnezia.awg.backend.Backend
|
||||
import org.amnezia.awg.backend.GoBackend
|
||||
import org.amnezia.awg.backend.RootTunnelActionHandler
|
||||
@@ -98,13 +101,20 @@ class TunnelModule {
|
||||
return TunnelManager(kernelTunnel, userspaceTunnel, appDataRepository, applicationScope, ioDispatcher)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideNetworkMonitor(@ApplicationContext context: Context, settingsRepository: AppSettingRepository): NetworkMonitor {
|
||||
return AndroidNetworkMonitor(context) { runBlocking { settingsRepository.get().isWifiNameByShellEnabled } }
|
||||
}
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
fun provideServiceManager(
|
||||
@ApplicationContext context: Context,
|
||||
@IoDispatcher ioDispatcher: CoroutineDispatcher,
|
||||
@ApplicationScope applicationScope: CoroutineScope,
|
||||
appDataRepository: AppDataRepository,
|
||||
): ServiceManager {
|
||||
return ServiceManager(context, ioDispatcher, appDataRepository)
|
||||
return ServiceManager(context, ioDispatcher, applicationScope, appDataRepository)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
package com.zaneschepke.wireguardautotunnel.domain.entity
|
||||
|
||||
import com.wireguard.android.backend.Tunnel
|
||||
import com.wireguard.config.Config
|
||||
import com.zaneschepke.wireguardautotunnel.domain.state.TunnelState
|
||||
import com.zaneschepke.wireguardautotunnel.util.Constants
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.asTunnelState
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.isReachable
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.toWgQuickString
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.amnezia.awg.backend.Tunnel
|
||||
import timber.log.Timber
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import java.io.InputStream
|
||||
import java.net.InetAddress
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
import kotlinx.coroutines.withContext
|
||||
import timber.log.Timber
|
||||
import java.nio.charset.StandardCharsets
|
||||
|
||||
data class TunnelConf(
|
||||
val id: Int = 0,
|
||||
@@ -29,10 +29,43 @@ data class TunnelConf(
|
||||
val pingCooldown: Long? = null,
|
||||
val pingIp: String? = null,
|
||||
val isEthernetTunnel: Boolean = false,
|
||||
val isIpv4Preferred: Boolean = false,
|
||||
) : Tunnel, com.wireguard.android.backend.Tunnel {
|
||||
val isIpv4Preferred: Boolean = true,
|
||||
@Transient
|
||||
private var stateChangeCallback: ((Any) -> Unit)? = null,
|
||||
) : Tunnel, org.amnezia.awg.backend.Tunnel {
|
||||
|
||||
val state = MutableStateFlow(TunnelState())
|
||||
private val callbackMutex = Mutex()
|
||||
|
||||
suspend fun setStateChangeCallback(callback: (Any) -> Unit) {
|
||||
callbackMutex.withLock {
|
||||
stateChangeCallback = callback
|
||||
}
|
||||
}
|
||||
|
||||
fun copyWithCallback(
|
||||
id: Int = this.id,
|
||||
tunName: String = this.tunName,
|
||||
wgQuick: String = this.wgQuick,
|
||||
tunnelNetworks: List<String> = this.tunnelNetworks,
|
||||
isMobileDataTunnel: Boolean = this.isMobileDataTunnel,
|
||||
isPrimaryTunnel: Boolean = this.isPrimaryTunnel,
|
||||
amQuick: String = this.amQuick,
|
||||
isActive: Boolean = this.isActive,
|
||||
isPingEnabled: Boolean = this.isPingEnabled,
|
||||
pingInterval: Long? = this.pingInterval,
|
||||
pingCooldown: Long? = this.pingCooldown,
|
||||
pingIp: String? = this.pingIp,
|
||||
isEthernetTunnel: Boolean = this.isEthernetTunnel,
|
||||
isIpv4Preferred: Boolean = this.isIpv4Preferred,
|
||||
): TunnelConf {
|
||||
return TunnelConf(
|
||||
id, tunName, wgQuick, tunnelNetworks, isMobileDataTunnel, isPrimaryTunnel,
|
||||
amQuick, isActive, isPingEnabled, pingInterval, pingCooldown, pingIp,
|
||||
isEthernetTunnel, isIpv4Preferred,
|
||||
).apply {
|
||||
stateChangeCallback = this@TunnelConf.stateChangeCallback
|
||||
}
|
||||
}
|
||||
|
||||
fun toAmConfig(): org.amnezia.awg.config.Config {
|
||||
return configFromAmQuick(amQuick.ifBlank { wgQuick })
|
||||
@@ -42,29 +75,40 @@ data class TunnelConf(
|
||||
return configFromWgQuick(wgQuick)
|
||||
}
|
||||
|
||||
override fun getName(): String {
|
||||
return tunName
|
||||
}
|
||||
override fun getName(): String = tunName
|
||||
|
||||
override fun isIpv4ResolutionPreferred(): Boolean {
|
||||
return isIpv4Preferred
|
||||
override fun isIpv4ResolutionPreferred(): Boolean = isIpv4Preferred
|
||||
|
||||
override fun onStateChange(newState: org.amnezia.awg.backend.Tunnel.State) {
|
||||
Timber.d("onStateChange called for tunnel $id: $tunName with state $newState")
|
||||
runBlocking {
|
||||
callbackMutex.withLock {
|
||||
if (stateChangeCallback != null) {
|
||||
Timber.d("Invoking stateChangeCallback for tunnel $id: $tunName with state $newState")
|
||||
stateChangeCallback?.invoke(newState)
|
||||
} else {
|
||||
Timber.w("No stateChangeCallback set for tunnel $id: $tunName")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStateChange(newState: Tunnel.State) {
|
||||
state.update {
|
||||
it.copy(state = newState.asTunnelState())
|
||||
Timber.d("onStateChange called for tunnel $id: $tunName with state $newState")
|
||||
runBlocking {
|
||||
callbackMutex.withLock {
|
||||
if (stateChangeCallback != null) {
|
||||
Timber.d("Invoking stateChangeCallback for tunnel $id: $tunName with state $newState")
|
||||
stateChangeCallback?.invoke(newState)
|
||||
} else {
|
||||
Timber.w("No stateChangeCallback set for tunnel $id: $tunName")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStateChange(newState: com.wireguard.android.backend.Tunnel.State) {
|
||||
state.update {
|
||||
it.copy(state = newState.asTunnelState())
|
||||
}
|
||||
}
|
||||
|
||||
fun isQuickConfigChanged(updatedConf: TunnelConf): Boolean {
|
||||
return updatedConf.wgQuick != wgQuick ||
|
||||
updatedConf.amQuick != amQuick
|
||||
fun isQuickConfigMatching(updatedConf: TunnelConf): Boolean {
|
||||
return updatedConf.wgQuick == wgQuick || updatedConf.amQuick == amQuick
|
||||
}
|
||||
|
||||
fun isPingConfigMatching(updatedConf: TunnelConf): Boolean {
|
||||
@@ -74,32 +118,30 @@ data class TunnelConf(
|
||||
updatedConf.pingInterval == pingInterval
|
||||
}
|
||||
|
||||
suspend fun pingTunnel(context: CoroutineContext): List<Boolean> {
|
||||
suspend fun isTunnelPingable(context: CoroutineContext): Boolean {
|
||||
return withContext(context) {
|
||||
val config = toWgConfig()
|
||||
if (pingIp != null) {
|
||||
Timber.i("Pinging custom ip")
|
||||
listOf(InetAddress.getByName(pingIp).isReachable(Constants.PING_TIMEOUT.toInt()))
|
||||
} else {
|
||||
Timber.i("Pinging all peers")
|
||||
config.peers.map { peer ->
|
||||
peer.isReachable(isIpv4Preferred)
|
||||
}
|
||||
return@withContext InetAddress.getByName(pingIp)
|
||||
.isReachable(Constants.PING_TIMEOUT.toInt())
|
||||
}
|
||||
config.peers.map { peer ->
|
||||
peer.isReachable(isIpv4Preferred)
|
||||
}.all { true }
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun configFromWgQuick(wgQuick: String): Config {
|
||||
val inputStream: InputStream = wgQuick.byteInputStream()
|
||||
return inputStream.bufferedReader(Charsets.UTF_8).use {
|
||||
return inputStream.bufferedReader(StandardCharsets.UTF_8).use {
|
||||
Config.parse(it)
|
||||
}
|
||||
}
|
||||
|
||||
fun configFromAmQuick(amQuick: String): org.amnezia.awg.config.Config {
|
||||
val inputStream: InputStream = amQuick.byteInputStream()
|
||||
return inputStream.bufferedReader(Charsets.UTF_8).use {
|
||||
return inputStream.bufferedReader(StandardCharsets.UTF_8).use {
|
||||
org.amnezia.awg.config.Config.parse(it)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,4 +4,5 @@ sealed class BackendError() {
|
||||
data object DNS : BackendError()
|
||||
data object Unauthorized : BackendError()
|
||||
data object Config : BackendError()
|
||||
data object Unknown : BackendError()
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@ package com.zaneschepke.wireguardautotunnel.domain.enums
|
||||
enum class TunnelStatus {
|
||||
UP,
|
||||
DOWN,
|
||||
STARTING,
|
||||
STOPPING,
|
||||
;
|
||||
|
||||
fun isDown(): Boolean {
|
||||
|
||||
+16
-13
@@ -1,5 +1,8 @@
|
||||
package com.zaneschepke.wireguardautotunnel.domain.state
|
||||
|
||||
import com.zaneschepke.wireguardautotunnel.core.tunnel.allDown
|
||||
import com.zaneschepke.wireguardautotunnel.core.tunnel.hasActive
|
||||
import com.zaneschepke.wireguardautotunnel.core.tunnel.isUp
|
||||
import com.zaneschepke.wireguardautotunnel.domain.events.KillSwitchEvent
|
||||
import com.zaneschepke.wireguardautotunnel.domain.entity.AppSettings
|
||||
import com.zaneschepke.wireguardautotunnel.domain.entity.TunnelConf
|
||||
@@ -7,7 +10,7 @@ import com.zaneschepke.wireguardautotunnel.domain.events.AutoTunnelEvent
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.isMatchingToWildcardList
|
||||
|
||||
data class AutoTunnelState(
|
||||
val activeTunnels: List<TunnelConf> = emptyList(),
|
||||
val activeTunnels: Map<TunnelConf, TunnelState> = emptyMap(),
|
||||
val networkState: NetworkState = NetworkState(),
|
||||
val settings: AppSettings = AppSettings(),
|
||||
val tunnels: List<TunnelConf> = emptyList(),
|
||||
@@ -20,12 +23,12 @@ data class AutoTunnelState(
|
||||
private fun isMobileTunnelDataChangeNeeded(): Boolean {
|
||||
val preferredTunnel = preferredMobileDataTunnel()
|
||||
return preferredTunnel != null &&
|
||||
activeTunnels.isNotEmpty() && !activeTunnels.any { it.id == preferredTunnel.id }
|
||||
activeTunnels.isNotEmpty() && !activeTunnels.isUp(preferredTunnel)
|
||||
}
|
||||
|
||||
private fun isEthernetTunnelChangeNeeded(): Boolean {
|
||||
val preferredTunnel = preferredEthernetTunnel()
|
||||
return preferredTunnel != null && activeTunnels.isNotEmpty() && !activeTunnels.any { it.id == preferredTunnel.id }
|
||||
return preferredTunnel != null && activeTunnels.isNotEmpty() && !activeTunnels.isUp(preferredTunnel)
|
||||
}
|
||||
|
||||
private fun preferredMobileDataTunnel(): TunnelConf? {
|
||||
@@ -45,11 +48,11 @@ data class AutoTunnelState(
|
||||
}
|
||||
|
||||
private fun startOnEthernet(): Boolean {
|
||||
return networkState.isEthernetConnected && settings.isTunnelOnEthernetEnabled && activeTunnels.isEmpty()
|
||||
return networkState.isEthernetConnected && settings.isTunnelOnEthernetEnabled && activeTunnels.allDown()
|
||||
}
|
||||
|
||||
private fun stopOnEthernet(): Boolean {
|
||||
return networkState.isEthernetConnected && !settings.isTunnelOnEthernetEnabled && activeTunnels.isNotEmpty()
|
||||
return networkState.isEthernetConnected && !settings.isTunnelOnEthernetEnabled && activeTunnels.hasActive()
|
||||
}
|
||||
|
||||
// TODO test removed kill switch state check
|
||||
@@ -62,16 +65,16 @@ data class AutoTunnelState(
|
||||
return settings.isVpnKillSwitchEnabled && (!settings.isDisableKillSwitchOnTrustedEnabled || !isCurrentSSIDTrusted())
|
||||
}
|
||||
|
||||
fun isNoConnectivity(): Boolean {
|
||||
private fun isNoConnectivity(): Boolean {
|
||||
return !networkState.isEthernetConnected && !networkState.isWifiConnected && !networkState.isMobileDataConnected
|
||||
}
|
||||
|
||||
private fun stopOnMobileData(): Boolean {
|
||||
return isMobileDataActive() && !settings.isTunnelOnMobileDataEnabled && activeTunnels.isNotEmpty()
|
||||
return isMobileDataActive() && !settings.isTunnelOnMobileDataEnabled && activeTunnels.hasActive()
|
||||
}
|
||||
|
||||
private fun startOnMobileData(): Boolean {
|
||||
return isMobileDataActive() && settings.isTunnelOnMobileDataEnabled && activeTunnels.isEmpty()
|
||||
return isMobileDataActive() && settings.isTunnelOnMobileDataEnabled && activeTunnels.allDown()
|
||||
}
|
||||
|
||||
private fun changeOnMobileData(): Boolean {
|
||||
@@ -83,24 +86,24 @@ data class AutoTunnelState(
|
||||
}
|
||||
|
||||
private fun stopOnWifi(): Boolean {
|
||||
return isWifiActive() && !settings.isTunnelOnWifiEnabled && activeTunnels.isNotEmpty()
|
||||
return isWifiActive() && !settings.isTunnelOnWifiEnabled && activeTunnels.hasActive()
|
||||
}
|
||||
|
||||
private fun stopOnTrustedWifi(): Boolean {
|
||||
return isWifiActive() && settings.isTunnelOnWifiEnabled && activeTunnels.isNotEmpty() && isCurrentSSIDTrusted()
|
||||
return isWifiActive() && settings.isTunnelOnWifiEnabled && activeTunnels.hasActive() && isCurrentSSIDTrusted()
|
||||
}
|
||||
|
||||
private fun startOnUntrustedWifi(): Boolean {
|
||||
return isWifiActive() && settings.isTunnelOnWifiEnabled && activeTunnels.isEmpty() && !isCurrentSSIDTrusted()
|
||||
return isWifiActive() && settings.isTunnelOnWifiEnabled && activeTunnels.allDown() && !isCurrentSSIDTrusted()
|
||||
}
|
||||
|
||||
private fun changeOnUntrustedWifi(): Boolean {
|
||||
return isWifiActive() && settings.isTunnelOnWifiEnabled && activeTunnels.isNotEmpty() && !isCurrentSSIDTrusted() && !isWifiTunnelPreferred()
|
||||
return isWifiActive() && settings.isTunnelOnWifiEnabled && activeTunnels.hasActive() && !isCurrentSSIDTrusted() && !isWifiTunnelPreferred()
|
||||
}
|
||||
|
||||
private fun isWifiTunnelPreferred(): Boolean {
|
||||
val preferred = preferredWifiTunnel()
|
||||
return activeTunnels.any { it.id == preferred?.id }
|
||||
return preferred?.let { activeTunnels.isUp(it) } ?: true
|
||||
}
|
||||
|
||||
fun asAutoTunnelEvent(): AutoTunnelEvent {
|
||||
|
||||
+1
-1
@@ -690,7 +690,7 @@ fun ConfigScreen(tunnelConf: TunnelConf?, appViewModel: AppViewModel) {
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
val presharedKeyEnabled = (tunnelConf == null) || isAuthenticated ||
|
||||
with(configPair.second?.peers[index]?.preSharedKey) { this?.isEmpty == true || this?.isPresent == false }
|
||||
with(configPair.second?.peers?.getOrNull(index)?.preSharedKey) { this?.isEmpty == true || this?.isPresent == false }
|
||||
OutlinedTextField(
|
||||
textStyle = MaterialTheme.typography.labelLarge,
|
||||
modifier =
|
||||
|
||||
+8
-13
@@ -38,6 +38,7 @@ import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.zaneschepke.wireguardautotunnel.R
|
||||
import com.zaneschepke.wireguardautotunnel.core.tunnel.getValueById
|
||||
import com.zaneschepke.wireguardautotunnel.domain.entity.TunnelConf
|
||||
import com.zaneschepke.wireguardautotunnel.domain.state.TunnelState
|
||||
import com.zaneschepke.wireguardautotunnel.ui.state.AppUiState
|
||||
@@ -77,7 +78,7 @@ fun MainScreen(viewModel: MainViewModel = hiltViewModel(), uiState: AppUiState)
|
||||
var selectedTunnel by remember { mutableStateOf<TunnelConf?>(null) }
|
||||
val isRunningOnTv = remember { context.isRunningOnTv() }
|
||||
|
||||
val activeTunnels by viewModel.activeTunnels.collectAsStateWithLifecycle(emptyList())
|
||||
val activeTunnels by viewModel.tunnelManager.activeTunnels.collectAsStateWithLifecycle(emptyMap())
|
||||
|
||||
val collator = Collator.getInstance(Locale.getDefault())
|
||||
|
||||
@@ -89,6 +90,7 @@ fun MainScreen(viewModel: MainViewModel = hiltViewModel(), uiState: AppUiState)
|
||||
val startTunnel = withVpnPermission<TunnelConf> {
|
||||
viewModel.onTunnelStart(it)
|
||||
}
|
||||
|
||||
val autoTunnelToggleBattery = withIgnoreBatteryOpt(uiState.generalState.isBatteryOptimizationDisableShown) {
|
||||
if (!uiState.generalState.isBatteryOptimizationDisableShown) viewModel.setBatteryOptimizeDisableShown()
|
||||
if (uiState.appSettings.isKernelEnabled) {
|
||||
@@ -132,15 +134,8 @@ fun MainScreen(viewModel: MainViewModel = hiltViewModel(), uiState: AppUiState)
|
||||
}
|
||||
|
||||
fun onTunnelToggle(checked: Boolean, tunnel: TunnelConf) {
|
||||
if (!checked) {
|
||||
viewModel.onTunnelStop(tunnel)
|
||||
return
|
||||
}
|
||||
if (uiState.appSettings.isKernelEnabled) {
|
||||
viewModel.onTunnelStart(tunnel)
|
||||
} else {
|
||||
startTunnel.invoke(tunnel)
|
||||
}
|
||||
if (!checked) return viewModel.onTunnelStop(tunnel).let { }
|
||||
if (uiState.appSettings.isKernelEnabled) viewModel.onTunnelStart(tunnel) else startTunnel(tunnel)
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
@@ -233,13 +228,13 @@ fun MainScreen(viewModel: MainViewModel = hiltViewModel(), uiState: AppUiState)
|
||||
key = { tunnel -> tunnel.id },
|
||||
) { tunnel ->
|
||||
val expanded = uiState.generalState.isTunnelStatsExpanded
|
||||
val tunnelState = activeTunnels.firstOrNull { it.id == tunnel.id }?.state?.collectAsStateWithLifecycle()
|
||||
val tunnelState = activeTunnels.getValueById(tunnel.id) ?: TunnelState()
|
||||
TunnelRowItem(
|
||||
tunnel.isActive,
|
||||
tunnelState.state.isUp(),
|
||||
expanded,
|
||||
selectedTunnel?.id == tunnel.id,
|
||||
tunnel,
|
||||
tunnelState = tunnelState?.value ?: TunnelState(),
|
||||
tunnelState = tunnelState,
|
||||
{ selectedTunnel = tunnel },
|
||||
{ viewModel.onExpandedChanged(!expanded) },
|
||||
onDelete = { showDeleteTunnelAlertDialog = true },
|
||||
|
||||
+4
-1
@@ -32,6 +32,7 @@ import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import com.zaneschepke.wireguardautotunnel.R
|
||||
import com.zaneschepke.wireguardautotunnel.core.tunnel.isUp
|
||||
import com.zaneschepke.wireguardautotunnel.domain.entity.TunnelConf
|
||||
import com.zaneschepke.wireguardautotunnel.ui.Route
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.ScaledSwitch
|
||||
@@ -41,6 +42,7 @@ import com.zaneschepke.wireguardautotunnel.ui.common.config.SubmitConfigurationT
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.navigation.LocalNavController
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.navigation.TopNavBar
|
||||
import com.zaneschepke.wireguardautotunnel.ui.screens.settings.components.ForwardButton
|
||||
import com.zaneschepke.wireguardautotunnel.ui.state.AppUiState
|
||||
import com.zaneschepke.wireguardautotunnel.util.Constants
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.isValidIpv4orIpv6Address
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.scaledHeight
|
||||
@@ -51,7 +53,7 @@ import kotlin.text.isNullOrBlank
|
||||
import kotlin.text.toLong
|
||||
|
||||
@Composable
|
||||
fun OptionsScreen(tunnelConf: TunnelConf, viewModel: TunnelOptionsViewModel = hiltViewModel()) {
|
||||
fun OptionsScreen(tunnelConf: TunnelConf, appUiState: AppUiState, viewModel: TunnelOptionsViewModel = hiltViewModel()) {
|
||||
val navController = LocalNavController.current
|
||||
|
||||
var currentText by remember { mutableStateOf("") }
|
||||
@@ -194,6 +196,7 @@ fun OptionsScreen(tunnelConf: TunnelConf, viewModel: TunnelOptionsViewModel = hi
|
||||
trailing = {
|
||||
ScaledSwitch(
|
||||
checked = tunnelConf.isPingEnabled,
|
||||
enabled = !appUiState.activeTunnels.isUp(tunnelConf),
|
||||
onClick = { onPingToggle() },
|
||||
)
|
||||
},
|
||||
|
||||
+5
-5
@@ -352,11 +352,11 @@ fun SettingsScreen(viewModel: SettingsViewModel = hiltViewModel(), appViewModel:
|
||||
ScaledSwitch(
|
||||
uiState.appSettings.isKernelEnabled,
|
||||
onClick = { appViewModel.onToggleKernelMode() },
|
||||
// enabled = !(
|
||||
// uiState.settings.isAutoTunnelEnabled ||
|
||||
// uiState.settings.isAlwaysOnVpnEnabled ||
|
||||
// (uiState.vpnState.status == TunnelState.UP)
|
||||
// ),
|
||||
enabled = !(
|
||||
uiState.appSettings.isAutoTunnelEnabled ||
|
||||
uiState.appSettings.isAlwaysOnVpnEnabled ||
|
||||
uiState.activeTunnels.isNotEmpty()
|
||||
),
|
||||
)
|
||||
},
|
||||
onClick = {
|
||||
|
||||
+2
-19
@@ -18,8 +18,6 @@ import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
@@ -36,7 +34,6 @@ import com.zaneschepke.wireguardautotunnel.ui.Route
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.ScaledSwitch
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SurfaceSelectionGroupButton
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.dialog.InfoDialog
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.label.GroupLabel
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.label.VersionLabel
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.navigation.LocalNavController
|
||||
@@ -51,20 +48,6 @@ import com.zaneschepke.wireguardautotunnel.util.extensions.scaledWidth
|
||||
fun SupportScreen(appUiState: AppUiState, appViewModel: AppViewModel) {
|
||||
val context = LocalContext.current
|
||||
val navController = LocalNavController.current
|
||||
|
||||
var showDialog by remember { mutableStateOf(false) }
|
||||
|
||||
if (showDialog) {
|
||||
InfoDialog(onAttest = {
|
||||
showDialog = false
|
||||
appViewModel.onToggleLocalLogging()
|
||||
}, onDismiss = {
|
||||
showDialog = false
|
||||
}, title = {
|
||||
Text(stringResource(R.string.configuration_change))
|
||||
}, body = { Text(stringResource(R.string.requires_app_relaunch)) }, confirmText = { Text(stringResource(R.string.yes)) })
|
||||
}
|
||||
|
||||
Column(
|
||||
horizontalAlignment = Alignment.Start,
|
||||
verticalArrangement = Arrangement.spacedBy(24.dp.scaledHeight(), Alignment.Top),
|
||||
@@ -115,12 +98,12 @@ fun SupportScreen(appUiState: AppUiState, appViewModel: AppViewModel) {
|
||||
ScaledSwitch(
|
||||
appUiState.generalState.isLocalLogsEnabled,
|
||||
onClick = {
|
||||
showDialog = true
|
||||
appViewModel.onToggleLocalLogging()
|
||||
},
|
||||
)
|
||||
},
|
||||
onClick = {
|
||||
showDialog = true
|
||||
appViewModel.onToggleLocalLogging()
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
@@ -3,10 +3,12 @@ package com.zaneschepke.wireguardautotunnel.ui.state
|
||||
import com.zaneschepke.wireguardautotunnel.data.model.GeneralState
|
||||
import com.zaneschepke.wireguardautotunnel.domain.entity.AppSettings
|
||||
import com.zaneschepke.wireguardautotunnel.domain.entity.TunnelConf
|
||||
import com.zaneschepke.wireguardautotunnel.domain.state.TunnelState
|
||||
|
||||
data class AppUiState(
|
||||
val appSettings: AppSettings = AppSettings(),
|
||||
val tunnels: List<TunnelConf> = emptyList(),
|
||||
val activeTunnels: Map<TunnelConf, TunnelState> = emptyMap(),
|
||||
val generalState: GeneralState = GeneralState(),
|
||||
val autoTunnelActive: Boolean = false,
|
||||
)
|
||||
|
||||
+2
-9
@@ -2,7 +2,6 @@ package com.zaneschepke.wireguardautotunnel.util.extensions
|
||||
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import com.wireguard.android.backend.BackendException
|
||||
import com.wireguard.android.util.RootShell
|
||||
import com.wireguard.config.Peer
|
||||
import com.zaneschepke.wireguardautotunnel.domain.enums.BackendError
|
||||
import com.zaneschepke.wireguardautotunnel.domain.enums.BackendState
|
||||
@@ -46,9 +45,9 @@ fun TunnelStatistics.PeerStats.handshakeStatus(): HandshakeStatus {
|
||||
fun Peer.isReachable(preferIpv4: Boolean): Boolean {
|
||||
val host =
|
||||
if (this.endpoint.isPresent &&
|
||||
this.endpoint.get().getResolved(preferIpv4).isPresent
|
||||
this.endpoint.get().resolved.isPresent
|
||||
) {
|
||||
this.endpoint.get().getResolved(preferIpv4).get().host
|
||||
this.endpoint.get().resolved.get().host
|
||||
} else {
|
||||
Constants.DEFAULT_PING_IP
|
||||
}
|
||||
@@ -87,12 +86,6 @@ fun Config.toWgQuickString(): String {
|
||||
return lines.joinToString(System.lineSeparator())
|
||||
}
|
||||
|
||||
fun RootShell.getCurrentWifiName(): String? {
|
||||
val response = mutableListOf<String>()
|
||||
this.run(response, "dumpsys wifi | grep 'Supplicant state: COMPLETED' | grep -o 'SSID: [^,]*' | cut -d ' ' -f2- | tr -d '\"'")
|
||||
return response.firstOrNull()
|
||||
}
|
||||
|
||||
fun Backend.BackendState.asBackendState(): BackendState {
|
||||
return BackendState.valueOf(this.name)
|
||||
}
|
||||
|
||||
@@ -8,12 +8,6 @@ import com.zaneschepke.wireguardautotunnel.WireGuardAutoTunnel
|
||||
import com.zaneschepke.wireguardautotunnel.ui.Route
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.navigation.isCurrentRoute
|
||||
|
||||
fun NavController.navigateAndForget(route: Route) {
|
||||
navigate(route) {
|
||||
popUpTo(0)
|
||||
}
|
||||
}
|
||||
|
||||
fun NavController.goFromRoot(route: Route) {
|
||||
if (currentBackStackEntry?.isCurrentRoute(route::class) == true) return
|
||||
this.navigate(route) {
|
||||
|
||||
@@ -31,11 +31,9 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.asSharedFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.onCompletion
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.flow.takeWhile
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.plus
|
||||
@@ -75,11 +73,13 @@ constructor(
|
||||
appDataRepository.settings.flow,
|
||||
appDataRepository.tunnels.flow,
|
||||
appDataRepository.appState.flow,
|
||||
tunnelManager.activeTunnels,
|
||||
serviceManager.autoTunnelActive,
|
||||
) { settings, tunnels, generalState, autoTunnel ->
|
||||
) { settings, tunnels, generalState, activeTunnels, autoTunnel ->
|
||||
AppUiState(
|
||||
settings,
|
||||
tunnels,
|
||||
activeTunnels,
|
||||
generalState,
|
||||
autoTunnel,
|
||||
)
|
||||
@@ -103,14 +103,13 @@ constructor(
|
||||
|
||||
private suspend fun appReadyCheck() {
|
||||
val tunnelCount = appDataRepository.tunnels.count()
|
||||
uiState.takeWhile { it.tunnels.size != tunnelCount }.onCompletion {
|
||||
_isAppReady.emit(true)
|
||||
}.collect()
|
||||
uiState.first { it.tunnels.count() == tunnelCount }
|
||||
_isAppReady.emit(true)
|
||||
}
|
||||
|
||||
private suspend fun initTunnels() {
|
||||
tunnels.withData {
|
||||
it.filter { it.isActive }.forEach {
|
||||
tunnels.withData { tunnels ->
|
||||
tunnels.filter { it.isActive }.forEach {
|
||||
tunnelManager.startTunnel(it)
|
||||
}
|
||||
}
|
||||
@@ -146,17 +145,14 @@ constructor(
|
||||
with(uiState.value.generalState) {
|
||||
val toggledOn = !isLocalLogsEnabled
|
||||
appDataRepository.appState.setLocalLogsEnabled(toggledOn)
|
||||
if (!toggledOn) onLoggerStop()
|
||||
_configurationChange.update {
|
||||
true
|
||||
if (!toggledOn) {
|
||||
logReader.stop()
|
||||
} else {
|
||||
logReader.start()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun onLoggerStop() {
|
||||
logReader.deleteAndClearLogs()
|
||||
}
|
||||
|
||||
fun onToggleAlwaysOnVPN() = viewModelScope.launch {
|
||||
with(uiState.value.appSettings) {
|
||||
appDataRepository.settings.save(
|
||||
@@ -281,13 +277,13 @@ constructor(
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun requestRoot(): Result<Unit> {
|
||||
private suspend fun requestRoot(): Result<Unit> {
|
||||
return withContext(ioDispatcher) {
|
||||
runCatching {
|
||||
rootShell.get().start()
|
||||
SnackbarController.Companion.showMessage(StringValue.StringResource(R.string.root_accepted))
|
||||
SnackbarController.showMessage(StringValue.StringResource(R.string.root_accepted))
|
||||
}.onFailure {
|
||||
SnackbarController.Companion.showMessage(StringValue.StringResource(R.string.error_root_denied))
|
||||
SnackbarController.showMessage(StringValue.StringResource(R.string.error_root_denied))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -357,7 +353,7 @@ constructor(
|
||||
runCatching {
|
||||
val amConfig = tunnelConfig.toAmConfig()
|
||||
val wgConfig = tunnelConfig.toWgConfig()
|
||||
val proxy = InterfaceProxy.Companion.from(amConfig.`interface`)
|
||||
val proxy = InterfaceProxy.from(amConfig.`interface`)
|
||||
if (proxy.includedApplications.isEmpty() && proxy.excludedApplications.isEmpty()) return@launch
|
||||
if (proxy.includedApplications.retainAll(packages.toSet()) || proxy.excludedApplications.retainAll(packages.toSet())) {
|
||||
updateTunnelConfig(tunnelConfig, amConfig = amConfig, wgConfig = wgConfig, `interface` = proxy)
|
||||
|
||||
@@ -24,8 +24,6 @@ import com.zaneschepke.wireguardautotunnel.util.extensions.toWgQuickString
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.withData
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.amnezia.awg.config.Config
|
||||
@@ -44,15 +42,6 @@ constructor(
|
||||
appDataRepository: AppDataRepository,
|
||||
) : BaseViewModel(appDataRepository) {
|
||||
|
||||
private val _activeTunnels = MutableStateFlow<List<TunnelConf>>(emptyList())
|
||||
val activeTunnels = _activeTunnels.asStateFlow()
|
||||
|
||||
init {
|
||||
viewModelScope.launch {
|
||||
tunnelManager.activeTunnels().collect(_activeTunnels::emit)
|
||||
}
|
||||
}
|
||||
|
||||
fun onDelete(tunnel: TunnelConf) = viewModelScope.launch {
|
||||
appSettings.withData { settings ->
|
||||
tunnels.withData {
|
||||
|
||||
@@ -162,7 +162,7 @@
|
||||
<string name="monitoring_state_changes">Überwache Statusänderungen</string>
|
||||
<string name="stop_on_no_internet">Stoppen wenn keine Internetverbindung besteht</string>
|
||||
<string name="ethernet_tunnel">Ethernet Tunnel</string>
|
||||
<string name="set_ethernet_tunnel">Als Ethernet Tunnel setzten</string>
|
||||
<string name="set_ethernet_tunnel">Als Ethernet Tunnel setzen</string>
|
||||
<string name="native_kill_switch">Nativer Notschalter</string>
|
||||
<string name="vpn_kill_switch">VPN Notschalter</string>
|
||||
<string name="kill_switch_options">Notschalteroptionen</string>
|
||||
@@ -192,4 +192,17 @@
|
||||
<string name="hide_amnezia_properties">Amnezia Eigenschaften verbergen</string>
|
||||
<string name="include_lan">LAN einschließen</string>
|
||||
<string name="exclude_lan">LAN ausschließen</string>
|
||||
</resources>
|
||||
<string name="tunnel_control">Tunnelsteuerung</string>
|
||||
<string name="kill_switch_off">Notschalter stoppen bei vertrauenswürdigen</string>
|
||||
<string name="error_tunnel_start">Start des Tunnels fehlgeschlagen</string>
|
||||
<string name="auto_tunnel">Auto-Tunnel</string>
|
||||
<string name="export_amnezia">Als Amnezia exportieren</string>
|
||||
<string name="export_wireguard">Als WireGuard exportieren</string>
|
||||
<string name="server_ipv4">IPv4 Hostnamensauflösung</string>
|
||||
<string name="prefer_ipv4">IPv4 Verbindung bevorzugen</string>
|
||||
<string name="dns_error">Fehler bei Endpunkt DNS Auflösung.</string>
|
||||
<string name="start_failed_config">Starten des Tunnels wegen Konfigfehler fehlgeschlagen.</string>
|
||||
<string name="unauthorized">Starten des Tunnels fehlgeschlagen, unberechtigt.</string>
|
||||
<string name="tunne_start_failed_title">Tunnelfehler</string>
|
||||
<string name="multiple">Mehrere</string>
|
||||
</resources>
|
||||
|
||||
@@ -192,4 +192,17 @@
|
||||
<string name="wg_compat_mode">Modo compatibilidad de WG</string>
|
||||
<string name="quick_actions">Acciones rápidas</string>
|
||||
<string name="remove_amnezia_compatibility">Eliminar compatibilidad con Amnezia</string>
|
||||
</resources>
|
||||
<string name="error_tunnel_start">Fallo al iniciar el túnel</string>
|
||||
<string name="tunnel_control">Control del túnel</string>
|
||||
<string name="auto_tunnel">Túnel automático</string>
|
||||
<string name="kill_switch_off">Detener interruptor de apagado en confianza</string>
|
||||
<string name="server_ipv4">Resolución de host IPv4</string>
|
||||
<string name="prefer_ipv4">Preferir conexión IPv4</string>
|
||||
<string name="dns_error">No se ha podido resolver el DNS del punto final.</string>
|
||||
<string name="start_failed_config">Fallo al iniciar túnel con error de configuración.</string>
|
||||
<string name="unauthorized">Fallo al iniciar túnel, no autorizado.</string>
|
||||
<string name="tunne_start_failed_title">Fallo del túnel</string>
|
||||
<string name="multiple">Múltiple</string>
|
||||
<string name="export_amnezia">Exportar cómo Amnezia</string>
|
||||
<string name="export_wireguard">Exportar cómo WireGuard</string>
|
||||
</resources>
|
||||
|
||||
@@ -13,8 +13,8 @@
|
||||
<string name="error_no_file_explorer">Nessun esploratore di file installato</string>
|
||||
<string name="error_invalid_code">QR code non valido</string>
|
||||
<string name="app_name">Tunnel WG</string>
|
||||
<string name="vpn_channel_name">Canale di notifica VPN</string>
|
||||
<string name="turn_off_tunnel">L\'operaz. richiede la disatt. del tunnel</string>
|
||||
<string name="vpn_channel_name">Canale di notifica VPN</string>
|
||||
<string name="turn_off_tunnel">L\'operaz. richiede la disatt. del tunnel</string>
|
||||
<string name="public_key">Chiave pubblica</string>
|
||||
<string name="addresses">Indirizzi</string>
|
||||
<string name="dns_servers">Server DNS</string>
|
||||
@@ -41,7 +41,7 @@
|
||||
<string name="always_on_vpn_support">Permetti VPN sempre attiva</string>
|
||||
<string name="location_services_not_detected">Servizi di localizzazione non rilevati</string>
|
||||
<string name="hint_search_packages">Cerca pacchetti</string>
|
||||
<string name="auto_tunneling">Tunnel automatico</string>
|
||||
<string name="auto_tunneling">Tunnel automatico</string>
|
||||
<string name="vpn_on">VPN on</string>
|
||||
<string name="vpn_off">VPN off</string>
|
||||
<string name="create_import">Crea da zero</string>
|
||||
@@ -56,4 +56,73 @@
|
||||
<string name="use_kernel">Usa modulo kernel</string>
|
||||
<string name="error_ssid_exists">L\'SSID esiste già</string>
|
||||
<string name="error_root_denied">Shell di root negata</string>
|
||||
<string name="prominent_background_location_title">Permesso localizzazione in background</string>
|
||||
<string name="location_services_missing_message">Questa app non rileva nessun servizio di localizzazione attiva sul tuo dispositivo. Dipendentemente dal dispositivo, questo potrebbe causare il fallimento a leggere il nome wifi da parte della funzione di wifi non fidate. Vuoi continuare comunque?</string>
|
||||
<string name="read_logs">Leggi i log</string>
|
||||
<string name="auto">(auto)</string>
|
||||
<string name="underload_packet_magic_header">Magic header pacchetto sottocarico</string>
|
||||
<string name="unsure_how">se non sei sicuro di come procedere</string>
|
||||
<string name="vpn_settings">Impostazioni sistema VPN</string>
|
||||
<string name="always_on_message">Permessi connessione VPN negati. Verifica la</string>
|
||||
<string name="chat_description">Unisciti alla community</string>
|
||||
<string name="junk_packet_maximum_size">Dimensione massima pacchetti indesiderati</string>
|
||||
<string name="delete_tunnel">Cancella tunnel</string>
|
||||
<string name="init_packet_junk_size">Inizializza la dimensione dei pacchetti indesiderati</string>
|
||||
<string name="always_on_message2">per essere sicuro che la VPN Always-on sia spenta per tutte le altre app e riprova</string>
|
||||
<string name="background_location_message">Permetti i permessi per la localizzazione durante tutto il tempo e/o la localizzazione precisa è richiesta per questa funzione. Vedi la</string>
|
||||
<string name="config_changes_saved">Salvate modifiche configurazione.</string>
|
||||
<string name="delete_tunnel_message">Sei certo di voler cancellare questo tunnel?</string>
|
||||
<string name="thank_you">Grazie per usare WG Tunnel!</string>
|
||||
<string name="trusted_ssid_value_description">Invia SSID</string>
|
||||
<string name="exclude">Escludi</string>
|
||||
<string name="include">Includi</string>
|
||||
<string name="yes">Si</string>
|
||||
<string name="all">tutte</string>
|
||||
<string name="no_email_detected">Nessuna app email rilevata</string>
|
||||
<string name="no_browser_detected">Nessun browser rilevato</string>
|
||||
<string name="incorrect_pin">Il PIN non è corretto</string>
|
||||
<string name="pin_created">PIN correttamente creato</string>
|
||||
<string name="enable_app_lock">Abilita blocco app</string>
|
||||
<string name="restart_on_ping">Riavvia su fallimento ping (beta)</string>
|
||||
<string name="mobile_data_tunnel">Imposta come tunnel dati mobili</string>
|
||||
<string name="set_primary_tunnel">Imposta come tunnel principale</string>
|
||||
<string name="use_tunnel_on_wifi_name">Usa tunnel su nome wifi</string>
|
||||
<string name="edit_tunnel">Modifica tunnel</string>
|
||||
<string name="version">Versione</string>
|
||||
<string name="settings">Impostazioni</string>
|
||||
<string name="support">Supporto</string>
|
||||
<string name="kernel">Kernel</string>
|
||||
<string name="junk_packet_count">Numero pacchetti indesiderati</string>
|
||||
<string name="junk_packet_minimum_size">Dimensione minima pacchetti indesiderati</string>
|
||||
<string name="response_packet_junk_size">Dimensione pacchetto indesiderato risposta</string>
|
||||
<string name="init_packet_magic_header">Magic header pacchetto inizializzazione</string>
|
||||
<string name="response_packet_magic_header">Magic header pacchetto risposta</string>
|
||||
<string name="getting_started_guide">guida di avvio rapido</string>
|
||||
<string name="see_the">Vedi la</string>
|
||||
<string name="error_file_format">Formato configurazione tunnel non valido</string>
|
||||
<string name="restart_at_boot">Riavvia al boot</string>
|
||||
<string name="vpn_denied_dialog_title">Permesso Negato</string>
|
||||
<string name="tunnel_required">Questa funzione richiede almeno un tunnel</string>
|
||||
<string name="app_settings">impostazioni app</string>
|
||||
<string name="background_location_message2">per assicurarti che questi permessi siano abilitati</string>
|
||||
<string name="root_accepted">Accesso alla shell root accettata</string>
|
||||
<string name="set_custom_ping_ip">Imposta ip ping personalizzato</string>
|
||||
<string name="default_ping_ip">(opzionale, default ai peers)</string>
|
||||
<string name="set_custom_ping_internal">Intervallo ping (sec)</string>
|
||||
<string name="optional_default">"opzionale, default: "</string>
|
||||
<string name="set_custom_ping_cooldown">Tempo attesa prima della ripartenza ping (sec)</string>
|
||||
<string name="show_amnezia_properties">Mostra proprietà Amnezia</string>
|
||||
<string name="add_tunnels_text">Aggiungi da file o zip</string>
|
||||
<string name="open_file">Apri file</string>
|
||||
<string name="add_from_qr">Aggiungi da codice QR</string>
|
||||
<string name="qr_scan">Scansione QR</string>
|
||||
<string name="tunnel_name">Nome Tunnel</string>
|
||||
<string name="tunneling_apps">App nel tunnel</string>
|
||||
<string name="open_issue">Apri un problema</string>
|
||||
<string name="enter_pin">Inserisci il tuo PIN</string>
|
||||
<string name="create_pin">Crea PIN</string>
|
||||
<string name="transport_packet_magic_header">Magic header pacchetto trasporto</string>
|
||||
<string name="kill_switch_off">Ferma interruttore di spegnimento su fidate</string>
|
||||
<string name="prominent_background_location_message">Questa caratteristica richiede il permesso di localizzazione in background per abilitare il monitoraggio dell\'SSID Wi-fi anche quando l\'applicazione è chiusa. Per più dettagli, verifica la Privacy Policy linkata nella schermata di supporto.</string>
|
||||
<string name="auto_tunnel_title">Servizio auto-tunnel</string>
|
||||
</resources>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<string name="tunnels">Tunele</string>
|
||||
<string name="app_name">WG Tunnel</string>
|
||||
<string name="unsure_how">jeśli nie masz pewności, jak postępować</string>
|
||||
<string name="getting_started_guide">przewodnik wprowadzający</string>
|
||||
<string name="getting_started_guide">przewodnik wprowadzający,</string>
|
||||
<string name="peer">Peer</string>
|
||||
<string name="background_location_message2">w celu upewnienia się, że uprawnienia te są włączone</string>
|
||||
<string name="rotate_keys">Rotuj klucze</string>
|
||||
@@ -196,4 +196,14 @@
|
||||
<string name="error_tunnel_start">Nie udało się uruchomić tunelu</string>
|
||||
<string name="tunnel_control">Kontrola tunelu</string>
|
||||
<string name="auto_tunnel">Autotunel</string>
|
||||
<string name="kill_switch_off">Zatrzymaj wyłącznik awaryjny w zaufanej</string>
|
||||
<string name="server_ipv4">Rozpoznawanie nazw hostów IPv6</string>
|
||||
<string name="prefer_ipv4">Preferuj połączenie IPv4</string>
|
||||
<string name="unauthorized">Nie udało się uruchomić tunelu, brak autoryzacji.</string>
|
||||
<string name="multiple">Wiele</string>
|
||||
<string name="start_failed_config">Nie udało się uruchomić tunelu z powodu błędu konfiguracji.</string>
|
||||
<string name="tunne_start_failed_title">Awaria tunelu</string>
|
||||
<string name="dns_error">Nie udało się rozpoznać punktu końcowego DNS.</string>
|
||||
<string name="export_amnezia">Eksportuj jako Amnezia</string>
|
||||
<string name="export_wireguard">Eksportuj jako WireGuard</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,2 +1,177 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
<resources>
|
||||
<string name="tunnel_on_ethernet">Túnel na ethernet</string>
|
||||
<string name="public_key">Chave pública</string>
|
||||
<string name="addresses">Endereços</string>
|
||||
<string name="dns_servers">Servidores DNS</string>
|
||||
<string name="endpoint">Endpoint</string>
|
||||
<string name="name">Nome</string>
|
||||
<string name="create_import">Criar do zero</string>
|
||||
<string name="rotate_keys">Revezar chaves</string>
|
||||
<string name="private_key">Chave privada</string>
|
||||
<string name="base64_key">Chave base64</string>
|
||||
<string name="optional_no_recommend">(opcional, não recomendado)</string>
|
||||
<string name="preshared_key">Chave pré-partilhada</string>
|
||||
<string name="seconds">segundos</string>
|
||||
<string name="export_configs">Exportar configurações</string>
|
||||
<string name="error_no_file_explorer">Nenhum explorador de ficheiros instalado</string>
|
||||
<string name="error_invalid_code">Código QR inválido</string>
|
||||
<string name="auto_tunnel_title">Serviço de Auto-túnel</string>
|
||||
<string name="all">todos</string>
|
||||
<string name="enter_pin">Digite o seu pin</string>
|
||||
<string name="use_tunnel_on_wifi_name">Usar túnel em wifi com nome</string>
|
||||
<string name="version">Versão</string>
|
||||
<string name="junk_packet_count">Quantidade de pacotes-lixo</string>
|
||||
<string name="junk_packet_minimum_size">Tamanho mínimo de pacote-lixo</string>
|
||||
<string name="junk_packet_maximum_size">Tamanho máximo de pacote-lixo</string>
|
||||
<string name="init_packet_junk_size">Tamanho de pacote-lixo inicial</string>
|
||||
<string name="response_packet_junk_size">Tamanho de resposta de pacote-lixo</string>
|
||||
<string name="app_name">WG Tunnel</string>
|
||||
<string name="no_tunnels">Nenhum túnel foi adicionado!</string>
|
||||
<string name="error_file_extension">O ficheiro não é .conf ou .zip</string>
|
||||
<string name="prominent_background_location_message">Este recurso precisa de permissões de localização em segundo plano para ativar o monitoramento do SSID da rede Wi-Fi mesmo quando a aplicação está fechado. Para mais pormenores, por favor veja a Política de Privacidade no ecrã de Suporte.</string>
|
||||
<string name="turn_off_tunnel">Esta ação só é possível com o túnel inativo</string>
|
||||
<string name="enabled_app_shortcuts">Ativar atalhos de aplicações</string>
|
||||
<string name="tunnels">Túneis</string>
|
||||
<string name="privacy_policy">Ver a Política de Privacidade</string>
|
||||
<string name="okay">OK</string>
|
||||
<string name="tunnel_mobile_data">Túnel em dados móveis</string>
|
||||
<string name="prominent_background_location_title">Revelar a localização em segundo plano</string>
|
||||
<string name="thank_you">Obrigado por usar o WG Tunnel!</string>
|
||||
<string name="trusted_ssid_value_description">Envie o SSID</string>
|
||||
<string name="open_file">Abrir Ficheiro</string>
|
||||
<string name="add_from_qr">Adicionar a partir de código QR</string>
|
||||
<string name="add_tunnels_text">Adicionar a partir de ficheiro ou zip</string>
|
||||
<string name="qr_scan">Escanear o código QR</string>
|
||||
<string name="tunnel_name">Nome do Túnel</string>
|
||||
<string name="config_changes_saved">Mudanças nas configurações gravadas.</string>
|
||||
<string name="exclude">Excluir</string>
|
||||
<string name="include">Incluir</string>
|
||||
<string name="mtu">MTU</string>
|
||||
<string name="always_on_vpn_support">Permitir VPN sempre ligada</string>
|
||||
<string name="allowed_ips">IPs Permitidos</string>
|
||||
<string name="peer">Par</string>
|
||||
<string name="location_services_not_detected">Serviço de localização não foi detetado</string>
|
||||
<string name="hint_search_packages">Procurar pacotes</string>
|
||||
<string name="auto_tunneling">Auto-túnel</string>
|
||||
<string name="vpn_on">VPN ligada</string>
|
||||
<string name="vpn_off">VPN desligada</string>
|
||||
<string name="listen_port">Porta de escuta</string>
|
||||
<string name="turn_on_tunnel">Esta ação precisa um túnel ativo</string>
|
||||
<string name="add_peer">Adicionar par</string>
|
||||
<string name="interface_">Interface</string>
|
||||
<string name="copy_public_key">Copiar chave pública</string>
|
||||
<string name="comma_separated_list">Lista separada por vírgulas</string>
|
||||
<string name="optional">(opcional)</string>
|
||||
<string name="random">(aleatório)</string>
|
||||
<string name="persistent_keepalive">Manter a conexão persistente (keepalive)</string>
|
||||
<string name="cancel">Cancelar</string>
|
||||
<string name="error_authentication_failed">Autenticação falhou</string>
|
||||
<string name="error_authorization_failed">Autorização falhou</string>
|
||||
<string name="restart_on_ping">Reiniciar em falha de ping (beta)</string>
|
||||
<string name="email_description">Me envie um email</string>
|
||||
<string name="error_ssid_exists">SSID já existe</string>
|
||||
<string name="delete_tunnel_message">Tem certeza que quer apagar este túnel?</string>
|
||||
<string name="yes">Sim</string>
|
||||
<string name="unknown_error">Ocorreu um erro desconhecido</string>
|
||||
<string name="email_subject">Apoio para o WG Tunnel</string>
|
||||
<string name="tunnel_on_wifi">Túnel em Wi-Fi não confiável</string>
|
||||
<string name="delete_tunnel">Apagar túnel</string>
|
||||
<string name="email_chooser">Enviar um email…</string>
|
||||
<string name="use_kernel">Usar o módulo do kernel</string>
|
||||
<string name="docs_description">Ler a documentação</string>
|
||||
<string name="error_root_denied">Shell Root negado</string>
|
||||
<string name="location_services_missing_message">A aplicação não detetou o serviço de localização ativado no seu dispositivo. Dependendo do dispositivo, isto pode causar que a função de Wi-Fi não confiável falhe em ler o nome do Wi-Fi. Quer continuar mesmo assim?</string>
|
||||
<string name="open_issue">Abrir um problema</string>
|
||||
<string name="tunneling_apps">Aplicações com túnel</string>
|
||||
<string name="no_email_detected">Nenhuma aplicação de email detetado</string>
|
||||
<string name="no_browser_detected">Nenhum navegador detetado</string>
|
||||
<string name="incorrect_pin">O Pin está errado</string>
|
||||
<string name="auto">(automático)</string>
|
||||
<string name="read_logs">Ler os registos</string>
|
||||
<string name="pin_created">Pin criado com sucesso</string>
|
||||
<string name="create_pin">Criar um pin</string>
|
||||
<string name="enable_app_lock">Ligar bloqueio de aplicação</string>
|
||||
<string name="edit_tunnel">Editar túnel</string>
|
||||
<string name="mobile_data_tunnel">Selecionar como túnel em dados móveis</string>
|
||||
<string name="set_primary_tunnel">Selecionar como túnel principal</string>
|
||||
<string name="support">Suporte</string>
|
||||
<string name="kernel">Kernel</string>
|
||||
<string name="settings">Configurações</string>
|
||||
<string name="unsure_how">se não tiver certeza em como continuar</string>
|
||||
<string name="see_the">Veja o</string>
|
||||
<string name="getting_started_guide">guia de início rápido</string>
|
||||
<string name="error_file_format">Formato de configuração inválido</string>
|
||||
<string name="vpn_channel_name">Canal de notificações VPN</string>
|
||||
<string name="set_custom_ping_ip">Definir ip ping personalizado</string>
|
||||
<string name="vpn_denied_dialog_title">Permissão negada</string>
|
||||
<string name="vpn_settings">Configurações do sistema VPN</string>
|
||||
<string name="always_on_message">A permissão de conexão VPN foi negada. Por favor, verifique</string>
|
||||
<string name="chat_description">Junte-se à comunidade</string>
|
||||
<string name="tunnel_required">Característica requer pelo menos um túnel</string>
|
||||
<string name="app_settings">configurações da app</string>
|
||||
<string name="background_location_message2">para garantir que essas permissões estejam ativadas.</string>
|
||||
<string name="root_accepted">Shell root aceito</string>
|
||||
<string name="optional_default">"opcional, padrão: "</string>
|
||||
<string name="show_amnezia_properties">Mostrar propriedades de Amnezia</string>
|
||||
<string name="never">nunca</string>
|
||||
<string name="default_ping_ip">(opcional, padrão para pares)</string>
|
||||
<string name="set_custom_ping_internal">Intervalo de Ping (seg)</string>
|
||||
<string name="handshake">handshake</string>
|
||||
<string name="background_location_message">Permitir que toda a permissão de localização do tempo e/ou localização precisa é necessária para este recurso. Por favor, veja</string>
|
||||
<string name="sec">seg</string>
|
||||
<string name="notifications">Notificações</string>
|
||||
<string name="exclude_lan">Excluir LAN</string>
|
||||
<string name="hide_scripts">Ocultar scripts</string>
|
||||
<string name="trusted_wifi_names">Nomes de Wi-Fi confiáveis</string>
|
||||
<string name="hide_amnezia_properties">Ocultar propriedades Amnezia</string>
|
||||
<string name="remove_amnezia_compatibility">Remover compatibilidade Amnezia</string>
|
||||
<string name="include_lan">Incluir LAN</string>
|
||||
<string name="language">Idioma</string>
|
||||
<string name="add_wifi_name">Adicionar nome Wi-Fi</string>
|
||||
<string name="display_theme">Tema</string>
|
||||
<string name="on_demand_rules">Regras de tunelamento sob demanda</string>
|
||||
<string name="dark">Escuro</string>
|
||||
<string name="dynamic">Dinâmico</string>
|
||||
<string name="skip">Pular</string>
|
||||
<string name="mobile_tunnel">Túnel com dados móveis</string>
|
||||
<string name="requires_app_relaunch">Para aplicar as mudanças é necessário reiniciar o aplicativo. Deseja prosseguir ?</string>
|
||||
<string name="add_from_clipboard">Adicionar da área de transferência</string>
|
||||
<string name="restart_at_boot">Ativar na inicialização</string>
|
||||
<string name="appearance">Aparência</string>
|
||||
<string name="automatic">Automático</string>
|
||||
<string name="light">Claro</string>
|
||||
<string name="wildcards_active">Wildcards ativos</string>
|
||||
<string name="learn_more">Saber mais</string>
|
||||
<string name="use_wildcards">Usar nomes coringas</string>
|
||||
<string name="wifi_name_via_shell">Nome do Wi-Fi por shell</string>
|
||||
<string name="use_root_shell_for_wifi">Obter o nome do Wi-Fi através do shell root</string>
|
||||
<string name="start_auto">Iniciar túnel automático</string>
|
||||
<string name="stop_auto">Pausar túnel automático</string>
|
||||
<string name="monitoring_state_changes">Monitorar status de alterações</string>
|
||||
<string name="tunnel_running">Túnel em execução</string>
|
||||
<string name="donate">Contribua com esse projeto</string>
|
||||
<string name="local_logging">Registro local</string>
|
||||
<string name="enable_local_logging">Ativar registro local</string>
|
||||
<string name="configuration_change">Configuração alterada</string>
|
||||
<string name="stop_on_no_internet">Interromper quando não há internet</string>
|
||||
<string name="stop_on_internet_loss">Interrompa o túnel quando a internet não estiver disponível</string>
|
||||
<string name="ethernet_tunnel">Túnel ethernet</string>
|
||||
<string name="set_ethernet_tunnel">Definir como túnel ethernet</string>
|
||||
<string name="native_kill_switch">Interruptor de desligamento padrão</string>
|
||||
<string name="vpn_kill_switch">Interruptor de desligamento VPN</string>
|
||||
<string name="kill_switch_options">Opções do interruptor de desligamento</string>
|
||||
<string name="allow_lan_traffic">Permitir tráfego LAN</string>
|
||||
<string name="bypass_lan_for_kill_switch">Ignorar LAN no interruptor de desligamento</string>
|
||||
<string name="splt_tunneling">Tunelamento dividido</string>
|
||||
<string name="stop">pausar</string>
|
||||
<string name="tunnel_specific_settings">Configurações específicas no túnel</string>
|
||||
<string name="show_scripts">Mostrar scripts</string>
|
||||
<string name="amnezia_kernel_message">Amnezia indisponível no kernel</string>
|
||||
<string name="enable_amnezia">Ativar Amnezia</string>
|
||||
<string name="wg_compat_mode">Modo de compatibilidade WG</string>
|
||||
<string name="quick_actions">Ações rápidas</string>
|
||||
<string name="kernel_not_supported">Kernel não suportado</string>
|
||||
<string name="advanced_settings">Configurações avançadas</string>
|
||||
<string name="enable_amnezia_compatibility">Ativar compatibilidade Amnezia</string>
|
||||
</resources>
|
||||
@@ -1,177 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="tunnel_on_ethernet">Túnel na ethernet</string>
|
||||
<string name="public_key">Chave pública</string>
|
||||
<string name="addresses">Endereços</string>
|
||||
<string name="dns_servers">Servidores DNS</string>
|
||||
<string name="endpoint">Endpoint</string>
|
||||
<string name="name">Nome</string>
|
||||
<string name="create_import">Criar do zero</string>
|
||||
<string name="rotate_keys">Revezar chaves</string>
|
||||
<string name="private_key">Chave privada</string>
|
||||
<string name="base64_key">Chave base64</string>
|
||||
<string name="optional_no_recommend">(opcional, não recomendado)</string>
|
||||
<string name="preshared_key">Chave pré-partilhada</string>
|
||||
<string name="seconds">segundos</string>
|
||||
<string name="export_configs">Exportar configurações</string>
|
||||
<string name="error_no_file_explorer">Nenhum explorador de ficheiros instalado</string>
|
||||
<string name="error_invalid_code">Código QR inválido</string>
|
||||
<string name="auto_tunnel_title">Serviço de Auto-túnel</string>
|
||||
<string name="all">todos</string>
|
||||
<string name="enter_pin">Digite o seu pin</string>
|
||||
<string name="use_tunnel_on_wifi_name">Usar túnel em wifi com nome</string>
|
||||
<string name="version">Versão</string>
|
||||
<string name="junk_packet_count">Quantidade de pacotes-lixo</string>
|
||||
<string name="junk_packet_minimum_size">Tamanho mínimo de pacote-lixo</string>
|
||||
<string name="junk_packet_maximum_size">Tamanho máximo de pacote-lixo</string>
|
||||
<string name="init_packet_junk_size">Tamanho de pacote-lixo inicial</string>
|
||||
<string name="response_packet_junk_size">Tamanho de resposta de pacote-lixo</string>
|
||||
<string name="app_name">WG Tunnel</string>
|
||||
<string name="no_tunnels">Nenhum túnel foi adicionado!</string>
|
||||
<string name="error_file_extension">O ficheiro não é .conf ou .zip</string>
|
||||
<string name="prominent_background_location_message">Este recurso precisa de permissões de localização em segundo plano para ativar o monitoramento do SSID da rede Wi-Fi mesmo quando a aplicação está fechado. Para mais pormenores, por favor veja a Política de Privacidade no ecrã de Suporte.</string>
|
||||
<string name="turn_off_tunnel">Esta ação só é possível com o túnel inativo</string>
|
||||
<string name="enabled_app_shortcuts">Ativar atalhos de aplicações</string>
|
||||
<string name="tunnels">Túneis</string>
|
||||
<string name="privacy_policy">Ver a Política de Privacidade</string>
|
||||
<string name="okay">OK</string>
|
||||
<string name="tunnel_mobile_data">Túnel em dados móveis</string>
|
||||
<string name="prominent_background_location_title">Revelar a localização em segundo plano</string>
|
||||
<string name="thank_you">Obrigado por usar o WG Tunnel!</string>
|
||||
<string name="trusted_ssid_value_description">Envie o SSID</string>
|
||||
<string name="open_file">Abrir Ficheiro</string>
|
||||
<string name="add_from_qr">Adicionar a partir de código QR</string>
|
||||
<string name="add_tunnels_text">Adicionar a partir de ficheiro ou zip</string>
|
||||
<string name="qr_scan">Escanear o código QR</string>
|
||||
<string name="tunnel_name">Nome do Túnel</string>
|
||||
<string name="config_changes_saved">Mudanças nas configurações gravadas.</string>
|
||||
<string name="exclude">Excluir</string>
|
||||
<string name="include">Incluir</string>
|
||||
<string name="mtu">MTU</string>
|
||||
<string name="always_on_vpn_support">Permitir VPN sempre ligada</string>
|
||||
<string name="allowed_ips">IPs Permitidos</string>
|
||||
<string name="peer">Par</string>
|
||||
<string name="location_services_not_detected">Serviço de localização não foi detetado</string>
|
||||
<string name="hint_search_packages">Procurar pacotes</string>
|
||||
<string name="auto_tunneling">Auto-túnel</string>
|
||||
<string name="vpn_on">VPN ligada</string>
|
||||
<string name="vpn_off">VPN desligada</string>
|
||||
<string name="listen_port">Porta de escuta</string>
|
||||
<string name="turn_on_tunnel">Esta ação precisa um túnel ativo</string>
|
||||
<string name="add_peer">Adicionar par</string>
|
||||
<string name="interface_">Interface</string>
|
||||
<string name="copy_public_key">Copiar chave pública</string>
|
||||
<string name="comma_separated_list">Lista separada por vírgulas</string>
|
||||
<string name="optional">(opcional)</string>
|
||||
<string name="random">(aleatório)</string>
|
||||
<string name="persistent_keepalive">Manter a conexão persistente (keepalive)</string>
|
||||
<string name="cancel">Cancelar</string>
|
||||
<string name="error_authentication_failed">Autenticação falhou</string>
|
||||
<string name="error_authorization_failed">Autorização falhou</string>
|
||||
<string name="restart_on_ping">Reiniciar em falha de ping (beta)</string>
|
||||
<string name="email_description">Me envie um email</string>
|
||||
<string name="error_ssid_exists">SSID já existe</string>
|
||||
<string name="delete_tunnel_message">Tem certeza que quer apagar este túnel?</string>
|
||||
<string name="yes">Sim</string>
|
||||
<string name="unknown_error">Ocorreu um erro desconhecido</string>
|
||||
<string name="email_subject">Apoio para o WG Tunnel</string>
|
||||
<string name="tunnel_on_wifi">Túnel em Wi-Fi não confiável</string>
|
||||
<string name="delete_tunnel">Apagar túnel</string>
|
||||
<string name="email_chooser">Enviar um email…</string>
|
||||
<string name="use_kernel">Usar o módulo do kernel</string>
|
||||
<string name="docs_description">Ler a documentação</string>
|
||||
<string name="error_root_denied">Shell Root negado</string>
|
||||
<string name="location_services_missing_message">A aplicação não detetou o serviço de localização ativado no seu dispositivo. Dependendo do dispositivo, isto pode causar que a função de Wi-Fi não confiável falhe em ler o nome do Wi-Fi. Quer continuar mesmo assim?</string>
|
||||
<string name="open_issue">Abrir um problema</string>
|
||||
<string name="tunneling_apps">Aplicações com túnel</string>
|
||||
<string name="no_email_detected">Nenhuma aplicação de email detetado</string>
|
||||
<string name="no_browser_detected">Nenhum navegador detetado</string>
|
||||
<string name="incorrect_pin">O Pin está errado</string>
|
||||
<string name="auto">(automático)</string>
|
||||
<string name="read_logs">Ler os registos</string>
|
||||
<string name="pin_created">Pin criado com sucesso</string>
|
||||
<string name="create_pin">Criar um pin</string>
|
||||
<string name="enable_app_lock">Ligar bloqueio de aplicação</string>
|
||||
<string name="edit_tunnel">Editar túnel</string>
|
||||
<string name="mobile_data_tunnel">Selecionar como túnel em dados móveis</string>
|
||||
<string name="set_primary_tunnel">Selecionar como túnel principal</string>
|
||||
<string name="support">Suporte</string>
|
||||
<string name="kernel">Kernel</string>
|
||||
<string name="settings">Configurações</string>
|
||||
<string name="unsure_how">se não tiver certeza em como continuar</string>
|
||||
<string name="see_the">Veja o</string>
|
||||
<string name="getting_started_guide">guia de início rápido</string>
|
||||
<string name="error_file_format">Formato de configuração inválido</string>
|
||||
<string name="vpn_channel_name">Canal de notificações VPN</string>
|
||||
<string name="set_custom_ping_ip">Definir ip ping personalizado</string>
|
||||
<string name="vpn_denied_dialog_title">Permissão negada</string>
|
||||
<string name="vpn_settings">Configurações do sistema VPN</string>
|
||||
<string name="always_on_message">A permissão de conexão VPN foi negada. Por favor, verifique</string>
|
||||
<string name="chat_description">Junte-se à comunidade</string>
|
||||
<string name="tunnel_required">Característica requer pelo menos um túnel</string>
|
||||
<string name="app_settings">configurações da app</string>
|
||||
<string name="background_location_message2">para garantir que essas permissões estejam ativadas.</string>
|
||||
<string name="root_accepted">Shell root aceito</string>
|
||||
<string name="optional_default">"opcional, padrão: "</string>
|
||||
<string name="show_amnezia_properties">Mostrar propriedades de Amnezia</string>
|
||||
<string name="never">nunca</string>
|
||||
<string name="default_ping_ip">(opcional, padrão para pares)</string>
|
||||
<string name="set_custom_ping_internal">Intervalo de Ping (seg)</string>
|
||||
<string name="handshake">handshake</string>
|
||||
<string name="background_location_message">Permitir que toda a permissão de localização do tempo e/ou localização precisa é necessária para este recurso. Por favor, veja</string>
|
||||
<string name="sec">seg</string>
|
||||
<string name="notifications">Notificações</string>
|
||||
<string name="exclude_lan">Excluir LAN</string>
|
||||
<string name="hide_scripts">Ocultar scripts</string>
|
||||
<string name="trusted_wifi_names">Nomes de Wi-Fi confiáveis</string>
|
||||
<string name="hide_amnezia_properties">Ocultar propriedades Amnezia</string>
|
||||
<string name="remove_amnezia_compatibility">Remover compatibilidade Amnezia</string>
|
||||
<string name="include_lan">Incluir LAN</string>
|
||||
<string name="language">Idioma</string>
|
||||
<string name="add_wifi_name">Adicionar nome Wi-Fi</string>
|
||||
<string name="display_theme">Tema</string>
|
||||
<string name="on_demand_rules">Regras de tunelamento sob demanda</string>
|
||||
<string name="dark">Escuro</string>
|
||||
<string name="dynamic">Dinâmico</string>
|
||||
<string name="skip">Pular</string>
|
||||
<string name="mobile_tunnel">Túnel com dados móveis</string>
|
||||
<string name="requires_app_relaunch">Para aplicar as mudanças é necessário reiniciar o aplicativo. Deseja prosseguir ?</string>
|
||||
<string name="add_from_clipboard">Adicionar da área de transferência</string>
|
||||
<string name="restart_at_boot">Ativar na inicialização</string>
|
||||
<string name="appearance">Aparência</string>
|
||||
<string name="automatic">Automático</string>
|
||||
<string name="light">Claro</string>
|
||||
<string name="wildcards_active">Wildcards ativos</string>
|
||||
<string name="learn_more">Saber mais</string>
|
||||
<string name="use_wildcards">Usar nomes coringas</string>
|
||||
<string name="wifi_name_via_shell">Nome do Wi-Fi por shell</string>
|
||||
<string name="use_root_shell_for_wifi">Obter o nome do Wi-Fi através do shell root</string>
|
||||
<string name="start_auto">Iniciar túnel automático</string>
|
||||
<string name="stop_auto">Pausar túnel automático</string>
|
||||
<string name="monitoring_state_changes">Monitorar status de alterações</string>
|
||||
<string name="tunnel_running">Túnel em execução</string>
|
||||
<string name="donate">Contribua com esse projeto</string>
|
||||
<string name="local_logging">Registro local</string>
|
||||
<string name="enable_local_logging">Ativar registro local</string>
|
||||
<string name="configuration_change">Configuração alterada</string>
|
||||
<string name="stop_on_no_internet">Interromper quando não há internet</string>
|
||||
<string name="stop_on_internet_loss">Interrompa o túnel quando a internet não estiver disponível</string>
|
||||
<string name="ethernet_tunnel">Túnel ethernet</string>
|
||||
<string name="set_ethernet_tunnel">Definir como túnel ethernet</string>
|
||||
<string name="native_kill_switch">Interruptor de desligamento padrão</string>
|
||||
<string name="vpn_kill_switch">Interruptor de desligamento VPN</string>
|
||||
<string name="kill_switch_options">Opções do interruptor de desligamento</string>
|
||||
<string name="allow_lan_traffic">Permitir tráfego LAN</string>
|
||||
<string name="bypass_lan_for_kill_switch">Ignorar LAN no interruptor de desligamento</string>
|
||||
<string name="splt_tunneling">Tunelamento dividido</string>
|
||||
<string name="stop">pausar</string>
|
||||
<string name="tunnel_specific_settings">Configurações específicas no túnel</string>
|
||||
<string name="show_scripts">Mostrar scripts</string>
|
||||
<string name="amnezia_kernel_message">Amnezia indisponível no kernel</string>
|
||||
<string name="enable_amnezia">Ativar Amnezia</string>
|
||||
<string name="wg_compat_mode">Modo de compatibilidade WG</string>
|
||||
<string name="quick_actions">Ações rápidas</string>
|
||||
<string name="kernel_not_supported">Kernel não suportado</string>
|
||||
<string name="advanced_settings">Configurações avançadas</string>
|
||||
<string name="enable_amnezia_compatibility">Ativar compatibilidade Amnezia</string>
|
||||
</resources>
|
||||
@@ -3,7 +3,7 @@
|
||||
<string name="turn_off_tunnel">Действие требует отключения туннеля</string>
|
||||
<string name="mtu">MTU</string>
|
||||
<string name="tunnel_name">Имя туннеля</string>
|
||||
<string name="public_key">Публичный ключ</string>
|
||||
<string name="public_key">Открытый ключ</string>
|
||||
<string name="name">Имя</string>
|
||||
<string name="peer">Пир</string>
|
||||
<string name="privacy_policy">Посмотреть политику конфиденциальности</string>
|
||||
@@ -129,7 +129,7 @@
|
||||
<string name="handshake">рукопожатие</string>
|
||||
<string name="logs">Журналы</string>
|
||||
<string name="light">Светлая</string>
|
||||
<string name="automatic">Автоматически</string>
|
||||
<string name="automatic">Автоматическая</string>
|
||||
<string name="dynamic">Динамическая</string>
|
||||
<string name="language">Язык</string>
|
||||
<string name="trusted_wifi_names">Доверенные сети Wi-Fi</string>
|
||||
@@ -192,4 +192,17 @@
|
||||
<string name="advanced_settings">Дополнительные настройки</string>
|
||||
<string name="enable_amnezia_compatibility">Включить совместимость с Amnezia</string>
|
||||
<string name="include_lan">Включить LAN</string>
|
||||
</resources>
|
||||
<string name="auto_tunnel">Автотуннель</string>
|
||||
<string name="tunnel_control">Управление туннелем</string>
|
||||
<string name="error_tunnel_start">Невозможно запустить туннель</string>
|
||||
<string name="kill_switch_off">Без экстренного отключения в доверенных</string>
|
||||
<string name="prefer_ipv4">Предпочитать соединение IPv4</string>
|
||||
<string name="dns_error">Не получить конечную точку DNS.</string>
|
||||
<string name="start_failed_config">Невозможно запустить туннель с ошибкой конфигурации.</string>
|
||||
<string name="unauthorized">Невозможно запустить туннель без авторизации.</string>
|
||||
<string name="tunne_start_failed_title">Ошибка туннеля</string>
|
||||
<string name="server_ipv4">Получение имени узла IPv4</string>
|
||||
<string name="multiple">Несколько</string>
|
||||
<string name="export_amnezia">Экспортировать как Amnezia</string>
|
||||
<string name="export_wireguard">Экспортировать как WireGuard</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,43 +1,50 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">WG Tunnel</string>
|
||||
<string name="vpn_channel_name">VPN Bildirim Kanalı</string>
|
||||
<string name="error_file_extension">Dosya .conf veya .zip değil</string>
|
||||
<string name="turn_off_tunnel">İşlem için tünelin kapalı olması gerekiyor</string>
|
||||
<string name="vpn_channel_id" translatable="false">VPN Channel</string>
|
||||
<string name="vpn_channel_name">VPN Bildirim Kanalı</string>
|
||||
<string name="github_url" translatable="false">https://github.com/zaneschepke/wgtunnel/issues</string>
|
||||
<string name="docs_url" translatable="false">https://zaneschepke.com/wgtunnel-docs/overview.html</string>
|
||||
<string name="privacy_policy_url" translatable="false">https://zaneschepke.com/wgtunnel-docs/privacypolicy.html</string>
|
||||
<string name="docs_wildcards" translatable="false">https://zaneschepke.com/wgtunnel-docs/features.html#wildcard-wi-fi-name-support</string>
|
||||
<string name="donate_url" translatable="false">https://zaneschepke.com/donate/</string>
|
||||
<string name="error_file_extension">Dosya .conf veya .zip değil</string>
|
||||
<string name="turn_off_tunnel">Bu işlem tünelin kapalı olmasını gerektirir</string>
|
||||
<string name="no_tunnels">Henüz tünel eklenmedi!</string>
|
||||
<string name="tunnels">Tüneller</string>
|
||||
<string name="tunnel_mobile_data">Mobil veride tünel</string>
|
||||
<string name="privacy_policy">Gizlilik Politikasını Görüntüle</string>
|
||||
<string name="privacy_policy">Gizlilik politikasını görüntüle</string>
|
||||
<string name="okay">Tamam</string>
|
||||
<string name="tunnel_on_ethernet">Ethernet\'te tünel</string>
|
||||
<string name="prominent_background_location_message">Bu özellik, uygulama kapalıyken bile Wi-Fi SSID izlemesini etkinleştirmek için arka plan konum iznine ihtiyaç duyar. Daha fazla ayrıntı için lütfen Destek ekranında bağlantısı verilen Gizlilik Politikasına bakın.</string>
|
||||
<string name="tunnel_on_ethernet">Ethernet üzerinde tünel</string>
|
||||
<string name="prominent_background_location_message">Bu özellik, uygulamanın kapalı olduğu durumlarda bile Wi-Fi SSID izlemesini etkinleştirmek için arka planda konum izni gerektirir. Daha fazla ayrıntı için lütfen Destek ekranında bağlantısı verilen Gizlilik Politikasına bakın.</string>
|
||||
<string name="prominent_background_location_title">Arka Plan Konum Açıklaması</string>
|
||||
<string name="thank_you">WG Tunnel\'ı kullandığınız için teşekkürler!</string>
|
||||
<string name="trusted_ssid_value_description">SSID\'yi gönder</string>
|
||||
<string name="add_tunnels_text">Dosyadan veya zip\'ten ekle</string>
|
||||
<string name="thank_you">WG Tunnel’i kullandığınız için teşekkürler!</string>
|
||||
<string name="trusted_ssid_value_description">SSID Gönder</string>
|
||||
<string name="add_tunnels_text">Dosyadan veya zip’ten ekle</string>
|
||||
<string name="open_file">Dosya Aç</string>
|
||||
<string name="add_from_qr">QR kodundan ekle</string>
|
||||
<string name="qr_scan">QR Tarama</string>
|
||||
<string name="qr_scan">QR Tara</string>
|
||||
<string name="tunnel_name">Tünel Adı</string>
|
||||
<string name="exclude">Hariç tut</string>
|
||||
<string name="include">Dahil et</string>
|
||||
<string name="exclude">Hariç Tut</string>
|
||||
<string name="include">Dahil Et</string>
|
||||
<string name="config_changes_saved">Yapılandırma değişiklikleri kaydedildi.</string>
|
||||
<string name="public_key">Genel anahtar</string>
|
||||
<string name="addresses">Adresler</string>
|
||||
<string name="dns_servers">DNS sunucuları</string>
|
||||
<string name="mtu">MTU</string>
|
||||
<string name="peer">Eş (peer)</string>
|
||||
<string name="allowed_ips">İzin verilen IP\'ler</string>
|
||||
<string name="endpoint">Uç nokta (endpoint)</string>
|
||||
<string name="peer">Eş</string>
|
||||
<string name="allowed_ips">İzin verilen IP’ler</string>
|
||||
<string name="endpoint">Uç Nokta</string>
|
||||
<string name="name">Ad</string>
|
||||
<string name="always_on_vpn_support">Her Zaman Açık VPN\'e İzin Ver</string>
|
||||
<string name="location_services_not_detected">Konum Hizmetleri Algılanmadı</string>
|
||||
<string name="hint_search_packages">Paketlerde ara</string>
|
||||
<string name="always_on_vpn_support">Her Zaman Açık VPN’e İzin Ver</string>
|
||||
<string name="location_services_not_detected">Konum Servisleri Algılanmadı</string>
|
||||
<string name="hint_search_packages">Paketleri ara</string>
|
||||
<string name="db_name" translatable="false">wg-tunnel-db</string>
|
||||
<string name="auto_tunneling">Otomatik tünelleme</string>
|
||||
<string name="vpn_on">VPN açık</string>
|
||||
<string name="vpn_off">VPN kapalı</string>
|
||||
<string name="create_import">Sıfırdan oluştur</string>
|
||||
<string name="turn_on_tunnel">İşlem için aktif tünel gerekiyor</string>
|
||||
<string name="turn_on_tunnel">Bu işlem aktif bir tünel gerektirir</string>
|
||||
<string name="add_peer">Eş ekle</string>
|
||||
<string name="interface_">Arayüz</string>
|
||||
<string name="rotate_keys">Anahtarları döndür</string>
|
||||
@@ -49,41 +56,42 @@
|
||||
<string name="random">(rastgele)</string>
|
||||
<string name="optional">(isteğe bağlı)</string>
|
||||
<string name="optional_no_recommend">(isteğe bağlı, önerilmez)</string>
|
||||
<string name="preshared_key">Önceden paylaşılmış anahtar</string>
|
||||
<string name="preshared_key">Ön paylaşımlı anahtar</string>
|
||||
<string name="seconds">saniye</string>
|
||||
<string name="persistent_keepalive">Kalıcı canlı tutma</string>
|
||||
<string name="cancel">İptal</string>
|
||||
<string name="error_authentication_failed">Kimlik doğrulama başarısız oldu</string>
|
||||
<string name="error_authorization_failed">Yetkilendirme başarısız oldu</string>
|
||||
<string name="error_authentication_failed">Kimlik doğrulama başarısız</string>
|
||||
<string name="error_authorization_failed">Yetkilendirme başarısız</string>
|
||||
<string name="enabled_app_shortcuts">Uygulama kısayollarını etkinleştir</string>
|
||||
<string name="export_configs">Yapılandırmaları dışa aktar</string>
|
||||
<string name="unknown_error">Bilinmeyen bir hata oluştu</string>
|
||||
<string name="tunnel_on_wifi">Güvenilmeyen wifi\'da tünel</string>
|
||||
<string name="email_subject">WG Tunnel Desteği</string>
|
||||
<string name="tunnel_on_wifi">Güvenilmeyen wifi’da tünel</string>
|
||||
<string name="my_email" translatable="false">support@zaneschepke.com</string>
|
||||
<string name="email_subject">WG Tunnel Desteği</string>
|
||||
<string name="email_chooser">E-posta gönder…</string>
|
||||
<string name="docs_description">Belgeleri oku</string>
|
||||
<string name="email_description">Bana e-posta gönder</string>
|
||||
<string name="use_kernel">Kernel modülünü kullan</string>
|
||||
<string name="use_kernel">Çekirdek modülünü kullan</string>
|
||||
<string name="error_ssid_exists">SSID zaten mevcut</string>
|
||||
<string name="error_root_denied">Root kabuğu reddedildi</string>
|
||||
<string name="error_no_file_explorer">Dosya gezgini yüklü değil</string>
|
||||
<string name="error_invalid_code">Geçersiz QR kodu</string>
|
||||
<string name="location_services_missing_message">Uygulama, cihazınızda etkinleştirilmiş herhangi bir konum hizmeti algılamıyor. Cihaza bağlı olarak, bu durum güvenilmeyen wifi özelliğinin wifi adını okumasını engelleyebilir. Yine de devam etmek istiyor musunuz?</string>
|
||||
<string name="auto_tunnel_title">Otomatik Tünel Hizmeti</string>
|
||||
<string name="location_services_missing_message">Uygulama, cihazınızda etkinleştirilmiş herhangi bir konum servisi algılamıyor. Cihaza bağlı olarak, bu durum güvenilmeyen wifi özelliğinin wifi adını okuyamamasını sağlayabilir. Yine de devam etmek ister misiniz?</string>
|
||||
<string name="auto_tunnel_title">Otomatik tünel servisi</string>
|
||||
<string name="delete_tunnel">Tüneli sil</string>
|
||||
<string name="delete_tunnel_message">Bu tüneli silmek istediğinizden emin misiniz?</string>
|
||||
<string name="yes">Evet</string>
|
||||
<string name="tunneling_apps">Tünellenen uygulamalar</string>
|
||||
<string name="tunneling_apps">Tünelleme uygulamaları</string>
|
||||
<string name="all">tümü</string>
|
||||
<string name="no_email_detected">E-posta uygulaması algılanmadı</string>
|
||||
<string name="no_browser_detected">Tarayıcı algılanmadı</string>
|
||||
<string name="open_issue">Sorun bildir</string>
|
||||
<string name="open_issue">Bir sorun aç</string>
|
||||
<string name="read_logs">Günlükleri oku</string>
|
||||
<string name="auto">(otomatik)</string>
|
||||
<string name="incorrect_pin">PIN yanlış</string>
|
||||
<string name="pin_created">PIN başarıyla oluşturuldu</string>
|
||||
<string name="enter_pin">PIN\'inizi girin</string>
|
||||
<string name="create_pin">PIN oluştur</string>
|
||||
<string name="incorrect_pin">Pin yanlış</string>
|
||||
<string name="pin_created">Pin başarıyla oluşturuldu</string>
|
||||
<string name="enter_pin">Pin’inizi girin</string>
|
||||
<string name="create_pin">Pin oluştur</string>
|
||||
<string name="enable_app_lock">Uygulama kilidini etkinleştir</string>
|
||||
<string name="restart_on_ping">Ping başarısız olduğunda yeniden başlat (beta)</string>
|
||||
<string name="mobile_data_tunnel">Mobil veri tüneli olarak ayarla</string>
|
||||
@@ -94,18 +102,118 @@
|
||||
<string name="settings">Ayarlar</string>
|
||||
<string name="support">Destek</string>
|
||||
<string name="kernel">Çekirdek</string>
|
||||
<string name="junk_packet_count">Gereksiz paket sayısı</string>
|
||||
<string name="junk_packet_minimum_size">Gereksiz paket minimum boyutu</string>
|
||||
<string name="junk_packet_maximum_size">Gereksiz paket maksimum boyutu</string>
|
||||
<string name="init_packet_junk_size">Başlatma paketi gereksiz boyutu</string>
|
||||
<string name="response_packet_junk_size">Yanıt paketi gereksiz boyutu</string>
|
||||
<string name="init_packet_magic_header">Başlatma paketi sihirli başlığı</string>
|
||||
<string name="junk_packet_count">Çöp paket sayısı</string>
|
||||
<string name="junk_packet_minimum_size">Çöp paket minimum boyutu</string>
|
||||
<string name="junk_packet_maximum_size">Çöp paket maksimum boyutu</string>
|
||||
<string name="init_packet_junk_size">Başlangıç paketi çöp boyutu</string>
|
||||
<string name="response_packet_junk_size">Yanıt paketi çöp boyutu</string>
|
||||
<string name="init_packet_magic_header">Başlangıç paketi sihirli başlığı</string>
|
||||
<string name="response_packet_magic_header">Yanıt paketi sihirli başlığı</string>
|
||||
<string name="transport_packet_magic_header">Taşıma paketi sihirli başlığı</string>
|
||||
<string name="underload_packet_magic_header">Düşük yük paketi sihirli başlığı</string>
|
||||
<string name="unsure_how">nasıl devam edeceğinizden emin değilseniz</string>
|
||||
<string name="see_the">Bakın:</string>
|
||||
<string name="getting_started_guide">başlangıç kılavuzu</string>
|
||||
<string name="telegram_url" translatable="false">https://t.me/wgtunnel</string>
|
||||
<string name="unsure_how">nasıl devam edeceğinizden emin değilseniz</string>
|
||||
<string name="see_the">Bakınız</string>
|
||||
<string name="getting_started_url" translatable="false">https://zaneschepke.com/wgtunnel-docs/getting-started.html</string>
|
||||
<string name="getting_started_guide">başlangıç kılavuzu</string>
|
||||
<string name="error_file_format">Geçersiz tünel yapılandırma formatı</string>
|
||||
<string name="restart_at_boot">Önyüklemede yeniden başlat</string>
|
||||
<string name="restart_at_boot">Başlangıçta yeniden başlat</string>
|
||||
<string name="vpn_denied_dialog_title">İzin Reddedildi</string>
|
||||
<string name="vpn_settings">VPN sistem ayarları</string>
|
||||
<string name="always_on_message">VPN bağlantı izni reddedildi. Lütfen</string>
|
||||
<string name="always_on_message2">diğer tüm uygulamalar için Her Zaman Açık VPN’in kapalı olduğundan emin olun ve tekrar deneyin</string>
|
||||
<string name="chat_description">Topluluğa katıl</string>
|
||||
<string name="tunnel_required">Bu özellik en az bir tünel gerektirir</string>
|
||||
<string name="background_location_message">Bu özellik için her zaman konum izni ve/veya hassas konum gereklidir. Lütfen</string>
|
||||
<string name="app_settings">uygulama ayarları</string>
|
||||
<string name="background_location_message2">bu izinlerin etkin olduğundan emin olun</string>
|
||||
<string name="root_accepted">Root kabuğu kabul edildi</string>
|
||||
<string name="set_custom_ping_ip">Özel ping IP’si ayarla</string>
|
||||
<string name="default_ping_ip">(isteğe bağlı, varsayılan eşler)</string>
|
||||
<string name="set_custom_ping_internal">Ping aralığı (saniye)</string>
|
||||
<string name="optional_default">"isteğe bağlı, varsayılan: "</string>
|
||||
<string name="set_custom_ping_cooldown">Ping yeniden başlatma bekleme süresi (saniye)</string>
|
||||
<string name="show_amnezia_properties">Amnezia özelliklerini göster</string>
|
||||
<string name="never">asla</string>
|
||||
<string name="sec">sn</string>
|
||||
<string name="handshake">el sıkışma</string>
|
||||
<string name="logs">Günlükler</string>
|
||||
<string name="kill_switch">Kill Switch</string>
|
||||
<string name="appearance">Görünüm</string>
|
||||
<string name="notifications">Bildirimler</string>
|
||||
<string name="automatic">Otomatik</string>
|
||||
<string name="light">Açık</string>
|
||||
<string name="dark">Koyu</string>
|
||||
<string name="dynamic">Dinamik</string>
|
||||
<string name="language">Dil</string>
|
||||
<string name="display_theme">Ekran teması</string>
|
||||
<string name="trusted_wifi_names">Güvenilir wifi adları</string>
|
||||
<string name="add_wifi_name">Wifi adı ekle</string>
|
||||
<string name="on_demand_rules">İsteğe bağlı tünel kuralları</string>
|
||||
<string name="primary_tunnel">Birincil tünel</string>
|
||||
<string name="mobile_tunnel">Mobil veri tüneli</string>
|
||||
<string name="skip">Atla</string>
|
||||
<string name="launch_app_settings">Uygulama ayarlarını başlat</string>
|
||||
<string name="use_wildcards">İsim jokerlerini kullan</string>
|
||||
<string name="learn_more">Daha fazla bilgi</string>
|
||||
<string name="wildcards_active">Jokerler etkin</string>
|
||||
<string name="wifi_name_via_shell">Kabuk üzerinden wifi adı</string>
|
||||
<string name="use_root_shell_for_wifi">Wifi adını almak için root kabuğunu kullan</string>
|
||||
<string name="kernel_not_supported">Çekirdek desteklenmiyor</string>
|
||||
<string name="start_auto">Otomatik tüneli başlat</string>
|
||||
<string name="stop_auto">Otomatik tüneli durdur</string>
|
||||
<string name="tunnel_running">Tünel çalışıyor</string>
|
||||
<string name="monitoring_state_changes">Durum değişikliklerini izleme</string>
|
||||
<string name="donate">Projeye bağış yap</string>
|
||||
<string name="local_logging">Yerel günlüğe kaydetme</string>
|
||||
<string name="enable_local_logging">Yerel günlüğe kaydetmeyi etkinleştir</string>
|
||||
<string name="configuration_change">Yapılandırma değişikliği</string>
|
||||
<string name="requires_app_relaunch">Bu değişiklik uygulamanın yeniden başlatılmasını gerektirir. Devam etmek ister misiniz?</string>
|
||||
<string name="add_from_clipboard">Panodan ekle</string>
|
||||
<string name="stop_on_no_internet">İnternet olmadığında durdur</string>
|
||||
<string name="stop_on_internet_loss">İnternet kaybında tüneli durdur</string>
|
||||
<string name="ethernet_tunnel">Ethernet tüneli</string>
|
||||
<string name="set_ethernet_tunnel">Ethernet tüneli olarak ayarla</string>
|
||||
<string name="native_kill_switch">Yerel kill switch</string>
|
||||
<string name="vpn_kill_switch">VPN kill switch</string>
|
||||
<string name="kill_switch_options">Kill switch seçenekleri</string>
|
||||
<string name="allow_lan_traffic">LAN trafiğine izin ver</string>
|
||||
<string name="bypass_lan_for_kill_switch">Kill switch için LAN’ı atla</string>
|
||||
<string name="vpn_channel_description">VPN durum bildirimleri için bir kanal</string>
|
||||
<string name="auto_tunnel_channel_id" translatable="false">Auto-tunnel Channel</string>
|
||||
<string name="auto_tunnel_channel_name">Otomatik Tünel Bildirim Kanalı</string>
|
||||
<string name="auto_tunnel_channel_description">Otomatik tünel durum bildirimleri için bir kanal</string>
|
||||
<string name="stop">durdur</string>
|
||||
<string name="splt_tunneling">Bölünmüş tünelleme</string>
|
||||
<string name="tunnel_specific_settings">Tünele özgü ayarlar</string>
|
||||
<string name="show_scripts">Komut dosyalarını göster</string>
|
||||
<string name="pre_up">Ön çalıştırma</string>
|
||||
<string name="post_up">Sonra çalıştırma</string>
|
||||
<string name="pre_down">Ön kapatma</string>
|
||||
<string name="post_down">Sonra kapatma</string>
|
||||
<string name="amnezia_kernel_message">Amnezia çekirdek modunda kullanılamaz</string>
|
||||
<string name="enable_amnezia">Amnezia’yı etkinleştir</string>
|
||||
<string name="wg_compat_mode">WG uyumluluk modu</string>
|
||||
<string name="quick_actions">Hızlı eylemler</string>
|
||||
<string name="advanced_settings">Gelişmiş ayarlar</string>
|
||||
<string name="debounce_delay">Gecikme süresi</string>
|
||||
<string name="hide_amnezia_properties">Amnezia özelliklerini gizle</string>
|
||||
<string name="hide_scripts">Komut dosyalarını gizle</string>
|
||||
<string name="enable_amnezia_compatibility">Amnezia uyumluluğunu etkinleştir</string>
|
||||
<string name="remove_amnezia_compatibility">Amnezia uyumluluğunu kaldır</string>
|
||||
<string name="exclude_lan">LAN’ı hariç tut</string>
|
||||
<string name="include_lan">LAN’ı dahil et</string>
|
||||
<string name="error_tunnel_start">Tünel başlatma başarısız</string>
|
||||
<string name="tunnel_control">Tünel kontrolü</string>
|
||||
<string name="auto_tunnel">Otomatik tünel</string>
|
||||
<string name="kill_switch_off">Güvenilirde kill switch’i durdur</string>
|
||||
<string name="server_ipv4">IPv4 ana makine çözünürlüğü</string>
|
||||
<string name="prefer_ipv4">IPv4 bağlantısını tercih et</string>
|
||||
<string name="dns_error">Uç nokta DNS’si çözülemedi.</string>
|
||||
<string name="start_failed_config">Yapılandırma hatası nedeniyle tünel başlatılamadı.</string>
|
||||
<string name="unauthorized">Yetkisiz, tünel başlatılamadı.</string>
|
||||
<string name="tunne_start_failed_title">Tünel hatası</string>
|
||||
<string name="multiple">Çoklu</string>
|
||||
<string name="export_amnezia">Amnezia olarak dışa aktar</string>
|
||||
<string name="export_wireguard">WireGuard olarak dışa aktar</string>
|
||||
</resources>
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
<string name="getting_started_guide">інструкція щодо початку роботи</string>
|
||||
<string name="error_file_format">некоректний формат конфігурації тунелю</string>
|
||||
<string name="vpn_channel_name">Канал сповіщення VPN</string>
|
||||
<string name="error_file_extension">Файл не є .conf або .zip файлом</string>
|
||||
<string name="error_file_extension">Файл не є .conf або .zip файлом</string>
|
||||
<string name="turn_off_tunnel">Дія потребує вимкнення тунелю</string>
|
||||
<string name="tunnel_mobile_data">Тунелювати мобільні дані</string>
|
||||
<string name="privacy_policy">Переглянути політику конфіденційності</string>
|
||||
@@ -51,7 +51,7 @@
|
||||
<string name="name">Ім\'я</string>
|
||||
<string name="always_on_vpn_support">Дозволили Always-ON VPN</string>
|
||||
<string name="location_services_not_detected">Сервіси місце знаходження не знайдено</string>
|
||||
<string name="auto_tunneling">Авто-тунелювання</string>
|
||||
<string name="auto_tunneling">Авто-тунелювання</string>
|
||||
<string name="vpn_on">VPN увімк.</string>
|
||||
<string name="vpn_off">VPN вимк.</string>
|
||||
<string name="create_import">Створити з нуля</string>
|
||||
@@ -59,9 +59,9 @@
|
||||
<string name="interface_">Інтерфейс</string>
|
||||
<string name="copy_public_key">Копіювати відкритий ключ</string>
|
||||
<string name="listen_port">Слухати порт</string>
|
||||
<string name="preshared_key">Pre-shared key</string>
|
||||
<string name="preshared_key">Загальний ключ</string>
|
||||
<string name="seconds">секунд</string>
|
||||
<string name="persistent_keepalive">Persistent keepalive</string>
|
||||
<string name="persistent_keepalive">Підтримка роботи тунелю (keepalive)</string>
|
||||
<string name="error_authorization_failed">Не вдалося авторизуватися</string>
|
||||
<string name="enabled_app_shortcuts">Дозволити ярлики</string>
|
||||
<string name="error_authentication_failed">Помилка автентифікації</string>
|
||||
@@ -74,7 +74,7 @@
|
||||
<string name="use_kernel">Використовувати модуль режиму ядра</string>
|
||||
<string name="error_ssid_exists">SSID вже існує</string>
|
||||
<string name="error_no_file_explorer">Не знайдено файловий менеджер</string>
|
||||
<string name="auto_tunnel_title">Сервіс авто-тунелювання</string>
|
||||
<string name="auto_tunnel_title">Сервіс автотунелювання</string>
|
||||
<string name="delete_tunnel">Видалити тунель</string>
|
||||
<string name="location_services_missing_message">Додаток не знайшов служб місце знаходження на вашому пристрої. На деяких пристроях це може привести до неможливості визначення назви мережі Wi-Fi і помилок функції недовірених Wi-Fi мереж. Все рівно хочете продовжити?</string>
|
||||
<string name="delete_tunnel_message">Ви дійсно хочете видалити цей тунель?</string>
|
||||
@@ -107,4 +107,102 @@
|
||||
<string name="response_packet_magic_header">Заголовок пакету відповіді</string>
|
||||
<string name="unsure_how">, якщо не впевнені що робити далі</string>
|
||||
<string name="see_the">Дивіться</string>
|
||||
<string name="skip">Пропустити</string>
|
||||
<string name="trusted_wifi_names">Довірені мережі Wi-Fi</string>
|
||||
<string name="vpn_denied_dialog_title">Немає дозволу</string>
|
||||
<string name="app_settings">налаштування програми</string>
|
||||
<string name="background_location_message2">, щоб переконатися, що ці дозволи надано</string>
|
||||
<string name="default_ping_ip">(необов\'язково, за замовчуванням для пірів)</string>
|
||||
<string name="set_custom_ping_internal">Інтервал пінгу (сек.)</string>
|
||||
<string name="set_custom_ping_cooldown">Час очікування перезапуску пінгу (сек.)</string>
|
||||
<string name="show_amnezia_properties">Показати налаштування Amnezia</string>
|
||||
<string name="sec">сек.</string>
|
||||
<string name="handshake">рукостискання</string>
|
||||
<string name="notifications">Сповіщення</string>
|
||||
<string name="light">Світла</string>
|
||||
<string name="dark">Темна</string>
|
||||
<string name="language">Мова</string>
|
||||
<string name="add_wifi_name">Додати мережу Wi-Fi</string>
|
||||
<string name="on_demand_rules">Правила тунелю на запит</string>
|
||||
<string name="primary_tunnel">Основний тунель</string>
|
||||
<string name="launch_app_settings">Налаштування запуску програми</string>
|
||||
<string name="learn_more">Дізнатись більше</string>
|
||||
<string name="wildcards_active">Підстановочні знаки використовуються</string>
|
||||
<string name="wifi_name_via_shell">Ім\'я Wi-Fi через root</string>
|
||||
<string name="kill_switch">Екстрене відключення</string>
|
||||
<string name="restart_at_boot">Перезапуск під час завантаження</string>
|
||||
<string name="vpn_settings">Системні налаштування VPN</string>
|
||||
<string name="always_on_message">Дозвіл на VPN-з\'єднання було відхилено, перевірте</string>
|
||||
<string name="root_accepted">Root-доступ дозволено</string>
|
||||
<string name="set_custom_ping_ip">Призначити свій IP для пінгу</string>
|
||||
<string name="logs">Журнали</string>
|
||||
<string name="mobile_tunnel">Тунель для мобільних даних</string>
|
||||
<string name="display_theme">Тема</string>
|
||||
<string name="chat_description">Приєднатися до спільноти</string>
|
||||
<string name="automatic">Автоматично</string>
|
||||
<string name="never">ніколи</string>
|
||||
<string name="appearance">Зовнішній вигляд</string>
|
||||
<string name="dynamic">Динамічна</string>
|
||||
<string name="use_root_shell_for_wifi">Використовувати root-доступ для отримання імені мережі Wi-Fi</string>
|
||||
<string name="use_wildcards">Використовувати підстановочні знаки в імені</string>
|
||||
<string name="optional_default">"необов\'язково, за замовчуванням: "</string>
|
||||
<string name="always_on_message2">, щоб переконатися, що функція «Постійний VPN» вимкнена для всіх інших програм, і спробуйте ще раз</string>
|
||||
<string name="tunnel_required">Для цієї функції необхідний хоча б один тунель</string>
|
||||
<string name="background_location_message">Дозволяти весь час, доки для роботи цієї функції потрібен доступ на місцезнаходження та/або точне місцезнаходження. Дивіться</string>
|
||||
<string name="advanced_settings">Додаткові налаштування</string>
|
||||
<string name="quick_actions">Швидкі дії</string>
|
||||
<string name="tunnel_running">Тунель працює</string>
|
||||
<string name="monitoring_state_changes">Відстеження змін стану</string>
|
||||
<string name="donate">Пожертвувати на проект</string>
|
||||
<string name="requires_app_relaunch">Дана зміна потребує перезапуску програми. Продовжити?</string>
|
||||
<string name="add_from_clipboard">Додати з буфера обміну</string>
|
||||
<string name="stop_on_no_internet">Зупинити без інтернету</string>
|
||||
<string name="native_kill_switch">Штатне екстрене відключення</string>
|
||||
<string name="vpn_kill_switch">Екстрене відключення VPN</string>
|
||||
<string name="kill_switch_options">Налаштування екстреного вимкнення</string>
|
||||
<string name="allow_lan_traffic">Обхід LAN</string>
|
||||
<string name="bypass_lan_for_kill_switch">Дозволяти трафік LAN при екстреному вимкненні</string>
|
||||
<string name="auto_tunnel_channel_name">Канал сповіщень автотунелю</string>
|
||||
<string name="auto_tunnel_channel_description">Канал сповіщень про стан автотунелю</string>
|
||||
<string name="stop">стоп</string>
|
||||
<string name="splt_tunneling">Роздільне тунелювання</string>
|
||||
<string name="show_scripts">Показати сценарії</string>
|
||||
<string name="pre_up">До активації</string>
|
||||
<string name="post_up">Після активації</string>
|
||||
<string name="pre_down">До деактивації</string>
|
||||
<string name="post_down">Після деактивації</string>
|
||||
<string name="amnezia_kernel_message">Amnezia недоступна у режимі ядра</string>
|
||||
<string name="enable_amnezia">Використовувати Amnezia</string>
|
||||
<string name="wg_compat_mode">Режим сумісності WG</string>
|
||||
<string name="debounce_delay">Затримка відбою</string>
|
||||
<string name="hide_amnezia_properties">Приховати налаштування Amnezia</string>
|
||||
<string name="hide_scripts">Приховати сценарії</string>
|
||||
<string name="remove_amnezia_compatibility">Вимкнути сумісність з Amnezia</string>
|
||||
<string name="exclude_lan">Виключити LAN</string>
|
||||
<string name="include_lan">Увімкнути LAN</string>
|
||||
<string name="error_tunnel_start">Неможливо запустити тунель</string>
|
||||
<string name="tunnel_control">Управління тунелем</string>
|
||||
<string name="auto_tunnel">Автотунель</string>
|
||||
<string name="local_logging">Локальне ведення журналу</string>
|
||||
<string name="kernel_not_supported">Ядро не підтримується</string>
|
||||
<string name="start_auto">Запустити автотунель</string>
|
||||
<string name="stop_auto">Зупинити автотунель</string>
|
||||
<string name="enable_local_logging">Увімкнути ведення журналу</string>
|
||||
<string name="configuration_change">Зміна конфігурації</string>
|
||||
<string name="stop_on_internet_loss">Зупинити тунель під час втрати інтернету</string>
|
||||
<string name="ethernet_tunnel">Тунель для Ethernet</string>
|
||||
<string name="set_ethernet_tunnel">Призначити як тунель для Ethernet</string>
|
||||
<string name="tunnel_specific_settings">Спеціальні налаштування тунелю</string>
|
||||
<string name="vpn_channel_description">Канал сповіщень про стан VPN</string>
|
||||
<string name="enable_amnezia_compatibility">Включити сумісність із Amnezia</string>
|
||||
<string name="kill_switch_off">Без екстреного вимкнення у довірених</string>
|
||||
<string name="server_ipv4">Отримання імені вузла IPv4</string>
|
||||
<string name="prefer_ipv4">Віддавати перевагу з\'єднанню IPv4</string>
|
||||
<string name="dns_error">Не отримати кінцеву точку DNS.</string>
|
||||
<string name="start_failed_config">Неможливо запустити тунель із помилкою конфігурації.</string>
|
||||
<string name="unauthorized">Неможливо запустити тунель без авторизації.</string>
|
||||
<string name="tunne_start_failed_title">Помилка тунелю</string>
|
||||
<string name="multiple">Декілька</string>
|
||||
<string name="export_amnezia">Експортувати як Amnezia</string>
|
||||
<string name="export_wireguard">Експортувати як WireGuard</string>
|
||||
</resources>
|
||||
|
||||
@@ -65,7 +65,7 @@
|
||||
<string name="config_changes_saved">设置已保存。</string>
|
||||
<string name="interface_">接口</string>
|
||||
<string name="email_subject">WG Tunnel 支持</string>
|
||||
<string name="auto_tunnel_title">自动连接服务</string>
|
||||
<string name="auto_tunnel_title">自动隧道服务</string>
|
||||
<string name="delete_tunnel">删除隧道</string>
|
||||
<string name="delete_tunnel_message">确定删除这个隧道吗?</string>
|
||||
<string name="location_services_missing_message">此应用不会在您的设备上检测任何已开启的定位服务。根据不同的设备,可能会导致无法获得不可信 WiFi 的名称。您想继续吗?</string>
|
||||
@@ -108,7 +108,7 @@
|
||||
<string name="vpn_denied_dialog_title">拒绝访问</string>
|
||||
<string name="tunnel_required">此功能需要至少一个隧道</string>
|
||||
<string name="app_settings">应用设置</string>
|
||||
<string name="background_location_message2">请确保这些权限已开启。</string>
|
||||
<string name="background_location_message2">请确保这些权限已开启</string>
|
||||
<string name="logs">日志</string>
|
||||
<string name="restart_on_ping">Ping 失败之后自动重启隧道(beta)</string>
|
||||
<string name="edit_tunnel">编辑隧道</string>
|
||||
@@ -160,4 +160,49 @@
|
||||
<string name="enable_local_logging">开启本地日志</string>
|
||||
<string name="configuration_change">配置更改</string>
|
||||
<string name="requires_app_relaunch">此更改需要重新启动应用程序。您是否要继续?</string>
|
||||
<string name="stop_on_no_internet">无网络时停用</string>
|
||||
<string name="stop_on_internet_loss">网络丢失时停止隧道</string>
|
||||
<string name="bypass_lan_for_kill_switch">绕过局域网流量</string>
|
||||
<string name="auto_tunnel_channel_name">自动隧道通知频道</string>
|
||||
<string name="auto_tunnel_channel_description">自动隧道状态通知频道</string>
|
||||
<string name="stop">停止</string>
|
||||
<string name="tunnel_specific_settings">隧道特殊设置</string>
|
||||
<string name="splt_tunneling">隧道分流</string>
|
||||
<string name="show_scripts">显示脚本</string>
|
||||
<string name="pre_up">启动前</string>
|
||||
<string name="post_up">启动后</string>
|
||||
<string name="pre_down">关闭前</string>
|
||||
<string name="post_down">关闭后</string>
|
||||
<string name="amnezia_kernel_message">Amnezia 在内核模式中不可用</string>
|
||||
<string name="enable_amnezia">开启 Amnezia</string>
|
||||
<string name="wg_compat_mode">WG 兼容性模式</string>
|
||||
<string name="quick_actions">快捷操作</string>
|
||||
<string name="advanced_settings">高级设置</string>
|
||||
<string name="debounce_delay">防抖延迟</string>
|
||||
<string name="hide_amnezia_properties">隐藏 Amnezia 属性</string>
|
||||
<string name="hide_scripts">隐藏脚本</string>
|
||||
<string name="enable_amnezia_compatibility">开启 Amnezia 兼容性</string>
|
||||
<string name="remove_amnezia_compatibility">移除 Amnezia 兼容性</string>
|
||||
<string name="exclude_lan">排除局域网</string>
|
||||
<string name="include_lan">包含局域网</string>
|
||||
<string name="error_tunnel_start">开启隧道失败</string>
|
||||
<string name="tunnel_control">隧道控制</string>
|
||||
<string name="auto_tunnel">自动隧道</string>
|
||||
<string name="ethernet_tunnel">以太网隧道</string>
|
||||
<string name="set_ethernet_tunnel">设置为以太网隧道</string>
|
||||
<string name="native_kill_switch">系统 VPN 开关</string>
|
||||
<string name="vpn_kill_switch">VPN 开关</string>
|
||||
<string name="kill_switch_options">开关选项</string>
|
||||
<string name="allow_lan_traffic">允许局域网流量</string>
|
||||
<string name="vpn_channel_description">VPN 状态通知频道</string>
|
||||
<string name="kill_switch_off">在受信任网络上停止 Kill Switch</string>
|
||||
<string name="prefer_ipv4">首选 IPv4 连接</string>
|
||||
<string name="dns_error">解析端点 DNS 失败。</string>
|
||||
<string name="unauthorized">身份验证未通过,启动流量隧道失败。</string>
|
||||
<string name="tunne_start_failed_title">隧道失败</string>
|
||||
<string name="multiple">多个</string>
|
||||
<string name="server_ipv4">IPv4 主机名解析</string>
|
||||
<string name="start_failed_config">配置文件错误,启动流量隧道失败。</string>
|
||||
<string name="export_amnezia">导出为 Amnezia</string>
|
||||
<string name="export_wireguard">导出为 WireGuard</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,2 +1,101 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
<resources>
|
||||
<string name="sec">秒</string>
|
||||
<string name="error_file_extension">檔案類型不是 .conf 或 .zip</string>
|
||||
<string name="language">語言</string>
|
||||
<string name="interface_">界面</string>
|
||||
<string name="launch_app_settings">打開應用程式設定</string>
|
||||
<string name="use_kernel">使用核心模組</string>
|
||||
<string name="version">版本</string>
|
||||
<string name="app_name">WG Tunnel</string>
|
||||
<string name="name">名稱</string>
|
||||
<string name="public_key">公鑰</string>
|
||||
<string name="privacy_policy">查看隱私政策</string>
|
||||
<string name="tunnels">隧道列表</string>
|
||||
<string name="thank_you">感謝您使用 WG Tunnel!</string>
|
||||
<string name="open_file">開啟檔案</string>
|
||||
<string name="mtu">MTU</string>
|
||||
<string name="okay">好</string>
|
||||
<string name="qr_scan">掃描 QR code</string>
|
||||
<string name="dns_servers">DNS 伺服器</string>
|
||||
<string name="tunnel_name">隧道名稱</string>
|
||||
<string name="config_changes_saved">組態變更已儲存。</string>
|
||||
<string name="exclude">排除</string>
|
||||
<string name="include">包含</string>
|
||||
<string name="addresses">地址</string>
|
||||
<string name="add_tunnels_text">從檔案或 zip 壓縮檔新增</string>
|
||||
<string name="add_from_qr">從 QR code 新增</string>
|
||||
<string name="copy_public_key">複製公鑰</string>
|
||||
<string name="optional_no_recommend">(可選, 不建議)</string>
|
||||
<string name="optional">(可選)</string>
|
||||
<string name="random">(隨機)</string>
|
||||
<string name="cancel">取消</string>
|
||||
<string name="private_key">私鑰</string>
|
||||
<string name="listen_port">監聽連接埠</string>
|
||||
<string name="export_configs">匯出組態</string>
|
||||
<string name="create_import">手動建立</string>
|
||||
<string name="seconds">秒</string>
|
||||
<string name="docs_description">閱讀文件</string>
|
||||
<string name="error_ssid_exists">SSID 已經存在</string>
|
||||
<string name="delete_tunnel">刪除隧道</string>
|
||||
<string name="delete_tunnel_message">您確定要刪除此隧道?</string>
|
||||
<string name="yes">是</string>
|
||||
<string name="error_invalid_code">無效的 QR code</string>
|
||||
<string name="no_browser_detected">未安裝瀏覽器</string>
|
||||
<string name="no_email_detected">未安裝電子郵件應用程式</string>
|
||||
<string name="open_issue">建立新的問題</string>
|
||||
<string name="auto">(自動)</string>
|
||||
<string name="create_pin">建立 PIN</string>
|
||||
<string name="enter_pin">輸入 PIN</string>
|
||||
<string name="pin_created">成功建立 PIN</string>
|
||||
<string name="incorrect_pin">PIN 不正確</string>
|
||||
<string name="enable_app_lock">啟用應用程式鎖定</string>
|
||||
<string name="set_primary_tunnel">設為主要隧道</string>
|
||||
<string name="edit_tunnel">編輯隧道</string>
|
||||
<string name="kernel">核心</string>
|
||||
<string name="vpn_settings">系統 VPN 設定</string>
|
||||
<string name="support">支持</string>
|
||||
<string name="getting_started_guide">取得入門指南</string>
|
||||
<string name="settings">設定</string>
|
||||
<string name="restart_at_boot">開機時重新啟動</string>
|
||||
<string name="chat_description">加入社區</string>
|
||||
<string name="junk_packet_count">無效封包數</string>
|
||||
<string name="set_custom_ping_internal">Ping 間隔 (秒)</string>
|
||||
<string name="app_settings">應用程式設定</string>
|
||||
<string name="logs">日誌</string>
|
||||
<string name="dark">暗色</string>
|
||||
<string name="light">亮色</string>
|
||||
<string name="donate">捐贈給專案</string>
|
||||
<string name="appearance">外觀</string>
|
||||
<string name="display_theme">主題</string>
|
||||
<string name="primary_tunnel">主要隧道</string>
|
||||
<string name="learn_more">了解更多</string>
|
||||
<string name="kernel_not_supported">核心不支持</string>
|
||||
<string name="notifications">通知</string>
|
||||
<string name="dynamic">動態</string>
|
||||
<string name="never">從不</string>
|
||||
<string name="automatic">自動</string>
|
||||
<string name="add_wifi_name">新增WiFi SSID</string>
|
||||
<string name="allow_lan_traffic">允許 LAN 流量</string>
|
||||
<string name="configuration_change">組態變更</string>
|
||||
<string name="stop_on_no_internet">沒有連上網路時停用</string>
|
||||
<string name="stop_on_internet_loss">網路連線斷開時停止隧道</string>
|
||||
<string name="add_from_clipboard">從剪貼簿新增</string>
|
||||
<string name="stop">停止</string>
|
||||
<string name="amnezia_kernel_message">Amnezia 無法在核心模式使用</string>
|
||||
<string name="enable_amnezia">啟用 Amnezia</string>
|
||||
<string name="wg_compat_mode">WG 相容性模式</string>
|
||||
<string name="advanced_settings">進階設定</string>
|
||||
<string name="exclude_lan">排除 LAN</string>
|
||||
<string name="include_lan">包含 LAN</string>
|
||||
<string name="error_tunnel_start">啟用隧道失敗</string>
|
||||
<string name="unknown_error">發生未知錯誤</string>
|
||||
<string name="error_file_format">無效的隧道組態檔案格式</string>
|
||||
<string name="endpoint">端點</string>
|
||||
<string name="location_services_not_detected">未啟用定位服務</string>
|
||||
<string name="junk_packet_maximum_size">最大無效封包</string>
|
||||
<string name="no_tunnels">還沒有新增任何隧道!</string>
|
||||
<string name="allowed_ips">允許的 IP</string>
|
||||
<string name="junk_packet_minimum_size">最小無效封包</string>
|
||||
<string name="error_no_file_explorer">未安裝任何檔案管理器</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
object Constants {
|
||||
const val VERSION_NAME = "3.6.6"
|
||||
const val VERSION_NAME = "3.7.2"
|
||||
const val JVM_TARGET = "17"
|
||||
const val VERSION_CODE = 36600
|
||||
const val VERSION_CODE = 37200
|
||||
const val TARGET_SDK = 35
|
||||
const val MIN_SDK = 26
|
||||
const val APP_ID = "com.zaneschepke.wireguardautotunnel"
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
Was ist neu?
|
||||
- Die Ping-Funktion funktioniert jetzt unabhängig vom automatischen Tunnel
|
||||
- Komfortaktion für Amnezia-Kompatibilität hinzugefügt
|
||||
- Komfortaktion zum Ausschließen von LAN aus dem Tunnel hinzugefügt
|
||||
- Option zur Einstellung der Entprellungszeit für Autotunnel hinzugefügt
|
||||
- Viele Fehlerkorrekturen und Verbesserungen
|
||||
@@ -0,0 +1,6 @@
|
||||
What's new:
|
||||
- Multiple tunnel support for kernel mode
|
||||
- Override for WG default DNS Ipv4 preference
|
||||
- Stop kill switch on trusted support
|
||||
- Limit location querying by auto tunnel
|
||||
- Various bug fixes and improvements
|
||||
@@ -0,0 +1,5 @@
|
||||
What's new:
|
||||
- Static IPv6 peer endpoint bug fix
|
||||
- Dynamic shortcuts bug fix
|
||||
- Localizations
|
||||
- Add peer bug fix
|
||||
@@ -0,0 +1,5 @@
|
||||
What's new:
|
||||
- Auto tunnel regression fix
|
||||
- Tile sync improvements
|
||||
- Optimize wifi name querying
|
||||
- Improve network monitoring permission checks
|
||||
@@ -0,0 +1,14 @@
|
||||
Features
|
||||
|
||||
- Add tunnels via .conf file, zip, manual entry, or QR code
|
||||
- Auto connect to VPN based on Wi-Fi SSID, ethernet, or mobile data
|
||||
- Split tunneling by application with search
|
||||
- WireGuard support for kernel and userspace modes
|
||||
- Amnezia support for userspace mode for DPI/censorship protection
|
||||
- Always-On VPN support
|
||||
- Export Amnezia and WireGuard tunnels to zip
|
||||
- Quick tile support for VPN toggling
|
||||
- Static shortcuts support for primary tunnel for automation integration
|
||||
- Intent automation support for all tunnels
|
||||
- Automatic service restart after reboot
|
||||
- Battery preservation measures
|
||||
@@ -0,0 +1,6 @@
|
||||
Novità:
|
||||
- aggiornamenti UI
|
||||
- Migliorie navigazione AndroidTV
|
||||
- Bug consumo batteria risolto
|
||||
- Correzione impostazioni opzionali caratteri jolly
|
||||
- Altre migliorie
|
||||
@@ -0,0 +1,7 @@
|
||||
Novità:
|
||||
- Aggiungi tunnel dagli appunti
|
||||
- Aggiunte localizzazioni
|
||||
- Corretto bug consumo batteria
|
||||
- Corretto bug cancellazione
|
||||
- Migliorata sincronizzazione tile tunnel
|
||||
- Altre correzioni e migliorie
|
||||
@@ -0,0 +1,5 @@
|
||||
Novità:
|
||||
- Aggiunto interruttore spegnimento VPN con esclusione LAN
|
||||
- Migliorata velocità e stabilità auto tunnel
|
||||
- Migliorata sincronizzazione tile
|
||||
- Vari bug risolti e migliorie
|
||||
@@ -0,0 +1,3 @@
|
||||
Novità:
|
||||
- Corretto bug commutatore modalità kernel
|
||||
- Corretto bug crash notifica
|
||||
@@ -0,0 +1,6 @@
|
||||
Novità:
|
||||
- Migliorata stabilità auto tunnel
|
||||
- Corretto salvataggio split tunnel e migliore prestazioni
|
||||
- Corretto bug modalità kernel
|
||||
- Corretto bug tile
|
||||
- Vare altre correzioni e migliorie
|
||||
@@ -0,0 +1,6 @@
|
||||
Novità:
|
||||
- Il ping ora funziona indipendentemente dall'auto tunnel
|
||||
- Aggiunte azioni per compatibilità Amnezia
|
||||
- Aggiunte azioni esclusione LAN dal tunnel
|
||||
- Aggiunta opzione regolazione tempo di rimbalzo per auto tunnel
|
||||
- Molte correzioni a bug e migliore
|
||||
@@ -1 +0,0 @@
|
||||
Uma alternativa de cliente WireGuard VPN com recursos adicionais
|
||||
@@ -0,0 +1,3 @@
|
||||
Поліпшення:
|
||||
- Виправлена помилка з дозволами на Android < 9
|
||||
- Інші оптимізації
|
||||
@@ -0,0 +1,5 @@
|
||||
Поліпшення:
|
||||
- Додано статистику тунелю на головний екран
|
||||
- Поліпшено навігацію в екрані налаштувань на Android TV
|
||||
- Видалено вібрацію при оповіщенні
|
||||
- Інші виправлення
|
||||
@@ -0,0 +1,5 @@
|
||||
Поліпшення:
|
||||
- Додана підтримка авто-тунелювання лише через мобільну мережу
|
||||
- Покращено інтерфейс екрану підтримки
|
||||
- Оновлено посилання на ресурси
|
||||
- Інші виправлення помилок
|
||||
@@ -0,0 +1,5 @@
|
||||
Поліпшення:
|
||||
- Базова підтримка модуля WireGuard режиму ядра
|
||||
- Покращено процес розкриття інформації про місцезнаходження
|
||||
- Виправлено помилку з дозволами авто-тунелю
|
||||
- Інші виправлення інтерфейсу
|
||||
@@ -0,0 +1,2 @@
|
||||
Виправлення:
|
||||
- Дозвіл переднього плану в Android 14
|
||||
@@ -0,0 +1,7 @@
|
||||
Поліпшення:
|
||||
- Рефакторинг зберігання даних додатком
|
||||
- Поліпшено навігацію в інтерфейсі для Android TV
|
||||
- Збільшено ефективність авто-тунелювання
|
||||
- Поліпшено навігацію
|
||||
- Можливість призупинити роботу авто-тунелю
|
||||
- Безліч виправлень помилок
|
||||
@@ -0,0 +1,7 @@
|
||||
Поліпшення:
|
||||
- Рефакторинг зберігання даних додатком
|
||||
- Поліпшено навігацію в інтерфейсі для Android TV
|
||||
- Збільшено ефективність авто-тунелювання
|
||||
- Поліпшено навігацію
|
||||
- Можливість призупинити роботу авто-тунелю
|
||||
- Безліч виправлень помилок
|
||||
@@ -0,0 +1,8 @@
|
||||
Поліпшення:
|
||||
- Рефакторинг зберігання даних додатком
|
||||
- Поліпшено навігацію в інтерфейсі для Android TV
|
||||
- Збільшено ефективність авто-тунелювання
|
||||
- Поліпшено навігацію
|
||||
- Можливість призупинити роботу авто-тунелю
|
||||
- Виправлено запуск авто-тунелю на передньому плані
|
||||
- Безліч виправлень помилок
|
||||
@@ -0,0 +1,7 @@
|
||||
Поліпшення:
|
||||
- Додано підтвердження під час видалення тунелю
|
||||
- Додано дозвіл на роботу у фоні
|
||||
Виправлення:
|
||||
- Зависання програми при відключенні тунелю
|
||||
- Помилка у полі отримувача електронної пошти
|
||||
- Редагування конфігурації з порожнім полем DNS
|
||||
@@ -0,0 +1,3 @@
|
||||
Поліпшення:
|
||||
- Виправлено збій збереження створених налаштувань
|
||||
- Збільшено номер версії
|
||||
@@ -0,0 +1,2 @@
|
||||
Що нового:
|
||||
- Тестова версія для налагодження Continuous Integration
|
||||
@@ -0,0 +1,5 @@
|
||||
Що нового:
|
||||
- Автозапуск «Завжди увімкненого» VPN режиму ядра при перезавантаженні системи
|
||||
- Підтримка піктограм адаптивної теми
|
||||
- Виправлені значки повідомлень та плитки
|
||||
- Виправлені значки Android TV
|
||||
@@ -0,0 +1,5 @@
|
||||
Що нового:
|
||||
- Покращено процес першого запуску програми
|
||||
- Перехід на альтернативну реалізацію бібліотеки WireGuard
|
||||
- Запит дозволу на доступ до VPN під час першого підключення VPN
|
||||
- Збільшено номер версії
|
||||
@@ -0,0 +1,2 @@
|
||||
Що нового:
|
||||
- Виправлена помилка в інтерфейсі тунелю
|
||||
@@ -0,0 +1,4 @@
|
||||
Що нового:
|
||||
- Виправлено помилку в інтерфейсі редактора конфігурації
|
||||
- Додано повідомлення AOVPN під час першого запуску на Graphene OS
|
||||
- Збільшено номер версії
|
||||
@@ -0,0 +1,5 @@
|
||||
Що нового:
|
||||
- Доданий екран із журналами виконання
|
||||
- Додано блокування програми
|
||||
- Додано перезапуск тунелю при збої пінгу
|
||||
- Різні виправлення помилок
|
||||
@@ -0,0 +1,5 @@
|
||||
Що нового:
|
||||
- Авто-тунель залежно від імені мережі Wi-Fi
|
||||
- Управління авто-тунелем через плитку та ярлики
|
||||
- Автозапуск тунелів, активних на момент перезавантаження
|
||||
- Інші виправлення та покращення продуктивності
|
||||
@@ -0,0 +1,5 @@
|
||||
Що нового:
|
||||
- Поліпшено надійність роботи авто-тунелю
|
||||
- Поліпшено синхронізацію плитки
|
||||
- Додані ресурси для інтерфейсу Android TV
|
||||
- Доданий відбиток APK
|
||||
@@ -0,0 +1,5 @@
|
||||
Що нового:
|
||||
- Виправлена регресія із зупинкою тунелю
|
||||
- Додано обфускацію лог-файлів
|
||||
- Приховування плаваючої кнопки під час прокручування екрану
|
||||
- Додано переклад на турецьку
|
||||
@@ -0,0 +1,3 @@
|
||||
Що нового:
|
||||
- Додавання профілів Amnezia пліч-о-пліч з WireGuard
|
||||
- Виправлена помилка з ярликами програми
|
||||
@@ -0,0 +1,6 @@
|
||||
Що нового:
|
||||
- Офіційна підтримка AmneziaWG
|
||||
- Імпорт/експорт конфігурації AmneziaWG
|
||||
- Одноразове перемикання авто-тунелю при зміні стану мережі
|
||||
- Підтримка нових мов
|
||||
- Інші виправлення та покращення
|
||||
@@ -0,0 +1,5 @@
|
||||
Що нового:
|
||||
- Покращено призначення імен під час імпорту тунелів
|
||||
- Виправлено помилку початкового стану авто-тунелю
|
||||
- Поліпшено обробку помилок
|
||||
- Виправлена помилка імпорту Amnezia з .zip
|
||||
@@ -0,0 +1,5 @@
|
||||
Що нового:
|
||||
- Додані нові переклади
|
||||
- Виправлено роботу авто-тунелю на мобільному інтернеті
|
||||
- Виправлено проблему з плаваючою кнопкою на Android TV
|
||||
- Інші оптимізації та покращення
|
||||
@@ -0,0 +1,4 @@
|
||||
Що нового:
|
||||
- Виправлено проблеми з роботою авто-тунелю
|
||||
- Виправлено проблему з резервним копіюванням Android
|
||||
- Збільшено номер версії
|
||||
@@ -0,0 +1,6 @@
|
||||
Що нового:
|
||||
- Виправлено проблеми зі збоями
|
||||
- Поліпшено продуктивність плиток
|
||||
- Повернуто блокування PIN-кодом
|
||||
- Додано функцію перезапуску під час завантаження
|
||||
- Різні виправлення продуктивності та помилок
|
||||
@@ -0,0 +1,5 @@
|
||||
Що нового:
|
||||
- Виправлення інтерфейсу керування тунелем на AndroidTV
|
||||
- Виправлення помилки блокування у портретній орієнтації
|
||||
- Виправлена помилка обходу блокування PIN-кодом
|
||||
- Виправлена помилка з плиткою авто-тунелю
|
||||
@@ -0,0 +1,6 @@
|
||||
Що нового:
|
||||
- Підвищена надійність авто-тунелювання
|
||||
- Додана підтримка світлих/темних/динамічних тем
|
||||
- Додана підтримка сценаріїв до/після запуску/зупинки інтерфейсу
|
||||
- Видалено постійне повідомлення про тунель
|
||||
- Різні інші виправлення та покращення
|
||||
@@ -0,0 +1,5 @@
|
||||
Що нового:
|
||||
- Виправлені помилки, через які тунелі не запускалися у фоновому режимі
|
||||
- Додана підтримка перезапуску служб після оновлення
|
||||
- Поліпшено швидкість анімації інтерфейсу
|
||||
- Інші оптимізації
|
||||
@@ -0,0 +1,6 @@
|
||||
Що нового:
|
||||
- Додано підтримку підстановочних знаків для імен мереж Wi-Fi
|
||||
- Редагування налаштувань тунелю/автотунелю безпосередньо під час його роботи
|
||||
- Виправлено уповільнення передачі даних у мобільних мережах
|
||||
- Різні виправлення помилок та покращення
|
||||
- Оптимізація інтерфейсу користувача
|
||||
@@ -0,0 +1,6 @@
|
||||
Що нового:
|
||||
- Пристрої з root-доступом тепер отримують ім'я Wi-Fi без використання розташування
|
||||
- Покращення прокручування екрана журналів та обміну
|
||||
- Виправлення помилок імпорту тунелів для AndroidTV 14
|
||||
- Поліпшення інтерфейсу статистики тунелів
|
||||
- Інші виправлення помилок та покращення
|
||||
@@ -0,0 +1,6 @@
|
||||
Що нового:
|
||||
- Оновлення інтерфейсу
|
||||
- Поліпшення навігації AndroidTV
|
||||
- Виправлення помилки розряду батареї
|
||||
- Виправлення підстановочних знаків додатковою настройкою
|
||||
- Інші покращення
|
||||
@@ -0,0 +1,7 @@
|
||||
Що нового:
|
||||
- Додавання тунелю з буфера обміну
|
||||
- Додавання перекладів
|
||||
- Виправлено помилку розряду батареї
|
||||
- Виправлена помилка видалення
|
||||
- Поліпшено синхронізацію плиток тунелю
|
||||
- Інші виправлення та покращення
|
||||
@@ -0,0 +1,5 @@
|
||||
Що нового:
|
||||
- Додано екстрене відключення VPN з обходом локальної мережі
|
||||
- Покращена швидкість та надійність автоматичного тунелювання
|
||||
- Покращена синхронізація плиток
|
||||
- Різні виправлення помилок та покращення
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user