mirror of
https://github.com/wgtunnel/android.git
synced 2026-07-03 14:07:49 +02:00
Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b7e4f3c3e5 | |||
| dedef38541 | |||
| aa5adec902 | |||
| e269aa88d9 | |||
| 7c3eabb19b | |||
| acdefd80fa | |||
| 09fa9dabdd | |||
| 33a02262a8 |
@@ -126,14 +126,8 @@ class MainActivity : AppCompatActivity() {
|
|||||||
val navController = rememberNavController()
|
val navController = rememberNavController()
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
var pinManagerInitialized by remember { mutableStateOf(false) }
|
|
||||||
|
|
||||||
LaunchedEffect(appState.isAppLoaded) {
|
LaunchedEffect(appState.isAppLoaded) {
|
||||||
if (appState.isAppLoaded) {
|
if (appState.isAppLoaded) {
|
||||||
if (appState.pinLockEnabled && !pinManagerInitialized) {
|
|
||||||
PinManager.initialize(this@MainActivity)
|
|
||||||
pinManagerInitialized = true
|
|
||||||
}
|
|
||||||
appState.locale.let { LocaleUtil.changeLocale(it) }
|
appState.locale.let { LocaleUtil.changeLocale(it) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -197,7 +191,6 @@ class MainActivity : AppCompatActivity() {
|
|||||||
requestingAppMode = Pair(sideEffect.requestingMode, sideEffect.config)
|
requestingAppMode = Pair(sideEffect.requestingMode, sideEffect.config)
|
||||||
vpnActivity.launch(VpnService.prepare(this@MainActivity))
|
vpnActivity.launch(VpnService.prepare(this@MainActivity))
|
||||||
}
|
}
|
||||||
is GlobalSideEffect.ShareFile -> context.launchShareFile(sideEffect.file)
|
|
||||||
is GlobalSideEffect.Snackbar ->
|
is GlobalSideEffect.Snackbar ->
|
||||||
scope.launch {
|
scope.launch {
|
||||||
snackbar.showSnackbar(sideEffect.message.asString(context))
|
snackbar.showSnackbar(sideEffect.message.asString(context))
|
||||||
@@ -271,7 +264,10 @@ class MainActivity : AppCompatActivity() {
|
|||||||
Route.Lock
|
Route.Lock
|
||||||
else Route.TunnelsGraph,
|
else Route.TunnelsGraph,
|
||||||
) {
|
) {
|
||||||
composable<Route.Lock> { PinLockScreen() }
|
composable<Route.Lock> {
|
||||||
|
PinManager.initialize(context = this@MainActivity)
|
||||||
|
PinLockScreen()
|
||||||
|
}
|
||||||
navigation<Route.TunnelsGraph>(
|
navigation<Route.TunnelsGraph>(
|
||||||
startDestination = Route.Tunnels
|
startDestination = Route.Tunnels
|
||||||
) {
|
) {
|
||||||
|
|||||||
+1
-1
@@ -34,7 +34,7 @@ class NotificationActionReceiver : BroadcastReceiver() {
|
|||||||
val tunnelId = intent.getIntExtra(NotificationManager.EXTRA_ID, 0)
|
val tunnelId = intent.getIntExtra(NotificationManager.EXTRA_ID, 0)
|
||||||
if (tunnelId == STOP_ALL_TUNNELS_ID)
|
if (tunnelId == STOP_ALL_TUNNELS_ID)
|
||||||
return@launch tunnelManager.stopActiveTunnels()
|
return@launch tunnelManager.stopActiveTunnels()
|
||||||
tunnelRepository.getById(tunnelId)?.let { tunnelManager.stopTunnel(it.id) }
|
tunnelManager.stopTunnel(tunnelId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-3
@@ -51,7 +51,8 @@ class WireGuardNotification @Inject constructor(@ApplicationContext override val
|
|||||||
PendingIntent.getActivity(
|
PendingIntent.getActivity(
|
||||||
context,
|
context,
|
||||||
0,
|
0,
|
||||||
Intent(context, MainActivity::class.java),
|
Intent(context, MainActivity::class.java)
|
||||||
|
.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP),
|
||||||
PendingIntent.FLAG_IMMUTABLE,
|
PendingIntent.FLAG_IMMUTABLE,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -94,12 +95,12 @@ class WireGuardNotification @Inject constructor(@ApplicationContext override val
|
|||||||
val pendingIntent =
|
val pendingIntent =
|
||||||
PendingIntent.getBroadcast(
|
PendingIntent.getBroadcast(
|
||||||
context,
|
context,
|
||||||
0,
|
extraId ?: 0,
|
||||||
Intent(context, NotificationActionReceiver::class.java).apply {
|
Intent(context, NotificationActionReceiver::class.java).apply {
|
||||||
action = notificationAction.name
|
action = notificationAction.name
|
||||||
if (extraId != null) putExtra(EXTRA_ID, extraId)
|
if (extraId != null) putExtra(EXTRA_ID, extraId)
|
||||||
},
|
},
|
||||||
PendingIntent.FLAG_IMMUTABLE,
|
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT,
|
||||||
)
|
)
|
||||||
return NotificationCompat.Action.Builder(
|
return NotificationCompat.Action.Builder(
|
||||||
R.drawable.ic_notification,
|
R.drawable.ic_notification,
|
||||||
|
|||||||
@@ -56,6 +56,10 @@ abstract class BaseTunnel(@ApplicationScope protected val applicationScope: Coro
|
|||||||
) {
|
) {
|
||||||
tunStatusMutex.withLock {
|
tunStatusMutex.withLock {
|
||||||
activeTuns.update { currentTuns ->
|
activeTuns.update { currentTuns ->
|
||||||
|
if (!currentTuns.containsKey(tunnelId) && status != TunnelStatus.Starting) {
|
||||||
|
Timber.d("Ignoring update for inactive tunnel $tunnelId")
|
||||||
|
return@update currentTuns
|
||||||
|
}
|
||||||
val existingState = currentTuns[tunnelId] ?: TunnelState()
|
val existingState = currentTuns[tunnelId] ?: TunnelState()
|
||||||
val newStatus = status ?: existingState.status
|
val newStatus = status ?: existingState.status
|
||||||
if (newStatus == TunnelStatus.Down) {
|
if (newStatus == TunnelStatus.Down) {
|
||||||
|
|||||||
+99
-84
@@ -21,8 +21,9 @@ import kotlin.concurrent.atomics.AtomicBoolean
|
|||||||
import kotlin.concurrent.atomics.AtomicReference
|
import kotlin.concurrent.atomics.AtomicReference
|
||||||
import kotlin.concurrent.atomics.ExperimentalAtomicApi
|
import kotlin.concurrent.atomics.ExperimentalAtomicApi
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import kotlinx.coroutines.channels.Channel
|
|
||||||
import kotlinx.coroutines.flow.*
|
import kotlinx.coroutines.flow.*
|
||||||
|
import kotlinx.coroutines.sync.Mutex
|
||||||
|
import kotlinx.coroutines.sync.withLock
|
||||||
import org.amnezia.awg.crypto.Key
|
import org.amnezia.awg.crypto.Key
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
@@ -41,6 +42,7 @@ constructor(
|
|||||||
@IoDispatcher private val ioDispatcher: CoroutineDispatcher,
|
@IoDispatcher private val ioDispatcher: CoroutineDispatcher,
|
||||||
) : TunnelProvider {
|
) : TunnelProvider {
|
||||||
|
|
||||||
|
private val monitoringMutex = Mutex()
|
||||||
private val monitoringJobs = ConcurrentHashMap<Int, Job>()
|
private val monitoringJobs = ConcurrentHashMap<Int, Job>()
|
||||||
|
|
||||||
private data class SideEffectState(
|
private data class SideEffectState(
|
||||||
@@ -55,9 +57,6 @@ constructor(
|
|||||||
val condition: (SideEffectState) -> Boolean,
|
val condition: (SideEffectState) -> Boolean,
|
||||||
)
|
)
|
||||||
|
|
||||||
private val sideEffectChannelFlow =
|
|
||||||
MutableStateFlow<Channel<SideEffectState>>(Channel(Channel.CONFLATED))
|
|
||||||
|
|
||||||
private val tunnelProviderFlow: StateFlow<TunnelProvider> = run {
|
private val tunnelProviderFlow: StateFlow<TunnelProvider> = run {
|
||||||
val currentBackend = AtomicReference(userspaceTunnel)
|
val currentBackend = AtomicReference(userspaceTunnel)
|
||||||
val currentSettings = AtomicReference(GeneralSettings())
|
val currentSettings = AtomicReference(GeneralSettings())
|
||||||
@@ -105,61 +104,9 @@ constructor(
|
|||||||
override val activeTunnels: StateFlow<Map<Int, TunnelState>> = run {
|
override val activeTunnels: StateFlow<Map<Int, TunnelState>> = run {
|
||||||
val activeTunsReference: AtomicReference<Map<Int, TunnelState>> =
|
val activeTunsReference: AtomicReference<Map<Int, TunnelState>> =
|
||||||
AtomicReference(emptyMap())
|
AtomicReference(emptyMap())
|
||||||
|
|
||||||
tunnelProviderFlow
|
tunnelProviderFlow
|
||||||
.flatMapLatest { backend ->
|
.flatMapLatest { backend ->
|
||||||
// Create a new channel for each backend to reset side-effect processing
|
|
||||||
val newChannel = Channel<SideEffectState>(Channel.CONFLATED)
|
|
||||||
sideEffectChannelFlow.value = newChannel
|
|
||||||
|
|
||||||
val sideEffects =
|
|
||||||
listOf(
|
|
||||||
SideEffectWithCondition(
|
|
||||||
effect = { s ->
|
|
||||||
handleTunnelServiceChange(s.settings.appMode, s.activeTuns)
|
|
||||||
},
|
|
||||||
condition = { s -> s.activeTuns.size != s.previouslyActive.size },
|
|
||||||
),
|
|
||||||
SideEffectWithCondition(
|
|
||||||
effect = { s ->
|
|
||||||
handleTunnelsActiveChange(s.previouslyActive, s.activeTuns, s.tuns)
|
|
||||||
},
|
|
||||||
condition = { s -> s.activeTuns.size != s.previouslyActive.size },
|
|
||||||
),
|
|
||||||
// TODO Not for kernel mode for now
|
|
||||||
SideEffectWithCondition(
|
|
||||||
effect = { s -> handleTunnelMonitoringChanges(s.activeTuns, s.tuns) },
|
|
||||||
condition = { s ->
|
|
||||||
s.tuns.any {
|
|
||||||
it.restartOnPingFailure && s.activeTuns.keys.contains(it.id)
|
|
||||||
} && s.settings.appMode != AppMode.KERNEL
|
|
||||||
},
|
|
||||||
),
|
|
||||||
SideEffectWithCondition(
|
|
||||||
effect = { s ->
|
|
||||||
handleFullTunnelMonitoring(s.activeTuns, s.tuns, s.settings)
|
|
||||||
},
|
|
||||||
condition = { s -> s.activeTuns.keys != s.previouslyActive.keys },
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
applicationScope.launch(ioDispatcher) {
|
|
||||||
for (state in newChannel) {
|
|
||||||
supervisorScope {
|
|
||||||
sideEffects
|
|
||||||
.filter { it.condition(state) }
|
|
||||||
.forEach { sideEffect ->
|
|
||||||
launch {
|
|
||||||
try {
|
|
||||||
sideEffect.effect(state)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Timber.e(e, "Side effect failed")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
combine(
|
combine(
|
||||||
backend.activeTunnels,
|
backend.activeTunnels,
|
||||||
tunnelsRepository.flow,
|
tunnelsRepository.flow,
|
||||||
@@ -171,9 +118,67 @@ constructor(
|
|||||||
.onStart { handleStateRestore() }
|
.onStart { handleStateRestore() }
|
||||||
.onEach { (activeTuns, tuns, settings) ->
|
.onEach { (activeTuns, tuns, settings) ->
|
||||||
val previouslyActive = activeTunsReference.exchange(activeTuns)
|
val previouslyActive = activeTunsReference.exchange(activeTuns)
|
||||||
sideEffectChannelFlow.value.trySend(
|
val state = SideEffectState(activeTuns, tuns, settings, previouslyActive)
|
||||||
SideEffectState(activeTuns, tuns, settings, previouslyActive)
|
|
||||||
)
|
applicationScope.launch(ioDispatcher) {
|
||||||
|
supervisorScope {
|
||||||
|
val sideEffects =
|
||||||
|
listOf(
|
||||||
|
SideEffectWithCondition(
|
||||||
|
effect = { s ->
|
||||||
|
handleTunnelServiceChange(s.settings.appMode, s.activeTuns)
|
||||||
|
},
|
||||||
|
condition = { s ->
|
||||||
|
s.activeTuns.size != s.previouslyActive.size
|
||||||
|
},
|
||||||
|
),
|
||||||
|
SideEffectWithCondition(
|
||||||
|
effect = { s ->
|
||||||
|
handleTunnelsActiveChange(
|
||||||
|
s.previouslyActive,
|
||||||
|
s.activeTuns,
|
||||||
|
s.tuns,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
condition = { s ->
|
||||||
|
s.activeTuns.size != s.previouslyActive.size
|
||||||
|
},
|
||||||
|
),
|
||||||
|
// TODO Not for kernel mode for now
|
||||||
|
SideEffectWithCondition(
|
||||||
|
effect = { s ->
|
||||||
|
handleTunnelMonitoringChanges(s.activeTuns, s.tuns)
|
||||||
|
},
|
||||||
|
condition = { s ->
|
||||||
|
s.tuns.any {
|
||||||
|
it.restartOnPingFailure &&
|
||||||
|
s.activeTuns.keys.contains(it.id)
|
||||||
|
} && s.settings.appMode != AppMode.KERNEL
|
||||||
|
},
|
||||||
|
),
|
||||||
|
SideEffectWithCondition(
|
||||||
|
effect = { s ->
|
||||||
|
handleFullTunnelMonitoring(s.activeTuns, s.tuns, s.settings)
|
||||||
|
},
|
||||||
|
condition = { s ->
|
||||||
|
s.activeTuns.keys != s.previouslyActive.keys
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
sideEffects
|
||||||
|
.filter { it.condition(state) }
|
||||||
|
.forEach { sideEffect ->
|
||||||
|
launch {
|
||||||
|
try {
|
||||||
|
sideEffect.effect(state)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Timber.e(e, "Side effect failed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.map { (activeTuns, _, _) -> activeTuns }
|
.map { (activeTuns, _, _) -> activeTuns }
|
||||||
.stateIn(
|
.stateIn(
|
||||||
@@ -375,29 +380,39 @@ constructor(
|
|||||||
activeTuns: Map<Int, TunnelState>,
|
activeTuns: Map<Int, TunnelState>,
|
||||||
configs: List<TunnelConf>,
|
configs: List<TunnelConf>,
|
||||||
settings: GeneralSettings,
|
settings: GeneralSettings,
|
||||||
) {
|
) =
|
||||||
val activeIds = activeTuns.keys
|
monitoringMutex.withLock {
|
||||||
val obsoleteIds = monitoringJobs.keys - activeIds
|
val activeIds = activeTuns.keys.toSet()
|
||||||
obsoleteIds.forEach { id ->
|
val currentJobs = monitoringJobs.keys.toSet()
|
||||||
monitoringJobs[id]?.cancel()
|
val obsoleteIds = currentJobs - activeIds
|
||||||
monitoringJobs.remove(id)
|
|
||||||
|
Timber.d(
|
||||||
|
"Monitoring: Active IDs: $activeIds, Obsolete IDs: $obsoleteIds, Total jobs before: ${monitoringJobs.size}"
|
||||||
|
)
|
||||||
|
|
||||||
|
obsoleteIds.forEach { id ->
|
||||||
|
monitoringJobs[id]?.cancel()
|
||||||
|
monitoringJobs.remove(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
activeIds.forEach { id ->
|
||||||
|
if (monitoringJobs.containsKey(id)) return@forEach // Skip if already monitored
|
||||||
|
val config = configs.find { it.id == id } ?: return@forEach
|
||||||
|
val tunStateFlow =
|
||||||
|
activeTunnels.map { it[id] }.stateIn(applicationScope + ioDispatcher)
|
||||||
|
val newJob =
|
||||||
|
applicationScope.launch(ioDispatcher) {
|
||||||
|
tunnelMonitor.startMonitoring(
|
||||||
|
id,
|
||||||
|
withLogs = settings.appMode != AppMode.KERNEL,
|
||||||
|
tunStateFlow = tunStateFlow,
|
||||||
|
getStatistics = { tunnelId -> getStatistics(tunnelId) },
|
||||||
|
updateTunnelStatus = { tid, status, stats, pings, logHealth ->
|
||||||
|
updateTunnelStatus(tid, null, stats, pings, logHealth)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
monitoringJobs[id] = newJob
|
||||||
|
}
|
||||||
}
|
}
|
||||||
activeIds.forEach { id ->
|
|
||||||
if (monitoringJobs.contains(id)) return@forEach
|
|
||||||
configs.find { it.id == id } ?: return@forEach
|
|
||||||
val tunStateFlow = activeTunnels.map { it[id] }.stateIn(applicationScope + ioDispatcher)
|
|
||||||
monitoringJobs[id] =
|
|
||||||
applicationScope.launch(ioDispatcher) {
|
|
||||||
tunnelMonitor.startMonitoring(
|
|
||||||
id,
|
|
||||||
withLogs = settings.appMode != AppMode.KERNEL,
|
|
||||||
tunStateFlow = tunStateFlow,
|
|
||||||
getStatistics = { tunnelId -> getStatistics(tunnelId) },
|
|
||||||
updateTunnelStatus = { tid, status, stats, pings, logHealth ->
|
|
||||||
updateTunnelStatus(tid, null, stats, pings, logHealth)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
+43
-33
@@ -139,6 +139,7 @@ constructor(
|
|||||||
val updates = ConcurrentMap<Key, PingState>()
|
val updates = ConcurrentMap<Key, PingState>()
|
||||||
|
|
||||||
pingablePeers.forEach { peer ->
|
pingablePeers.forEach { peer ->
|
||||||
|
ensureActive()
|
||||||
val previousState = pingStatsFlow.value[peer.publicKey] ?: PingState()
|
val previousState = pingStatsFlow.value[peer.publicKey] ?: PingState()
|
||||||
|
|
||||||
val allowedIpStr = peer.allowedIps.firstOrNull()?.toString()
|
val allowedIpStr = peer.allowedIps.firstOrNull()?.toString()
|
||||||
@@ -172,41 +173,45 @@ constructor(
|
|||||||
|
|
||||||
val attemptTime = System.currentTimeMillis()
|
val attemptTime = System.currentTimeMillis()
|
||||||
runCatching {
|
runCatching {
|
||||||
val pingStats =
|
withTimeout(
|
||||||
settings.tunnelPingTimeoutSeconds?.let {
|
settings.tunnelPingTimeoutSeconds?.toMillis() ?: 5000L
|
||||||
networkUtils.pingWithStats(
|
) {
|
||||||
host,
|
val pingStats =
|
||||||
settings.tunnelPingAttempts,
|
settings.tunnelPingTimeoutSeconds?.let {
|
||||||
it.toMillis(),
|
networkUtils.pingWithStats(
|
||||||
)
|
host,
|
||||||
}
|
settings.tunnelPingAttempts,
|
||||||
?: networkUtils.pingWithStats(
|
it.toMillis(),
|
||||||
host,
|
)
|
||||||
settings.tunnelPingAttempts,
|
}
|
||||||
)
|
?: networkUtils.pingWithStats(
|
||||||
|
host,
|
||||||
|
settings.tunnelPingAttempts,
|
||||||
|
)
|
||||||
|
|
||||||
updates[peer.publicKey] =
|
updates[peer.publicKey] =
|
||||||
previousState.copy(
|
previousState.copy(
|
||||||
transmitted = pingStats.transmitted,
|
transmitted = pingStats.transmitted,
|
||||||
received = pingStats.received,
|
received = pingStats.received,
|
||||||
packetLoss = pingStats.packetLoss,
|
packetLoss = pingStats.packetLoss,
|
||||||
rttMin = pingStats.rttMin,
|
rttMin = pingStats.rttMin,
|
||||||
rttMax = pingStats.rttMax,
|
rttMax = pingStats.rttMax,
|
||||||
rttAvg = pingStats.rttAvg,
|
rttAvg = pingStats.rttAvg,
|
||||||
rttStddev = pingStats.rttStddev,
|
rttStddev = pingStats.rttStddev,
|
||||||
isReachable = pingStats.isReachable,
|
isReachable = pingStats.isReachable,
|
||||||
failureReason =
|
failureReason =
|
||||||
if (pingStats.isReachable) null
|
if (pingStats.isReachable) null
|
||||||
else FailureReason.PingFailed,
|
else FailureReason.PingFailed,
|
||||||
lastSuccessfulPingMillis =
|
lastSuccessfulPingMillis =
|
||||||
pingStats.lastSuccessfulPingMillis
|
pingStats.lastSuccessfulPingMillis
|
||||||
?: previousState.lastSuccessfulPingMillis,
|
?: previousState.lastSuccessfulPingMillis,
|
||||||
pingTarget = host,
|
pingTarget = host,
|
||||||
lastPingAttemptMillis = attemptTime,
|
lastPingAttemptMillis = attemptTime,
|
||||||
|
)
|
||||||
|
Timber.d(
|
||||||
|
"Ping completed for peer ${peer.publicKey.toBase64().substring(0, 5)}.. to host $host with stats: $pingStats"
|
||||||
)
|
)
|
||||||
Timber.d(
|
}
|
||||||
"Ping completed for peer ${peer.publicKey.toBase64().substring(0, 5)}.. to host $host with stats: $pingStats"
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
.onFailure {
|
.onFailure {
|
||||||
Timber.e(
|
Timber.e(
|
||||||
@@ -224,6 +229,7 @@ constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (updates.isNotEmpty()) {
|
if (updates.isNotEmpty()) {
|
||||||
|
ensureActive()
|
||||||
pingStatsFlow.update { updates }
|
pingStatsFlow.update { updates }
|
||||||
updateTunnelStatus(tunnelConf.id, null, null, updates, null)
|
updateTunnelStatus(tunnelConf.id, null, null, updates, null)
|
||||||
}
|
}
|
||||||
@@ -236,6 +242,7 @@ constructor(
|
|||||||
delay(3_000L)
|
delay(3_000L)
|
||||||
|
|
||||||
while (isActive) {
|
while (isActive) {
|
||||||
|
ensureActive()
|
||||||
if (isNetworkConnected.value) {
|
if (isNetworkConnected.value) {
|
||||||
performPing()
|
performPing()
|
||||||
} else {
|
} else {
|
||||||
@@ -248,6 +255,7 @@ constructor(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
ensureActive()
|
||||||
updateTunnelStatus(tunnelConf.id, null, null, pingStatsFlow.value, null)
|
updateTunnelStatus(tunnelConf.id, null, null, pingStatsFlow.value, null)
|
||||||
}
|
}
|
||||||
delay(settings.tunnelPingIntervalSeconds.toMillis())
|
delay(settings.tunnelPingIntervalSeconds.toMillis())
|
||||||
@@ -264,7 +272,9 @@ constructor(
|
|||||||
) -> Unit,
|
) -> Unit,
|
||||||
) = coroutineScope {
|
) = coroutineScope {
|
||||||
while (isActive) {
|
while (isActive) {
|
||||||
|
ensureActive()
|
||||||
val stats = getStatistics(tunnelId)
|
val stats = getStatistics(tunnelId)
|
||||||
|
ensureActive()
|
||||||
updateTunnelStatus(tunnelId, null, stats, null, null)
|
updateTunnelStatus(tunnelId, null, stats, null, null)
|
||||||
delay(STATS_DELAY)
|
delay(STATS_DELAY)
|
||||||
}
|
}
|
||||||
|
|||||||
-2
@@ -14,8 +14,6 @@ sealed class GlobalSideEffect {
|
|||||||
|
|
||||||
data object PopBackStack : GlobalSideEffect()
|
data object PopBackStack : GlobalSideEffect()
|
||||||
|
|
||||||
data class ShareFile(val file: File) : GlobalSideEffect()
|
|
||||||
|
|
||||||
data class LaunchUrl(val url: String) : GlobalSideEffect()
|
data class LaunchUrl(val url: String) : GlobalSideEffect()
|
||||||
|
|
||||||
data object ConfigChanged : GlobalSideEffect()
|
data object ConfigChanged : GlobalSideEffect()
|
||||||
|
|||||||
+9
-6
@@ -24,12 +24,18 @@ fun PinLockScreen() {
|
|||||||
val pinAlreadyExists by rememberSaveable { mutableStateOf(PinManager.pinExists()) }
|
val pinAlreadyExists by rememberSaveable { mutableStateOf(PinManager.pinExists()) }
|
||||||
var pinCreated by rememberSaveable { mutableStateOf(false) }
|
var pinCreated by rememberSaveable { mutableStateOf(false) }
|
||||||
|
|
||||||
|
fun onPinCorrect() {
|
||||||
|
sharedViewModel.authenticated()
|
||||||
|
navController.popBackStack()
|
||||||
|
navController.navigate(Route.TunnelsGraph)
|
||||||
|
}
|
||||||
|
|
||||||
PinLock(
|
PinLock(
|
||||||
title = {
|
title = {
|
||||||
Text(
|
Text(
|
||||||
color = MaterialTheme.colorScheme.onSurface,
|
color = MaterialTheme.colorScheme.onSurface,
|
||||||
text =
|
text =
|
||||||
if (pinAlreadyExists || pinCreated) {
|
if (pinAlreadyExists) {
|
||||||
stringResource(id = R.string.enter_pin)
|
stringResource(id = R.string.enter_pin)
|
||||||
} else {
|
} else {
|
||||||
stringResource(id = R.string.create_pin)
|
stringResource(id = R.string.create_pin)
|
||||||
@@ -38,11 +44,7 @@ fun PinLockScreen() {
|
|||||||
},
|
},
|
||||||
backgroundColor = MaterialTheme.colorScheme.surface,
|
backgroundColor = MaterialTheme.colorScheme.surface,
|
||||||
textColor = MaterialTheme.colorScheme.onSurface,
|
textColor = MaterialTheme.colorScheme.onSurface,
|
||||||
onPinCorrect = {
|
onPinCorrect = { onPinCorrect() },
|
||||||
sharedViewModel.authenticated()
|
|
||||||
navController.popBackStack()
|
|
||||||
navController.navigate(Route.TunnelsGraph)
|
|
||||||
},
|
|
||||||
onPinIncorrect = {
|
onPinIncorrect = {
|
||||||
sharedViewModel.showToast(StringValue.StringResource(R.string.incorrect_pin))
|
sharedViewModel.showToast(StringValue.StringResource(R.string.incorrect_pin))
|
||||||
},
|
},
|
||||||
@@ -50,6 +52,7 @@ fun PinLockScreen() {
|
|||||||
pinCreated = true
|
pinCreated = true
|
||||||
sharedViewModel.showToast(StringValue.StringResource(R.string.pin_created))
|
sharedViewModel.showToast(StringValue.StringResource(R.string.pin_created))
|
||||||
sharedViewModel.setPinLockEnabled(true)
|
sharedViewModel.setPinLockEnabled(true)
|
||||||
|
onPinCorrect()
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
BackHandler(enabled = (!pinAlreadyExists && !pinCreated)) {
|
BackHandler(enabled = (!pinAlreadyExists && !pinCreated)) {
|
||||||
|
|||||||
-2
@@ -32,7 +32,6 @@ import com.zaneschepke.wireguardautotunnel.util.StringValue
|
|||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.asString
|
import com.zaneschepke.wireguardautotunnel.util.extensions.asString
|
||||||
import com.zaneschepke.wireguardautotunnel.viewmodel.SettingsViewModel
|
import com.zaneschepke.wireguardautotunnel.viewmodel.SettingsViewModel
|
||||||
import org.orbitmvi.orbit.compose.collectSideEffect
|
import org.orbitmvi.orbit.compose.collectSideEffect
|
||||||
import xyz.teamgravity.pin_lock_compose.PinManager
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SettingsScreen(viewModel: SettingsViewModel = hiltViewModel()) {
|
fun SettingsScreen(viewModel: SettingsViewModel = hiltViewModel()) {
|
||||||
@@ -158,7 +157,6 @@ fun SettingsScreen(viewModel: SettingsViewModel = hiltViewModel()) {
|
|||||||
add(
|
add(
|
||||||
pinLockItem(settingsState.isPinLockEnabled) { enabled ->
|
pinLockItem(settingsState.isPinLockEnabled) { enabled ->
|
||||||
if (enabled) {
|
if (enabled) {
|
||||||
PinManager.initialize(context)
|
|
||||||
navController.navigate(Route.Lock)
|
navController.navigate(Route.Lock)
|
||||||
} else {
|
} else {
|
||||||
sharedViewModel.setPinLockEnabled(false)
|
sharedViewModel.setPinLockEnabled(false)
|
||||||
|
|||||||
+10
-1
@@ -69,7 +69,16 @@ fun LogsScreen(viewModel: LoggerViewModel = hiltViewModel()) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (showLogsSheet) {
|
if (showLogsSheet) {
|
||||||
LogsBottomSheet({ viewModel.exportLogs() }, { viewModel.deleteLogs() }) {
|
LogsBottomSheet(
|
||||||
|
{ uri ->
|
||||||
|
viewModel.exportLogs(uri)
|
||||||
|
showLogsSheet = false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
viewModel.deleteLogs()
|
||||||
|
showLogsSheet = false
|
||||||
|
},
|
||||||
|
) {
|
||||||
showLogsSheet = false
|
showLogsSheet = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+31
-2
@@ -1,24 +1,53 @@
|
|||||||
package com.zaneschepke.wireguardautotunnel.ui.screens.settings.logs.components
|
package com.zaneschepke.wireguardautotunnel.ui.screens.settings.logs.components
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.outlined.Delete
|
import androidx.compose.material.icons.outlined.Delete
|
||||||
import androidx.compose.material.icons.outlined.FolderZip
|
import androidx.compose.material.icons.outlined.FolderZip
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import com.zaneschepke.wireguardautotunnel.BuildConfig
|
||||||
import com.zaneschepke.wireguardautotunnel.R
|
import com.zaneschepke.wireguardautotunnel.R
|
||||||
|
import com.zaneschepke.wireguardautotunnel.ui.common.functions.rememberFileExportLauncherForResult
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.sheet.CustomBottomSheet
|
import com.zaneschepke.wireguardautotunnel.ui.common.sheet.CustomBottomSheet
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.sheet.SheetOption
|
import com.zaneschepke.wireguardautotunnel.ui.common.sheet.SheetOption
|
||||||
|
import com.zaneschepke.wireguardautotunnel.util.Constants
|
||||||
|
import com.zaneschepke.wireguardautotunnel.util.FileUtils
|
||||||
|
import com.zaneschepke.wireguardautotunnel.util.extensions.hasSAFSupport
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun LogsBottomSheet(onExport: () -> Unit, onDelete: () -> Unit, onDismiss: () -> Unit) {
|
fun LogsBottomSheet(onExport: (file: Uri?) -> Unit, onDelete: () -> Unit, onDismiss: () -> Unit) {
|
||||||
|
val context = LocalContext.current
|
||||||
|
|
||||||
|
val selectedTunnelsExportLauncher =
|
||||||
|
rememberFileExportLauncherForResult(
|
||||||
|
mimeType = FileUtils.ZIP_FILE_MIME_TYPE,
|
||||||
|
onResult = { file ->
|
||||||
|
if (file != null) {
|
||||||
|
onExport(file)
|
||||||
|
} else onDismiss()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
fun handleFileExport() {
|
||||||
|
if (context.hasSAFSupport(FileUtils.ZIP_FILE_MIME_TYPE)) {
|
||||||
|
selectedTunnelsExportLauncher.launch(
|
||||||
|
"${Constants.BASE_LOG_FILE_NAME}_${BuildConfig.VERSION_NAME}_${BuildConfig.FLAVOR}.zip"
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
onExport(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
CustomBottomSheet(
|
CustomBottomSheet(
|
||||||
listOf(
|
listOf(
|
||||||
SheetOption(
|
SheetOption(
|
||||||
Icons.Outlined.FolderZip,
|
Icons.Outlined.FolderZip,
|
||||||
stringResource(R.string.export_logs),
|
stringResource(R.string.export_logs),
|
||||||
onClick = onExport,
|
onClick = { handleFileExport() },
|
||||||
),
|
),
|
||||||
SheetOption(
|
SheetOption(
|
||||||
Icons.Outlined.Delete,
|
Icons.Outlined.Delete,
|
||||||
|
|||||||
+7
-2
@@ -11,9 +11,9 @@ import com.zaneschepke.wireguardautotunnel.domain.enums.ConfigType
|
|||||||
import com.zaneschepke.wireguardautotunnel.ui.common.functions.rememberFileExportLauncherForResult
|
import com.zaneschepke.wireguardautotunnel.ui.common.functions.rememberFileExportLauncherForResult
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.sheet.CustomBottomSheet
|
import com.zaneschepke.wireguardautotunnel.ui.common.sheet.CustomBottomSheet
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.sheet.SheetOption
|
import com.zaneschepke.wireguardautotunnel.ui.common.sheet.SheetOption
|
||||||
import com.zaneschepke.wireguardautotunnel.util.Constants
|
|
||||||
import com.zaneschepke.wireguardautotunnel.util.FileUtils
|
import com.zaneschepke.wireguardautotunnel.util.FileUtils
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.hasSAFSupport
|
import com.zaneschepke.wireguardautotunnel.util.extensions.hasSAFSupport
|
||||||
|
import java.time.Instant
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ExportTunnelsBottomSheet(
|
fun ExportTunnelsBottomSheet(
|
||||||
@@ -37,7 +37,12 @@ fun ExportTunnelsBottomSheet(
|
|||||||
|
|
||||||
fun handleFileExport() {
|
fun handleFileExport() {
|
||||||
if (context.hasSAFSupport(FileUtils.ZIP_FILE_MIME_TYPE)) {
|
if (context.hasSAFSupport(FileUtils.ZIP_FILE_MIME_TYPE)) {
|
||||||
selectedTunnelsExportLauncher.launch(Constants.DEFAULT_EXPORT_FILE_NAME)
|
val fileName =
|
||||||
|
when (exportConfigType) {
|
||||||
|
ConfigType.AM -> "am_export_${Instant.now().epochSecond}.zip"
|
||||||
|
ConfigType.WG -> "wg_export_${Instant.now().epochSecond}.zip"
|
||||||
|
}
|
||||||
|
selectedTunnelsExportLauncher.launch(fileName)
|
||||||
} else {
|
} else {
|
||||||
onExport(exportConfigType, null)
|
onExport(exportConfigType, null)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,8 +9,6 @@ object Constants {
|
|||||||
const val SYSTEM_EXEMPT_SERVICE_TYPE_ID = 1 shl 10
|
const val SYSTEM_EXEMPT_SERVICE_TYPE_ID = 1 shl 10
|
||||||
const val SPECIAL_USE_SERVICE_TYPE_ID = 1 shl 30
|
const val SPECIAL_USE_SERVICE_TYPE_ID = 1 shl 30
|
||||||
|
|
||||||
const val DEFAULT_EXPORT_FILE_NAME = "wgtunnel-export.zip"
|
|
||||||
|
|
||||||
const val QR_CODE_NAME_PROPERTY = "# Name ="
|
const val QR_CODE_NAME_PROPERTY = "# Name ="
|
||||||
|
|
||||||
const val FDROID_FLAVOR = "fdroid"
|
const val FDROID_FLAVOR = "fdroid"
|
||||||
|
|||||||
+1
-1
@@ -117,7 +117,7 @@ constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun setTunnelOnEthernet(to: Boolean) = intent {
|
fun setTunnelOnEthernet(to: Boolean) = intent {
|
||||||
settingsRepository.save(state.generalSettings.copy(isTunnelOnMobileDataEnabled = to))
|
settingsRepository.save(state.generalSettings.copy(isTunnelOnEthernetEnabled = to))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setDebounceDelay(to: Int) = intent {
|
fun setDebounceDelay(to: Int) = intent {
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
package com.zaneschepke.wireguardautotunnel.viewmodel
|
package com.zaneschepke.wireguardautotunnel.viewmodel
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import com.zaneschepke.logcatter.LogReader
|
import com.zaneschepke.logcatter.LogReader
|
||||||
|
import com.zaneschepke.wireguardautotunnel.BuildConfig
|
||||||
import com.zaneschepke.wireguardautotunnel.R
|
import com.zaneschepke.wireguardautotunnel.R
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.repository.AppStateRepository
|
import com.zaneschepke.wireguardautotunnel.domain.repository.AppStateRepository
|
||||||
import com.zaneschepke.wireguardautotunnel.domain.repository.GlobalEffectRepository
|
import com.zaneschepke.wireguardautotunnel.domain.repository.GlobalEffectRepository
|
||||||
@@ -11,7 +13,6 @@ import com.zaneschepke.wireguardautotunnel.util.Constants
|
|||||||
import com.zaneschepke.wireguardautotunnel.util.FileUtils
|
import com.zaneschepke.wireguardautotunnel.util.FileUtils
|
||||||
import com.zaneschepke.wireguardautotunnel.util.StringValue
|
import com.zaneschepke.wireguardautotunnel.util.StringValue
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import java.time.Instant
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
@@ -71,18 +72,30 @@ constructor(
|
|||||||
globalEffectRepository.post(globalSideEffect)
|
globalEffectRepository.post(globalSideEffect)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun exportLogs() = intent {
|
fun exportLogs(uri: Uri?) = intent {
|
||||||
val result =
|
val result =
|
||||||
fileUtils.createNewShareFile(
|
fileUtils.createNewShareFile(
|
||||||
"${Constants.BASE_LOG_FILE_NAME}-${Instant.now().epochSecond}.zip"
|
"${Constants.BASE_LOG_FILE_NAME}_${BuildConfig.VERSION_NAME}_${BuildConfig.FLAVOR}.zip"
|
||||||
)
|
)
|
||||||
result.onSuccess { file -> postSideEffect(GlobalSideEffect.ShareFile(file)) }
|
val onFailure = { action: Throwable ->
|
||||||
result.onFailure { error ->
|
Timber.e(action)
|
||||||
val message =
|
intent {
|
||||||
error.message?.let { StringValue.DynamicString(it) }
|
postSideEffect(
|
||||||
?: StringValue.StringResource(R.string.unknown_error)
|
GlobalSideEffect.Toast(
|
||||||
postSideEffect(GlobalSideEffect.Toast(message))
|
StringValue.StringResource(
|
||||||
|
R.string.export_failed,
|
||||||
|
": ${action.localizedMessage}",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Unit
|
||||||
}
|
}
|
||||||
|
result.onSuccess { file ->
|
||||||
|
logReader.zipLogFiles(file.absolutePath)
|
||||||
|
fileUtils.exportFile(file, uri, FileUtils.ZIP_FILE_MIME_TYPE).onFailure(onFailure)
|
||||||
|
}
|
||||||
|
result.onFailure(onFailure)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun deleteLogs() = intent {
|
fun deleteLogs() = intent {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
object Constants {
|
object Constants {
|
||||||
const val VERSION_NAME = "4.0.1"
|
const val VERSION_NAME = "4.0.3"
|
||||||
const val VERSION_CODE = 40001
|
const val VERSION_CODE = 40003
|
||||||
const val TARGET_SDK = 36
|
const val TARGET_SDK = 36
|
||||||
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:
|
||||||
|
- App lock crash bugfix
|
||||||
|
- Fdroid publishing bugfix
|
||||||
|
- Exporting logs bugfix
|
||||||
|
- Auto-tunnel ethernet toggle bugfix
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
What's new:
|
||||||
|
- Monitoring failing to shut down race bugfix
|
||||||
|
- Notifications stop action bugfix
|
||||||
|
- Notification relaunch activity when already active bugfix
|
||||||
@@ -12,28 +12,28 @@ coreKtx = "1.17.0"
|
|||||||
datastorePreferences = "1.2.0-alpha02"
|
datastorePreferences = "1.2.0-alpha02"
|
||||||
desugar_jdk_libs = "2.1.5"
|
desugar_jdk_libs = "2.1.5"
|
||||||
espressoCore = "3.7.0"
|
espressoCore = "3.7.0"
|
||||||
hiltAndroid = "2.57.1"
|
hiltAndroid = "2.57.2"
|
||||||
hiltCompiler = "1.3.0"
|
hiltCompiler = "1.3.0"
|
||||||
junit = "4.13.2"
|
junit = "4.13.2"
|
||||||
kotlinx-serialization-json = "1.9.0"
|
kotlinx-serialization-json = "1.9.0"
|
||||||
ktorClientCore = "3.3.0"
|
ktorClientCore = "3.3.0"
|
||||||
lifecycle-runtime-compose = "2.9.4"
|
lifecycle-runtime-compose = "2.9.4"
|
||||||
material3 = "1.3.2"
|
material3 = "1.4.0"
|
||||||
navigationCompose = "2.9.4"
|
navigationCompose = "2.9.5"
|
||||||
pinLockCompose = "1.0.5"
|
pinLockCompose = "1.0.5"
|
||||||
qrose = "1.0.1"
|
qrose = "1.0.1"
|
||||||
roomVersion = "2.8.0"
|
roomVersion = "2.8.1"
|
||||||
semver4j = "3.1.0"
|
semver4j = "3.1.0"
|
||||||
slf4jAndroid = "1.7.36"
|
slf4jAndroid = "1.7.36"
|
||||||
timber = "5.0.1"
|
timber = "5.0.1"
|
||||||
tunnel = "1.4.0"
|
tunnel = "1.4.0"
|
||||||
androidGradlePlugin = "8.12.0"
|
androidGradlePlugin = "8.11.1"
|
||||||
kotlin = "2.2.20"
|
kotlin = "2.2.20"
|
||||||
ksp = "2.2.20-2.0.3"
|
ksp = "2.2.20-2.0.3"
|
||||||
composeBom = "2025.09.00"
|
composeBom = "2025.09.01"
|
||||||
compose = "1.9.1"
|
compose = "1.9.2"
|
||||||
icons = "1.7.8"
|
icons = "1.7.8"
|
||||||
workRuntimeKtxVersion = "2.10.4"
|
workRuntimeKtxVersion = "2.10.5"
|
||||||
zxingAndroidEmbedded = "4.3.0"
|
zxingAndroidEmbedded = "4.3.0"
|
||||||
coreSplashscreen = "1.0.1"
|
coreSplashscreen = "1.0.1"
|
||||||
gradlePlugins-grgit = "5.3.3"
|
gradlePlugins-grgit = "5.3.3"
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ interface LogReader {
|
|||||||
|
|
||||||
fun stop()
|
fun stop()
|
||||||
|
|
||||||
fun zipLogFiles(path: String)
|
suspend fun zipLogFiles(path: String)
|
||||||
|
|
||||||
suspend fun deleteAndClearLogs()
|
suspend fun deleteAndClearLogs()
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.zaneschepke.logcatter
|
package com.zaneschepke.logcatter
|
||||||
|
|
||||||
|
import android.R.attr.path
|
||||||
import androidx.lifecycle.DefaultLifecycleObserver
|
import androidx.lifecycle.DefaultLifecycleObserver
|
||||||
import androidx.lifecycle.LifecycleOwner
|
import androidx.lifecycle.LifecycleOwner
|
||||||
import com.zaneschepke.logcatter.model.LogMessage
|
import com.zaneschepke.logcatter.model.LogMessage
|
||||||
@@ -61,15 +62,13 @@ class LogcatManager(pid: Int, logDir: String, maxFileSize: Long, maxFolderSize:
|
|||||||
isStarted = false
|
isStarted = false
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun zipLogFiles(path: String) {
|
override suspend fun zipLogFiles(path: String) {
|
||||||
logScope.launch {
|
val wasStarted = isStarted
|
||||||
val wasStarted = isStarted
|
stop()
|
||||||
stop()
|
fileManager.zipLogs(path)
|
||||||
fileManager.zipLogs(path)
|
if (wasStarted) {
|
||||||
if (wasStarted) {
|
logcatReader.clearLogs()
|
||||||
logcatReader.clearLogs()
|
start()
|
||||||
start()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user