mirror of
https://github.com/wgtunnel/android.git
synced 2026-07-03 14:07:49 +02:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 594ed85a71 |
@@ -144,8 +144,8 @@ android {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
|
||||||
implementation(project(":logcatter"))
|
implementation(project(":logcatter"))
|
||||||
implementation(project(":networkmonitor"))
|
|
||||||
|
|
||||||
implementation(libs.androidx.core.ktx)
|
implementation(libs.androidx.core.ktx)
|
||||||
implementation(libs.androidx.lifecycle.runtime.ktx)
|
implementation(libs.androidx.lifecycle.runtime.ktx)
|
||||||
|
|||||||
+1
-38
@@ -2,41 +2,4 @@
|
|||||||
|
|
||||||
-keepclassmembers class * extends androidx.datastore.preferences.protobuf.GeneratedMessageLite {
|
-keepclassmembers class * extends androidx.datastore.preferences.protobuf.GeneratedMessageLite {
|
||||||
<fields>;
|
<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
+1
-38
@@ -21,41 +21,4 @@
|
|||||||
#-renamesourcefileattribute SourceFile
|
#-renamesourcefileattribute SourceFile
|
||||||
-keepclassmembers class * extends androidx.datastore.preferences.protobuf.GeneratedMessageLite {
|
-keepclassmembers class * extends androidx.datastore.preferences.protobuf.GeneratedMessageLite {
|
||||||
<fields>;
|
<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,8 +3,13 @@
|
|||||||
xmlns:tools="http://schemas.android.com/tools">
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
<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" />
|
||||||
<!--foreground service exempt android 14-->
|
<!--foreground service exempt android 14-->
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SYSTEM_EXEMPTED" />
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SYSTEM_EXEMPTED" />
|
||||||
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM"
|
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM"
|
||||||
@@ -163,17 +168,25 @@
|
|||||||
</service>
|
</service>
|
||||||
|
|
||||||
<receiver
|
<receiver
|
||||||
android:name=".core.broadcast.RestartReceiver"
|
android:name=".core.broadcast.BootReceiver"
|
||||||
android:enabled="true"
|
android:enabled="true"
|
||||||
android:exported="true">
|
android:exported="false">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
|
||||||
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
<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="android.intent.action.QUICKBOOT_POWERON" />
|
||||||
<action android:name="com.htc.intent.action.QUICKBOOT_POWERON" />
|
<action android:name="com.htc.intent.action.QUICKBOOT_POWERON" />
|
||||||
<action android:name="android.intent.action.MY_PACKAGE_REPLACED" />
|
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</receiver>
|
</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
|
<receiver
|
||||||
android:name=".core.broadcast.KernelReceiver"
|
android:name=".core.broadcast.KernelReceiver"
|
||||||
android:exported="false"
|
android:exported="false"
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ import androidx.navigation.compose.rememberNavController
|
|||||||
import androidx.navigation.toRoute
|
import androidx.navigation.toRoute
|
||||||
import com.zaneschepke.wireguardautotunnel.core.shortcut.ShortcutManager
|
import com.zaneschepke.wireguardautotunnel.core.shortcut.ShortcutManager
|
||||||
import com.zaneschepke.wireguardautotunnel.core.tunnel.TunnelManager
|
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.domain.repository.AppStateRepository
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.Route
|
import com.zaneschepke.wireguardautotunnel.ui.Route
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.navigation.BottomNavBar
|
import com.zaneschepke.wireguardautotunnel.ui.common.navigation.BottomNavBar
|
||||||
@@ -66,6 +67,7 @@ import com.zaneschepke.wireguardautotunnel.ui.screens.support.LogsScreen
|
|||||||
import com.zaneschepke.wireguardautotunnel.ui.screens.support.SupportScreen
|
import com.zaneschepke.wireguardautotunnel.ui.screens.support.SupportScreen
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.theme.WireguardAutoTunnelTheme
|
import com.zaneschepke.wireguardautotunnel.ui.theme.WireguardAutoTunnelTheme
|
||||||
import com.zaneschepke.wireguardautotunnel.util.Constants
|
import com.zaneschepke.wireguardautotunnel.util.Constants
|
||||||
|
import com.zaneschepke.wireguardautotunnel.util.extensions.requestAutoTunnelTileServiceUpdate
|
||||||
import com.zaneschepke.wireguardautotunnel.viewmodel.AppViewModel
|
import com.zaneschepke.wireguardautotunnel.viewmodel.AppViewModel
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
@@ -119,13 +121,22 @@ class MainActivity : AppCompatActivity() {
|
|||||||
viewModel.getEmitSplitTunnelApps(this@MainActivity)
|
viewModel.getEmitSplitTunnelApps(this@MainActivity)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LaunchedEffect(appUiState.autoTunnelActive) {
|
||||||
|
requestAutoTunnelTileServiceUpdate()
|
||||||
|
}
|
||||||
|
|
||||||
with(appUiState.appSettings) {
|
with(appUiState.appSettings) {
|
||||||
|
LaunchedEffect(isAutoTunnelEnabled) {
|
||||||
|
this@MainActivity.requestAutoTunnelTileServiceUpdate()
|
||||||
|
}
|
||||||
LaunchedEffect(isShortcutsEnabled) {
|
LaunchedEffect(isShortcutsEnabled) {
|
||||||
if (!isShortcutsEnabled) return@LaunchedEffect shortcutManager.removeShortcuts()
|
if (!isShortcutsEnabled) return@LaunchedEffect shortcutManager.removeShortcuts()
|
||||||
shortcutManager.addShortcuts()
|
shortcutManager.addShortcuts()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ServiceWorker.start(this)
|
||||||
|
|
||||||
CompositionLocalProvider(LocalNavController provides navController) {
|
CompositionLocalProvider(LocalNavController provides navController) {
|
||||||
SnackbarControllerProvider { host ->
|
SnackbarControllerProvider { host ->
|
||||||
WireguardAutoTunnelTheme(theme = appUiState.generalState.theme) {
|
WireguardAutoTunnelTheme(theme = appUiState.generalState.theme) {
|
||||||
@@ -210,17 +221,16 @@ class MainActivity : AppCompatActivity() {
|
|||||||
composable<Route.Logs> {
|
composable<Route.Logs> {
|
||||||
LogsScreen()
|
LogsScreen()
|
||||||
}
|
}
|
||||||
composable<Route.Config> { backStack ->
|
composable<Route.Config> {
|
||||||
val args = backStack.toRoute<Route.Config>()
|
val args = it.toRoute<Route.Config>()
|
||||||
val config =
|
val config =
|
||||||
appUiState.tunnels.firstOrNull { it.id == args.id }
|
appUiState.tunnels.firstOrNull { it.id == args.id }
|
||||||
ConfigScreen(config, viewModel)
|
ConfigScreen(config, viewModel)
|
||||||
}
|
}
|
||||||
composable<Route.TunnelOptions> { backStack ->
|
composable<Route.TunnelOptions> {
|
||||||
val args = backStack.toRoute<Route.TunnelOptions>()
|
val args = it.toRoute<Route.TunnelOptions>()
|
||||||
appUiState.tunnels.firstOrNull { it.id == args.id }?.let { config ->
|
val config = appUiState.tunnels.first { it.id == args.id }
|
||||||
OptionsScreen(config, appUiState)
|
OptionsScreen(config)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
composable<Route.Lock> {
|
composable<Route.Lock> {
|
||||||
PinLockScreen(viewModel)
|
PinLockScreen(viewModel)
|
||||||
@@ -231,17 +241,15 @@ class MainActivity : AppCompatActivity() {
|
|||||||
composable<Route.KillSwitch> {
|
composable<Route.KillSwitch> {
|
||||||
KillSwitchScreen(appUiState, viewModel)
|
KillSwitchScreen(appUiState, viewModel)
|
||||||
}
|
}
|
||||||
composable<Route.SplitTunnel> { backStack ->
|
composable<Route.SplitTunnel> {
|
||||||
val args = backStack.toRoute<Route.SplitTunnel>()
|
val args = it.toRoute<Route.SplitTunnel>()
|
||||||
appUiState.tunnels.firstOrNull { it.id == args.id }?.let {
|
val config = appUiState.tunnels.first { it.id == args.id }
|
||||||
SplitTunnelScreen(it, viewModel)
|
SplitTunnelScreen(config, viewModel)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
composable<Route.TunnelAutoTunnel> { backStack ->
|
composable<Route.TunnelAutoTunnel> {
|
||||||
val args = backStack.toRoute<Route.TunnelOptions>()
|
val args = it.toRoute<Route.TunnelOptions>()
|
||||||
appUiState.tunnels.firstOrNull { it.id == args.id }?.let {
|
val config = appUiState.tunnels.first { it.id == args.id }
|
||||||
TunnelAutoTunnelScreen(it, appUiState.appSettings)
|
TunnelAutoTunnelScreen(config, appUiState.appSettings)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
BackHandler {
|
BackHandler {
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import androidx.work.Configuration
|
|||||||
import com.wireguard.android.backend.GoBackend
|
import com.wireguard.android.backend.GoBackend
|
||||||
import com.zaneschepke.logcatter.LogReader
|
import com.zaneschepke.logcatter.LogReader
|
||||||
import com.zaneschepke.wireguardautotunnel.core.tunnel.TunnelManager
|
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.ApplicationScope
|
||||||
import com.zaneschepke.wireguardautotunnel.di.IoDispatcher
|
import com.zaneschepke.wireguardautotunnel.di.IoDispatcher
|
||||||
import com.zaneschepke.wireguardautotunnel.di.MainDispatcher
|
import com.zaneschepke.wireguardautotunnel.di.MainDispatcher
|
||||||
@@ -92,11 +91,9 @@ class WireGuardAutoTunnel : Application(), Configuration.Provider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ServiceWorker.start(this)
|
|
||||||
|
|
||||||
applicationScope.launch {
|
applicationScope.launch {
|
||||||
withContext(mainDispatcher) {
|
withContext(mainDispatcher) {
|
||||||
if (appDataRepository.appState.isLocalLogsEnabled() && !isRunningOnTv()) logReader.start()
|
if (appDataRepository.appState.isLocalLogsEnabled() && !isRunningOnTv()) logReader.initialize()
|
||||||
}
|
}
|
||||||
if (!appDataRepository.settings.get().isKernelEnabled) {
|
if (!appDataRepository.settings.get().isKernelEnabled) {
|
||||||
tunnelManager.setBackendState(BackendState.SERVICE_ACTIVE, emptyList())
|
tunnelManager.setBackendState(BackendState.SERVICE_ACTIVE, emptyList())
|
||||||
|
|||||||
+45
@@ -0,0 +1,45 @@
|
|||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
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
|
val action = intent.action ?: return
|
||||||
applicationScope.launch {
|
applicationScope.launch {
|
||||||
if (action == REFRESH_TUNNELS_ACTION) {
|
if (action == REFRESH_TUNNELS_ACTION) {
|
||||||
tunnelManager.runningTunnelNames().forEach { name ->
|
tunnelManager.runningTunnelNames().forEach {
|
||||||
val tunnel = tunnelRepository.findByTunnelName(name)
|
val tunnel = tunnelRepository.findByTunnelName(it)
|
||||||
tunnel?.let {
|
tunnel?.let {
|
||||||
tunnelRepository.save(it.copy(isActive = true))
|
tunnelRepository.save(it.copy(isActive = true))
|
||||||
}
|
}
|
||||||
|
|||||||
-64
@@ -1,64 +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 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
@@ -0,0 +1,161 @@
|
|||||||
|
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
@@ -0,0 +1,16 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
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?
|
||||||
|
}
|
||||||
+53
-94
@@ -3,35 +3,29 @@ package com.zaneschepke.wireguardautotunnel.core.service
|
|||||||
import android.app.Service
|
import android.app.Service
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
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.autotunnel.AutoTunnelService
|
||||||
import com.zaneschepke.wireguardautotunnel.core.service.tile.AutoTunnelControlTile
|
import com.zaneschepke.wireguardautotunnel.core.service.tile.AutoTunnelControlTile
|
||||||
import com.zaneschepke.wireguardautotunnel.core.service.tile.TunnelControlTile
|
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.entity.TunnelConf
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.repository.AppDataRepository
|
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.requestAutoTunnelTileServiceUpdate
|
import com.zaneschepke.wireguardautotunnel.util.extensions.requestAutoTunnelTileServiceUpdate
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.requestTunnelTileServiceStateUpdate
|
import com.zaneschepke.wireguardautotunnel.util.extensions.requestTunnelTileServiceStateUpdate
|
||||||
import jakarta.inject.Inject
|
import jakarta.inject.Inject
|
||||||
import kotlinx.coroutines.CompletableDeferred
|
import kotlinx.coroutines.CompletableDeferred
|
||||||
import kotlinx.coroutines.CoroutineDispatcher
|
import kotlinx.coroutines.CoroutineDispatcher
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import kotlinx.coroutines.withTimeoutOrNull
|
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
class ServiceManager @Inject constructor(
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
private val context: Context,
|
class ServiceManager
|
||||||
@IoDispatcher private val ioDispatcher: CoroutineDispatcher,
|
@Inject constructor(private val context: Context, private val ioDispatcher: CoroutineDispatcher, private val appDataRepository: AppDataRepository) {
|
||||||
@ApplicationScope private val applicationScope: CoroutineScope,
|
|
||||||
private val appDataRepository: AppDataRepository,
|
|
||||||
) {
|
|
||||||
|
|
||||||
private val _autoTunnelActive = MutableStateFlow(false)
|
private val _autoTunnelActive = MutableStateFlow(false)
|
||||||
|
|
||||||
val autoTunnelActive = _autoTunnelActive.asStateFlow()
|
val autoTunnelActive = _autoTunnelActive.asStateFlow()
|
||||||
|
|
||||||
var autoTunnelService = CompletableDeferred<AutoTunnelService>()
|
var autoTunnelService = CompletableDeferred<AutoTunnelService>()
|
||||||
@@ -50,111 +44,76 @@ class ServiceManager @Inject constructor(
|
|||||||
}.onFailure { Timber.e(it) }
|
}.onFailure { Timber.e(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun startAutoTunnel(background: Boolean) {
|
suspend fun startAutoTunnel(background: Boolean) {
|
||||||
applicationScope.launch(ioDispatcher) {
|
val settings = appDataRepository.settings.get()
|
||||||
val settings = appDataRepository.settings.get()
|
appDataRepository.settings.save(settings.copy(isAutoTunnelEnabled = true))
|
||||||
appDataRepository.settings.save(settings.copy(isAutoTunnelEnabled = true))
|
if (autoTunnelService.isCompleted) return _autoTunnelActive.update { true }
|
||||||
if (autoTunnelService.isCompleted) {
|
runCatching {
|
||||||
_autoTunnelActive.update { true }
|
startService(AutoTunnelService::class.java, background)
|
||||||
return@launch
|
autoTunnelService.await()
|
||||||
}
|
autoTunnelService.getCompleted().start()
|
||||||
runCatching {
|
_autoTunnelActive.update { true }
|
||||||
autoTunnelService = CompletableDeferred()
|
updateAutoTunnelTile()
|
||||||
startService(AutoTunnelService::class.java, background)
|
}.onFailure {
|
||||||
val service = withTimeoutOrNull(SERVICE_START_TIMEOUT) { autoTunnelService.await() }
|
Timber.e(it)
|
||||||
?: throw IllegalStateException("AutoTunnelService start timed out")
|
|
||||||
service.start()
|
|
||||||
_autoTunnelActive.update { true }
|
|
||||||
updateAutoTunnelTile()
|
|
||||||
}.onFailure {
|
|
||||||
Timber.e(it)
|
|
||||||
_autoTunnelActive.update { false }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun startBackgroundService(tunnelConf: TunnelConf) {
|
suspend fun startBackgroundService(tunnelConf: TunnelConf) {
|
||||||
applicationScope.launch(ioDispatcher) {
|
if (backgroundService.isCompleted) return
|
||||||
if (backgroundService.isCompleted) return@launch
|
runCatching {
|
||||||
runCatching {
|
startService(TunnelForegroundService::class.java, true)
|
||||||
backgroundService = CompletableDeferred()
|
backgroundService.await()
|
||||||
startService(TunnelForegroundService::class.java, true)
|
backgroundService.getCompleted().start(tunnelConf)
|
||||||
val service = withTimeoutOrNull(SERVICE_START_TIMEOUT) { backgroundService.await() }
|
}.onFailure {
|
||||||
?: throw IllegalStateException("Background service start timed out")
|
Timber.e(it)
|
||||||
service.start(tunnelConf)
|
|
||||||
}.onFailure {
|
|
||||||
Timber.e(it)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun stopBackgroundService() {
|
fun stopBackgroundService() {
|
||||||
applicationScope.launch(ioDispatcher) {
|
if (!backgroundService.isCompleted) return
|
||||||
if (!backgroundService.isCompleted) return@launch
|
runCatching {
|
||||||
runCatching {
|
backgroundService.getCompleted().stop()
|
||||||
val service = backgroundService.await()
|
}.onFailure {
|
||||||
service.stop()
|
Timber.e(it)
|
||||||
backgroundService = CompletableDeferred()
|
|
||||||
}.onFailure {
|
|
||||||
Timber.e(it)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun toggleAutoTunnel(background: Boolean) {
|
suspend fun toggleAutoTunnel(background: Boolean) {
|
||||||
applicationScope.launch(ioDispatcher) {
|
|
||||||
if (_autoTunnelActive.value) stopAutoTunnel() else startAutoTunnel(background)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun updateAutoTunnelTile() {
|
|
||||||
withContext(ioDispatcher) {
|
withContext(ioDispatcher) {
|
||||||
runCatching {
|
if (_autoTunnelActive.value) return@withContext stopAutoTunnel()
|
||||||
val service = withTimeoutOrNull(SERVICE_START_TIMEOUT) { autoTunnelTile.await() }
|
startAutoTunnel(background)
|
||||||
?: run {
|
|
||||||
context.requestAutoTunnelTileServiceUpdate()
|
|
||||||
return@withContext
|
|
||||||
}
|
|
||||||
service.updateTileState()
|
|
||||||
}.onFailure {
|
|
||||||
Timber.e(it)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun updateTunnelTile() {
|
fun updateAutoTunnelTile() {
|
||||||
|
if (autoTunnelTile.isCompleted) {
|
||||||
|
autoTunnelTile.getCompleted().updateTileState()
|
||||||
|
} else {
|
||||||
|
context.requestAutoTunnelTileServiceUpdate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateTunnelTile() {
|
||||||
|
if (tunnelControlTile.isCompleted) {
|
||||||
|
tunnelControlTile.getCompleted().updateTileState()
|
||||||
|
} else {
|
||||||
|
context.requestTunnelTileServiceStateUpdate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun stopAutoTunnel() {
|
||||||
withContext(ioDispatcher) {
|
withContext(ioDispatcher) {
|
||||||
runCatching {
|
|
||||||
val service = withTimeoutOrNull(SERVICE_START_TIMEOUT) { tunnelControlTile.await() }
|
|
||||||
?: run {
|
|
||||||
context.requestTunnelTileServiceStateUpdate()
|
|
||||||
return@withContext
|
|
||||||
}
|
|
||||||
service.updateTileState()
|
|
||||||
}.onFailure {
|
|
||||||
Timber.e(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun stopAutoTunnel() {
|
|
||||||
applicationScope.launch(ioDispatcher) {
|
|
||||||
val settings = appDataRepository.settings.get()
|
val settings = appDataRepository.settings.get()
|
||||||
appDataRepository.settings.save(settings.copy(isAutoTunnelEnabled = false))
|
appDataRepository.settings.save(settings.copy(isAutoTunnelEnabled = false))
|
||||||
if (!autoTunnelService.isCompleted) return@launch
|
if (!autoTunnelService.isCompleted) return@withContext
|
||||||
runCatching {
|
runCatching {
|
||||||
val service = autoTunnelService.await()
|
autoTunnelService.getCompleted().stop()
|
||||||
service.stop()
|
|
||||||
_autoTunnelActive.update { false }
|
_autoTunnelActive.update { false }
|
||||||
autoTunnelService = CompletableDeferred()
|
|
||||||
updateAutoTunnelTile()
|
updateAutoTunnelTile()
|
||||||
}.onFailure {
|
}.onFailure {
|
||||||
Timber.e(it)
|
Timber.e(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
|
||||||
const val SERVICE_START_TIMEOUT = 5_000L
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-1
@@ -35,8 +35,9 @@ class TunnelForegroundService : LifecycleService() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||||
|
super.onStartCommand(intent, flags, startId)
|
||||||
serviceManager.backgroundService.complete(this)
|
serviceManager.backgroundService.complete(this)
|
||||||
return super.onStartCommand(intent, flags, startId)
|
return START_NOT_STICKY
|
||||||
}
|
}
|
||||||
|
|
||||||
fun start(tunnelConf: TunnelConf) {
|
fun start(tunnelConf: TunnelConf) {
|
||||||
|
|||||||
+48
-46
@@ -1,42 +1,44 @@
|
|||||||
package com.zaneschepke.wireguardautotunnel.core.service.autotunnel
|
package com.zaneschepke.wireguardautotunnel.core.service.autotunnel
|
||||||
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.net.NetworkCapabilities
|
||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
import android.os.PowerManager
|
import android.os.PowerManager
|
||||||
import androidx.core.app.ServiceCompat
|
import androidx.core.app.ServiceCompat
|
||||||
import androidx.lifecycle.LifecycleService
|
import androidx.lifecycle.LifecycleService
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import com.zaneschepke.networkmonitor.NetworkMonitor
|
import com.wireguard.android.util.RootShell
|
||||||
import com.zaneschepke.networkmonitor.NetworkStatus
|
|
||||||
import com.zaneschepke.wireguardautotunnel.R
|
import com.zaneschepke.wireguardautotunnel.R
|
||||||
import com.zaneschepke.wireguardautotunnel.core.notification.NotificationManager
|
import com.zaneschepke.wireguardautotunnel.domain.repository.AppDataRepository
|
||||||
import com.zaneschepke.wireguardautotunnel.core.notification.WireGuardNotification
|
import com.zaneschepke.wireguardautotunnel.di.AppShell
|
||||||
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.IoDispatcher
|
||||||
import com.zaneschepke.wireguardautotunnel.di.MainImmediateDispatcher
|
import com.zaneschepke.wireguardautotunnel.di.MainImmediateDispatcher
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.entity.AppSettings
|
import com.zaneschepke.wireguardautotunnel.core.service.ServiceManager
|
||||||
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.AutoTunnelState
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.state.NetworkState
|
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.tunnel.TunnelManager
|
||||||
|
import com.zaneschepke.wireguardautotunnel.domain.entity.AppSettings
|
||||||
|
import com.zaneschepke.wireguardautotunnel.domain.enums.BackendState
|
||||||
|
import com.zaneschepke.wireguardautotunnel.domain.events.AutoTunnelEvent
|
||||||
|
import com.zaneschepke.wireguardautotunnel.domain.events.KillSwitchEvent
|
||||||
import com.zaneschepke.wireguardautotunnel.util.Constants
|
import com.zaneschepke.wireguardautotunnel.util.Constants
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.Tunnels
|
import com.zaneschepke.wireguardautotunnel.util.extensions.Tunnels
|
||||||
|
import com.zaneschepke.wireguardautotunnel.util.extensions.getCurrentWifiName
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import kotlinx.coroutines.CompletableDeferred
|
import kotlinx.coroutines.CompletableDeferred
|
||||||
import kotlinx.coroutines.CoroutineDispatcher
|
import kotlinx.coroutines.CoroutineDispatcher
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
|
||||||
import kotlinx.coroutines.FlowPreview
|
import kotlinx.coroutines.FlowPreview
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
import kotlinx.coroutines.flow.debounce
|
import kotlinx.coroutines.flow.debounce
|
||||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
import kotlinx.coroutines.flow.flatMapLatest
|
|
||||||
import kotlinx.coroutines.flow.flowOn
|
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
@@ -47,6 +49,10 @@ import javax.inject.Provider
|
|||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
class AutoTunnelService : LifecycleService() {
|
class AutoTunnelService : LifecycleService() {
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
@AppShell
|
||||||
|
lateinit var rootShell: Provider<RootShell>
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var networkMonitor: NetworkMonitor
|
lateinit var networkMonitor: NetworkMonitor
|
||||||
|
|
||||||
@@ -94,9 +100,10 @@ class AutoTunnelService : LifecycleService() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||||
|
super.onStartCommand(intent, flags, startId)
|
||||||
Timber.d("onStartCommand executed with startId: $startId")
|
Timber.d("onStartCommand executed with startId: $startId")
|
||||||
serviceManager.autoTunnelService.complete(this)
|
serviceManager.autoTunnelService.complete(this)
|
||||||
return super.onStartCommand(intent, flags, startId)
|
return START_NOT_STICKY
|
||||||
}
|
}
|
||||||
|
|
||||||
fun start() {
|
fun start() {
|
||||||
@@ -155,54 +162,49 @@ class AutoTunnelService : LifecycleService() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun buildNetworkState(networkStatus: NetworkStatus): NetworkState {
|
private suspend fun buildNetworkState(connectivityState: ConnectivityState): NetworkState {
|
||||||
return with(autoTunnelStateFlow.value.networkState) {
|
return with(autoTunnelStateFlow.value.networkState) {
|
||||||
val wifiName = when (networkStatus) {
|
val wifiName = when {
|
||||||
is NetworkStatus.Connected -> {
|
connectivityState.wifiAvailable &&
|
||||||
networkStatus.wifiSsid
|
(wifiName == null || wifiName == Constants.UNREADABLE_SSID || networkMonitor.didWifiChangeSinceLastCapabilitiesQuery) -> {
|
||||||
|
networkMonitor.getWifiCapabilities()?.let { getWifiName(it) } ?: wifiName
|
||||||
}
|
}
|
||||||
else -> null
|
!connectivityState.wifiAvailable -> null
|
||||||
|
else -> wifiName
|
||||||
}
|
}
|
||||||
copy(
|
copy(
|
||||||
isWifiConnected = networkStatus.wifiConnected,
|
isWifiConnected = connectivityState.wifiAvailable,
|
||||||
isMobileDataConnected = networkStatus.cellularConnected,
|
isMobileDataConnected = connectivityState.cellularAvailable,
|
||||||
isEthernetConnected = networkStatus.ethernetConnected,
|
isEthernetConnected = isEthernetConnected,
|
||||||
wifiName = wifiName,
|
wifiName = wifiName,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
|
||||||
private fun startAutoTunnelStateJob() = lifecycleScope.launch(ioDispatcher) {
|
private fun startAutoTunnelStateJob() = lifecycleScope.launch(ioDispatcher) {
|
||||||
combine(
|
combine(
|
||||||
combineSettings(),
|
combineSettings(),
|
||||||
appDataRepository.get().settings.flow
|
networkMonitor.status.map {
|
||||||
.distinctUntilChanged { old, new -> old.isKernelEnabled == new.isKernelEnabled } // Only emit when isKernelEnabled changes
|
buildNetworkState(it)
|
||||||
.flatMapLatest { settings ->
|
}.distinctUntilChanged(),
|
||||||
networkMonitor.getNetworkStatusFlow(true, settings.isKernelEnabled)
|
|
||||||
.flowOn(ioDispatcher)
|
|
||||||
.map { buildNetworkState(it) }
|
|
||||||
}
|
|
||||||
.distinctUntilChanged(),
|
|
||||||
) { double, networkState ->
|
) { double, networkState ->
|
||||||
AutoTunnelState(
|
AutoTunnelState(tunnelManager.activeTunnels().value, networkState, double.first, double.second)
|
||||||
tunnelManager.activeTunnels.value,
|
|
||||||
networkState,
|
|
||||||
double.first,
|
|
||||||
double.second,
|
|
||||||
)
|
|
||||||
}.collect { state ->
|
}.collect { state ->
|
||||||
autoTunnelStateFlow.update {
|
autoTunnelStateFlow.update {
|
||||||
it.copy(
|
it.copy(activeTunnels = state.activeTunnels, networkState = state.networkState, settings = state.settings, tunnels = state.tunnels)
|
||||||
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>> {
|
private fun combineSettings(): Flow<Pair<AppSettings, Tunnels>> {
|
||||||
return combine(
|
return combine(
|
||||||
appDataRepository.get().settings.flow,
|
appDataRepository.get().settings.flow,
|
||||||
@@ -239,7 +241,7 @@ class AutoTunnelService : LifecycleService() {
|
|||||||
Timber.d("Starting with debounce delay of: ${settings.debounceDelaySeconds} seconds")
|
Timber.d("Starting with debounce delay of: ${settings.debounceDelaySeconds} seconds")
|
||||||
autoTunnelStateFlow.debounce(settings.debounceDelayMillis()).collect { watcherState ->
|
autoTunnelStateFlow.debounce(settings.debounceDelayMillis()).collect { watcherState ->
|
||||||
if (watcherState == defaultState) return@collect
|
if (watcherState == defaultState) return@collect
|
||||||
Timber.d("New auto tunnel state emitted ${watcherState.networkState}")
|
Timber.d("New auto tunnel state emitted")
|
||||||
when (val event = watcherState.asAutoTunnelEvent()) {
|
when (val event = watcherState.asAutoTunnelEvent()) {
|
||||||
is AutoTunnelEvent.Start -> (event.tunnelConf ?: appDataRepository.get().getPrimaryOrFirstTunnel())?.let {
|
is AutoTunnelEvent.Start -> (event.tunnelConf ?: appDataRepository.get().getPrimaryOrFirstTunnel())?.let {
|
||||||
tunnelManager.startTunnel(it)
|
tunnelManager.startTunnel(it)
|
||||||
|
|||||||
+4
-9
@@ -52,14 +52,9 @@ class TunnelControlTile : TileService() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun updateTileState() = applicationScope.launch {
|
fun updateTileState() = applicationScope.launch {
|
||||||
val tunnels = appDataRepository.tunnels.getAll()
|
if (appDataRepository.tunnels.getAll().isEmpty()) return@launch setUnavailable()
|
||||||
if (tunnels.isEmpty()) return@launch setUnavailable()
|
with(tunnelManager.activeTunnels().value) {
|
||||||
with(tunnelManager.activeTunnels.value) {
|
if (isNotEmpty()) return@launch updateTile(if (size == 1) first().tunName else getString(R.string.multiple), true)
|
||||||
if (isNotEmpty()) if (size == 1) {
|
|
||||||
tunnels.firstOrNull { it.id == keys.first() }?.let { return@launch updateTile(it.tunName, true) }
|
|
||||||
} else {
|
|
||||||
return@launch updateTile(getString(R.string.multiple), true)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
appDataRepository.getStartTunnelConfig()?.let {
|
appDataRepository.getStartTunnelConfig()?.let {
|
||||||
updateTile(it.tunName, false)
|
updateTile(it.tunName, false)
|
||||||
@@ -70,7 +65,7 @@ class TunnelControlTile : TileService() {
|
|||||||
super.onClick()
|
super.onClick()
|
||||||
unlockAndRun {
|
unlockAndRun {
|
||||||
applicationScope.launch {
|
applicationScope.launch {
|
||||||
if (tunnelManager.activeTunnels.value.isNotEmpty()) return@launch tunnelManager.stopTunnel()
|
if (tunnelManager.activeTunnels().value.isNotEmpty()) return@launch tunnelManager.stopTunnel()
|
||||||
appDataRepository.getStartTunnelConfig()?.let {
|
appDataRepository.getStartTunnelConfig()?.let {
|
||||||
tunnelManager.startTunnel(it)
|
tunnelManager.startTunnel(it)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
package com.zaneschepke.wireguardautotunnel.core.tunnel
|
package com.zaneschepke.wireguardautotunnel.core.tunnel
|
||||||
|
|
||||||
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.R
|
||||||
import com.zaneschepke.wireguardautotunnel.WireGuardAutoTunnel
|
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.NotificationManager
|
||||||
import com.zaneschepke.wireguardautotunnel.core.notification.WireGuardNotification
|
import com.zaneschepke.wireguardautotunnel.core.notification.WireGuardNotification
|
||||||
import com.zaneschepke.wireguardautotunnel.core.service.ServiceManager
|
import com.zaneschepke.wireguardautotunnel.core.service.ServiceManager
|
||||||
@@ -16,12 +14,10 @@ import com.zaneschepke.wireguardautotunnel.domain.enums.BackendError
|
|||||||
import com.zaneschepke.wireguardautotunnel.domain.enums.BackendState
|
import com.zaneschepke.wireguardautotunnel.domain.enums.BackendState
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.enums.TunnelStatus
|
import com.zaneschepke.wireguardautotunnel.domain.enums.TunnelStatus
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.repository.AppDataRepository
|
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.domain.state.TunnelStatistics
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.snackbar.SnackbarController
|
import com.zaneschepke.wireguardautotunnel.ui.common.snackbar.SnackbarController
|
||||||
import com.zaneschepke.wireguardautotunnel.util.Constants
|
import com.zaneschepke.wireguardautotunnel.util.Constants
|
||||||
import com.zaneschepke.wireguardautotunnel.util.StringValue
|
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.cancelWithMessage
|
||||||
import kotlinx.coroutines.CoroutineDispatcher
|
import kotlinx.coroutines.CoroutineDispatcher
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
@@ -31,12 +27,11 @@ import kotlinx.coroutines.delay
|
|||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
import kotlinx.coroutines.flow.flowOn
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.isActive
|
import kotlinx.coroutines.isActive
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean
|
import java.util.concurrent.atomic.AtomicBoolean
|
||||||
|
|
||||||
open class BaseTunnel(
|
open class BaseTunnel(
|
||||||
@@ -50,98 +45,79 @@ open class BaseTunnel(
|
|||||||
|
|
||||||
internal val tunnels = MutableStateFlow<List<TunnelConf>>(emptyList())
|
internal val tunnels = MutableStateFlow<List<TunnelConf>>(emptyList())
|
||||||
|
|
||||||
private val _tunnelStates = MutableStateFlow<Map<Int, TunnelState>>(emptyMap())
|
private val tunnelJobs = mutableMapOf<TunnelConf, Job>()
|
||||||
|
|
||||||
private val tunnelJobs = ConcurrentHashMap<Int, Job>()
|
|
||||||
|
|
||||||
private val isNetworkAvailable = AtomicBoolean(false)
|
private val isNetworkAvailable = AtomicBoolean(false)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
applicationScope.launch(ioDispatcher) {
|
applicationScope.launch(ioDispatcher) {
|
||||||
launch { startNetworkJob() }
|
launch {
|
||||||
launch { monitorTunnelConfigChanges() }
|
startNetworkJob()
|
||||||
|
}
|
||||||
tunnels.collect { tuns ->
|
tunnels.collect { tuns ->
|
||||||
val previousTunIds = tunnelJobs.keys.toSet()
|
val previousTuns = tunnelJobs.keys.toSet()
|
||||||
val currentTunIds = tuns.map { it.id }.toSet()
|
val newTuns = tuns - previousTuns
|
||||||
val newTuns = tuns.filter { it.id !in previousTunIds }
|
val removedItems = previousTuns - tuns.toSet()
|
||||||
val removedTunIds = previousTunIds - currentTunIds
|
|
||||||
|
|
||||||
newTuns.forEach { tun ->
|
newTuns.forEach { tun ->
|
||||||
Timber.d("Starting tunnel jobs for tun ${tun.name} (ID: ${tun.id})")
|
Timber.d("Starting tunnel jobs for tun ${tun.name}")
|
||||||
tunnelJobs[tun.id] = startTunnelJobs(tun)
|
tunnelJobs[tun] = startTunnelJobs(tun)
|
||||||
}
|
}
|
||||||
|
|
||||||
removedTunIds.forEach { tunId ->
|
removedItems.forEach { tun ->
|
||||||
tunnelJobs[tunId]?.cancelWithMessage("Canceling tunnel jobs for tunnel ID: $tunId")
|
tunnelJobs[tun]?.cancelWithMessage("Canceling tunnel jobs for tunnel: ${tun.name}")
|
||||||
tunnelJobs.remove(tunId)
|
tunnelJobs.remove(tun)
|
||||||
_tunnelStates.update { it - tunId }
|
|
||||||
serviceManager.updateTunnelTile()
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun startTunnelJobs(tunnel: TunnelConf) = applicationScope.launch(ioDispatcher) {
|
|
||||||
launch { startTunnelStatisticsJob(tunnel) }
|
|
||||||
if (tunnel.isPingEnabled) launch { startPingJob(tunnel) }
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun updateTunnelState(tunnelId: Int, newState: TunnelStatus) {
|
|
||||||
Timber.d("Updating tunnel state for ID $tunnelId to $newState")
|
|
||||||
_tunnelStates.update { current ->
|
|
||||||
val currentState = current[tunnelId]
|
|
||||||
val updatedState = currentState?.copy(state = newState) ?: TunnelState(state = newState)
|
|
||||||
val newMap = current + (tunnelId to updatedState)
|
|
||||||
Timber.d("New tunnel states: $newMap")
|
|
||||||
newMap
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun beforeStartTunnel(tunnelConf: TunnelConf) {
|
|
||||||
tunnelConf.setStateChangeCallback { state ->
|
|
||||||
Timber.d("New tunnel state $state")
|
|
||||||
when (state) {
|
|
||||||
is Tunnel.State -> updateTunnelState(tunnelConf.id, state.asTunnelState())
|
|
||||||
is org.amnezia.awg.backend.Tunnel.State -> updateTunnelState(tunnelConf.id, state.asTunnelState())
|
|
||||||
}
|
|
||||||
applicationScope.launch(ioDispatcher) {
|
|
||||||
serviceManager.updateTunnelTile()
|
serviceManager.updateTunnelTile()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun startTunnel(tunnelConf: TunnelConf) {
|
private fun startTunnelJobs(tunnel: TunnelConf) = applicationScope.launch(ioDispatcher) {
|
||||||
applicationScope.launch(ioDispatcher) {
|
launch {
|
||||||
serviceManager.startBackgroundService(tunnelConf)
|
startTunnelStatisticsJob(tunnel)
|
||||||
appDataRepository.tunnels.save(tunnelConf.copy(isActive = true))
|
}
|
||||||
addToActiveTunnels(tunnelConf)
|
launch {
|
||||||
|
startPingJob(tunnel)
|
||||||
|
}
|
||||||
|
launch {
|
||||||
|
startTunnelConfigChangeJob(tunnel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun stopTunnel(tunnelConf: TunnelConf?) {
|
|
||||||
// Default empty implementation; subclasses override
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun bounceTunnel(tunnelConf: TunnelConf) {
|
override suspend fun bounceTunnel(tunnelConf: TunnelConf) {
|
||||||
stopTunnel(tunnelConf)
|
if (tunnels.value.any { it.id == tunnelConf.id }) {
|
||||||
delay(1000)
|
toggleTunnel(tunnelConf, TunnelStatus.DOWN)
|
||||||
startTunnel(tunnelConf)
|
toggleTunnel(tunnelConf, TunnelStatus.UP)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun setBackendState(backendState: BackendState, allowedIps: Collection<String>) {
|
override suspend fun setBackendState(backendState: BackendState, allowedIps: Collection<String>) {
|
||||||
// Default empty implementation
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun runningTunnelNames(): Set<String> {
|
override suspend fun runningTunnelNames(): Set<String> {
|
||||||
return emptySet()
|
return emptySet()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getStatistics(tunnelConf: TunnelConf): TunnelStatistics {
|
override suspend fun activeTunnels(): StateFlow<List<TunnelConf>> {
|
||||||
throw NotImplementedError("Get statistics not implemented in base class")
|
return tunnels.asStateFlow()
|
||||||
}
|
}
|
||||||
|
|
||||||
override val activeTunnels: StateFlow<Map<Int, TunnelState>>
|
override suspend fun startTunnel(tunnelConf: TunnelConf) {
|
||||||
get() = _tunnelStates.asStateFlow()
|
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) {
|
internal suspend fun onTunnelStop(tunnelConf: TunnelConf) {
|
||||||
appDataRepository.tunnels.save(tunnelConf.copy(isActive = false))
|
appDataRepository.tunnels.save(tunnelConf.copy(isActive = false))
|
||||||
@@ -149,13 +125,13 @@ open class BaseTunnel(
|
|||||||
if (tunnels.value.isEmpty()) serviceManager.stopBackgroundService()
|
if (tunnels.value.isEmpty()) serviceManager.stopBackgroundService()
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun stopAllTunnels() {
|
internal suspend fun stopAllTunnels() {
|
||||||
tunnels.value.forEach {
|
tunnels.value.forEach {
|
||||||
stopTunnel(it)
|
stopTunnel(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun addToActiveTunnels(conf: TunnelConf) {
|
internal fun addToActiveTunnels(conf: TunnelConf) {
|
||||||
tunnels.update {
|
tunnels.update {
|
||||||
it.toMutableList().apply {
|
it.toMutableList().apply {
|
||||||
add(conf)
|
add(conf)
|
||||||
@@ -172,26 +148,23 @@ open class BaseTunnel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun startNetworkJob() = coroutineScope {
|
private suspend fun startNetworkJob() = coroutineScope {
|
||||||
networkMonitor.getNetworkStatusFlow(includeWifiSsid = false, useRootShell = false)
|
networkMonitor.status.distinctUntilChanged().collect {
|
||||||
.flowOn(ioDispatcher).collect {
|
isNetworkAvailable.set(!it.allOffline)
|
||||||
isNetworkAvailable.set(it !is NetworkStatus.Disconnected)
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun startPingJob(tunnel: TunnelConf) = coroutineScope {
|
private suspend fun startPingJob(tunnel: TunnelConf) = coroutineScope {
|
||||||
while (isActive) {
|
while (isActive) {
|
||||||
runCatching {
|
if (isNetworkAvailable.get() && tunnel.isActive) {
|
||||||
if (isNetworkAvailable.get() && tunnel.isActive) {
|
val pingResult = tunnel.pingTunnel(ioDispatcher)
|
||||||
val pingSuccess = tunnel.isTunnelPingable(ioDispatcher)
|
handlePingResult(tunnel, pingResult)
|
||||||
handlePingResult(tunnel, pingSuccess)
|
|
||||||
}
|
|
||||||
delay(tunnel.pingInterval ?: Constants.PING_INTERVAL)
|
|
||||||
}
|
}
|
||||||
|
delay(CHECK_INTERVAL)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun handlePingResult(tunnel: TunnelConf, pingSuccess: Boolean) {
|
private suspend fun handlePingResult(tunnel: TunnelConf, pingResult: List<Boolean>) {
|
||||||
if (!pingSuccess) {
|
if (pingResult.contains(false)) {
|
||||||
if (isNetworkAvailable.get()) {
|
if (isNetworkAvailable.get()) {
|
||||||
Timber.i("Ping result: target was not reachable, bouncing the tunnel")
|
Timber.i("Ping result: target was not reachable, bouncing the tunnel")
|
||||||
bounceTunnel(tunnel)
|
bounceTunnel(tunnel)
|
||||||
@@ -224,33 +197,23 @@ open class BaseTunnel(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun monitorTunnelConfigChanges() = coroutineScope {
|
private suspend fun startTunnelConfigChangeJob(tunnel: TunnelConf) = coroutineScope {
|
||||||
appDataRepository.tunnels.flow.collect { storageTuns ->
|
appDataRepository.tunnels.flow.collect { storageTuns ->
|
||||||
storageTuns.forEach { storageTun ->
|
storageTuns.firstOrNull { it.id == tunnel.id }?.let { storageTun ->
|
||||||
val currentTun = tunnels.value.firstOrNull { it.id == storageTun.id }
|
if (tunnel.isQuickConfigChanged(storageTun) || tunnel.isPingConfigMatching(storageTun)) {
|
||||||
if (currentTun != null) {
|
bounceTunnel(tunnel)
|
||||||
if (!currentTun.isQuickConfigMatching(storageTun)) {
|
|
||||||
Timber.d("Tunnel config changed for ID $storageTun, bouncing tunnel")
|
|
||||||
bounceTunnel(storageTun)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun startTunnelStatisticsJob(tunnel: TunnelConf) = coroutineScope {
|
private suspend fun startTunnelStatisticsJob(tunnel: TunnelConf) = coroutineScope {
|
||||||
while (this.isActive) {
|
while (isActive) {
|
||||||
runCatching {
|
val stats = getStatistics(tunnel)
|
||||||
val stats = getStatistics(tunnel)
|
tunnel.state.update {
|
||||||
_tunnelStates.update { currentStates ->
|
it.copy(statistics = stats)
|
||||||
val updatedState = currentStates[tunnel.id]?.copy(statistics = stats)
|
|
||||||
?: TunnelState(statistics = stats)
|
|
||||||
currentStates + (tunnel.id to updatedState)
|
|
||||||
}
|
|
||||||
delay(CHECK_INTERVAL)
|
|
||||||
}.onFailure { exception ->
|
|
||||||
Timber.e(exception, "Failed to update tunnel statistics for ${tunnel.tunName}")
|
|
||||||
}
|
}
|
||||||
|
delay(CHECK_INTERVAL)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,20 +3,21 @@ package com.zaneschepke.wireguardautotunnel.core.tunnel
|
|||||||
import com.wireguard.android.backend.Backend
|
import com.wireguard.android.backend.Backend
|
||||||
import com.wireguard.android.backend.BackendException
|
import com.wireguard.android.backend.BackendException
|
||||||
import com.wireguard.android.backend.Tunnel
|
import com.wireguard.android.backend.Tunnel
|
||||||
import com.zaneschepke.networkmonitor.NetworkMonitor
|
import com.zaneschepke.wireguardautotunnel.core.network.NetworkMonitor
|
||||||
import com.zaneschepke.wireguardautotunnel.core.notification.NotificationManager
|
import com.zaneschepke.wireguardautotunnel.core.notification.NotificationManager
|
||||||
import com.zaneschepke.wireguardautotunnel.core.service.ServiceManager
|
import com.zaneschepke.wireguardautotunnel.core.service.ServiceManager
|
||||||
import com.zaneschepke.wireguardautotunnel.di.ApplicationScope
|
import com.zaneschepke.wireguardautotunnel.di.ApplicationScope
|
||||||
import com.zaneschepke.wireguardautotunnel.di.IoDispatcher
|
import com.zaneschepke.wireguardautotunnel.di.IoDispatcher
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.entity.TunnelConf
|
import com.zaneschepke.wireguardautotunnel.domain.entity.TunnelConf
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.enums.BackendState
|
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.repository.AppDataRepository
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.state.TunnelStatistics
|
import com.zaneschepke.wireguardautotunnel.domain.state.TunnelStatistics
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.state.WireGuardStatistics
|
import com.zaneschepke.wireguardautotunnel.domain.state.WireGuardStatistics
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.toBackendError
|
import com.zaneschepke.wireguardautotunnel.util.extensions.toBackendError
|
||||||
import kotlinx.coroutines.CoroutineDispatcher
|
import kotlinx.coroutines.CoroutineDispatcher
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.withContext
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@@ -30,18 +31,13 @@ class KernelTunnel @Inject constructor(
|
|||||||
networkMonitor: NetworkMonitor,
|
networkMonitor: NetworkMonitor,
|
||||||
) : BaseTunnel(ioDispatcher, applicationScope, networkMonitor, appDataRepository, serviceManager, notificationManager) {
|
) : BaseTunnel(ioDispatcher, applicationScope, networkMonitor, appDataRepository, serviceManager, notificationManager) {
|
||||||
|
|
||||||
override fun startTunnel(tunnelConf: TunnelConf) {
|
override suspend fun startTunnel(tunnelConf: TunnelConf) {
|
||||||
Timber.d("Starting tunnel ${tunnelConf.id} kernel")
|
withContext(ioDispatcher) {
|
||||||
applicationScope.launch(ioDispatcher) {
|
super.startTunnel(tunnelConf)
|
||||||
if (tunnels.value.any { it.id == tunnelConf.id }) return@launch Timber.w("Tunnel already running")
|
|
||||||
runCatching {
|
runCatching {
|
||||||
Timber.d("Setting backend state UP")
|
|
||||||
super.beforeStartTunnel(tunnelConf)
|
|
||||||
backend.setState(tunnelConf, Tunnel.State.UP, tunnelConf.toWgConfig())
|
backend.setState(tunnelConf, Tunnel.State.UP, tunnelConf.toWgConfig())
|
||||||
Timber.d("Calling super.startTunnel")
|
addToActiveTunnels(tunnelConf)
|
||||||
super.startTunnel(tunnelConf)
|
|
||||||
}.onFailure {
|
}.onFailure {
|
||||||
Timber.e(it, "Failed to start tunnel ${tunnelConf.id} kernel")
|
|
||||||
onTunnelStop(tunnelConf)
|
onTunnelStop(tunnelConf)
|
||||||
if (it is BackendException) {
|
if (it is BackendException) {
|
||||||
handleBackendThrowable(it.toBackendError())
|
handleBackendThrowable(it.toBackendError())
|
||||||
@@ -52,14 +48,15 @@ class KernelTunnel @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getStatistics(tunnelConf: TunnelConf): TunnelStatistics {
|
override suspend fun getStatistics(tunnelConf: TunnelConf): TunnelStatistics {
|
||||||
return WireGuardStatistics(backend.getStatistics(tunnelConf))
|
return WireGuardStatistics(backend.getStatistics(tunnelConf))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun stopTunnel(tunnelConf: TunnelConf?) {
|
override suspend fun stopTunnel(tunnelConf: TunnelConf?) {
|
||||||
applicationScope.launch(ioDispatcher) {
|
withContext(ioDispatcher) {
|
||||||
|
val tunnel = tunnels.value.firstOrNull { it.id == tunnelConf?.id }
|
||||||
runCatching {
|
runCatching {
|
||||||
tunnels.value.firstOrNull { it.id == tunnelConf?.id }?.let {
|
tunnel?.let {
|
||||||
backend.setState(it, Tunnel.State.DOWN, it.toWgConfig())
|
backend.setState(it, Tunnel.State.DOWN, it.toWgConfig())
|
||||||
onTunnelStop(it)
|
onTunnelStop(it)
|
||||||
} ?: stopAllTunnels()
|
} ?: stopAllTunnels()
|
||||||
@@ -69,6 +66,13 @@ class KernelTunnel @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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>) {
|
override suspend fun setBackendState(backendState: BackendState, allowedIps: Collection<String>) {
|
||||||
Timber.w("Not yet implemented for kernel")
|
Timber.w("Not yet implemented for kernel")
|
||||||
}
|
}
|
||||||
|
|||||||
+53
-51
@@ -4,20 +4,20 @@ import com.zaneschepke.wireguardautotunnel.di.ApplicationScope
|
|||||||
import com.zaneschepke.wireguardautotunnel.di.IoDispatcher
|
import com.zaneschepke.wireguardautotunnel.di.IoDispatcher
|
||||||
import com.zaneschepke.wireguardautotunnel.di.Kernel
|
import com.zaneschepke.wireguardautotunnel.di.Kernel
|
||||||
import com.zaneschepke.wireguardautotunnel.di.Userspace
|
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.entity.TunnelConf
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.enums.BackendState
|
import com.zaneschepke.wireguardautotunnel.domain.enums.BackendState
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.repository.AppDataRepository
|
import com.zaneschepke.wireguardautotunnel.domain.repository.AppDataRepository
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.state.TunnelStatistics
|
import com.zaneschepke.wireguardautotunnel.util.extensions.withData
|
||||||
import kotlinx.coroutines.CoroutineDispatcher
|
import kotlinx.coroutines.CoroutineDispatcher
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
|
||||||
import kotlinx.coroutines.flow.SharingStarted
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.filterNotNull
|
import kotlinx.coroutines.flow.filterNotNull
|
||||||
import kotlinx.coroutines.flow.flatMapLatest
|
import kotlinx.coroutines.flow.first
|
||||||
import kotlinx.coroutines.flow.stateIn
|
import kotlinx.coroutines.flow.stateIn
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlinx.coroutines.plus
|
import kotlinx.coroutines.plus
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class TunnelManager @Inject constructor(
|
class TunnelManager @Inject constructor(
|
||||||
@@ -28,69 +28,71 @@ class TunnelManager @Inject constructor(
|
|||||||
@IoDispatcher private val ioDispatcher: CoroutineDispatcher,
|
@IoDispatcher private val ioDispatcher: CoroutineDispatcher,
|
||||||
) : TunnelProvider {
|
) : TunnelProvider {
|
||||||
|
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
val appSettings: StateFlow<AppSettings?> = appDataRepository.settings.flow.stateIn(
|
||||||
private val tunnelProviderFlow = appDataRepository.settings.flow
|
scope = applicationScope.plus(ioDispatcher),
|
||||||
.filterNotNull()
|
started = SharingStarted.Eagerly,
|
||||||
.flatMapLatest { settings ->
|
initialValue = null,
|
||||||
MutableStateFlow(if (settings.isKernelEnabled) kernelTunnel else userspaceTunnel)
|
)
|
||||||
}
|
|
||||||
.stateIn(
|
|
||||||
scope = applicationScope.plus(ioDispatcher),
|
|
||||||
started = SharingStarted.Eagerly,
|
|
||||||
initialValue = userspaceTunnel,
|
|
||||||
)
|
|
||||||
|
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
override suspend fun activeTunnels(): StateFlow<List<TunnelConf>> {
|
||||||
override val activeTunnels = appDataRepository.settings.flow
|
return withContext(ioDispatcher) {
|
||||||
.filterNotNull()
|
appSettings.filterNotNull().first().let {
|
||||||
.flatMapLatest { settings ->
|
if (it.isKernelEnabled) return@withContext kernelTunnel.activeTunnels()
|
||||||
if (settings.isKernelEnabled) {
|
userspaceTunnel.activeTunnels()
|
||||||
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 fun stopTunnel(tunnelConf: TunnelConf?) {
|
override suspend fun startTunnel(tunnelConf: TunnelConf) {
|
||||||
tunnelProviderFlow.value.stopTunnel(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 suspend fun bounceTunnel(tunnelConf: TunnelConf) {
|
override suspend fun bounceTunnel(tunnelConf: TunnelConf) {
|
||||||
tunnelProviderFlow.value.bounceTunnel(tunnelConf)
|
appSettings.withData {
|
||||||
|
if (it.isKernelEnabled) return@withData kernelTunnel.stopTunnel(tunnelConf)
|
||||||
|
userspaceTunnel.stopTunnel(tunnelConf)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun setBackendState(backendState: BackendState, allowedIps: Collection<String>) {
|
override suspend fun setBackendState(backendState: BackendState, allowedIps: Collection<String>) {
|
||||||
tunnelProviderFlow.value.setBackendState(backendState, allowedIps)
|
appSettings.withData {
|
||||||
|
if (it.isKernelEnabled) return@withData kernelTunnel.setBackendState(backendState, allowedIps)
|
||||||
|
userspaceTunnel.setBackendState(backendState, allowedIps)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun runningTunnelNames(): Set<String> {
|
override suspend fun runningTunnelNames(): Set<String> {
|
||||||
return tunnelProviderFlow.value.runningTunnelNames()
|
appSettings.filterNotNull().first().let {
|
||||||
|
if (it.isKernelEnabled) return kernelTunnel.runningTunnelNames()
|
||||||
|
return userspaceTunnel.runningTunnelNames()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getStatistics(tunnelConf: TunnelConf): TunnelStatistics {
|
suspend fun restorePreviousState() {
|
||||||
return tunnelProviderFlow.value.getStatistics(tunnelConf)
|
withContext(ioDispatcher) {
|
||||||
}
|
with(appDataRepository.settings.get()) {
|
||||||
|
if (isRestoreOnBootEnabled) {
|
||||||
fun restorePreviousState() = applicationScope.launch(ioDispatcher) {
|
val previouslyActiveTuns = appDataRepository.tunnels.getActive()
|
||||||
val settings = appDataRepository.settings.get()
|
// handle kernel mode
|
||||||
if (settings.isRestoreOnBootEnabled) {
|
val tunsToStart = previouslyActiveTuns.filterNot { tun -> activeTunnels().value.any { tun.id == it.id } }
|
||||||
val previouslyActiveTuns = appDataRepository.tunnels.getActive()
|
if (isKernelEnabled) {
|
||||||
val tunsToStart = previouslyActiveTuns.filterNot { tun -> activeTunnels.value.any { tun.id == it.key } }
|
return@withContext tunsToStart.forEach {
|
||||||
if (settings.isKernelEnabled) {
|
startTunnel(it)
|
||||||
return@launch tunsToStart.forEach {
|
}
|
||||||
startTunnel(it)
|
}
|
||||||
|
// handle userspace
|
||||||
|
if (activeTunnels().value.isEmpty()) tunsToStart.firstOrNull()?.let { startTunnel(it) }
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
tunsToStart.firstOrNull()?.let { startTunnel(it) }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+12
-9
@@ -2,20 +2,23 @@ package com.zaneschepke.wireguardautotunnel.core.tunnel
|
|||||||
|
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.entity.TunnelConf
|
import com.zaneschepke.wireguardautotunnel.domain.entity.TunnelConf
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.enums.BackendState
|
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
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
|
||||||
interface TunnelProvider {
|
interface TunnelProvider {
|
||||||
companion object {
|
|
||||||
const val CHECK_INTERVAL = 1000L
|
|
||||||
}
|
|
||||||
|
|
||||||
fun startTunnel(tunnelConf: TunnelConf)
|
suspend fun activeTunnels(): StateFlow<List<TunnelConf>>
|
||||||
fun stopTunnel(tunnelConf: TunnelConf? = null)
|
|
||||||
|
suspend fun startTunnel(tunnelConf: TunnelConf)
|
||||||
|
|
||||||
|
suspend fun stopTunnel(tunnelConf: TunnelConf? = null)
|
||||||
|
|
||||||
suspend fun bounceTunnel(tunnelConf: TunnelConf)
|
suspend fun bounceTunnel(tunnelConf: TunnelConf)
|
||||||
|
|
||||||
suspend fun setBackendState(backendState: BackendState, allowedIps: Collection<String>)
|
suspend fun setBackendState(backendState: BackendState, allowedIps: Collection<String>)
|
||||||
|
|
||||||
suspend fun runningTunnelNames(): Set<String>
|
suspend fun runningTunnelNames(): Set<String>
|
||||||
fun getStatistics(tunnelConf: TunnelConf): TunnelStatistics
|
|
||||||
val activeTunnels: StateFlow<Map<Int, TunnelState>>
|
companion object {
|
||||||
|
const val CHECK_INTERVAL = 1_000L
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+21
-22
@@ -1,13 +1,14 @@
|
|||||||
package com.zaneschepke.wireguardautotunnel.core.tunnel
|
package com.zaneschepke.wireguardautotunnel.core.tunnel
|
||||||
|
|
||||||
import com.wireguard.android.backend.BackendException
|
import com.wireguard.android.backend.BackendException
|
||||||
import com.zaneschepke.networkmonitor.NetworkMonitor
|
import com.zaneschepke.wireguardautotunnel.core.network.NetworkMonitor
|
||||||
import com.zaneschepke.wireguardautotunnel.core.notification.NotificationManager
|
import com.zaneschepke.wireguardautotunnel.core.notification.NotificationManager
|
||||||
import com.zaneschepke.wireguardautotunnel.core.service.ServiceManager
|
import com.zaneschepke.wireguardautotunnel.core.service.ServiceManager
|
||||||
import com.zaneschepke.wireguardautotunnel.di.ApplicationScope
|
import com.zaneschepke.wireguardautotunnel.di.ApplicationScope
|
||||||
import com.zaneschepke.wireguardautotunnel.di.IoDispatcher
|
import com.zaneschepke.wireguardautotunnel.di.IoDispatcher
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.entity.TunnelConf
|
import com.zaneschepke.wireguardautotunnel.domain.entity.TunnelConf
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.enums.BackendState
|
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.repository.AppDataRepository
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.state.AmneziaStatistics
|
import com.zaneschepke.wireguardautotunnel.domain.state.AmneziaStatistics
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.state.TunnelStatistics
|
import com.zaneschepke.wireguardautotunnel.domain.state.TunnelStatistics
|
||||||
@@ -15,7 +16,7 @@ import com.zaneschepke.wireguardautotunnel.util.extensions.asAmBackendState
|
|||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.toBackendError
|
import com.zaneschepke.wireguardautotunnel.util.extensions.toBackendError
|
||||||
import kotlinx.coroutines.CoroutineDispatcher
|
import kotlinx.coroutines.CoroutineDispatcher
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.withContext
|
||||||
import org.amnezia.awg.backend.Backend
|
import org.amnezia.awg.backend.Backend
|
||||||
import org.amnezia.awg.backend.Tunnel
|
import org.amnezia.awg.backend.Tunnel
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
@@ -29,24 +30,15 @@ class UserspaceTunnel @Inject constructor(
|
|||||||
notificationManager: NotificationManager,
|
notificationManager: NotificationManager,
|
||||||
private val backend: Backend,
|
private val backend: Backend,
|
||||||
networkMonitor: NetworkMonitor,
|
networkMonitor: NetworkMonitor,
|
||||||
) : BaseTunnel(ioDispatcher, applicationScope, networkMonitor, appDataRepository, serviceManager, notificationManager) {
|
) : TunnelProvider, BaseTunnel(ioDispatcher, applicationScope, networkMonitor, appDataRepository, serviceManager, notificationManager) {
|
||||||
|
|
||||||
override fun startTunnel(tunnelConf: TunnelConf) {
|
override suspend fun startTunnel(tunnelConf: TunnelConf) {
|
||||||
applicationScope.launch(ioDispatcher) {
|
withContext(ioDispatcher) {
|
||||||
Timber.d("Starting tunnel ${tunnelConf.id} userspace")
|
super.startTunnel(tunnelConf)
|
||||||
if (tunnels.value.any { it.id == tunnelConf.id }) return@launch Timber.w("Tunnel already running")
|
|
||||||
if (tunnels.value.isNotEmpty()) {
|
|
||||||
Timber.d("Stopping all tunnels")
|
|
||||||
stopAllTunnels()
|
|
||||||
}
|
|
||||||
runCatching {
|
runCatching {
|
||||||
Timber.d("Setting backend state UP")
|
|
||||||
super.beforeStartTunnel(tunnelConf)
|
|
||||||
backend.setState(tunnelConf, Tunnel.State.UP, tunnelConf.toAmConfig())
|
backend.setState(tunnelConf, Tunnel.State.UP, tunnelConf.toAmConfig())
|
||||||
Timber.d("Calling super.startTunnel")
|
addToActiveTunnels(tunnelConf)
|
||||||
super.startTunnel(tunnelConf)
|
|
||||||
}.onFailure {
|
}.onFailure {
|
||||||
Timber.e(it, "Failed to start tunnel ${tunnelConf.id} userspace")
|
|
||||||
onTunnelStop(tunnelConf)
|
onTunnelStop(tunnelConf)
|
||||||
if (it is BackendException) {
|
if (it is BackendException) {
|
||||||
handleBackendThrowable(it.toBackendError())
|
handleBackendThrowable(it.toBackendError())
|
||||||
@@ -57,8 +49,19 @@ class UserspaceTunnel @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun stopTunnel(tunnelConf: TunnelConf?) {
|
override suspend fun getStatistics(tunnelConf: TunnelConf): TunnelStatistics {
|
||||||
applicationScope.launch(ioDispatcher) {
|
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())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun stopTunnel(tunnelConf: TunnelConf?) {
|
||||||
|
withContext(ioDispatcher) {
|
||||||
runCatching {
|
runCatching {
|
||||||
tunnels.value.firstOrNull { it.id == tunnelConf?.id }?.let {
|
tunnels.value.firstOrNull { it.id == tunnelConf?.id }?.let {
|
||||||
backend.setState(it, Tunnel.State.DOWN, it.toAmConfig())
|
backend.setState(it, Tunnel.State.DOWN, it.toAmConfig())
|
||||||
@@ -77,8 +80,4 @@ class UserspaceTunnel @Inject constructor(
|
|||||||
override suspend fun runningTunnelNames(): Set<String> {
|
override suspend fun runningTunnelNames(): Set<String> {
|
||||||
return backend.runningTunnelNames
|
return backend.runningTunnelNames
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getStatistics(tunnelConf: TunnelConf): TunnelStatistics {
|
|
||||||
return AmneziaStatistics(backend.getStatistics(tunnelConf))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ class ServiceWorker @AssistedInject constructor(
|
|||||||
Timber.i("Service worker started")
|
Timber.i("Service worker started")
|
||||||
with(appDataRepository.settings.get()) {
|
with(appDataRepository.settings.get()) {
|
||||||
if (isAutoTunnelEnabled && !serviceManager.autoTunnelActive.value) return@with serviceManager.startAutoTunnel(true)
|
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()
|
Result.success()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
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,8 +4,7 @@ import android.content.Context
|
|||||||
import com.wireguard.android.backend.WgQuickBackend
|
import com.wireguard.android.backend.WgQuickBackend
|
||||||
import com.wireguard.android.util.RootShell
|
import com.wireguard.android.util.RootShell
|
||||||
import com.wireguard.android.util.ToolsInstaller
|
import com.wireguard.android.util.ToolsInstaller
|
||||||
import com.zaneschepke.networkmonitor.AndroidNetworkMonitor
|
import com.zaneschepke.wireguardautotunnel.core.network.NetworkMonitor
|
||||||
import com.zaneschepke.networkmonitor.NetworkMonitor
|
|
||||||
import com.zaneschepke.wireguardautotunnel.core.notification.NotificationManager
|
import com.zaneschepke.wireguardautotunnel.core.notification.NotificationManager
|
||||||
import com.zaneschepke.wireguardautotunnel.core.service.ServiceManager
|
import com.zaneschepke.wireguardautotunnel.core.service.ServiceManager
|
||||||
import com.zaneschepke.wireguardautotunnel.core.tunnel.KernelTunnel
|
import com.zaneschepke.wireguardautotunnel.core.tunnel.KernelTunnel
|
||||||
@@ -99,20 +98,13 @@ class TunnelModule {
|
|||||||
return TunnelManager(kernelTunnel, userspaceTunnel, appDataRepository, applicationScope, ioDispatcher)
|
return TunnelManager(kernelTunnel, userspaceTunnel, appDataRepository, applicationScope, ioDispatcher)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
|
||||||
@Singleton
|
|
||||||
fun provideNetworkMonitor(@ApplicationContext context: Context): NetworkMonitor {
|
|
||||||
return AndroidNetworkMonitor(context)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
@Provides
|
@Provides
|
||||||
fun provideServiceManager(
|
fun provideServiceManager(
|
||||||
@ApplicationContext context: Context,
|
@ApplicationContext context: Context,
|
||||||
@IoDispatcher ioDispatcher: CoroutineDispatcher,
|
@IoDispatcher ioDispatcher: CoroutineDispatcher,
|
||||||
@ApplicationScope applicationScope: CoroutineScope,
|
|
||||||
appDataRepository: AppDataRepository,
|
appDataRepository: AppDataRepository,
|
||||||
): ServiceManager {
|
): ServiceManager {
|
||||||
return ServiceManager(context, ioDispatcher, applicationScope, appDataRepository)
|
return ServiceManager(context, ioDispatcher, appDataRepository)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
package com.zaneschepke.wireguardautotunnel.domain.entity
|
package com.zaneschepke.wireguardautotunnel.domain.entity
|
||||||
|
|
||||||
import com.wireguard.config.Config
|
import com.wireguard.config.Config
|
||||||
|
import com.zaneschepke.wireguardautotunnel.domain.state.TunnelState
|
||||||
import com.zaneschepke.wireguardautotunnel.util.Constants
|
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.isReachable
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.toWgQuickString
|
import com.zaneschepke.wireguardautotunnel.util.extensions.toWgQuickString
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import kotlinx.serialization.Transient
|
|
||||||
import org.amnezia.awg.backend.Tunnel
|
import org.amnezia.awg.backend.Tunnel
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
@@ -27,13 +30,9 @@ data class TunnelConf(
|
|||||||
val pingIp: String? = null,
|
val pingIp: String? = null,
|
||||||
val isEthernetTunnel: Boolean = false,
|
val isEthernetTunnel: Boolean = false,
|
||||||
val isIpv4Preferred: Boolean = false,
|
val isIpv4Preferred: Boolean = false,
|
||||||
@Transient
|
|
||||||
private var stateChangeCallback: ((Any) -> Unit)? = null,
|
|
||||||
) : Tunnel, com.wireguard.android.backend.Tunnel {
|
) : Tunnel, com.wireguard.android.backend.Tunnel {
|
||||||
|
|
||||||
fun setStateChangeCallback(callback: (Any) -> Unit) {
|
val state = MutableStateFlow(TunnelState())
|
||||||
stateChangeCallback = callback
|
|
||||||
}
|
|
||||||
|
|
||||||
fun toAmConfig(): org.amnezia.awg.config.Config {
|
fun toAmConfig(): org.amnezia.awg.config.Config {
|
||||||
return configFromAmQuick(amQuick.ifBlank { wgQuick })
|
return configFromAmQuick(amQuick.ifBlank { wgQuick })
|
||||||
@@ -51,17 +50,21 @@ data class TunnelConf(
|
|||||||
return isIpv4Preferred
|
return isIpv4Preferred
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onStateChange(newState: com.wireguard.android.backend.Tunnel.State) {
|
|
||||||
stateChangeCallback?.invoke(newState)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onStateChange(newState: Tunnel.State) {
|
override fun onStateChange(newState: Tunnel.State) {
|
||||||
stateChangeCallback?.invoke(newState)
|
state.update {
|
||||||
|
it.copy(state = newState.asTunnelState())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isQuickConfigMatching(updatedConf: TunnelConf): Boolean {
|
override fun onStateChange(newState: com.wireguard.android.backend.Tunnel.State) {
|
||||||
return updatedConf.wgQuick == wgQuick ||
|
state.update {
|
||||||
updatedConf.amQuick == amQuick
|
it.copy(state = newState.asTunnelState())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isQuickConfigChanged(updatedConf: TunnelConf): Boolean {
|
||||||
|
return updatedConf.wgQuick != wgQuick ||
|
||||||
|
updatedConf.amQuick != amQuick
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isPingConfigMatching(updatedConf: TunnelConf): Boolean {
|
fun isPingConfigMatching(updatedConf: TunnelConf): Boolean {
|
||||||
@@ -71,17 +74,18 @@ data class TunnelConf(
|
|||||||
updatedConf.pingInterval == pingInterval
|
updatedConf.pingInterval == pingInterval
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun isTunnelPingable(context: CoroutineContext): Boolean {
|
suspend fun pingTunnel(context: CoroutineContext): List<Boolean> {
|
||||||
return withContext(context) {
|
return withContext(context) {
|
||||||
val config = toWgConfig()
|
val config = toWgConfig()
|
||||||
if (pingIp != null) {
|
if (pingIp != null) {
|
||||||
return@withContext InetAddress.getByName(pingIp)
|
Timber.i("Pinging custom ip")
|
||||||
.isReachable(Constants.PING_TIMEOUT.toInt())
|
listOf(InetAddress.getByName(pingIp).isReachable(Constants.PING_TIMEOUT.toInt()))
|
||||||
|
} else {
|
||||||
|
Timber.i("Pinging all peers")
|
||||||
|
config.peers.map { peer ->
|
||||||
|
peer.isReachable(isIpv4Preferred)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Timber.i("Pinging all peers")
|
|
||||||
config.peers.map { peer ->
|
|
||||||
peer.isReachable(isIpv4Preferred)
|
|
||||||
}.all { true }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+5
-5
@@ -7,7 +7,7 @@ import com.zaneschepke.wireguardautotunnel.domain.events.AutoTunnelEvent
|
|||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.isMatchingToWildcardList
|
import com.zaneschepke.wireguardautotunnel.util.extensions.isMatchingToWildcardList
|
||||||
|
|
||||||
data class AutoTunnelState(
|
data class AutoTunnelState(
|
||||||
val activeTunnels: Map<Int, TunnelState> = emptyMap(),
|
val activeTunnels: List<TunnelConf> = emptyList(),
|
||||||
val networkState: NetworkState = NetworkState(),
|
val networkState: NetworkState = NetworkState(),
|
||||||
val settings: AppSettings = AppSettings(),
|
val settings: AppSettings = AppSettings(),
|
||||||
val tunnels: List<TunnelConf> = emptyList(),
|
val tunnels: List<TunnelConf> = emptyList(),
|
||||||
@@ -20,12 +20,12 @@ data class AutoTunnelState(
|
|||||||
private fun isMobileTunnelDataChangeNeeded(): Boolean {
|
private fun isMobileTunnelDataChangeNeeded(): Boolean {
|
||||||
val preferredTunnel = preferredMobileDataTunnel()
|
val preferredTunnel = preferredMobileDataTunnel()
|
||||||
return preferredTunnel != null &&
|
return preferredTunnel != null &&
|
||||||
activeTunnels.isNotEmpty() && !activeTunnels.any { it.key == preferredTunnel.id }
|
activeTunnels.isNotEmpty() && !activeTunnels.any { it.id == preferredTunnel.id }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun isEthernetTunnelChangeNeeded(): Boolean {
|
private fun isEthernetTunnelChangeNeeded(): Boolean {
|
||||||
val preferredTunnel = preferredEthernetTunnel()
|
val preferredTunnel = preferredEthernetTunnel()
|
||||||
return preferredTunnel != null && activeTunnels.isNotEmpty() && !activeTunnels.any { it.key == preferredTunnel.id }
|
return preferredTunnel != null && activeTunnels.isNotEmpty() && !activeTunnels.any { it.id == preferredTunnel.id }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun preferredMobileDataTunnel(): TunnelConf? {
|
private fun preferredMobileDataTunnel(): TunnelConf? {
|
||||||
@@ -62,7 +62,7 @@ data class AutoTunnelState(
|
|||||||
return settings.isVpnKillSwitchEnabled && (!settings.isDisableKillSwitchOnTrustedEnabled || !isCurrentSSIDTrusted())
|
return settings.isVpnKillSwitchEnabled && (!settings.isDisableKillSwitchOnTrustedEnabled || !isCurrentSSIDTrusted())
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun isNoConnectivity(): Boolean {
|
fun isNoConnectivity(): Boolean {
|
||||||
return !networkState.isEthernetConnected && !networkState.isWifiConnected && !networkState.isMobileDataConnected
|
return !networkState.isEthernetConnected && !networkState.isWifiConnected && !networkState.isMobileDataConnected
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,7 +100,7 @@ data class AutoTunnelState(
|
|||||||
|
|
||||||
private fun isWifiTunnelPreferred(): Boolean {
|
private fun isWifiTunnelPreferred(): Boolean {
|
||||||
val preferred = preferredWifiTunnel()
|
val preferred = preferredWifiTunnel()
|
||||||
return activeTunnels.any { it.key == preferred?.id }
|
return activeTunnels.any { it.id == preferred?.id }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun asAutoTunnelEvent(): AutoTunnelEvent {
|
fun asAutoTunnelEvent(): AutoTunnelEvent {
|
||||||
|
|||||||
+13
-7
@@ -77,7 +77,7 @@ fun MainScreen(viewModel: MainViewModel = hiltViewModel(), uiState: AppUiState)
|
|||||||
var selectedTunnel by remember { mutableStateOf<TunnelConf?>(null) }
|
var selectedTunnel by remember { mutableStateOf<TunnelConf?>(null) }
|
||||||
val isRunningOnTv = remember { context.isRunningOnTv() }
|
val isRunningOnTv = remember { context.isRunningOnTv() }
|
||||||
|
|
||||||
val activeTunnels by viewModel.tunnelManager.activeTunnels.collectAsStateWithLifecycle(emptyMap())
|
val activeTunnels by viewModel.activeTunnels.collectAsStateWithLifecycle(emptyList())
|
||||||
|
|
||||||
val collator = Collator.getInstance(Locale.getDefault())
|
val collator = Collator.getInstance(Locale.getDefault())
|
||||||
|
|
||||||
@@ -89,7 +89,6 @@ fun MainScreen(viewModel: MainViewModel = hiltViewModel(), uiState: AppUiState)
|
|||||||
val startTunnel = withVpnPermission<TunnelConf> {
|
val startTunnel = withVpnPermission<TunnelConf> {
|
||||||
viewModel.onTunnelStart(it)
|
viewModel.onTunnelStart(it)
|
||||||
}
|
}
|
||||||
|
|
||||||
val autoTunnelToggleBattery = withIgnoreBatteryOpt(uiState.generalState.isBatteryOptimizationDisableShown) {
|
val autoTunnelToggleBattery = withIgnoreBatteryOpt(uiState.generalState.isBatteryOptimizationDisableShown) {
|
||||||
if (!uiState.generalState.isBatteryOptimizationDisableShown) viewModel.setBatteryOptimizeDisableShown()
|
if (!uiState.generalState.isBatteryOptimizationDisableShown) viewModel.setBatteryOptimizeDisableShown()
|
||||||
if (uiState.appSettings.isKernelEnabled) {
|
if (uiState.appSettings.isKernelEnabled) {
|
||||||
@@ -133,8 +132,15 @@ fun MainScreen(viewModel: MainViewModel = hiltViewModel(), uiState: AppUiState)
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun onTunnelToggle(checked: Boolean, tunnel: TunnelConf) {
|
fun onTunnelToggle(checked: Boolean, tunnel: TunnelConf) {
|
||||||
if (!checked) return viewModel.onTunnelStop(tunnel).let { }
|
if (!checked) {
|
||||||
if (uiState.appSettings.isKernelEnabled) viewModel.onTunnelStart(tunnel) else startTunnel(tunnel)
|
viewModel.onTunnelStop(tunnel)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (uiState.appSettings.isKernelEnabled) {
|
||||||
|
viewModel.onTunnelStart(tunnel)
|
||||||
|
} else {
|
||||||
|
startTunnel.invoke(tunnel)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
@@ -227,13 +233,13 @@ fun MainScreen(viewModel: MainViewModel = hiltViewModel(), uiState: AppUiState)
|
|||||||
key = { tunnel -> tunnel.id },
|
key = { tunnel -> tunnel.id },
|
||||||
) { tunnel ->
|
) { tunnel ->
|
||||||
val expanded = uiState.generalState.isTunnelStatsExpanded
|
val expanded = uiState.generalState.isTunnelStatsExpanded
|
||||||
val tunnelState = activeTunnels.getOrDefault(tunnel.id, TunnelState())
|
val tunnelState = activeTunnels.firstOrNull { it.id == tunnel.id }?.state?.collectAsStateWithLifecycle()
|
||||||
TunnelRowItem(
|
TunnelRowItem(
|
||||||
tunnelState.state.isUp(),
|
tunnel.isActive,
|
||||||
expanded,
|
expanded,
|
||||||
selectedTunnel?.id == tunnel.id,
|
selectedTunnel?.id == tunnel.id,
|
||||||
tunnel,
|
tunnel,
|
||||||
tunnelState = tunnelState,
|
tunnelState = tunnelState?.value ?: TunnelState(),
|
||||||
{ selectedTunnel = tunnel },
|
{ selectedTunnel = tunnel },
|
||||||
{ viewModel.onExpandedChanged(!expanded) },
|
{ viewModel.onExpandedChanged(!expanded) },
|
||||||
onDelete = { showDeleteTunnelAlertDialog = true },
|
onDelete = { showDeleteTunnelAlertDialog = true },
|
||||||
|
|||||||
+1
-3
@@ -41,7 +41,6 @@ import com.zaneschepke.wireguardautotunnel.ui.common.config.SubmitConfigurationT
|
|||||||
import com.zaneschepke.wireguardautotunnel.ui.common.navigation.LocalNavController
|
import com.zaneschepke.wireguardautotunnel.ui.common.navigation.LocalNavController
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.navigation.TopNavBar
|
import com.zaneschepke.wireguardautotunnel.ui.common.navigation.TopNavBar
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.screens.settings.components.ForwardButton
|
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.Constants
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.isValidIpv4orIpv6Address
|
import com.zaneschepke.wireguardautotunnel.util.extensions.isValidIpv4orIpv6Address
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.scaledHeight
|
import com.zaneschepke.wireguardautotunnel.util.extensions.scaledHeight
|
||||||
@@ -52,7 +51,7 @@ import kotlin.text.isNullOrBlank
|
|||||||
import kotlin.text.toLong
|
import kotlin.text.toLong
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun OptionsScreen(tunnelConf: TunnelConf, appUiState: AppUiState, viewModel: TunnelOptionsViewModel = hiltViewModel()) {
|
fun OptionsScreen(tunnelConf: TunnelConf, viewModel: TunnelOptionsViewModel = hiltViewModel()) {
|
||||||
val navController = LocalNavController.current
|
val navController = LocalNavController.current
|
||||||
|
|
||||||
var currentText by remember { mutableStateOf("") }
|
var currentText by remember { mutableStateOf("") }
|
||||||
@@ -195,7 +194,6 @@ fun OptionsScreen(tunnelConf: TunnelConf, appUiState: AppUiState, viewModel: Tun
|
|||||||
trailing = {
|
trailing = {
|
||||||
ScaledSwitch(
|
ScaledSwitch(
|
||||||
checked = tunnelConf.isPingEnabled,
|
checked = tunnelConf.isPingEnabled,
|
||||||
enabled = !appUiState.activeTunnels.containsKey(tunnelConf.id),
|
|
||||||
onClick = { onPingToggle() },
|
onClick = { onPingToggle() },
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|||||||
+5
-5
@@ -352,11 +352,11 @@ fun SettingsScreen(viewModel: SettingsViewModel = hiltViewModel(), appViewModel:
|
|||||||
ScaledSwitch(
|
ScaledSwitch(
|
||||||
uiState.appSettings.isKernelEnabled,
|
uiState.appSettings.isKernelEnabled,
|
||||||
onClick = { appViewModel.onToggleKernelMode() },
|
onClick = { appViewModel.onToggleKernelMode() },
|
||||||
enabled = !(
|
// enabled = !(
|
||||||
uiState.appSettings.isAutoTunnelEnabled ||
|
// uiState.settings.isAutoTunnelEnabled ||
|
||||||
uiState.appSettings.isAlwaysOnVpnEnabled ||
|
// uiState.settings.isAlwaysOnVpnEnabled ||
|
||||||
uiState.activeTunnels.isNotEmpty()
|
// (uiState.vpnState.status == TunnelState.UP)
|
||||||
),
|
// ),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
onClick = {
|
onClick = {
|
||||||
|
|||||||
+19
-2
@@ -18,6 +18,8 @@ import androidx.compose.material3.MaterialTheme
|
|||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
@@ -34,6 +36,7 @@ import com.zaneschepke.wireguardautotunnel.ui.Route
|
|||||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.ScaledSwitch
|
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.SelectionItem
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SurfaceSelectionGroupButton
|
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.GroupLabel
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.label.VersionLabel
|
import com.zaneschepke.wireguardautotunnel.ui.common.label.VersionLabel
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.navigation.LocalNavController
|
import com.zaneschepke.wireguardautotunnel.ui.common.navigation.LocalNavController
|
||||||
@@ -48,6 +51,20 @@ import com.zaneschepke.wireguardautotunnel.util.extensions.scaledWidth
|
|||||||
fun SupportScreen(appUiState: AppUiState, appViewModel: AppViewModel) {
|
fun SupportScreen(appUiState: AppUiState, appViewModel: AppViewModel) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val navController = LocalNavController.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(
|
Column(
|
||||||
horizontalAlignment = Alignment.Start,
|
horizontalAlignment = Alignment.Start,
|
||||||
verticalArrangement = Arrangement.spacedBy(24.dp.scaledHeight(), Alignment.Top),
|
verticalArrangement = Arrangement.spacedBy(24.dp.scaledHeight(), Alignment.Top),
|
||||||
@@ -98,12 +115,12 @@ fun SupportScreen(appUiState: AppUiState, appViewModel: AppViewModel) {
|
|||||||
ScaledSwitch(
|
ScaledSwitch(
|
||||||
appUiState.generalState.isLocalLogsEnabled,
|
appUiState.generalState.isLocalLogsEnabled,
|
||||||
onClick = {
|
onClick = {
|
||||||
appViewModel.onToggleLocalLogging()
|
showDialog = true
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
onClick = {
|
onClick = {
|
||||||
appViewModel.onToggleLocalLogging()
|
showDialog = true
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -3,12 +3,10 @@ package com.zaneschepke.wireguardautotunnel.ui.state
|
|||||||
import com.zaneschepke.wireguardautotunnel.data.model.GeneralState
|
import com.zaneschepke.wireguardautotunnel.data.model.GeneralState
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.entity.AppSettings
|
import com.zaneschepke.wireguardautotunnel.domain.entity.AppSettings
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.entity.TunnelConf
|
import com.zaneschepke.wireguardautotunnel.domain.entity.TunnelConf
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.state.TunnelState
|
|
||||||
|
|
||||||
data class AppUiState(
|
data class AppUiState(
|
||||||
val appSettings: AppSettings = AppSettings(),
|
val appSettings: AppSettings = AppSettings(),
|
||||||
val tunnels: List<TunnelConf> = emptyList(),
|
val tunnels: List<TunnelConf> = emptyList(),
|
||||||
val activeTunnels: Map<Int, TunnelState> = emptyMap(),
|
|
||||||
val generalState: GeneralState = GeneralState(),
|
val generalState: GeneralState = GeneralState(),
|
||||||
val autoTunnelActive: Boolean = false,
|
val autoTunnelActive: Boolean = false,
|
||||||
)
|
)
|
||||||
|
|||||||
+9
-2
@@ -2,6 +2,7 @@ package com.zaneschepke.wireguardautotunnel.util.extensions
|
|||||||
|
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import com.wireguard.android.backend.BackendException
|
import com.wireguard.android.backend.BackendException
|
||||||
|
import com.wireguard.android.util.RootShell
|
||||||
import com.wireguard.config.Peer
|
import com.wireguard.config.Peer
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.enums.BackendError
|
import com.zaneschepke.wireguardautotunnel.domain.enums.BackendError
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.enums.BackendState
|
import com.zaneschepke.wireguardautotunnel.domain.enums.BackendState
|
||||||
@@ -45,9 +46,9 @@ fun TunnelStatistics.PeerStats.handshakeStatus(): HandshakeStatus {
|
|||||||
fun Peer.isReachable(preferIpv4: Boolean): Boolean {
|
fun Peer.isReachable(preferIpv4: Boolean): Boolean {
|
||||||
val host =
|
val host =
|
||||||
if (this.endpoint.isPresent &&
|
if (this.endpoint.isPresent &&
|
||||||
this.endpoint.get().resolved.isPresent
|
this.endpoint.get().getResolved(preferIpv4).isPresent
|
||||||
) {
|
) {
|
||||||
this.endpoint.get().resolved.get().host
|
this.endpoint.get().getResolved(preferIpv4).get().host
|
||||||
} else {
|
} else {
|
||||||
Constants.DEFAULT_PING_IP
|
Constants.DEFAULT_PING_IP
|
||||||
}
|
}
|
||||||
@@ -86,6 +87,12 @@ fun Config.toWgQuickString(): String {
|
|||||||
return lines.joinToString(System.lineSeparator())
|
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 {
|
fun Backend.BackendState.asBackendState(): BackendState {
|
||||||
return BackendState.valueOf(this.name)
|
return BackendState.valueOf(this.name)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,12 @@ import com.zaneschepke.wireguardautotunnel.WireGuardAutoTunnel
|
|||||||
import com.zaneschepke.wireguardautotunnel.ui.Route
|
import com.zaneschepke.wireguardautotunnel.ui.Route
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.navigation.isCurrentRoute
|
import com.zaneschepke.wireguardautotunnel.ui.common.navigation.isCurrentRoute
|
||||||
|
|
||||||
|
fun NavController.navigateAndForget(route: Route) {
|
||||||
|
navigate(route) {
|
||||||
|
popUpTo(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun NavController.goFromRoot(route: Route) {
|
fun NavController.goFromRoot(route: Route) {
|
||||||
if (currentBackStackEntry?.isCurrentRoute(route::class) == true) return
|
if (currentBackStackEntry?.isCurrentRoute(route::class) == true) return
|
||||||
this.navigate(route) {
|
this.navigate(route) {
|
||||||
|
|||||||
@@ -31,9 +31,11 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
|||||||
import kotlinx.coroutines.flow.SharingStarted
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
import kotlinx.coroutines.flow.asSharedFlow
|
import kotlinx.coroutines.flow.asSharedFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
import kotlinx.coroutines.flow.collect
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.onCompletion
|
||||||
import kotlinx.coroutines.flow.stateIn
|
import kotlinx.coroutines.flow.stateIn
|
||||||
|
import kotlinx.coroutines.flow.takeWhile
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.plus
|
import kotlinx.coroutines.plus
|
||||||
@@ -73,13 +75,11 @@ constructor(
|
|||||||
appDataRepository.settings.flow,
|
appDataRepository.settings.flow,
|
||||||
appDataRepository.tunnels.flow,
|
appDataRepository.tunnels.flow,
|
||||||
appDataRepository.appState.flow,
|
appDataRepository.appState.flow,
|
||||||
tunnelManager.activeTunnels,
|
|
||||||
serviceManager.autoTunnelActive,
|
serviceManager.autoTunnelActive,
|
||||||
) { settings, tunnels, generalState, activeTunnels, autoTunnel ->
|
) { settings, tunnels, generalState, autoTunnel ->
|
||||||
AppUiState(
|
AppUiState(
|
||||||
settings,
|
settings,
|
||||||
tunnels,
|
tunnels,
|
||||||
activeTunnels,
|
|
||||||
generalState,
|
generalState,
|
||||||
autoTunnel,
|
autoTunnel,
|
||||||
)
|
)
|
||||||
@@ -103,13 +103,14 @@ constructor(
|
|||||||
|
|
||||||
private suspend fun appReadyCheck() {
|
private suspend fun appReadyCheck() {
|
||||||
val tunnelCount = appDataRepository.tunnels.count()
|
val tunnelCount = appDataRepository.tunnels.count()
|
||||||
uiState.first { it.tunnels.count() == tunnelCount }
|
uiState.takeWhile { it.tunnels.size != tunnelCount }.onCompletion {
|
||||||
_isAppReady.emit(true)
|
_isAppReady.emit(true)
|
||||||
|
}.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun initTunnels() {
|
private suspend fun initTunnels() {
|
||||||
tunnels.withData { tunnels ->
|
tunnels.withData {
|
||||||
tunnels.filter { it.isActive }.forEach {
|
it.filter { it.isActive }.forEach {
|
||||||
tunnelManager.startTunnel(it)
|
tunnelManager.startTunnel(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -145,12 +146,17 @@ constructor(
|
|||||||
with(uiState.value.generalState) {
|
with(uiState.value.generalState) {
|
||||||
val toggledOn = !isLocalLogsEnabled
|
val toggledOn = !isLocalLogsEnabled
|
||||||
appDataRepository.appState.setLocalLogsEnabled(toggledOn)
|
appDataRepository.appState.setLocalLogsEnabled(toggledOn)
|
||||||
if (!toggledOn) {
|
if (!toggledOn) onLoggerStop()
|
||||||
logReader.stop()
|
_configurationChange.update {
|
||||||
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private suspend fun onLoggerStop() {
|
||||||
|
logReader.deleteAndClearLogs()
|
||||||
|
}
|
||||||
|
|
||||||
fun onToggleAlwaysOnVPN() = viewModelScope.launch {
|
fun onToggleAlwaysOnVPN() = viewModelScope.launch {
|
||||||
with(uiState.value.appSettings) {
|
with(uiState.value.appSettings) {
|
||||||
appDataRepository.settings.save(
|
appDataRepository.settings.save(
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ import com.zaneschepke.wireguardautotunnel.util.extensions.toWgQuickString
|
|||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.withData
|
import com.zaneschepke.wireguardautotunnel.util.extensions.withData
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import kotlinx.coroutines.CoroutineDispatcher
|
import kotlinx.coroutines.CoroutineDispatcher
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import org.amnezia.awg.config.Config
|
import org.amnezia.awg.config.Config
|
||||||
@@ -42,6 +44,15 @@ constructor(
|
|||||||
appDataRepository: AppDataRepository,
|
appDataRepository: AppDataRepository,
|
||||||
) : BaseViewModel(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 {
|
fun onDelete(tunnel: TunnelConf) = viewModelScope.launch {
|
||||||
appSettings.withData { settings ->
|
appSettings.withData { settings ->
|
||||||
tunnels.withData {
|
tunnels.withData {
|
||||||
|
|||||||
@@ -1,177 +1,2 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?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>
|
|
||||||
@@ -0,0 +1,177 @@
|
|||||||
|
<?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>
|
||||||
@@ -1,50 +1,43 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<string name="app_name">WG Tunnel</string>
|
<string name="app_name">WG Tunnel</string>
|
||||||
<string name="vpn_channel_id" translatable="false">VPN Channel</string>
|
<string name="vpn_channel_name">VPN Bildirim Kanalı</string>
|
||||||
<string name="vpn_channel_name">VPN Bildirim Kanalı</string>
|
<string name="error_file_extension">Dosya .conf veya .zip değil</string>
|
||||||
<string name="github_url" translatable="false">https://github.com/zaneschepke/wgtunnel/issues</string>
|
<string name="turn_off_tunnel">İşlem için tünelin kapalı olması gerekiyor</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="no_tunnels">Henüz tünel eklenmedi!</string>
|
||||||
<string name="tunnels">Tüneller</string>
|
<string name="tunnels">Tüneller</string>
|
||||||
<string name="tunnel_mobile_data">Mobil veride tünel</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="okay">Tamam</string>
|
||||||
<string name="tunnel_on_ethernet">Ethernet üzerinde tünel</string>
|
<string name="tunnel_on_ethernet">Ethernet\'te 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_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="prominent_background_location_title">Arka Plan Konum Açıklaması</string>
|
<string name="prominent_background_location_title">Arka Plan Konum Açıklaması</string>
|
||||||
<string name="thank_you">WG Tunnel’i kullandığınız için teşekkürler!</string>
|
<string name="thank_you">WG Tunnel\'ı kullandığınız için teşekkürler!</string>
|
||||||
<string name="trusted_ssid_value_description">SSID Gönder</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="add_tunnels_text">Dosyadan veya zip\'ten ekle</string>
|
||||||
<string name="open_file">Dosya Aç</string>
|
<string name="open_file">Dosya Aç</string>
|
||||||
<string name="add_from_qr">QR kodundan ekle</string>
|
<string name="add_from_qr">QR kodundan ekle</string>
|
||||||
<string name="qr_scan">QR Tara</string>
|
<string name="qr_scan">QR Tarama</string>
|
||||||
<string name="tunnel_name">Tünel Adı</string>
|
<string name="tunnel_name">Tünel Adı</string>
|
||||||
<string name="exclude">Hariç Tut</string>
|
<string name="exclude">Hariç tut</string>
|
||||||
<string name="include">Dahil Et</string>
|
<string name="include">Dahil et</string>
|
||||||
<string name="config_changes_saved">Yapılandırma değişiklikleri kaydedildi.</string>
|
<string name="config_changes_saved">Yapılandırma değişiklikleri kaydedildi.</string>
|
||||||
<string name="public_key">Genel anahtar</string>
|
<string name="public_key">Genel anahtar</string>
|
||||||
<string name="addresses">Adresler</string>
|
<string name="addresses">Adresler</string>
|
||||||
<string name="dns_servers">DNS sunucuları</string>
|
<string name="dns_servers">DNS sunucuları</string>
|
||||||
<string name="mtu">MTU</string>
|
<string name="mtu">MTU</string>
|
||||||
<string name="peer">Eş</string>
|
<string name="peer">Eş (peer)</string>
|
||||||
<string name="allowed_ips">İzin verilen IP’ler</string>
|
<string name="allowed_ips">İzin verilen IP\'ler</string>
|
||||||
<string name="endpoint">Uç Nokta</string>
|
<string name="endpoint">Uç nokta (endpoint)</string>
|
||||||
<string name="name">Ad</string>
|
<string name="name">Ad</string>
|
||||||
<string name="always_on_vpn_support">Her Zaman Açık VPN’e İzin Ver</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="location_services_not_detected">Konum Hizmetleri Algılanmadı</string>
|
||||||
<string name="hint_search_packages">Paketleri ara</string>
|
<string name="hint_search_packages">Paketlerde ara</string>
|
||||||
<string name="db_name" translatable="false">wg-tunnel-db</string>
|
|
||||||
<string name="auto_tunneling">Otomatik tünelleme</string>
|
<string name="auto_tunneling">Otomatik tünelleme</string>
|
||||||
<string name="vpn_on">VPN açık</string>
|
<string name="vpn_on">VPN açık</string>
|
||||||
<string name="vpn_off">VPN kapalı</string>
|
<string name="vpn_off">VPN kapalı</string>
|
||||||
<string name="create_import">Sıfırdan oluştur</string>
|
<string name="create_import">Sıfırdan oluştur</string>
|
||||||
<string name="turn_on_tunnel">Bu işlem aktif bir tünel gerektirir</string>
|
<string name="turn_on_tunnel">İşlem için aktif tünel gerekiyor</string>
|
||||||
<string name="add_peer">Eş ekle</string>
|
<string name="add_peer">Eş ekle</string>
|
||||||
<string name="interface_">Arayüz</string>
|
<string name="interface_">Arayüz</string>
|
||||||
<string name="rotate_keys">Anahtarları döndür</string>
|
<string name="rotate_keys">Anahtarları döndür</string>
|
||||||
@@ -56,42 +49,41 @@
|
|||||||
<string name="random">(rastgele)</string>
|
<string name="random">(rastgele)</string>
|
||||||
<string name="optional">(isteğe bağlı)</string>
|
<string name="optional">(isteğe bağlı)</string>
|
||||||
<string name="optional_no_recommend">(isteğe bağlı, önerilmez)</string>
|
<string name="optional_no_recommend">(isteğe bağlı, önerilmez)</string>
|
||||||
<string name="preshared_key">Ön paylaşımlı anahtar</string>
|
<string name="preshared_key">Önceden paylaşılmış anahtar</string>
|
||||||
<string name="seconds">saniye</string>
|
<string name="seconds">saniye</string>
|
||||||
<string name="persistent_keepalive">Kalıcı canlı tutma</string>
|
<string name="persistent_keepalive">Kalıcı canlı tutma</string>
|
||||||
<string name="cancel">İptal</string>
|
<string name="cancel">İptal</string>
|
||||||
<string name="error_authentication_failed">Kimlik doğrulama başarısız</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</string>
|
<string name="error_authorization_failed">Yetkilendirme başarısız oldu</string>
|
||||||
<string name="enabled_app_shortcuts">Uygulama kısayollarını etkinleştir</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="export_configs">Yapılandırmaları dışa aktar</string>
|
||||||
<string name="unknown_error">Bilinmeyen bir hata oluştu</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="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_subject">WG Tunnel Desteği</string>
|
|
||||||
<string name="email_chooser">E-posta gönder…</string>
|
<string name="email_chooser">E-posta gönder…</string>
|
||||||
<string name="docs_description">Belgeleri oku</string>
|
<string name="docs_description">Belgeleri oku</string>
|
||||||
<string name="email_description">Bana e-posta gönder</string>
|
<string name="email_description">Bana e-posta gönder</string>
|
||||||
<string name="use_kernel">Çekirdek modülünü kullan</string>
|
<string name="use_kernel">Kernel modülünü kullan</string>
|
||||||
<string name="error_ssid_exists">SSID zaten mevcut</string>
|
<string name="error_ssid_exists">SSID zaten mevcut</string>
|
||||||
<string name="error_root_denied">Root kabuğu reddedildi</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_no_file_explorer">Dosya gezgini yüklü değil</string>
|
||||||
<string name="error_invalid_code">Geçersiz QR kodu</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 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="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 servisi</string>
|
<string name="auto_tunnel_title">Otomatik Tünel Hizmeti</string>
|
||||||
<string name="delete_tunnel">Tüneli sil</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="delete_tunnel_message">Bu tüneli silmek istediğinizden emin misiniz?</string>
|
||||||
<string name="yes">Evet</string>
|
<string name="yes">Evet</string>
|
||||||
<string name="tunneling_apps">Tünelleme uygulamaları</string>
|
<string name="tunneling_apps">Tünellenen uygulamalar</string>
|
||||||
<string name="all">tümü</string>
|
<string name="all">tümü</string>
|
||||||
<string name="no_email_detected">E-posta uygulaması algılanmadı</string>
|
<string name="no_email_detected">E-posta uygulaması algılanmadı</string>
|
||||||
<string name="no_browser_detected">Tarayıcı algılanmadı</string>
|
<string name="no_browser_detected">Tarayıcı algılanmadı</string>
|
||||||
<string name="open_issue">Bir sorun aç</string>
|
<string name="open_issue">Sorun bildir</string>
|
||||||
<string name="read_logs">Günlükleri oku</string>
|
<string name="read_logs">Günlükleri oku</string>
|
||||||
<string name="auto">(otomatik)</string>
|
<string name="auto">(otomatik)</string>
|
||||||
<string name="incorrect_pin">Pin yanlış</string>
|
<string name="incorrect_pin">PIN yanlış</string>
|
||||||
<string name="pin_created">Pin başarıyla oluşturuldu</string>
|
<string name="pin_created">PIN başarıyla oluşturuldu</string>
|
||||||
<string name="enter_pin">Pin’inizi girin</string>
|
<string name="enter_pin">PIN\'inizi girin</string>
|
||||||
<string name="create_pin">Pin oluştur</string>
|
<string name="create_pin">PIN oluştur</string>
|
||||||
<string name="enable_app_lock">Uygulama kilidini etkinleştir</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="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>
|
<string name="mobile_data_tunnel">Mobil veri tüneli olarak ayarla</string>
|
||||||
@@ -102,118 +94,18 @@
|
|||||||
<string name="settings">Ayarlar</string>
|
<string name="settings">Ayarlar</string>
|
||||||
<string name="support">Destek</string>
|
<string name="support">Destek</string>
|
||||||
<string name="kernel">Çekirdek</string>
|
<string name="kernel">Çekirdek</string>
|
||||||
<string name="junk_packet_count">Çöp paket sayısı</string>
|
<string name="junk_packet_count">Gereksiz paket sayısı</string>
|
||||||
<string name="junk_packet_minimum_size">Çöp paket minimum boyutu</string>
|
<string name="junk_packet_minimum_size">Gereksiz paket minimum boyutu</string>
|
||||||
<string name="junk_packet_maximum_size">Çöp paket maksimum boyutu</string>
|
<string name="junk_packet_maximum_size">Gereksiz paket maksimum boyutu</string>
|
||||||
<string name="init_packet_junk_size">Başlangıç paketi çöp boyutu</string>
|
<string name="init_packet_junk_size">Başlatma paketi gereksiz boyutu</string>
|
||||||
<string name="response_packet_junk_size">Yanıt paketi çöp boyutu</string>
|
<string name="response_packet_junk_size">Yanıt paketi gereksiz boyutu</string>
|
||||||
<string name="init_packet_magic_header">Başlangıç paketi sihirli başlığı</string>
|
<string name="init_packet_magic_header">Başlatma paketi sihirli başlığı</string>
|
||||||
<string name="response_packet_magic_header">Yanıt 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="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="underload_packet_magic_header">Düşük yük paketi sihirli başlığı</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="unsure_how">nasıl devam edeceğinizden emin değilseniz</string>
|
<string name="see_the">Bakın:</string>
|
||||||
<string name="see_the">Bakınız</string>
|
<string name="getting_started_guide">başlangıç kılavuzu</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="error_file_format">Geçersiz tünel yapılandırma formatı</string>
|
||||||
<string name="restart_at_boot">Başlangıçta yeniden başlat</string>
|
<string name="restart_at_boot">Önyüklemede 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>
|
</resources>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
object Constants {
|
object Constants {
|
||||||
const val VERSION_NAME = "3.7.0"
|
const val VERSION_NAME = "3.6.6"
|
||||||
const val JVM_TARGET = "17"
|
const val JVM_TARGET = "17"
|
||||||
const val VERSION_CODE = 37000
|
const val VERSION_CODE = 36600
|
||||||
const val TARGET_SDK = 35
|
const val TARGET_SDK = 35
|
||||||
const val MIN_SDK = 26
|
const val MIN_SDK = 26
|
||||||
const val APP_ID = "com.zaneschepke.wireguardautotunnel"
|
const val APP_ID = "com.zaneschepke.wireguardautotunnel"
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
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
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
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 @@
|
|||||||
|
Uma alternativa de cliente WireGuard VPN com recursos adicionais
|
||||||
+10
-10
@@ -1,13 +1,13 @@
|
|||||||
[versions]
|
[versions]
|
||||||
accompanist = "0.37.2"
|
accompanist = "0.37.0"
|
||||||
activityCompose = "1.10.1"
|
activityCompose = "1.10.0"
|
||||||
amneziawgAndroid = "1.3.0"
|
amneziawgAndroid = "1.2.6"
|
||||||
androidx-junit = "1.2.1"
|
androidx-junit = "1.2.1"
|
||||||
appcompat = "1.7.0"
|
appcompat = "1.7.0"
|
||||||
biometricKtx = "1.2.0-alpha05"
|
biometricKtx = "1.2.0-alpha05"
|
||||||
coreKtx = "1.15.0"
|
coreKtx = "1.15.0"
|
||||||
datastorePreferences = "1.1.3"
|
datastorePreferences = "1.1.2"
|
||||||
desugar_jdk_libs = "2.1.5"
|
desugar_jdk_libs = "2.1.4"
|
||||||
espressoCore = "3.6.1"
|
espressoCore = "3.6.1"
|
||||||
hiltAndroid = "2.55"
|
hiltAndroid = "2.55"
|
||||||
hiltCompiler = "1.2.0"
|
hiltCompiler = "1.2.0"
|
||||||
@@ -15,15 +15,15 @@ junit = "4.13.2"
|
|||||||
kotlinx-serialization-json = "1.8.0"
|
kotlinx-serialization-json = "1.8.0"
|
||||||
lifecycle-runtime-compose = "2.8.7"
|
lifecycle-runtime-compose = "2.8.7"
|
||||||
material3 = "1.3.1"
|
material3 = "1.3.1"
|
||||||
navigationCompose = "2.8.8"
|
navigationCompose = "2.8.7"
|
||||||
pinLockCompose = "1.0.4"
|
pinLockCompose = "1.0.4"
|
||||||
roomVersion = "2.6.1"
|
roomVersion = "2.6.1"
|
||||||
timber = "5.0.1"
|
timber = "5.0.1"
|
||||||
tunnel = "1.2.6"
|
tunnel = "1.2.2"
|
||||||
androidGradlePlugin = "8.8.0-alpha05"
|
androidGradlePlugin = "8.8.0-alpha05"
|
||||||
kotlin = "2.1.10"
|
kotlin = "2.1.10"
|
||||||
ksp = "2.1.10-1.0.31"
|
ksp = "2.1.10-1.0.30"
|
||||||
composeBom = "2025.03.00"
|
composeBom = "2025.02.00"
|
||||||
compose = "1.7.8"
|
compose = "1.7.8"
|
||||||
workRuntimeKtxVersion = "2.10.0"
|
workRuntimeKtxVersion = "2.10.0"
|
||||||
zxingAndroidEmbedded = "4.3.0"
|
zxingAndroidEmbedded = "4.3.0"
|
||||||
@@ -32,7 +32,7 @@ gradlePlugins-grgit = "5.3.0"
|
|||||||
|
|
||||||
#plugins
|
#plugins
|
||||||
material = "1.12.0"
|
material = "1.12.0"
|
||||||
gradlePlugins-ktlint="12.2.0"
|
gradlePlugins-ktlint="12.1.2"
|
||||||
|
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
|
|||||||
@@ -1,90 +0,0 @@
|
|||||||
package com.zaneschepke.logcatter
|
|
||||||
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
import java.io.BufferedOutputStream
|
|
||||||
import java.io.File
|
|
||||||
import java.io.FileOutputStream
|
|
||||||
import java.util.zip.ZipEntry
|
|
||||||
import java.util.zip.ZipOutputStream
|
|
||||||
|
|
||||||
class LogFileManager(
|
|
||||||
private val logDir: String,
|
|
||||||
private val maxFileSize: Long,
|
|
||||||
private val maxFolderSize: Long,
|
|
||||||
) {
|
|
||||||
private var currentFile: File? = null
|
|
||||||
private var outputStream: FileOutputStream? = null
|
|
||||||
|
|
||||||
val ioDispatcher = Dispatchers.IO
|
|
||||||
|
|
||||||
init {
|
|
||||||
rotateIfNeeded()
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun writeLog(line: String) = withContext(ioDispatcher) {
|
|
||||||
rotateIfNeeded()
|
|
||||||
outputStream?.write((line + System.lineSeparator()).toByteArray())
|
|
||||||
outputStream?.flush()
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun zipLogs(zipFilePath: String) = withContext(ioDispatcher) {
|
|
||||||
outputStream?.close()
|
|
||||||
val sourceDir = File(logDir)
|
|
||||||
if (!sourceDir.exists() || !sourceDir.isDirectory) return@withContext
|
|
||||||
val outputZipFile = File(zipFilePath)
|
|
||||||
ZipOutputStream(BufferedOutputStream(FileOutputStream(outputZipFile))).use { zos ->
|
|
||||||
sourceDir.walkTopDown().forEach { file ->
|
|
||||||
val zipFileName = file.absolutePath.removePrefix(sourceDir.absolutePath).removePrefix("/")
|
|
||||||
val entry = ZipEntry("$zipFileName${if (file.isDirectory) "/" else ""}")
|
|
||||||
zos.putNextEntry(entry)
|
|
||||||
if (file.isFile) {
|
|
||||||
file.inputStream().use { it.copyTo(zos) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
rotateIfNeeded()
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun deleteAllLogs() = withContext(ioDispatcher) {
|
|
||||||
outputStream?.close()
|
|
||||||
File(logDir).listFiles()?.forEach { it.delete() }
|
|
||||||
rotateIfNeeded()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun close() {
|
|
||||||
outputStream?.close()
|
|
||||||
outputStream = null
|
|
||||||
currentFile = null
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun rotateIfNeeded() {
|
|
||||||
val folderSize = getFolderSize(File(logDir))
|
|
||||||
if (folderSize >= maxFolderSize) {
|
|
||||||
deleteOldestFile()
|
|
||||||
}
|
|
||||||
val fileSize = currentFile?.length() ?: 0L
|
|
||||||
if (currentFile == null || fileSize >= maxFileSize) {
|
|
||||||
outputStream?.close()
|
|
||||||
currentFile = File(logDir, "logcat_${System.currentTimeMillis()}.txt")
|
|
||||||
outputStream = FileOutputStream(currentFile!!)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getFolderSize(dir: File): Long {
|
|
||||||
var size = 0L
|
|
||||||
if (dir.isDirectory && dir.listFiles() != null) {
|
|
||||||
dir.listFiles()!!.forEach { file ->
|
|
||||||
size += if (file.isDirectory) getFolderSize(file) else file.length()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return size
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun deleteOldestFile() {
|
|
||||||
File(logDir).listFiles()
|
|
||||||
?.toList()
|
|
||||||
?.minByOrNull { it.lastModified() }
|
|
||||||
?.delete()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -4,8 +4,7 @@ import com.zaneschepke.logcatter.model.LogMessage
|
|||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
interface LogReader {
|
interface LogReader {
|
||||||
fun start()
|
fun initialize(onLogMessage: ((message: LogMessage) -> Unit)? = null)
|
||||||
fun stop()
|
|
||||||
fun zipLogFiles(path: String)
|
fun zipLogFiles(path: String)
|
||||||
suspend fun deleteAndClearLogs()
|
suspend fun deleteAndClearLogs()
|
||||||
val bufferedLogs: Flow<LogMessage>
|
val bufferedLogs: Flow<LogMessage>
|
||||||
|
|||||||
@@ -1,92 +0,0 @@
|
|||||||
package com.zaneschepke.logcatter
|
|
||||||
|
|
||||||
import androidx.lifecycle.DefaultLifecycleObserver
|
|
||||||
import androidx.lifecycle.LifecycleOwner
|
|
||||||
import com.zaneschepke.logcatter.model.LogMessage
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
|
||||||
import kotlinx.coroutines.Job
|
|
||||||
import kotlinx.coroutines.SupervisorJob
|
|
||||||
import kotlinx.coroutines.cancel
|
|
||||||
import kotlinx.coroutines.channels.BufferOverflow
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
|
||||||
import kotlinx.coroutines.flow.asSharedFlow
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
|
|
||||||
class LogcatManager(
|
|
||||||
pid: Int,
|
|
||||||
logDir: String,
|
|
||||||
maxFileSize: Long,
|
|
||||||
maxFolderSize: Long,
|
|
||||||
) : LogReader, DefaultLifecycleObserver {
|
|
||||||
private val logScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
|
|
||||||
private val fileManager = LogFileManager(logDir, maxFileSize, maxFolderSize)
|
|
||||||
private val logcatReader = LogcatStreamReader(pid, fileManager)
|
|
||||||
private var logJob: Job? = null
|
|
||||||
private var isStarted = false
|
|
||||||
|
|
||||||
private val _bufferedLogs = MutableSharedFlow<LogMessage>(
|
|
||||||
replay = 10_000,
|
|
||||||
onBufferOverflow = BufferOverflow.DROP_OLDEST,
|
|
||||||
)
|
|
||||||
private val _liveLogs = MutableSharedFlow<LogMessage>(
|
|
||||||
replay = 1,
|
|
||||||
onBufferOverflow = BufferOverflow.DROP_OLDEST,
|
|
||||||
)
|
|
||||||
|
|
||||||
override val bufferedLogs: Flow<LogMessage> = _bufferedLogs.asSharedFlow()
|
|
||||||
override val liveLogs: Flow<LogMessage> = _liveLogs.asSharedFlow()
|
|
||||||
|
|
||||||
override fun onCreate(owner: LifecycleOwner) {
|
|
||||||
// for auto start
|
|
||||||
// start()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDestroy(owner: LifecycleOwner) {
|
|
||||||
stop()
|
|
||||||
logScope.cancel()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun start() {
|
|
||||||
if (isStarted) return
|
|
||||||
stop()
|
|
||||||
logJob = logScope.launch {
|
|
||||||
logcatReader.readLogs().collect { logMessage ->
|
|
||||||
_bufferedLogs.emit(logMessage)
|
|
||||||
_liveLogs.emit(logMessage)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
isStarted = true
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun stop() {
|
|
||||||
if (!isStarted) return
|
|
||||||
logJob?.cancel()
|
|
||||||
logcatReader.stop()
|
|
||||||
fileManager.close()
|
|
||||||
isStarted = false
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun zipLogFiles(path: String) {
|
|
||||||
logScope.launch {
|
|
||||||
val wasStarted = isStarted
|
|
||||||
stop()
|
|
||||||
fileManager.zipLogs(path)
|
|
||||||
if (wasStarted) {
|
|
||||||
logcatReader.clearLogs()
|
|
||||||
start()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
|
||||||
override suspend fun deleteAndClearLogs() {
|
|
||||||
val wasStarted = isStarted
|
|
||||||
stop()
|
|
||||||
_bufferedLogs.resetReplayCache()
|
|
||||||
fileManager.deleteAllLogs()
|
|
||||||
if (wasStarted) start()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,32 +1,250 @@
|
|||||||
package com.zaneschepke.logcatter
|
package com.zaneschepke.logcatter
|
||||||
|
|
||||||
|
import androidx.lifecycle.DefaultLifecycleObserver
|
||||||
|
import androidx.lifecycle.LifecycleOwner
|
||||||
import androidx.lifecycle.ProcessLifecycleOwner
|
import androidx.lifecycle.ProcessLifecycleOwner
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import com.zaneschepke.logcatter.model.LogMessage
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.channels.BufferOverflow
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
|
import kotlinx.coroutines.flow.asSharedFlow
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import timber.log.Timber
|
||||||
|
import java.io.BufferedOutputStream
|
||||||
|
import java.io.BufferedReader
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.io.FileOutputStream
|
||||||
|
import java.io.IOException
|
||||||
|
import java.io.InputStreamReader
|
||||||
|
import java.util.zip.ZipEntry
|
||||||
|
import java.util.zip.ZipOutputStream
|
||||||
|
|
||||||
object LogcatReader {
|
object LogcatReader {
|
||||||
|
|
||||||
private const val MAX_FILE_SIZE = 2097152L // 2MB
|
private const val MAX_FILE_SIZE = 2097152L // 2MB
|
||||||
private const val MAX_FOLDER_SIZE = 10485760L // 10MB
|
private const val MAX_FOLDER_SIZE = 10485760L // 10MB
|
||||||
|
|
||||||
private lateinit var logcatManager: LogcatManager
|
private val findKeyRegex = """[A-Za-z0-9+/]{42}[AEIMQUYcgkosw480]=""".toRegex()
|
||||||
private var isInitialized = false
|
private val findIpv6AddressRegex = """^([0-9A-Fa-f]{1,4}:){7}[0-9A-Fa-f]{1,4}${'$'}""".toRegex()
|
||||||
|
private val findIpv4AddressRegex = """((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\.?\b){4}""".toRegex()
|
||||||
|
private val findTunnelNameRegex = """(?<=tunnel ).*?(?= UP| DOWN)""".toRegex()
|
||||||
|
|
||||||
|
private val ioDispatcher = Dispatchers.IO
|
||||||
|
|
||||||
|
private object LogcatHelperInit {
|
||||||
|
var maxFileSize: Long = MAX_FILE_SIZE
|
||||||
|
var maxFolderSize: Long = MAX_FOLDER_SIZE
|
||||||
|
var pID: Int = 0
|
||||||
|
var publicAppDirectory = ""
|
||||||
|
var logcatPath = ""
|
||||||
|
}
|
||||||
|
|
||||||
fun init(maxFileSize: Long = MAX_FILE_SIZE, maxFolderSize: Long = MAX_FOLDER_SIZE, storageDir: String): LogReader {
|
fun init(maxFileSize: Long = MAX_FILE_SIZE, maxFolderSize: Long = MAX_FOLDER_SIZE, storageDir: String): LogReader {
|
||||||
if (maxFileSize > maxFolderSize) {
|
if (maxFileSize > maxFolderSize) {
|
||||||
throw IllegalStateException("maxFileSize must be less than maxFolderSize")
|
throw IllegalStateException("maxFileSize must be less than maxFolderSize")
|
||||||
}
|
}
|
||||||
synchronized(this) {
|
synchronized(LogcatHelperInit) {
|
||||||
if (isInitialized) return logcatManager
|
LogcatHelperInit.maxFileSize = maxFileSize
|
||||||
val logDir = "$storageDir${File.separator}logs"
|
LogcatHelperInit.maxFolderSize = maxFolderSize
|
||||||
File(logDir).mkdirs()
|
LogcatHelperInit.pID = android.os.Process.myPid()
|
||||||
logcatManager = LogcatManager(
|
LogcatHelperInit.publicAppDirectory = storageDir
|
||||||
pid = android.os.Process.myPid(),
|
LogcatHelperInit.logcatPath = LogcatHelperInit.publicAppDirectory + File.separator + "logs"
|
||||||
logDir = logDir,
|
val logDirectory = File(LogcatHelperInit.logcatPath)
|
||||||
maxFileSize = maxFileSize,
|
if (!logDirectory.exists()) {
|
||||||
maxFolderSize = maxFolderSize,
|
logDirectory.mkdir()
|
||||||
)
|
}
|
||||||
ProcessLifecycleOwner.get().lifecycle.addObserver(logcatManager)
|
return Logcat
|
||||||
isInitialized = true
|
}
|
||||||
return logcatManager
|
}
|
||||||
|
|
||||||
|
internal object Logcat : LogReader {
|
||||||
|
|
||||||
|
private lateinit var logcatReader: LogcatReader
|
||||||
|
|
||||||
|
override fun initialize(onLogMessage: ((message: LogMessage) -> Unit)?) {
|
||||||
|
logcatReader = LogcatReader(LogcatHelperInit.pID.toString(), LogcatHelperInit.logcatPath, onLogMessage)
|
||||||
|
ProcessLifecycleOwner.get().lifecycle.addObserver(logcatReader)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun obfuscator(log: String): String {
|
||||||
|
return findKeyRegex.replace(log, "<crypto-key>").let { first ->
|
||||||
|
findIpv6AddressRegex.replace(first, "<ipv6-address>").let { second ->
|
||||||
|
findTunnelNameRegex.replace(second, "<tunnel>")
|
||||||
|
}
|
||||||
|
}.let { last -> findIpv4AddressRegex.replace(last, "<ipv4-address>") }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun zipLogFiles(path: String) {
|
||||||
|
logcatReader.cancel()
|
||||||
|
zipAll(path)
|
||||||
|
logcatReader.onCreate(ProcessLifecycleOwner.get())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun zipAll(zipFilePath: String) {
|
||||||
|
val sourceFile = File(LogcatHelperInit.logcatPath)
|
||||||
|
val outputZipFile = File(zipFilePath)
|
||||||
|
ZipOutputStream(BufferedOutputStream(FileOutputStream(outputZipFile))).use { zos ->
|
||||||
|
sourceFile.walkTopDown().forEach { file ->
|
||||||
|
val zipFileName = file.absolutePath.removePrefix(sourceFile.absolutePath).removePrefix("/")
|
||||||
|
val entry = ZipEntry("$zipFileName${(if (file.isDirectory) "/" else "")}")
|
||||||
|
zos.putNextEntry(entry)
|
||||||
|
if (file.isFile) {
|
||||||
|
file.inputStream().use {
|
||||||
|
it.copyTo(zos)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
|
override suspend fun deleteAndClearLogs() {
|
||||||
|
withContext(ioDispatcher) {
|
||||||
|
logcatReader.cancel()
|
||||||
|
_bufferedLogs.resetReplayCache()
|
||||||
|
logcatReader.deleteAllFiles()
|
||||||
|
logcatReader.onCreate(ProcessLifecycleOwner.get())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val _bufferedLogs = MutableSharedFlow<LogMessage>(
|
||||||
|
replay = 10_000,
|
||||||
|
onBufferOverflow = BufferOverflow.DROP_OLDEST,
|
||||||
|
)
|
||||||
|
private val _liveLogs = MutableSharedFlow<LogMessage>(
|
||||||
|
replay = 1,
|
||||||
|
onBufferOverflow = BufferOverflow.DROP_OLDEST,
|
||||||
|
)
|
||||||
|
|
||||||
|
override val bufferedLogs: Flow<LogMessage> = _bufferedLogs.asSharedFlow()
|
||||||
|
|
||||||
|
override val liveLogs: Flow<LogMessage> = _liveLogs.asSharedFlow()
|
||||||
|
|
||||||
|
private class LogcatReader(
|
||||||
|
pID: String,
|
||||||
|
private val logcatPath: String,
|
||||||
|
private val callback: ((input: LogMessage) -> Unit)?,
|
||||||
|
) : DefaultLifecycleObserver {
|
||||||
|
private var logcatProc: Process? = null
|
||||||
|
private var reader: BufferedReader? = null
|
||||||
|
|
||||||
|
private val command = "logcat -v epoch | grep \"($pID)\""
|
||||||
|
private val clearLogCommand = "logcat -c"
|
||||||
|
private var logJob: Job? = null
|
||||||
|
private var outputStream: FileOutputStream? = null
|
||||||
|
|
||||||
|
override fun onCreate(owner: LifecycleOwner) {
|
||||||
|
super.onCreate(owner)
|
||||||
|
logJob = owner.lifecycleScope.launch(ioDispatcher) {
|
||||||
|
try {
|
||||||
|
if (outputStream == null) outputStream = createNewLogFileStream()
|
||||||
|
clear()
|
||||||
|
logcatProc = Runtime.getRuntime().exec(command)
|
||||||
|
reader = BufferedReader(InputStreamReader(logcatProc!!.inputStream), 1024)
|
||||||
|
var line: String? = null
|
||||||
|
while (true) {
|
||||||
|
line = reader?.readLine()
|
||||||
|
if (line.isNullOrEmpty()) continue
|
||||||
|
outputStream?.let {
|
||||||
|
if (it.channel.size() >= LogcatHelperInit.maxFileSize) {
|
||||||
|
it.close()
|
||||||
|
outputStream = createNewLogFileStream()
|
||||||
|
}
|
||||||
|
if (getFolderSize(logcatPath) >= LogcatHelperInit.maxFolderSize) {
|
||||||
|
deleteOldestFile()
|
||||||
|
}
|
||||||
|
line.let { text ->
|
||||||
|
val sanitized = obfuscator(text)
|
||||||
|
it.write((sanitized + System.lineSeparator()).toByteArray())
|
||||||
|
try {
|
||||||
|
val logMessage = LogMessage.from(text)
|
||||||
|
_bufferedLogs.tryEmit(logMessage)
|
||||||
|
_liveLogs.tryEmit(logMessage)
|
||||||
|
callback?.let {
|
||||||
|
it(logMessage)
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Timber.e(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: IOException) {
|
||||||
|
Timber.e(e)
|
||||||
|
} finally {
|
||||||
|
reset()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
logJob?.invokeOnCompletion {
|
||||||
|
reset()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroy(owner: LifecycleOwner) {
|
||||||
|
super.onDestroy(owner)
|
||||||
|
logJob?.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun cancel() {
|
||||||
|
logJob?.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun reset() {
|
||||||
|
logcatProc?.destroy()
|
||||||
|
logcatProc = null
|
||||||
|
reader?.close()
|
||||||
|
outputStream?.close()
|
||||||
|
reader = null
|
||||||
|
outputStream = null
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clear() {
|
||||||
|
Runtime.getRuntime().exec(clearLogCommand)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getFolderSize(path: String): Long {
|
||||||
|
File(path).run {
|
||||||
|
var size = 0L
|
||||||
|
if (this.isDirectory && this.listFiles() != null) {
|
||||||
|
for (file in this.listFiles()!!) {
|
||||||
|
size += getFolderSize(file.absolutePath)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
size = this.length()
|
||||||
|
}
|
||||||
|
return size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createLogFile(dir: String): File {
|
||||||
|
return File(dir, "logcat_" + System.currentTimeMillis() + ".txt")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun deleteOldestFile() {
|
||||||
|
val directory = File(logcatPath)
|
||||||
|
if (directory.isDirectory) {
|
||||||
|
directory.listFiles()?.toMutableList()?.run {
|
||||||
|
this.sortBy { it.lastModified() }
|
||||||
|
this.first().delete()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createNewLogFileStream(): FileOutputStream {
|
||||||
|
return FileOutputStream(createLogFile(logcatPath))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun deleteAllFiles() {
|
||||||
|
val directory = File(logcatPath)
|
||||||
|
directory.listFiles()?.toMutableList()?.run {
|
||||||
|
this.forEach { it.delete() }
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,63 +0,0 @@
|
|||||||
package com.zaneschepke.logcatter
|
|
||||||
|
|
||||||
import com.zaneschepke.logcatter.model.LogMessage
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
import kotlinx.coroutines.flow.flow
|
|
||||||
import kotlinx.coroutines.flow.flowOn
|
|
||||||
import java.io.BufferedReader
|
|
||||||
import java.io.IOException
|
|
||||||
import java.io.InputStreamReader
|
|
||||||
|
|
||||||
class LogcatStreamReader(
|
|
||||||
private val pid: Int,
|
|
||||||
private val fileManager: LogFileManager,
|
|
||||||
) {
|
|
||||||
private val bufferSize = 1024
|
|
||||||
private var process: Process? = null
|
|
||||||
private var reader: BufferedReader? = null
|
|
||||||
private val command = "logcat -v epoch | grep \"($pid)\""
|
|
||||||
private val clearCommand = "logcat -c"
|
|
||||||
|
|
||||||
private val ioDispatcher = Dispatchers.IO
|
|
||||||
|
|
||||||
fun readLogs(): Flow<LogMessage> = flow {
|
|
||||||
try {
|
|
||||||
clearLogs()
|
|
||||||
process = Runtime.getRuntime().exec(command)
|
|
||||||
reader = BufferedReader(InputStreamReader(process!!.inputStream), bufferSize)
|
|
||||||
reader!!.lineSequence().forEach { line ->
|
|
||||||
if (line.isNotEmpty()) {
|
|
||||||
fileManager.writeLog(line)
|
|
||||||
emit(LogMessage.from(line))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e: IOException) {
|
|
||||||
// do nothing
|
|
||||||
} finally {
|
|
||||||
stop()
|
|
||||||
}
|
|
||||||
}.flowOn(ioDispatcher)
|
|
||||||
|
|
||||||
fun start() {
|
|
||||||
if (process == null) {
|
|
||||||
try {
|
|
||||||
process = Runtime.getRuntime().exec(command)
|
|
||||||
reader = BufferedReader(InputStreamReader(process!!.inputStream), bufferSize)
|
|
||||||
} catch (e: IOException) {
|
|
||||||
// do nothing
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun stop() {
|
|
||||||
process?.destroy()
|
|
||||||
reader?.close()
|
|
||||||
process = null
|
|
||||||
reader = null
|
|
||||||
}
|
|
||||||
|
|
||||||
fun clearLogs() {
|
|
||||||
Runtime.getRuntime().exec(clearCommand)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
/build
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
plugins {
|
|
||||||
alias(libs.plugins.androidLibrary)
|
|
||||||
alias(libs.plugins.kotlin.android)
|
|
||||||
}
|
|
||||||
|
|
||||||
android {
|
|
||||||
namespace = "com.zaneschepke.networkmonitor"
|
|
||||||
compileSdk = 34
|
|
||||||
|
|
||||||
defaultConfig {
|
|
||||||
minSdk = 26
|
|
||||||
|
|
||||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
|
||||||
consumerProguardFiles("consumer-rules.pro")
|
|
||||||
}
|
|
||||||
|
|
||||||
buildTypes {
|
|
||||||
release {
|
|
||||||
isMinifyEnabled = false
|
|
||||||
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
|
|
||||||
}
|
|
||||||
create(Constants.PRERELEASE) {
|
|
||||||
initWith(getByName(Constants.RELEASE))
|
|
||||||
}
|
|
||||||
|
|
||||||
create(Constants.NIGHTLY) {
|
|
||||||
initWith(getByName(Constants.RELEASE))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
compileOptions {
|
|
||||||
sourceCompatibility = JavaVersion.VERSION_17
|
|
||||||
targetCompatibility = JavaVersion.VERSION_17
|
|
||||||
}
|
|
||||||
kotlinOptions {
|
|
||||||
jvmTarget = Constants.JVM_TARGET
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
|
|
||||||
implementation(libs.androidx.core.ktx)
|
|
||||||
implementation(libs.androidx.appcompat)
|
|
||||||
implementation(libs.material)
|
|
||||||
testImplementation(libs.junit)
|
|
||||||
androidTestImplementation(libs.androidx.junit)
|
|
||||||
androidTestImplementation(libs.androidx.espresso.core)
|
|
||||||
|
|
||||||
implementation(libs.tunnel)
|
|
||||||
|
|
||||||
implementation(libs.timber)
|
|
||||||
}
|
|
||||||
Vendored
-21
@@ -1,21 +0,0 @@
|
|||||||
# Add project specific ProGuard rules here.
|
|
||||||
# You can control the set of applied configuration files using the
|
|
||||||
# proguardFiles setting in build.gradle.
|
|
||||||
#
|
|
||||||
# For more details, see
|
|
||||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
|
||||||
|
|
||||||
# If your project uses WebView with JS, uncomment the following
|
|
||||||
# and specify the fully qualified class name to the JavaScript interface
|
|
||||||
# class:
|
|
||||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
|
||||||
# public *;
|
|
||||||
#}
|
|
||||||
|
|
||||||
# Uncomment this to preserve the line number information for
|
|
||||||
# debugging stack traces.
|
|
||||||
#-keepattributes SourceFile,LineNumberTable
|
|
||||||
|
|
||||||
# If you keep the line number information, uncomment this to
|
|
||||||
# hide the original source file name.
|
|
||||||
#-renamesourcefileattribute SourceFile
|
|
||||||
-24
@@ -1,24 +0,0 @@
|
|||||||
package com.zaneschepke.networkmonitor
|
|
||||||
|
|
||||||
import androidx.test.platform.app.InstrumentationRegistry
|
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
|
||||||
|
|
||||||
import org.junit.Test
|
|
||||||
import org.junit.runner.RunWith
|
|
||||||
|
|
||||||
import org.junit.Assert.*
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Instrumented test, which will execute on an Android device.
|
|
||||||
*
|
|
||||||
* See [testing documentation](http://d.android.com/tools/testing).
|
|
||||||
*/
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
|
||||||
class ExampleInstrumentedTest {
|
|
||||||
@Test
|
|
||||||
fun useAppContext() {
|
|
||||||
// Context of the app under test.
|
|
||||||
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
|
||||||
assertEquals("com.zaneschepke.networkmonitor.test", appContext.packageName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
>
|
|
||||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
|
||||||
<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_BACKGROUND_LOCATION" />
|
|
||||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
|
|
||||||
</manifest>
|
|
||||||
@@ -1,151 +0,0 @@
|
|||||||
package com.zaneschepke.networkmonitor
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.net.ConnectivityManager
|
|
||||||
import android.net.Network
|
|
||||||
import android.net.NetworkCapabilities
|
|
||||||
import android.net.NetworkRequest
|
|
||||||
import android.net.wifi.WifiManager
|
|
||||||
import com.wireguard.android.util.RootShell
|
|
||||||
import kotlinx.coroutines.channels.awaitClose
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
import kotlinx.coroutines.flow.callbackFlow
|
|
||||||
import kotlinx.coroutines.flow.combine
|
|
||||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
|
||||||
import timber.log.Timber
|
|
||||||
|
|
||||||
class AndroidNetworkMonitor(
|
|
||||||
context: Context,
|
|
||||||
) : NetworkMonitor {
|
|
||||||
|
|
||||||
private val appContext = context.applicationContext
|
|
||||||
private val connectivityManager = appContext.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
|
|
||||||
private val wifiManager = appContext.getSystemService(Context.WIFI_SERVICE) as WifiManager?
|
|
||||||
private val rootShell = RootShell(context)
|
|
||||||
|
|
||||||
private var includeWifiSsid = false
|
|
||||||
private var useRootShell = false
|
|
||||||
|
|
||||||
data class WifiState(val connected: Boolean = false, val ssid: String? = null)
|
|
||||||
data class TransportState(val connected: Boolean = false)
|
|
||||||
|
|
||||||
private val wifiFlow: Flow<WifiState> = callbackFlow {
|
|
||||||
var currentSsid: String? = null
|
|
||||||
|
|
||||||
@Suppress("DEPRECATION")
|
|
||||||
fun getWifiSsid(): String? {
|
|
||||||
if (!includeWifiSsid || wifiManager == null) return null
|
|
||||||
return if (useRootShell) {
|
|
||||||
rootShell.getCurrentWifiName()
|
|
||||||
} else {
|
|
||||||
wifiManager.connectionInfo?.ssid?.trim('"')?.takeIf {
|
|
||||||
it != "<unknown>" && it.isNotEmpty()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val callback = object : ConnectivityManager.NetworkCallback() {
|
|
||||||
override fun onAvailable(network: Network) {
|
|
||||||
Timber.d("Wi-Fi onAvailable: network=$network")
|
|
||||||
val capabilities = connectivityManager.getNetworkCapabilities(network)
|
|
||||||
val connected = capabilities?.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) == true
|
|
||||||
if (connected) {
|
|
||||||
val ssid = getWifiSsid()
|
|
||||||
currentSsid = ssid
|
|
||||||
trySend(WifiState(connected = true, ssid = ssid))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onLost(network: Network) {
|
|
||||||
Timber.d("Wi-Fi onLost: network=$network")
|
|
||||||
currentSsid = null
|
|
||||||
trySend(WifiState(connected = false, ssid = null))
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) {
|
|
||||||
val connected = networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)
|
|
||||||
val ssid = if (connected) getWifiSsid() else null
|
|
||||||
if (ssid != currentSsid) {
|
|
||||||
currentSsid = ssid
|
|
||||||
trySend(WifiState(connected = connected, ssid = ssid))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val request = NetworkRequest.Builder()
|
|
||||||
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
|
|
||||||
.addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
|
|
||||||
.build()
|
|
||||||
|
|
||||||
connectivityManager.registerNetworkCallback(request, callback)
|
|
||||||
trySend(WifiState())
|
|
||||||
|
|
||||||
awaitClose { connectivityManager.unregisterNetworkCallback(callback) }
|
|
||||||
}
|
|
||||||
|
|
||||||
private val cellularFlow: Flow<TransportState> = callbackFlow {
|
|
||||||
val callback = object : ConnectivityManager.NetworkCallback() {
|
|
||||||
override fun onAvailable(network: Network) {
|
|
||||||
Timber.d("Cellular onAvailable: network=$network")
|
|
||||||
trySend(TransportState(connected = true))
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onLost(network: Network) {
|
|
||||||
Timber.d("Cellular onLost: network=$network")
|
|
||||||
trySend(TransportState(connected = false))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val request = NetworkRequest.Builder()
|
|
||||||
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
|
|
||||||
.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
|
|
||||||
.build()
|
|
||||||
|
|
||||||
connectivityManager.registerNetworkCallback(request, callback)
|
|
||||||
trySend(TransportState())
|
|
||||||
|
|
||||||
awaitClose { connectivityManager.unregisterNetworkCallback(callback) }
|
|
||||||
}
|
|
||||||
|
|
||||||
private val ethernetFlow: Flow<TransportState> = callbackFlow {
|
|
||||||
val callback = object : ConnectivityManager.NetworkCallback() {
|
|
||||||
override fun onAvailable(network: Network) {
|
|
||||||
Timber.d("Ethernet onAvailable: network=$network")
|
|
||||||
trySend(TransportState(connected = true))
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onLost(network: Network) {
|
|
||||||
Timber.d("Ethernet onLost: network=$network")
|
|
||||||
trySend(TransportState(connected = false))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val request = NetworkRequest.Builder()
|
|
||||||
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
|
|
||||||
.addTransportType(NetworkCapabilities.TRANSPORT_ETHERNET)
|
|
||||||
.build()
|
|
||||||
|
|
||||||
connectivityManager.registerNetworkCallback(request, callback)
|
|
||||||
trySend(TransportState())
|
|
||||||
|
|
||||||
awaitClose { connectivityManager.unregisterNetworkCallback(callback) }
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getNetworkStatusFlow(includeWifiSsid: Boolean, useRootShell: Boolean): Flow<NetworkStatus> {
|
|
||||||
this.includeWifiSsid = includeWifiSsid
|
|
||||||
this.useRootShell = useRootShell
|
|
||||||
return combine(wifiFlow, cellularFlow, ethernetFlow) { wifi, cellular, ethernet ->
|
|
||||||
val hasAnyConnection = wifi.connected || cellular.connected || ethernet.connected
|
|
||||||
if (hasAnyConnection) {
|
|
||||||
NetworkStatus.Connected(
|
|
||||||
wifiSsid = wifi.ssid,
|
|
||||||
wifiConnected = wifi.connected,
|
|
||||||
cellularConnected = cellular.connected,
|
|
||||||
ethernetConnected = ethernet.connected,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
NetworkStatus.Disconnected
|
|
||||||
}.also { Timber.d("NetworkStatus: $it") }
|
|
||||||
}.distinctUntilChanged()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
package com.zaneschepke.networkmonitor
|
|
||||||
|
|
||||||
import com.wireguard.android.util.RootShell
|
|
||||||
|
|
||||||
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()
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
package com.zaneschepke.networkmonitor
|
|
||||||
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
|
|
||||||
interface NetworkMonitor {
|
|
||||||
fun getNetworkStatusFlow(includeWifiSsid: Boolean, useRootShell: Boolean): Flow<NetworkStatus>
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
package com.zaneschepke.networkmonitor
|
|
||||||
|
|
||||||
sealed class NetworkStatus {
|
|
||||||
data object Disconnected : NetworkStatus() {
|
|
||||||
override val wifiConnected = false
|
|
||||||
override val ethernetConnected = false
|
|
||||||
override val cellularConnected = false
|
|
||||||
}
|
|
||||||
|
|
||||||
data class Connected(
|
|
||||||
val wifiSsid: String? = null,
|
|
||||||
override val wifiConnected: Boolean = false,
|
|
||||||
override val ethernetConnected: Boolean = false,
|
|
||||||
override val cellularConnected: Boolean = false,
|
|
||||||
) : NetworkStatus()
|
|
||||||
|
|
||||||
abstract val wifiConnected: Boolean
|
|
||||||
abstract val ethernetConnected: Boolean
|
|
||||||
abstract val cellularConnected: Boolean
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
package com.zaneschepke.networkmonitor
|
|
||||||
|
|
||||||
import org.junit.Test
|
|
||||||
|
|
||||||
import org.junit.Assert.*
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Example local unit test, which will execute on the development machine (host).
|
|
||||||
*
|
|
||||||
* See [testing documentation](http://d.android.com/tools/testing).
|
|
||||||
*/
|
|
||||||
class ExampleUnitTest {
|
|
||||||
@Test
|
|
||||||
fun addition_isCorrect() {
|
|
||||||
assertEquals(4, 2 + 2)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -34,4 +34,3 @@ rootProject.name = "WG Tunnel"
|
|||||||
|
|
||||||
include(":app")
|
include(":app")
|
||||||
include(":logcatter")
|
include(":logcatter")
|
||||||
include(":networkmonitor")
|
|
||||||
|
|||||||
+1
-1
@@ -1 +1 @@
|
|||||||
4
|
1
|
||||||
|
|||||||
Reference in New Issue
Block a user