mirror of
https://github.com/wgtunnel/android.git
synced 2026-07-03 14:07:49 +02:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 30851a7d7b | |||
| 3f4673b2a7 | |||
| 528a1f84e4 | |||
| 1af474c449 | |||
| 7e3405f3fd | |||
| ffeb089aa7 | |||
| 3838c32ddf |
@@ -167,6 +167,16 @@
|
||||
android:stopWithTask="false"
|
||||
tools:node="merge" />
|
||||
|
||||
<service
|
||||
android:name=".service.foreground.TunnelBackgroundService"
|
||||
android:exported="false"
|
||||
android:foregroundServiceType="systemExempted"
|
||||
android:permission="android.permission.BIND_VPN_SERVICE">
|
||||
<intent-filter>
|
||||
<action android:name="android.net.VpnService" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
|
||||
<receiver
|
||||
android:name=".receiver.BootReceiver"
|
||||
android:enabled="true"
|
||||
@@ -184,6 +194,13 @@
|
||||
android:name=".receiver.BackgroundActionReceiver"
|
||||
android:enabled="true"
|
||||
android:exported="false"/>
|
||||
<receiver
|
||||
android:name=".receiver.AppUpdateReceiver"
|
||||
android:exported="false">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MY_PACKAGE_REPLACED" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
<receiver
|
||||
android:name=".receiver.KernelReceiver"
|
||||
android:exported="false"
|
||||
|
||||
@@ -3,10 +3,15 @@ package com.zaneschepke.wireguardautotunnel
|
||||
import android.app.Application
|
||||
import android.os.StrictMode
|
||||
import android.os.StrictMode.ThreadPolicy
|
||||
import com.zaneschepke.logcatter.LocalLogCollector
|
||||
import com.zaneschepke.wireguardautotunnel.module.ApplicationScope
|
||||
import com.zaneschepke.wireguardautotunnel.module.IoDispatcher
|
||||
import com.zaneschepke.wireguardautotunnel.util.ReleaseTree
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.isRunningOnTv
|
||||
import dagger.hilt.android.HiltAndroidApp
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
@@ -17,6 +22,13 @@ class WireGuardAutoTunnel : Application() {
|
||||
@ApplicationScope
|
||||
lateinit var applicationScope: CoroutineScope
|
||||
|
||||
@Inject
|
||||
lateinit var localLogCollector: LocalLogCollector
|
||||
|
||||
@Inject
|
||||
@IoDispatcher
|
||||
lateinit var ioDispatcher: CoroutineDispatcher
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
instance = this
|
||||
@@ -33,6 +45,11 @@ class WireGuardAutoTunnel : Application() {
|
||||
} else {
|
||||
Timber.plant(ReleaseTree())
|
||||
}
|
||||
if (!isRunningOnTv()) {
|
||||
applicationScope.launch(ioDispatcher) {
|
||||
localLogCollector.start()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
package com.zaneschepke.wireguardautotunnel.receiver
|
||||
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
|
||||
import com.zaneschepke.wireguardautotunnel.module.ApplicationScope
|
||||
import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager
|
||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelService
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.startTunnelBackground
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
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 tunnelService: TunnelService
|
||||
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
if (intent.action != Intent.ACTION_MY_PACKAGE_REPLACED) return
|
||||
applicationScope.launch {
|
||||
val settings = appDataRepository.settings.getSettings()
|
||||
if (settings.isAutoTunnelEnabled) {
|
||||
Timber.i("Restarting services after upgrade")
|
||||
serviceManager.startWatcherServiceForeground(context)
|
||||
}
|
||||
if (!settings.isAutoTunnelEnabled || settings.isAutoTunnelPaused) {
|
||||
val tunnels = appDataRepository.tunnels.getAll().filter { it.isActive }
|
||||
if (tunnels.isNotEmpty()) context.startTunnelBackground(tunnels.first().id)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+8
@@ -5,10 +5,12 @@ import android.content.Context
|
||||
import android.content.Intent
|
||||
import com.zaneschepke.wireguardautotunnel.data.repository.TunnelConfigRepository
|
||||
import com.zaneschepke.wireguardautotunnel.module.ApplicationScope
|
||||
import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager
|
||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelService
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Provider
|
||||
|
||||
@@ -25,14 +27,19 @@ class BackgroundActionReceiver : BroadcastReceiver() {
|
||||
@Inject
|
||||
lateinit var tunnelConfigRepository: TunnelConfigRepository
|
||||
|
||||
@Inject
|
||||
lateinit var serviceManager: ServiceManager
|
||||
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
val id = intent.getIntExtra(TUNNEL_ID_EXTRA_KEY, 0)
|
||||
if (id == 0) return
|
||||
when (intent.action) {
|
||||
ACTION_CONNECT -> {
|
||||
Timber.d("Connect actions")
|
||||
applicationScope.launch {
|
||||
val tunnel = tunnelConfigRepository.getById(id)
|
||||
tunnel?.let {
|
||||
serviceManager.startTunnelBackgroundService(context)
|
||||
tunnelService.get().startTunnel(it)
|
||||
}
|
||||
}
|
||||
@@ -41,6 +48,7 @@ class BackgroundActionReceiver : BroadcastReceiver() {
|
||||
applicationScope.launch {
|
||||
val tunnel = tunnelConfigRepository.getById(id)
|
||||
tunnel?.let {
|
||||
serviceManager.stopTunnelBackgroundService(context)
|
||||
tunnelService.get().stopTunnel(it)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
|
||||
import com.zaneschepke.wireguardautotunnel.module.ApplicationScope
|
||||
import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager
|
||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelService
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.startTunnelBackground
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
@@ -35,7 +36,7 @@ class BootReceiver : BroadcastReceiver() {
|
||||
val settings = appDataRepository.settings.getSettings()
|
||||
if (settings.isRestoreOnBootEnabled) {
|
||||
appDataRepository.getStartTunnelConfig()?.let {
|
||||
tunnelService.get().startTunnel(it)
|
||||
context.startTunnelBackground(it.id)
|
||||
}
|
||||
}
|
||||
if (settings.isAutoTunnelEnabled) {
|
||||
|
||||
+16
@@ -50,4 +50,20 @@ class ServiceManager {
|
||||
AutoTunnelService::class.java,
|
||||
)
|
||||
}
|
||||
|
||||
fun startTunnelBackgroundService(context: Context) {
|
||||
actionOnService(
|
||||
Action.START_FOREGROUND,
|
||||
context,
|
||||
TunnelBackgroundService::class.java,
|
||||
)
|
||||
}
|
||||
|
||||
fun stopTunnelBackgroundService(context: Context) {
|
||||
actionOnService(
|
||||
Action.STOP,
|
||||
context,
|
||||
TunnelBackgroundService::class.java,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
package com.zaneschepke.wireguardautotunnel.service.foreground
|
||||
|
||||
import android.app.Notification
|
||||
import android.os.Bundle
|
||||
import com.zaneschepke.wireguardautotunnel.R
|
||||
import com.zaneschepke.wireguardautotunnel.service.notification.NotificationService
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
class TunnelBackgroundService : ForegroundService() {
|
||||
|
||||
@Inject
|
||||
lateinit var notificationService: NotificationService
|
||||
|
||||
private val foregroundId = 123
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
startForeground(foregroundId, createNotification())
|
||||
}
|
||||
|
||||
override fun startService(extras: Bundle?) {
|
||||
super.startService(extras)
|
||||
startForeground(foregroundId, createNotification())
|
||||
}
|
||||
|
||||
override fun stopService() {
|
||||
super.stopService()
|
||||
stopForeground(STOP_FOREGROUND_REMOVE)
|
||||
}
|
||||
|
||||
private fun createNotification(): Notification {
|
||||
return notificationService.createNotification(
|
||||
getString(R.string.vpn_channel_id),
|
||||
getString(R.string.vpn_channel_name),
|
||||
getString(R.string.tunnel_start_text),
|
||||
description = "",
|
||||
)
|
||||
}
|
||||
}
|
||||
+4
-2
@@ -7,6 +7,8 @@ import com.zaneschepke.wireguardautotunnel.module.ApplicationScope
|
||||
import com.zaneschepke.wireguardautotunnel.service.foreground.Action
|
||||
import com.zaneschepke.wireguardautotunnel.service.foreground.AutoTunnelService
|
||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelService
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.startTunnelBackground
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.stopTunnelBackground
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
@@ -42,8 +44,8 @@ class ShortcutsActivity : ComponentActivity() {
|
||||
Timber.d("Shortcut action on name: ${tunnelConfig?.name}")
|
||||
tunnelConfig?.let {
|
||||
when (intent.action) {
|
||||
Action.START.name -> tunnelService.get().startTunnel(it)
|
||||
Action.STOP.name -> tunnelService.get().stopTunnel(it)
|
||||
Action.START.name -> this@ShortcutsActivity.startTunnelBackground(it.id)
|
||||
Action.STOP.name -> this@ShortcutsActivity.stopTunnelBackground(it.id)
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
+1
@@ -68,6 +68,7 @@ class TunnelControlTile : TileService(), LifecycleOwner {
|
||||
override fun onClick() {
|
||||
super.onClick()
|
||||
unlockAndRun {
|
||||
Timber.d("Click")
|
||||
lifecycleScope.launch {
|
||||
val context = this@TunnelControlTile
|
||||
val lastActive = appDataRepository.getStartTunnelConfig()
|
||||
|
||||
@@ -14,4 +14,7 @@ interface TunnelService : Tunnel, org.amnezia.awg.backend.Tunnel {
|
||||
suspend fun runningTunnelNames(): Set<String>
|
||||
|
||||
suspend fun getState(): TunnelState
|
||||
|
||||
fun cancelStatsJob()
|
||||
fun startStatsJob()
|
||||
}
|
||||
|
||||
+22
-12
@@ -14,7 +14,6 @@ import com.zaneschepke.wireguardautotunnel.service.tunnel.statistics.TunnelStati
|
||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.statistics.WireGuardStatistics
|
||||
import com.zaneschepke.wireguardautotunnel.util.Constants
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.requestTunnelTileServiceStateUpdate
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Job
|
||||
@@ -56,8 +55,17 @@ constructor(
|
||||
return runCatching {
|
||||
when (val backend = backend()) {
|
||||
is Backend -> backend.setState(this, tunnelState.toWgState(), TunnelConfig.configFromWgQuick(tunnelConfig.wgQuick)).let { TunnelState.from(it) }
|
||||
is org.amnezia.awg.backend.Backend -> backend.setState(this, tunnelState.toAmState(), TunnelConfig.configFromAmQuick(tunnelConfig.amQuick)).let {
|
||||
TunnelState.from(it)
|
||||
is org.amnezia.awg.backend.Backend -> {
|
||||
val config = if (tunnelConfig.amQuick.isBlank()) {
|
||||
TunnelConfig.configFromAmQuick(
|
||||
tunnelConfig.wgQuick,
|
||||
)
|
||||
} else {
|
||||
TunnelConfig.configFromAmQuick(tunnelConfig.amQuick)
|
||||
}
|
||||
backend.setState(this, tunnelState.toAmState(), config).let {
|
||||
TunnelState.from(it)
|
||||
}
|
||||
}
|
||||
else -> throw NotImplementedError()
|
||||
}
|
||||
@@ -144,6 +152,14 @@ constructor(
|
||||
}
|
||||
}
|
||||
|
||||
override fun cancelStatsJob() {
|
||||
statsJob?.cancel()
|
||||
}
|
||||
|
||||
override fun startStatsJob() {
|
||||
statsJob = startTunnelStatisticsJob()
|
||||
}
|
||||
|
||||
override fun getName(): String {
|
||||
return _vpnState.value.tunnelConfig?.name ?: ""
|
||||
}
|
||||
@@ -155,15 +171,9 @@ constructor(
|
||||
private fun handleStateChange(state: TunnelState) {
|
||||
emitTunnelState(state)
|
||||
WireGuardAutoTunnel.instance.requestTunnelTileServiceStateUpdate()
|
||||
if (state == TunnelState.UP) {
|
||||
statsJob = startTunnelStatisticsJob()
|
||||
}
|
||||
if (state == TunnelState.DOWN) {
|
||||
try {
|
||||
statsJob?.cancel()
|
||||
} catch (e: CancellationException) {
|
||||
Timber.i("Stats job cancelled")
|
||||
}
|
||||
when (state) {
|
||||
TunnelState.UP -> startStatsJob()
|
||||
else -> cancelStatsJob()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,9 @@ import androidx.activity.SystemBarStyle
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.foundation.focusable
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
@@ -15,6 +18,7 @@ import androidx.compose.material3.SnackbarDuration
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.material3.SnackbarResult
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.surfaceColorAtElevation
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
@@ -35,8 +39,7 @@ import androidx.navigation.compose.currentBackStackEntryAsState
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import androidx.navigation.navArgument
|
||||
import com.zaneschepke.wireguardautotunnel.data.repository.AppStateRepository
|
||||
import com.zaneschepke.wireguardautotunnel.data.repository.SettingsRepository
|
||||
import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager
|
||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelService
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.navigation.BottomNavBar
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.prompt.CustomSnackBar
|
||||
import com.zaneschepke.wireguardautotunnel.ui.screens.config.ConfigScreen
|
||||
@@ -48,6 +51,7 @@ import com.zaneschepke.wireguardautotunnel.ui.screens.settings.SettingsScreen
|
||||
import com.zaneschepke.wireguardautotunnel.ui.screens.support.SupportScreen
|
||||
import com.zaneschepke.wireguardautotunnel.ui.screens.support.logs.LogsScreen
|
||||
import com.zaneschepke.wireguardautotunnel.ui.theme.WireguardAutoTunnelTheme
|
||||
import com.zaneschepke.wireguardautotunnel.util.Constants
|
||||
import com.zaneschepke.wireguardautotunnel.util.StringValue
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -60,10 +64,7 @@ class MainActivity : AppCompatActivity() {
|
||||
lateinit var appStateRepository: AppStateRepository
|
||||
|
||||
@Inject
|
||||
lateinit var settingsRepository: SettingsRepository
|
||||
|
||||
@Inject
|
||||
lateinit var serviceManager: ServiceManager
|
||||
lateinit var tunnelService: TunnelService
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
@@ -72,13 +73,6 @@ class MainActivity : AppCompatActivity() {
|
||||
|
||||
enableEdgeToEdge(navigationBarStyle = SystemBarStyle.dark(Color.Transparent.toArgb()))
|
||||
|
||||
lifecycleScope.launch {
|
||||
val settings = settingsRepository.getSettings()
|
||||
if (settings.isAutoTunnelEnabled) {
|
||||
serviceManager.startWatcherService(application.applicationContext)
|
||||
}
|
||||
}
|
||||
|
||||
setContent {
|
||||
val appViewModel = hiltViewModel<AppViewModel>()
|
||||
val appUiState by appViewModel.appUiState.collectAsStateWithLifecycle()
|
||||
@@ -127,7 +121,7 @@ class MainActivity : AppCompatActivity() {
|
||||
)
|
||||
}
|
||||
},
|
||||
// TODO refactor
|
||||
containerColor = MaterialTheme.colorScheme.background,
|
||||
modifier =
|
||||
Modifier
|
||||
.focusable()
|
||||
@@ -148,92 +142,97 @@ class MainActivity : AppCompatActivity() {
|
||||
)
|
||||
},
|
||||
) { padding ->
|
||||
NavHost(
|
||||
navController,
|
||||
startDestination = (if (isPinLockEnabled == true) Screen.Lock.route else Screen.Main.route),
|
||||
modifier =
|
||||
Modifier
|
||||
.padding(padding)
|
||||
.fillMaxSize(),
|
||||
) {
|
||||
composable(
|
||||
Screen.Main.route,
|
||||
Surface(modifier = Modifier.fillMaxSize().padding(padding)) {
|
||||
NavHost(
|
||||
navController,
|
||||
enterTransition = { fadeIn(tween(Constants.TRANSITION_ANIMATION_TIME)) },
|
||||
exitTransition = { fadeOut(tween(Constants.TRANSITION_ANIMATION_TIME)) },
|
||||
startDestination = (if (isPinLockEnabled == true) Screen.Lock.route else Screen.Main.route),
|
||||
) {
|
||||
MainScreen(
|
||||
focusRequester = focusRequester,
|
||||
appViewModel = appViewModel,
|
||||
navController = navController,
|
||||
)
|
||||
}
|
||||
composable(
|
||||
Screen.Settings.route,
|
||||
) {
|
||||
SettingsScreen(
|
||||
appViewModel = appViewModel,
|
||||
navController = navController,
|
||||
focusRequester = focusRequester,
|
||||
)
|
||||
}
|
||||
composable(
|
||||
Screen.Support.route,
|
||||
) {
|
||||
SupportScreen(
|
||||
focusRequester = focusRequester,
|
||||
navController = navController,
|
||||
)
|
||||
}
|
||||
composable(Screen.Support.Logs.route) {
|
||||
LogsScreen()
|
||||
}
|
||||
composable(
|
||||
"${Screen.Config.route}/{id}?configType={configType}",
|
||||
arguments =
|
||||
listOf(
|
||||
navArgument("id") {
|
||||
type = NavType.StringType
|
||||
defaultValue = "0"
|
||||
},
|
||||
navArgument("configType") {
|
||||
type = NavType.StringType
|
||||
defaultValue = ConfigType.WIREGUARD.name
|
||||
},
|
||||
),
|
||||
) {
|
||||
val id = it.arguments?.getString("id")
|
||||
val configType =
|
||||
ConfigType.valueOf(
|
||||
it.arguments?.getString("configType") ?: ConfigType.WIREGUARD.name,
|
||||
)
|
||||
if (!id.isNullOrBlank()) {
|
||||
ConfigScreen(
|
||||
navController = navController,
|
||||
tunnelId = id,
|
||||
appViewModel = appViewModel,
|
||||
composable(
|
||||
Screen.Main.route,
|
||||
) {
|
||||
MainScreen(
|
||||
focusRequester = focusRequester,
|
||||
configType = configType,
|
||||
appViewModel = appViewModel,
|
||||
navController = navController,
|
||||
)
|
||||
}
|
||||
}
|
||||
composable("${Screen.Option.route}/{id}") {
|
||||
val id = it.arguments?.getString("id")
|
||||
if (!id.isNullOrBlank()) {
|
||||
OptionsScreen(
|
||||
navController = navController,
|
||||
tunnelId = id,
|
||||
composable(
|
||||
Screen.Settings.route,
|
||||
) {
|
||||
SettingsScreen(
|
||||
appViewModel = appViewModel,
|
||||
navController = navController,
|
||||
focusRequester = focusRequester,
|
||||
)
|
||||
}
|
||||
}
|
||||
composable(Screen.Lock.route) {
|
||||
PinLockScreen(
|
||||
navController = navController,
|
||||
appViewModel = appViewModel,
|
||||
)
|
||||
composable(
|
||||
Screen.Support.route,
|
||||
) {
|
||||
SupportScreen(
|
||||
focusRequester = focusRequester,
|
||||
navController = navController,
|
||||
)
|
||||
}
|
||||
composable(Screen.Support.Logs.route) {
|
||||
LogsScreen()
|
||||
}
|
||||
composable(
|
||||
"${Screen.Config.route}/{id}?configType={configType}",
|
||||
arguments =
|
||||
listOf(
|
||||
navArgument("id") {
|
||||
type = NavType.StringType
|
||||
defaultValue = "0"
|
||||
},
|
||||
navArgument("configType") {
|
||||
type = NavType.StringType
|
||||
defaultValue = ConfigType.WIREGUARD.name
|
||||
},
|
||||
),
|
||||
) {
|
||||
val id = it.arguments?.getString("id")
|
||||
val configType =
|
||||
ConfigType.valueOf(
|
||||
it.arguments?.getString("configType") ?: ConfigType.WIREGUARD.name,
|
||||
)
|
||||
if (!id.isNullOrBlank()) {
|
||||
ConfigScreen(
|
||||
navController = navController,
|
||||
tunnelId = id,
|
||||
appViewModel = appViewModel,
|
||||
focusRequester = focusRequester,
|
||||
configType = configType,
|
||||
)
|
||||
}
|
||||
}
|
||||
composable("${Screen.Option.route}/{id}") {
|
||||
val id = it.arguments?.getString("id")
|
||||
if (!id.isNullOrBlank()) {
|
||||
OptionsScreen(
|
||||
navController = navController,
|
||||
tunnelId = id,
|
||||
appViewModel = appViewModel,
|
||||
focusRequester = focusRequester,
|
||||
)
|
||||
}
|
||||
}
|
||||
composable(Screen.Lock.route) {
|
||||
PinLockScreen(
|
||||
navController = navController,
|
||||
appViewModel = appViewModel,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
tunnelService.cancelStatsJob()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,21 +9,16 @@ import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import com.zaneschepke.logcatter.LocalLogCollector
|
||||
import com.zaneschepke.wireguardautotunnel.WireGuardAutoTunnel
|
||||
import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
|
||||
import com.zaneschepke.wireguardautotunnel.data.repository.AppStateRepository
|
||||
import com.zaneschepke.wireguardautotunnel.module.ApplicationScope
|
||||
import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager
|
||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelService
|
||||
import com.zaneschepke.wireguardautotunnel.util.Constants
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.isRunningOnTv
|
||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelState
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.requestAutoTunnelTileServiceUpdate
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.requestTunnelTileServiceStateUpdate
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import xyz.teamgravity.pin_lock_compose.PinManager
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Provider
|
||||
@@ -41,11 +36,7 @@ class SplashActivity : ComponentActivity() {
|
||||
lateinit var tunnelService: Provider<TunnelService>
|
||||
|
||||
@Inject
|
||||
lateinit var localLogCollector: LocalLogCollector
|
||||
|
||||
@Inject
|
||||
@ApplicationScope
|
||||
lateinit var applicationScope: CoroutineScope
|
||||
lateinit var serviceManager: ServiceManager
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
@@ -54,29 +45,17 @@ class SplashActivity : ComponentActivity() {
|
||||
}
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
applicationScope.launch {
|
||||
if (!this@SplashActivity.isRunningOnTv()) localLogCollector.start()
|
||||
}
|
||||
|
||||
lifecycleScope.launch {
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
val pinLockEnabled = appStateRepository.isPinLockEnabled()
|
||||
if (pinLockEnabled) {
|
||||
PinManager.initialize(WireGuardAutoTunnel.instance)
|
||||
}
|
||||
// TODO eventually make this support multi-tunnel
|
||||
Timber.d("Check for active tunnels")
|
||||
val settings = appDataRepository.settings.getSettings()
|
||||
if (settings.isKernelEnabled) {
|
||||
// delay in case state change is underway while app is opened
|
||||
delay(Constants.FOCUS_REQUEST_DELAY)
|
||||
val activeTunnels = appDataRepository.tunnels.getActive()
|
||||
Timber.d("Kernel mode enabled, seeing if we need to start a tunnel")
|
||||
activeTunnels.firstOrNull()?.let {
|
||||
Timber.d("Trying to start active kernel tunnel: ${it.name}")
|
||||
tunnelService.get().startTunnel(it)
|
||||
}
|
||||
}
|
||||
if (settings.isAutoTunnelEnabled) serviceManager.startWatcherService(application.applicationContext)
|
||||
if (tunnelService.get().getState() == TunnelState.UP) tunnelService.get().startStatsJob()
|
||||
val tunnels = appDataRepository.tunnels.getActive()
|
||||
if (tunnels.isNotEmpty() && tunnelService.get().getState() == TunnelState.DOWN) tunnelService.get().startTunnel(tunnels.first())
|
||||
requestTunnelTileServiceStateUpdate()
|
||||
requestAutoTunnelTileServiceUpdate()
|
||||
|
||||
|
||||
+7
-15
@@ -10,37 +10,29 @@ import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.navigation.NavController
|
||||
import androidx.navigation.NavGraph.Companion.findStartDestination
|
||||
import androidx.navigation.compose.currentBackStackEntryAsState
|
||||
import com.zaneschepke.wireguardautotunnel.ui.Screen
|
||||
|
||||
@Composable
|
||||
fun BottomNavBar(navController: NavController, bottomNavItems: List<BottomNavItem>) {
|
||||
val backStackEntry = navController.currentBackStackEntryAsState()
|
||||
|
||||
var showBottomBar by rememberSaveable { mutableStateOf(true) }
|
||||
val navBackStackEntry by navController.currentBackStackEntryAsState()
|
||||
|
||||
// TODO find a better way to hide nav bar
|
||||
showBottomBar =
|
||||
when (navBackStackEntry?.destination?.route) {
|
||||
Screen.Lock.route -> false
|
||||
else -> true
|
||||
}
|
||||
showBottomBar = bottomNavItems.firstOrNull { navBackStackEntry?.destination?.route?.contains(it.route) == true } != null
|
||||
|
||||
NavigationBar(
|
||||
containerColor = if (!showBottomBar) Color.Transparent else MaterialTheme.colorScheme.background,
|
||||
) {
|
||||
if (showBottomBar) {
|
||||
if (showBottomBar) {
|
||||
NavigationBar(
|
||||
containerColor = MaterialTheme.colorScheme.surface,
|
||||
) {
|
||||
bottomNavItems.forEach { item ->
|
||||
val selected = item.route == backStackEntry.value?.destination?.route
|
||||
val selected = navBackStackEntry?.destination?.route?.contains(item.route) == true
|
||||
|
||||
NavigationBarItem(
|
||||
selected = selected,
|
||||
onClick = {
|
||||
if (navBackStackEntry?.destination?.route == item.route) return@NavigationBarItem
|
||||
navController.navigate(item.route) {
|
||||
// Pop up to the start destination of the graph to
|
||||
// avoid building up a large stack of destinations
|
||||
|
||||
+6
-1
@@ -110,7 +110,12 @@ fun ConfigScreen(
|
||||
LaunchedEffect(uiState.loading) {
|
||||
if (!uiState.loading && context.isRunningOnTv()) {
|
||||
delay(Constants.FOCUS_REQUEST_DELAY)
|
||||
focusRequester.requestFocus()
|
||||
kotlin.runCatching {
|
||||
focusRequester.requestFocus()
|
||||
}.onFailure {
|
||||
delay(Constants.FOCUS_REQUEST_DELAY)
|
||||
focusRequester.requestFocus()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -296,7 +296,7 @@ constructor(
|
||||
val wgQuick = buildConfig().toWgQuickString(true)
|
||||
val amQuick =
|
||||
if (configType == ConfigType.AMNEZIA) {
|
||||
buildAmConfig().toAwgQuickString()
|
||||
buildAmConfig().toAwgQuickString(true)
|
||||
} else {
|
||||
TunnelConfig.AM_QUICK_DEFAULT
|
||||
}
|
||||
|
||||
+8
-10
@@ -4,9 +4,6 @@ import android.annotation.SuppressLint
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.appcompat.app.AppCompatActivity.RESULT_OK
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.focusable
|
||||
import androidx.compose.foundation.gestures.ScrollableDefaults
|
||||
@@ -147,7 +144,12 @@ fun MainScreen(
|
||||
LaunchedEffect(Unit) {
|
||||
if (context.isRunningOnTv()) {
|
||||
delay(Constants.FOCUS_REQUEST_DELAY)
|
||||
focusRequester.requestFocus()
|
||||
kotlin.runCatching {
|
||||
focusRequester.requestFocus()
|
||||
}.onFailure {
|
||||
delay(Constants.FOCUS_REQUEST_DELAY)
|
||||
focusRequester.requestFocus()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -265,12 +267,8 @@ fun MainScreen(
|
||||
reverseLayout = false,
|
||||
flingBehavior = ScrollableDefaults.flingBehavior(),
|
||||
) {
|
||||
item {
|
||||
AnimatedVisibility(
|
||||
uiState.tunnels.isEmpty(),
|
||||
exit = fadeOut(),
|
||||
enter = fadeIn(),
|
||||
) {
|
||||
if (uiState.tunnels.isEmpty()) {
|
||||
item {
|
||||
GettingStartedLabel(onClick = { context.openWebUrl(it) })
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -179,7 +179,7 @@ constructor(
|
||||
when (type) {
|
||||
ConfigType.AMNEZIA -> {
|
||||
val config = org.amnezia.awg.config.Config.parse(it)
|
||||
amQuick = config.toAwgQuickString()
|
||||
amQuick = config.toAwgQuickString(true)
|
||||
config.toWgQuickString()
|
||||
}
|
||||
|
||||
@@ -252,7 +252,7 @@ constructor(
|
||||
org.amnezia.awg.config.Config.parse(
|
||||
zip,
|
||||
)
|
||||
amQuick = config.toAwgQuickString()
|
||||
amQuick = config.toAwgQuickString(true)
|
||||
config.toWgQuickString()
|
||||
}
|
||||
|
||||
|
||||
+6
-1
@@ -88,7 +88,12 @@ fun OptionsScreen(
|
||||
optionsViewModel.init(tunnelId)
|
||||
if (context.isRunningOnTv()) {
|
||||
delay(Constants.FOCUS_REQUEST_DELAY)
|
||||
focusRequester.requestFocus()
|
||||
kotlin.runCatching {
|
||||
focusRequester.requestFocus()
|
||||
}.onFailure {
|
||||
delay(Constants.FOCUS_REQUEST_DELAY)
|
||||
focusRequester.requestFocus()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+21
-55
@@ -12,7 +12,6 @@ import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.ActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.appcompat.app.AppCompatActivity.RESULT_OK
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
@@ -67,7 +66,6 @@ import com.google.accompanist.permissions.isGranted
|
||||
import com.google.accompanist.permissions.rememberPermissionState
|
||||
import com.zaneschepke.wireguardautotunnel.R
|
||||
import com.zaneschepke.wireguardautotunnel.WireGuardAutoTunnel
|
||||
import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
|
||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelState
|
||||
import com.zaneschepke.wireguardautotunnel.ui.AppViewModel
|
||||
import com.zaneschepke.wireguardautotunnel.ui.Screen
|
||||
@@ -84,9 +82,7 @@ import com.zaneschepke.wireguardautotunnel.util.extensions.isRunningOnTv
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.launchAppSettings
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.showToast
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import xyz.teamgravity.pin_lock_compose.PinManager
|
||||
import java.io.File
|
||||
|
||||
@OptIn(
|
||||
ExperimentalPermissionsApi::class,
|
||||
@@ -155,43 +151,6 @@ fun SettingsScreen(
|
||||
},
|
||||
)
|
||||
|
||||
fun exportAllConfigs() {
|
||||
try {
|
||||
val wgFiles =
|
||||
uiState.tunnels.map { config ->
|
||||
val file = File(context.cacheDir, "${config.name}-wg.conf")
|
||||
file.outputStream().use {
|
||||
it.write(config.wgQuick.toByteArray())
|
||||
}
|
||||
file
|
||||
}
|
||||
val amFiles =
|
||||
uiState.tunnels.mapNotNull { config ->
|
||||
if (config.amQuick != TunnelConfig.AM_QUICK_DEFAULT) {
|
||||
val file = File(context.cacheDir, "${config.name}-am.conf")
|
||||
file.outputStream().use {
|
||||
it.write(config.amQuick.toByteArray())
|
||||
}
|
||||
file
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
scope.launch {
|
||||
viewModel.onExportTunnels(wgFiles + amFiles).onFailure {
|
||||
appViewModel.showSnackbarMessage(it.getMessage(context))
|
||||
}.onSuccess {
|
||||
didExportFiles = true
|
||||
appViewModel.showSnackbarMessage(
|
||||
context.getString(R.string.exported_configs_message),
|
||||
)
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e)
|
||||
}
|
||||
}
|
||||
|
||||
fun isBatteryOptimizationsDisabled(): Boolean {
|
||||
val pm = context.getSystemService(POWER_SERVICE) as PowerManager
|
||||
return pm.isIgnoringBatteryOptimizations(context.packageName)
|
||||
@@ -269,17 +228,18 @@ fun SettingsScreen(
|
||||
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.P) {
|
||||
checkFineLocationGranted()
|
||||
}
|
||||
|
||||
BackgroundLocationDisclosure(
|
||||
!uiState.isLocationDisclosureShown,
|
||||
onDismiss = { viewModel.setLocationDisclosureShown() },
|
||||
onAttest = {
|
||||
context.launchAppSettings()
|
||||
viewModel.setLocationDisclosureShown()
|
||||
},
|
||||
scrollState,
|
||||
focusRequester,
|
||||
)
|
||||
if (!uiState.isLocationDisclosureShown) {
|
||||
BackgroundLocationDisclosure(
|
||||
onDismiss = { viewModel.setLocationDisclosureShown() },
|
||||
onAttest = {
|
||||
context.launchAppSettings()
|
||||
viewModel.setLocationDisclosureShown()
|
||||
},
|
||||
scrollState,
|
||||
focusRequester,
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
BackgroundLocationDialog(
|
||||
showLocationDialog,
|
||||
@@ -299,7 +259,13 @@ fun SettingsScreen(
|
||||
AuthorizationPrompt(
|
||||
onSuccess = {
|
||||
showAuthPrompt = false
|
||||
exportAllConfigs()
|
||||
scope.launch {
|
||||
viewModel.exportAllConfigs().onSuccess {
|
||||
appViewModel.showSnackbarMessage(context.getString(R.string.exported_configs_message))
|
||||
}.onFailure {
|
||||
appViewModel.showSnackbarMessage(context.getString(R.string.export_configs_failed))
|
||||
}
|
||||
}
|
||||
},
|
||||
onError = { _ ->
|
||||
showAuthPrompt = false
|
||||
@@ -378,7 +344,7 @@ fun SettingsScreen(
|
||||
.focusRequester(focusRequester)
|
||||
},
|
||||
)
|
||||
AnimatedVisibility(visible = uiState.settings.isTunnelOnWifiEnabled) {
|
||||
if (uiState.settings.isTunnelOnWifiEnabled) {
|
||||
Column {
|
||||
FlowRow(
|
||||
modifier =
|
||||
@@ -628,7 +594,7 @@ fun SettingsScreen(
|
||||
Modifier
|
||||
.fillMaxWidth(fillMaxWidth)
|
||||
.padding(vertical = 10.dp)
|
||||
.padding(bottom = 140.dp),
|
||||
.padding(bottom = 10.dp),
|
||||
) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.Start,
|
||||
|
||||
+9
@@ -248,4 +248,13 @@ constructor(
|
||||
onSuccess()
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun exportAllConfigs(): Result<Unit> {
|
||||
return kotlin.runCatching {
|
||||
val tunnels = appDataRepository.tunnels.getAll()
|
||||
val wgFiles = fileUtils.createWgFiles(tunnels)
|
||||
val amFiles = fileUtils.createAmFiles(tunnels)
|
||||
onExportTunnels(wgFiles + amFiles)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+47
-55
@@ -28,68 +28,60 @@ import com.zaneschepke.wireguardautotunnel.R
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.isRunningOnTv
|
||||
|
||||
@Composable
|
||||
fun BackgroundLocationDisclosure(
|
||||
show: Boolean,
|
||||
onDismiss: () -> Unit,
|
||||
onAttest: () -> Unit,
|
||||
scrollState: ScrollState,
|
||||
focusRequester: FocusRequester,
|
||||
) {
|
||||
fun BackgroundLocationDisclosure(onDismiss: () -> Unit, onAttest: () -> Unit, scrollState: ScrollState, focusRequester: FocusRequester) {
|
||||
val context = LocalContext.current
|
||||
if (show) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.Top,
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.Top,
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
.verticalScroll(scrollState),
|
||||
) {
|
||||
Icon(
|
||||
Icons.Rounded.LocationOff,
|
||||
contentDescription = stringResource(id = R.string.map),
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
.verticalScroll(scrollState),
|
||||
) {
|
||||
Icon(
|
||||
Icons.Rounded.LocationOff,
|
||||
contentDescription = stringResource(id = R.string.map),
|
||||
modifier =
|
||||
.padding(30.dp)
|
||||
.size(128.dp),
|
||||
)
|
||||
Text(
|
||||
stringResource(R.string.prominent_background_location_title),
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier.padding(30.dp),
|
||||
fontSize = 20.sp,
|
||||
)
|
||||
Text(
|
||||
stringResource(R.string.prominent_background_location_message),
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier.padding(30.dp),
|
||||
fontSize = 15.sp,
|
||||
)
|
||||
Row(
|
||||
modifier =
|
||||
if (context.isRunningOnTv()) {
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(10.dp)
|
||||
} else {
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(30.dp)
|
||||
.size(128.dp),
|
||||
)
|
||||
Text(
|
||||
stringResource(R.string.prominent_background_location_title),
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier.padding(30.dp),
|
||||
fontSize = 20.sp,
|
||||
)
|
||||
Text(
|
||||
stringResource(R.string.prominent_background_location_message),
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier.padding(30.dp),
|
||||
fontSize = 15.sp,
|
||||
)
|
||||
Row(
|
||||
modifier =
|
||||
if (context.isRunningOnTv()) {
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(10.dp)
|
||||
} else {
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(30.dp)
|
||||
},
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.SpaceEvenly,
|
||||
) {
|
||||
TextButton(onClick = { onDismiss() }) {
|
||||
Text(stringResource(id = R.string.no_thanks))
|
||||
}
|
||||
TextButton(
|
||||
modifier = Modifier.focusRequester(focusRequester),
|
||||
onClick = {
|
||||
onAttest()
|
||||
},
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.SpaceEvenly,
|
||||
) {
|
||||
TextButton(onClick = { onDismiss() }) {
|
||||
Text(stringResource(id = R.string.no_thanks))
|
||||
}
|
||||
TextButton(
|
||||
modifier = Modifier.focusRequester(focusRequester),
|
||||
onClick = {
|
||||
onAttest()
|
||||
},
|
||||
) {
|
||||
Text(stringResource(id = R.string.turn_on))
|
||||
}
|
||||
Text(stringResource(id = R.string.turn_on))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,9 +20,10 @@ private val DarkColorScheme =
|
||||
darkColorScheme(
|
||||
// primary = Purple80,
|
||||
primary = virdigris,
|
||||
secondary = virdigris,
|
||||
secondary = PurpleGrey40,
|
||||
// secondary = PurpleGrey80,
|
||||
tertiary = virdigris,
|
||||
tertiary = Pink40,
|
||||
surfaceTint = Pink80,
|
||||
// tertiary = Pink80
|
||||
)
|
||||
|
||||
@@ -31,6 +32,7 @@ private val LightColorScheme =
|
||||
primary = Purple40,
|
||||
secondary = PurpleGrey40,
|
||||
tertiary = Pink40,
|
||||
surfaceTint = Pink80,
|
||||
/* Other default colors to override
|
||||
background = Color(0xFFFFFBFE),
|
||||
surface = Color(0xFFFFFBFE),
|
||||
|
||||
@@ -7,7 +7,7 @@ object Constants {
|
||||
const val MANUAL_TUNNEL_CONFIG_ID = "0"
|
||||
const val BATTERY_SAVER_WATCHER_WAKE_LOCK_TIMEOUT = 10 * 60 * 1_000L // 10 minutes
|
||||
const val VPN_STATISTIC_CHECK_INTERVAL = 1_000L
|
||||
const val WATCHER_COLLECTION_DELAY = 1_000L
|
||||
const val WATCHER_COLLECTION_DELAY = 3_000L
|
||||
const val CONF_FILE_EXTENSION = ".conf"
|
||||
const val ZIP_FILE_EXTENSION = ".zip"
|
||||
const val URI_CONTENT_SCHEME = "content"
|
||||
@@ -24,6 +24,8 @@ object Constants {
|
||||
const val SUBSCRIPTION_TIMEOUT = 5_000L
|
||||
const val FOCUS_REQUEST_DELAY = 500L
|
||||
|
||||
const val TRANSITION_ANIMATION_TIME = 200
|
||||
|
||||
const val DEFAULT_PING_IP = "1.1.1.1"
|
||||
const val PING_TIMEOUT = 5_000L
|
||||
const val VPN_RESTART_DELAY = 1_000L
|
||||
|
||||
@@ -6,6 +6,8 @@ import android.os.Build
|
||||
import android.os.Environment
|
||||
import android.provider.MediaStore
|
||||
import android.provider.MediaStore.MediaColumns
|
||||
import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.TunnelConfigs
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.withContext
|
||||
import timber.log.Timber
|
||||
@@ -39,6 +41,26 @@ class FileUtils(
|
||||
}
|
||||
}
|
||||
|
||||
fun createWgFiles(tunnels: TunnelConfigs): List<File> {
|
||||
return tunnels.map { config ->
|
||||
val file = File(context.cacheDir, "${config.name}-wg.conf")
|
||||
file.outputStream().use {
|
||||
it.write(config.wgQuick.toByteArray())
|
||||
}
|
||||
file
|
||||
}
|
||||
}
|
||||
|
||||
fun createAmFiles(tunnels: TunnelConfigs): List<File> {
|
||||
return tunnels.filter { it.amQuick != TunnelConfig.AM_QUICK_DEFAULT }.map { config ->
|
||||
val file = File(context.cacheDir, "${config.name}-am.conf")
|
||||
file.outputStream().use {
|
||||
it.write(config.amQuick.toByteArray())
|
||||
}
|
||||
file
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun saveByteArrayToDownloads(content: ByteArray, fileName: String): Result<Unit> {
|
||||
return withContext(ioDispatcher) {
|
||||
try {
|
||||
|
||||
+1
-1
@@ -29,7 +29,7 @@ fun TunnelStatistics.PeerStats.handshakeStatus(): HandshakeStatus {
|
||||
}
|
||||
|
||||
fun Config.toWgQuickString(): String {
|
||||
val amQuick = toAwgQuickString()
|
||||
val amQuick = toAwgQuickString(true)
|
||||
val lines = amQuick.lines().toMutableList()
|
||||
val linesIterator = lines.iterator()
|
||||
while (linesIterator.hasNext()) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
object Constants {
|
||||
const val VERSION_NAME = "3.5.0"
|
||||
const val VERSION_NAME = "3.5.1"
|
||||
const val JVM_TARGET = "17"
|
||||
const val VERSION_CODE = 35000
|
||||
const val VERSION_CODE = 35100
|
||||
const val TARGET_SDK = 34
|
||||
const val MIN_SDK = 26
|
||||
const val APP_ID = "com.zaneschepke.wireguardautotunnel"
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
What's new:
|
||||
- Fixes for tunnels not launching from background
|
||||
- Add support for restart services after update
|
||||
- UI animation speed improvements
|
||||
- Other optimizations
|
||||
@@ -1,3 +0,0 @@
|
||||
Melhorias:
|
||||
- Corrige o bug de permissões do Android 9
|
||||
- Outras otimizações
|
||||
@@ -1,5 +0,0 @@
|
||||
Melhorias:
|
||||
- Adicionada estatísticas do túnel na tela principal
|
||||
- Melhoria de navegação de configurações na tela do AndroidTV
|
||||
- Removida a vibração nas notificações
|
||||
- Outras correções de bugs
|
||||
@@ -1,14 +0,0 @@
|
||||
Recursos
|
||||
|
||||
- Adiciona túneis por arquivos .conf, zip, manualmente ou por código QR
|
||||
- Auto connecta à VPN baseado no nome (SSID) do Wi-Fi, ethernet ou dados móveis
|
||||
- Túnel dividido por aplicativo com busca
|
||||
- Suporte à WireGuard em modo kernel ou usuário
|
||||
- Suporte à Amnezia em modo usuário para proteção contra censura e DPI (Inspeção Profunda de Pacote)
|
||||
- Suporte à VPN sempre ligada
|
||||
- Exportação de túneis Amnezia e WireGuard em arquivos zip
|
||||
- Suporte à quick tile para ligar e desligar a VPN
|
||||
- Atalhos para o túnel principal para integração com automações
|
||||
- Intent automation para todos os túneis
|
||||
- Início automático depois de reiniciar o aparelho
|
||||
- Medidas para economia de bateria
|
||||
@@ -1 +0,0 @@
|
||||
Um cliente de VPN alternativo para WireGuard com recursos adicionais
|
||||
@@ -1 +0,0 @@
|
||||
WG Tunnel
|
||||
@@ -1,7 +1,7 @@
|
||||
[versions]
|
||||
accompanist = "0.34.0"
|
||||
activityCompose = "1.9.1"
|
||||
amneziawgAndroid = "1.2.1"
|
||||
amneziawgAndroid = "1.2.2"
|
||||
androidx-junit = "1.2.1"
|
||||
appcompat = "1.7.0"
|
||||
biometricKtx = "1.2.0-alpha05"
|
||||
@@ -16,7 +16,7 @@ junit = "4.13.2"
|
||||
kotlinx-serialization-json = "1.7.1"
|
||||
lifecycle-runtime-compose = "2.8.4"
|
||||
material3 = "1.2.1"
|
||||
multifabVersion = "1.1.0"
|
||||
multifabVersion = "1.1.1"
|
||||
navigationCompose = "2.7.7"
|
||||
pinLockCompose = "1.0.3"
|
||||
roomVersion = "2.6.1"
|
||||
|
||||
@@ -5,7 +5,7 @@ import kotlinx.coroutines.flow.Flow
|
||||
import java.io.File
|
||||
|
||||
interface LocalLogCollector {
|
||||
fun start(onLogMessage: ((message: LogMessage) -> Unit)? = null)
|
||||
suspend fun start(onLogMessage: ((message: LogMessage) -> Unit)? = null)
|
||||
|
||||
fun stop()
|
||||
|
||||
|
||||
@@ -69,7 +69,7 @@ object LogcatUtil {
|
||||
internal object Logcat : LocalLogCollector {
|
||||
private var logcatReader: LogcatReader? = null
|
||||
|
||||
override fun start(onLogMessage: ((message: LogMessage) -> Unit)?) {
|
||||
override suspend fun start(onLogMessage: ((message: LogMessage) -> Unit)?) {
|
||||
logcatReader ?: run {
|
||||
logcatReader =
|
||||
LogcatReader(
|
||||
@@ -78,9 +78,7 @@ object LogcatUtil {
|
||||
onLogMessage,
|
||||
)
|
||||
}
|
||||
logcatReader?.let { logReader ->
|
||||
if (!logReader.isAlive) logReader.start()
|
||||
}
|
||||
logcatReader?.run()
|
||||
}
|
||||
|
||||
override fun stop() {
|
||||
@@ -142,7 +140,7 @@ object LogcatUtil {
|
||||
pID: String,
|
||||
private val logcatPath: String,
|
||||
private val callback: ((input: LogMessage) -> Unit)?,
|
||||
) : Thread() {
|
||||
) {
|
||||
private var logcatProc: Process? = null
|
||||
private var reader: BufferedReader? = null
|
||||
private var mRunning = true
|
||||
@@ -177,7 +175,7 @@ object LogcatUtil {
|
||||
}.let { last -> findIpv4AddressRegex.replace(last, "<ipv4-address>") }
|
||||
}
|
||||
|
||||
override fun run() {
|
||||
fun run() {
|
||||
if (outputStream == null) return
|
||||
try {
|
||||
clear()
|
||||
|
||||
Reference in New Issue
Block a user