fix: tile control and kernel sync (#320)
increase auto tunnel delay to 3 seconds optimize stats job by killing it when app is backgrounded fix tunnel launch from background add restart of services and tunnels after update
This commit is contained in:
parent
3f4673b2a7
commit
30851a7d7b
|
@ -167,6 +167,16 @@
|
||||||
android:stopWithTask="false"
|
android:stopWithTask="false"
|
||||||
tools:node="merge" />
|
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
|
<receiver
|
||||||
android:name=".receiver.BootReceiver"
|
android:name=".receiver.BootReceiver"
|
||||||
android:enabled="true"
|
android:enabled="true"
|
||||||
|
@ -184,6 +194,13 @@
|
||||||
android:name=".receiver.BackgroundActionReceiver"
|
android:name=".receiver.BackgroundActionReceiver"
|
||||||
android:enabled="true"
|
android:enabled="true"
|
||||||
android:exported="false"/>
|
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
|
<receiver
|
||||||
android:name=".receiver.KernelReceiver"
|
android:name=".receiver.KernelReceiver"
|
||||||
android:exported="false"
|
android:exported="false"
|
||||||
|
|
|
@ -3,10 +3,15 @@ package com.zaneschepke.wireguardautotunnel
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.os.StrictMode
|
import android.os.StrictMode
|
||||||
import android.os.StrictMode.ThreadPolicy
|
import android.os.StrictMode.ThreadPolicy
|
||||||
|
import com.zaneschepke.logcatter.LocalLogCollector
|
||||||
import com.zaneschepke.wireguardautotunnel.module.ApplicationScope
|
import com.zaneschepke.wireguardautotunnel.module.ApplicationScope
|
||||||
|
import com.zaneschepke.wireguardautotunnel.module.IoDispatcher
|
||||||
import com.zaneschepke.wireguardautotunnel.util.ReleaseTree
|
import com.zaneschepke.wireguardautotunnel.util.ReleaseTree
|
||||||
|
import com.zaneschepke.wireguardautotunnel.util.extensions.isRunningOnTv
|
||||||
import dagger.hilt.android.HiltAndroidApp
|
import dagger.hilt.android.HiltAndroidApp
|
||||||
|
import kotlinx.coroutines.CoroutineDispatcher
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@ -17,6 +22,13 @@ class WireGuardAutoTunnel : Application() {
|
||||||
@ApplicationScope
|
@ApplicationScope
|
||||||
lateinit var applicationScope: CoroutineScope
|
lateinit var applicationScope: CoroutineScope
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var localLogCollector: LocalLogCollector
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
@IoDispatcher
|
||||||
|
lateinit var ioDispatcher: CoroutineDispatcher
|
||||||
|
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
instance = this
|
instance = this
|
||||||
|
@ -33,6 +45,11 @@ class WireGuardAutoTunnel : Application() {
|
||||||
} else {
|
} else {
|
||||||
Timber.plant(ReleaseTree())
|
Timber.plant(ReleaseTree())
|
||||||
}
|
}
|
||||||
|
if (!isRunningOnTv()) {
|
||||||
|
applicationScope.launch(ioDispatcher) {
|
||||||
|
localLogCollector.start()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,10 +5,12 @@ import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import com.zaneschepke.wireguardautotunnel.data.repository.TunnelConfigRepository
|
import com.zaneschepke.wireguardautotunnel.data.repository.TunnelConfigRepository
|
||||||
import com.zaneschepke.wireguardautotunnel.module.ApplicationScope
|
import com.zaneschepke.wireguardautotunnel.module.ApplicationScope
|
||||||
|
import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager
|
||||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelService
|
import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelService
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Provider
|
import javax.inject.Provider
|
||||||
|
|
||||||
|
@ -25,14 +27,19 @@ class BackgroundActionReceiver : BroadcastReceiver() {
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var tunnelConfigRepository: TunnelConfigRepository
|
lateinit var tunnelConfigRepository: TunnelConfigRepository
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var serviceManager: ServiceManager
|
||||||
|
|
||||||
override fun onReceive(context: Context, intent: Intent) {
|
override fun onReceive(context: Context, intent: Intent) {
|
||||||
val id = intent.getIntExtra(TUNNEL_ID_EXTRA_KEY, 0)
|
val id = intent.getIntExtra(TUNNEL_ID_EXTRA_KEY, 0)
|
||||||
if (id == 0) return
|
if (id == 0) return
|
||||||
when (intent.action) {
|
when (intent.action) {
|
||||||
ACTION_CONNECT -> {
|
ACTION_CONNECT -> {
|
||||||
|
Timber.d("Connect actions")
|
||||||
applicationScope.launch {
|
applicationScope.launch {
|
||||||
val tunnel = tunnelConfigRepository.getById(id)
|
val tunnel = tunnelConfigRepository.getById(id)
|
||||||
tunnel?.let {
|
tunnel?.let {
|
||||||
|
serviceManager.startTunnelBackgroundService(context)
|
||||||
tunnelService.get().startTunnel(it)
|
tunnelService.get().startTunnel(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -41,6 +48,7 @@ class BackgroundActionReceiver : BroadcastReceiver() {
|
||||||
applicationScope.launch {
|
applicationScope.launch {
|
||||||
val tunnel = tunnelConfigRepository.getById(id)
|
val tunnel = tunnelConfigRepository.getById(id)
|
||||||
tunnel?.let {
|
tunnel?.let {
|
||||||
|
serviceManager.stopTunnelBackgroundService(context)
|
||||||
tunnelService.get().stopTunnel(it)
|
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.module.ApplicationScope
|
||||||
import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager
|
import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager
|
||||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelService
|
import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelService
|
||||||
|
import com.zaneschepke.wireguardautotunnel.util.extensions.startTunnelBackground
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
@ -35,7 +36,7 @@ class BootReceiver : BroadcastReceiver() {
|
||||||
val settings = appDataRepository.settings.getSettings()
|
val settings = appDataRepository.settings.getSettings()
|
||||||
if (settings.isRestoreOnBootEnabled) {
|
if (settings.isRestoreOnBootEnabled) {
|
||||||
appDataRepository.getStartTunnelConfig()?.let {
|
appDataRepository.getStartTunnelConfig()?.let {
|
||||||
tunnelService.get().startTunnel(it)
|
context.startTunnelBackground(it.id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (settings.isAutoTunnelEnabled) {
|
if (settings.isAutoTunnelEnabled) {
|
||||||
|
|
|
@ -50,4 +50,20 @@ class ServiceManager {
|
||||||
AutoTunnelService::class.java,
|
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,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 = "",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,6 +7,8 @@ import com.zaneschepke.wireguardautotunnel.module.ApplicationScope
|
||||||
import com.zaneschepke.wireguardautotunnel.service.foreground.Action
|
import com.zaneschepke.wireguardautotunnel.service.foreground.Action
|
||||||
import com.zaneschepke.wireguardautotunnel.service.foreground.AutoTunnelService
|
import com.zaneschepke.wireguardautotunnel.service.foreground.AutoTunnelService
|
||||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelService
|
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 dagger.hilt.android.AndroidEntryPoint
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
@ -42,8 +44,8 @@ class ShortcutsActivity : ComponentActivity() {
|
||||||
Timber.d("Shortcut action on name: ${tunnelConfig?.name}")
|
Timber.d("Shortcut action on name: ${tunnelConfig?.name}")
|
||||||
tunnelConfig?.let {
|
tunnelConfig?.let {
|
||||||
when (intent.action) {
|
when (intent.action) {
|
||||||
Action.START.name -> tunnelService.get().startTunnel(it)
|
Action.START.name -> this@ShortcutsActivity.startTunnelBackground(it.id)
|
||||||
Action.STOP.name -> tunnelService.get().stopTunnel(it)
|
Action.STOP.name -> this@ShortcutsActivity.stopTunnelBackground(it.id)
|
||||||
else -> Unit
|
else -> Unit
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -68,6 +68,7 @@ class TunnelControlTile : TileService(), LifecycleOwner {
|
||||||
override fun onClick() {
|
override fun onClick() {
|
||||||
super.onClick()
|
super.onClick()
|
||||||
unlockAndRun {
|
unlockAndRun {
|
||||||
|
Timber.d("Click")
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
val context = this@TunnelControlTile
|
val context = this@TunnelControlTile
|
||||||
val lastActive = appDataRepository.getStartTunnelConfig()
|
val lastActive = appDataRepository.getStartTunnelConfig()
|
||||||
|
|
|
@ -14,4 +14,7 @@ interface TunnelService : Tunnel, org.amnezia.awg.backend.Tunnel {
|
||||||
suspend fun runningTunnelNames(): Set<String>
|
suspend fun runningTunnelNames(): Set<String>
|
||||||
|
|
||||||
suspend fun getState(): TunnelState
|
suspend fun getState(): TunnelState
|
||||||
|
|
||||||
|
fun cancelStatsJob()
|
||||||
|
fun startStatsJob()
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,6 @@ import com.zaneschepke.wireguardautotunnel.service.tunnel.statistics.TunnelStati
|
||||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.statistics.WireGuardStatistics
|
import com.zaneschepke.wireguardautotunnel.service.tunnel.statistics.WireGuardStatistics
|
||||||
import com.zaneschepke.wireguardautotunnel.util.Constants
|
import com.zaneschepke.wireguardautotunnel.util.Constants
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.requestTunnelTileServiceStateUpdate
|
import com.zaneschepke.wireguardautotunnel.util.extensions.requestTunnelTileServiceStateUpdate
|
||||||
import kotlinx.coroutines.CancellationException
|
|
||||||
import kotlinx.coroutines.CoroutineDispatcher
|
import kotlinx.coroutines.CoroutineDispatcher
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
|
@ -153,6 +152,14 @@ constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun cancelStatsJob() {
|
||||||
|
statsJob?.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun startStatsJob() {
|
||||||
|
statsJob = startTunnelStatisticsJob()
|
||||||
|
}
|
||||||
|
|
||||||
override fun getName(): String {
|
override fun getName(): String {
|
||||||
return _vpnState.value.tunnelConfig?.name ?: ""
|
return _vpnState.value.tunnelConfig?.name ?: ""
|
||||||
}
|
}
|
||||||
|
@ -164,15 +171,9 @@ constructor(
|
||||||
private fun handleStateChange(state: TunnelState) {
|
private fun handleStateChange(state: TunnelState) {
|
||||||
emitTunnelState(state)
|
emitTunnelState(state)
|
||||||
WireGuardAutoTunnel.instance.requestTunnelTileServiceStateUpdate()
|
WireGuardAutoTunnel.instance.requestTunnelTileServiceStateUpdate()
|
||||||
if (state == TunnelState.UP) {
|
when (state) {
|
||||||
statsJob = startTunnelStatisticsJob()
|
TunnelState.UP -> startStatsJob()
|
||||||
}
|
else -> cancelStatsJob()
|
||||||
if (state == TunnelState.DOWN) {
|
|
||||||
try {
|
|
||||||
statsJob?.cancel()
|
|
||||||
} catch (e: CancellationException) {
|
|
||||||
Timber.i("Stats job cancelled")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -39,8 +39,7 @@ import androidx.navigation.compose.currentBackStackEntryAsState
|
||||||
import androidx.navigation.compose.rememberNavController
|
import androidx.navigation.compose.rememberNavController
|
||||||
import androidx.navigation.navArgument
|
import androidx.navigation.navArgument
|
||||||
import com.zaneschepke.wireguardautotunnel.data.repository.AppStateRepository
|
import com.zaneschepke.wireguardautotunnel.data.repository.AppStateRepository
|
||||||
import com.zaneschepke.wireguardautotunnel.data.repository.SettingsRepository
|
import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelService
|
||||||
import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager
|
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.navigation.BottomNavBar
|
import com.zaneschepke.wireguardautotunnel.ui.common.navigation.BottomNavBar
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.prompt.CustomSnackBar
|
import com.zaneschepke.wireguardautotunnel.ui.common.prompt.CustomSnackBar
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.screens.config.ConfigScreen
|
import com.zaneschepke.wireguardautotunnel.ui.screens.config.ConfigScreen
|
||||||
|
@ -65,10 +64,7 @@ class MainActivity : AppCompatActivity() {
|
||||||
lateinit var appStateRepository: AppStateRepository
|
lateinit var appStateRepository: AppStateRepository
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var settingsRepository: SettingsRepository
|
lateinit var tunnelService: TunnelService
|
||||||
|
|
||||||
@Inject
|
|
||||||
lateinit var serviceManager: ServiceManager
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
@ -77,13 +73,6 @@ class MainActivity : AppCompatActivity() {
|
||||||
|
|
||||||
enableEdgeToEdge(navigationBarStyle = SystemBarStyle.dark(Color.Transparent.toArgb()))
|
enableEdgeToEdge(navigationBarStyle = SystemBarStyle.dark(Color.Transparent.toArgb()))
|
||||||
|
|
||||||
lifecycleScope.launch {
|
|
||||||
val settings = settingsRepository.getSettings()
|
|
||||||
if (settings.isAutoTunnelEnabled) {
|
|
||||||
serviceManager.startWatcherService(application.applicationContext)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setContent {
|
setContent {
|
||||||
val appViewModel = hiltViewModel<AppViewModel>()
|
val appViewModel = hiltViewModel<AppViewModel>()
|
||||||
val appUiState by appViewModel.appUiState.collectAsStateWithLifecycle()
|
val appUiState by appViewModel.appUiState.collectAsStateWithLifecycle()
|
||||||
|
@ -241,4 +230,9 @@ class MainActivity : AppCompatActivity() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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.Lifecycle
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.lifecycle.repeatOnLifecycle
|
import androidx.lifecycle.repeatOnLifecycle
|
||||||
import com.zaneschepke.logcatter.LocalLogCollector
|
|
||||||
import com.zaneschepke.wireguardautotunnel.WireGuardAutoTunnel
|
import com.zaneschepke.wireguardautotunnel.WireGuardAutoTunnel
|
||||||
import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
|
import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
|
||||||
import com.zaneschepke.wireguardautotunnel.data.repository.AppStateRepository
|
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.service.tunnel.TunnelService
|
||||||
import com.zaneschepke.wireguardautotunnel.util.Constants
|
import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelState
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.isRunningOnTv
|
|
||||||
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 dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.delay
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import timber.log.Timber
|
|
||||||
import xyz.teamgravity.pin_lock_compose.PinManager
|
import xyz.teamgravity.pin_lock_compose.PinManager
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Provider
|
import javax.inject.Provider
|
||||||
|
@ -41,11 +36,7 @@ class SplashActivity : ComponentActivity() {
|
||||||
lateinit var tunnelService: Provider<TunnelService>
|
lateinit var tunnelService: Provider<TunnelService>
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var localLogCollector: LocalLogCollector
|
lateinit var serviceManager: ServiceManager
|
||||||
|
|
||||||
@Inject
|
|
||||||
@ApplicationScope
|
|
||||||
lateinit var applicationScope: CoroutineScope
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||||
|
@ -54,29 +45,17 @@ class SplashActivity : ComponentActivity() {
|
||||||
}
|
}
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
applicationScope.launch {
|
|
||||||
if (!this@SplashActivity.isRunningOnTv()) localLogCollector.start()
|
|
||||||
}
|
|
||||||
|
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||||
val pinLockEnabled = appStateRepository.isPinLockEnabled()
|
val pinLockEnabled = appStateRepository.isPinLockEnabled()
|
||||||
if (pinLockEnabled) {
|
if (pinLockEnabled) {
|
||||||
PinManager.initialize(WireGuardAutoTunnel.instance)
|
PinManager.initialize(WireGuardAutoTunnel.instance)
|
||||||
}
|
}
|
||||||
// TODO eventually make this support multi-tunnel
|
|
||||||
Timber.d("Check for active tunnels")
|
|
||||||
val settings = appDataRepository.settings.getSettings()
|
val settings = appDataRepository.settings.getSettings()
|
||||||
if (settings.isKernelEnabled) {
|
if (settings.isAutoTunnelEnabled) serviceManager.startWatcherService(application.applicationContext)
|
||||||
// delay in case state change is underway while app is opened
|
if (tunnelService.get().getState() == TunnelState.UP) tunnelService.get().startStatsJob()
|
||||||
delay(Constants.FOCUS_REQUEST_DELAY)
|
val tunnels = appDataRepository.tunnels.getActive()
|
||||||
val activeTunnels = appDataRepository.tunnels.getActive()
|
if (tunnels.isNotEmpty() && tunnelService.get().getState() == TunnelState.DOWN) tunnelService.get().startTunnel(tunnels.first())
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
requestTunnelTileServiceStateUpdate()
|
requestTunnelTileServiceStateUpdate()
|
||||||
requestAutoTunnelTileServiceUpdate()
|
requestAutoTunnelTileServiceUpdate()
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,6 @@ import androidx.activity.compose.rememberLauncherForActivityResult
|
||||||
import androidx.activity.result.ActivityResult
|
import androidx.activity.result.ActivityResult
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.appcompat.app.AppCompatActivity.RESULT_OK
|
import androidx.appcompat.app.AppCompatActivity.RESULT_OK
|
||||||
import androidx.compose.animation.AnimatedVisibility
|
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
@ -67,7 +66,6 @@ import com.google.accompanist.permissions.isGranted
|
||||||
import com.google.accompanist.permissions.rememberPermissionState
|
import com.google.accompanist.permissions.rememberPermissionState
|
||||||
import com.zaneschepke.wireguardautotunnel.R
|
import com.zaneschepke.wireguardautotunnel.R
|
||||||
import com.zaneschepke.wireguardautotunnel.WireGuardAutoTunnel
|
import com.zaneschepke.wireguardautotunnel.WireGuardAutoTunnel
|
||||||
import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
|
|
||||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelState
|
import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelState
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.AppViewModel
|
import com.zaneschepke.wireguardautotunnel.ui.AppViewModel
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.Screen
|
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.launchAppSettings
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.showToast
|
import com.zaneschepke.wireguardautotunnel.util.extensions.showToast
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import timber.log.Timber
|
|
||||||
import xyz.teamgravity.pin_lock_compose.PinManager
|
import xyz.teamgravity.pin_lock_compose.PinManager
|
||||||
import java.io.File
|
|
||||||
|
|
||||||
@OptIn(
|
@OptIn(
|
||||||
ExperimentalPermissionsApi::class,
|
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 {
|
fun isBatteryOptimizationsDisabled(): Boolean {
|
||||||
val pm = context.getSystemService(POWER_SERVICE) as PowerManager
|
val pm = context.getSystemService(POWER_SERVICE) as PowerManager
|
||||||
return pm.isIgnoringBatteryOptimizations(context.packageName)
|
return pm.isIgnoringBatteryOptimizations(context.packageName)
|
||||||
|
@ -300,7 +259,13 @@ fun SettingsScreen(
|
||||||
AuthorizationPrompt(
|
AuthorizationPrompt(
|
||||||
onSuccess = {
|
onSuccess = {
|
||||||
showAuthPrompt = false
|
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 = { _ ->
|
onError = { _ ->
|
||||||
showAuthPrompt = false
|
showAuthPrompt = false
|
||||||
|
@ -379,7 +344,7 @@ fun SettingsScreen(
|
||||||
.focusRequester(focusRequester)
|
.focusRequester(focusRequester)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
AnimatedVisibility(visible = uiState.settings.isTunnelOnWifiEnabled) {
|
if (uiState.settings.isTunnelOnWifiEnabled) {
|
||||||
Column {
|
Column {
|
||||||
FlowRow(
|
FlowRow(
|
||||||
modifier =
|
modifier =
|
||||||
|
|
|
@ -248,4 +248,13 @@ constructor(
|
||||||
onSuccess()
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@ object Constants {
|
||||||
const val MANUAL_TUNNEL_CONFIG_ID = "0"
|
const val MANUAL_TUNNEL_CONFIG_ID = "0"
|
||||||
const val BATTERY_SAVER_WATCHER_WAKE_LOCK_TIMEOUT = 10 * 60 * 1_000L // 10 minutes
|
const val BATTERY_SAVER_WATCHER_WAKE_LOCK_TIMEOUT = 10 * 60 * 1_000L // 10 minutes
|
||||||
const val VPN_STATISTIC_CHECK_INTERVAL = 1_000L
|
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 CONF_FILE_EXTENSION = ".conf"
|
||||||
const val ZIP_FILE_EXTENSION = ".zip"
|
const val ZIP_FILE_EXTENSION = ".zip"
|
||||||
const val URI_CONTENT_SCHEME = "content"
|
const val URI_CONTENT_SCHEME = "content"
|
||||||
|
|
|
@ -6,6 +6,8 @@ import android.os.Build
|
||||||
import android.os.Environment
|
import android.os.Environment
|
||||||
import android.provider.MediaStore
|
import android.provider.MediaStore
|
||||||
import android.provider.MediaStore.MediaColumns
|
import android.provider.MediaStore.MediaColumns
|
||||||
|
import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
|
||||||
|
import com.zaneschepke.wireguardautotunnel.util.extensions.TunnelConfigs
|
||||||
import kotlinx.coroutines.CoroutineDispatcher
|
import kotlinx.coroutines.CoroutineDispatcher
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import timber.log.Timber
|
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> {
|
suspend fun saveByteArrayToDownloads(content: ByteArray, fileName: String): Result<Unit> {
|
||||||
return withContext(ioDispatcher) {
|
return withContext(ioDispatcher) {
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
object Constants {
|
object Constants {
|
||||||
const val VERSION_NAME = "3.5.0"
|
const val VERSION_NAME = "3.5.1"
|
||||||
const val JVM_TARGET = "17"
|
const val JVM_TARGET = "17"
|
||||||
const val VERSION_CODE = 35001
|
const val VERSION_CODE = 35100
|
||||||
const val TARGET_SDK = 34
|
const val TARGET_SDK = 34
|
||||||
const val MIN_SDK = 26
|
const val MIN_SDK = 26
|
||||||
const val APP_ID = "com.zaneschepke.wireguardautotunnel"
|
const val APP_ID = "com.zaneschepke.wireguardautotunnel"
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
What's new:
|
||||||
|
- Fixes for tunnels not launching from background
|
||||||
|
- Add support for restart services after update
|
||||||
|
- UI animation speed improvements
|
||||||
|
- Other optimizations
|
|
@ -5,7 +5,7 @@ import kotlinx.coroutines.flow.Flow
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
interface LocalLogCollector {
|
interface LocalLogCollector {
|
||||||
fun start(onLogMessage: ((message: LogMessage) -> Unit)? = null)
|
suspend fun start(onLogMessage: ((message: LogMessage) -> Unit)? = null)
|
||||||
|
|
||||||
fun stop()
|
fun stop()
|
||||||
|
|
||||||
|
|
|
@ -69,7 +69,7 @@ object LogcatUtil {
|
||||||
internal object Logcat : LocalLogCollector {
|
internal object Logcat : LocalLogCollector {
|
||||||
private var logcatReader: LogcatReader? = null
|
private var logcatReader: LogcatReader? = null
|
||||||
|
|
||||||
override fun start(onLogMessage: ((message: LogMessage) -> Unit)?) {
|
override suspend fun start(onLogMessage: ((message: LogMessage) -> Unit)?) {
|
||||||
logcatReader ?: run {
|
logcatReader ?: run {
|
||||||
logcatReader =
|
logcatReader =
|
||||||
LogcatReader(
|
LogcatReader(
|
||||||
|
@ -78,9 +78,7 @@ object LogcatUtil {
|
||||||
onLogMessage,
|
onLogMessage,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
logcatReader?.let { logReader ->
|
logcatReader?.run()
|
||||||
if (!logReader.isAlive) logReader.start()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun stop() {
|
override fun stop() {
|
||||||
|
@ -142,7 +140,7 @@ object LogcatUtil {
|
||||||
pID: String,
|
pID: String,
|
||||||
private val logcatPath: String,
|
private val logcatPath: String,
|
||||||
private val callback: ((input: LogMessage) -> Unit)?,
|
private val callback: ((input: LogMessage) -> Unit)?,
|
||||||
) : Thread() {
|
) {
|
||||||
private var logcatProc: Process? = null
|
private var logcatProc: Process? = null
|
||||||
private var reader: BufferedReader? = null
|
private var reader: BufferedReader? = null
|
||||||
private var mRunning = true
|
private var mRunning = true
|
||||||
|
@ -177,7 +175,7 @@ object LogcatUtil {
|
||||||
}.let { last -> findIpv4AddressRegex.replace(last, "<ipv4-address>") }
|
}.let { last -> findIpv4AddressRegex.replace(last, "<ipv4-address>") }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun run() {
|
fun run() {
|
||||||
if (outputStream == null) return
|
if (outputStream == null) return
|
||||||
try {
|
try {
|
||||||
clear()
|
clear()
|
||||||
|
|
Loading…
Reference in New Issue