diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/WireGuardAutoTunnel.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/WireGuardAutoTunnel.kt index 37633fa..e18c9e5 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/WireGuardAutoTunnel.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/WireGuardAutoTunnel.kt @@ -8,6 +8,7 @@ import com.zaneschepke.wireguardautotunnel.data.repository.AppStateRepository import com.zaneschepke.wireguardautotunnel.data.repository.SettingsRepository import com.zaneschepke.wireguardautotunnel.module.ApplicationScope import com.zaneschepke.wireguardautotunnel.module.IoDispatcher +import com.zaneschepke.wireguardautotunnel.module.MainDispatcher import com.zaneschepke.wireguardautotunnel.service.tunnel.BackendState import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelService import com.zaneschepke.wireguardautotunnel.util.LocaleUtil @@ -17,6 +18,7 @@ import dagger.hilt.android.HiltAndroidApp import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import timber.log.Timber import javax.inject.Inject @@ -43,6 +45,10 @@ class WireGuardAutoTunnel : Application() { @IoDispatcher lateinit var ioDispatcher: CoroutineDispatcher + @Inject + @MainDispatcher + lateinit var mainDispatcher: CoroutineDispatcher + override fun onCreate() { super.onCreate() instance = this @@ -59,22 +65,19 @@ class WireGuardAutoTunnel : Application() { } else { Timber.plant(ReleaseTree()) } + applicationScope.launch { + withContext(mainDispatcher) { + if (appStateRepository.isLocalLogsEnabled() && !isRunningOnTv()) logReader.initialize() + } if (!settingsRepository.getSettings().isKernelEnabled) { tunnelService.setBackendState(BackendState.SERVICE_ACTIVE, emptyList()) } + appStateRepository.getLocale()?.let { LocaleUtil.changeLocale(it) } } - if (!isRunningOnTv()) { - applicationScope.launch(ioDispatcher) { - if (appStateRepository.isLocalLogsEnabled()) { - Timber.d("Starting logger") - logReader.start() - } - } - } } override fun onTerminate() { diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/module/AppModule.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/module/AppModule.kt index bb5b780..2a7b48c 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/module/AppModule.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/module/AppModule.kt @@ -2,7 +2,7 @@ package com.zaneschepke.wireguardautotunnel.module import android.content.Context import com.zaneschepke.logcatter.LogReader -import com.zaneschepke.logcatter.LogcatCollector +import com.zaneschepke.logcatter.LogcatReader import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -25,6 +25,6 @@ class AppModule { @Singleton @Provides fun provideLogCollect(@ApplicationContext context: Context): LogReader { - return LogcatCollector.init(context = context) + return LogcatReader.init(storageDir = context.filesDir.absolutePath) } } diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/foreground/autotunnel/AutoTunnelService.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/foreground/autotunnel/AutoTunnelService.kt index 6e71b3f..df8b9ef 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/foreground/autotunnel/AutoTunnelService.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/foreground/autotunnel/AutoTunnelService.kt @@ -41,7 +41,6 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.filterNot import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/AppViewModel.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/AppViewModel.kt index e34b0e9..b08775a 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/AppViewModel.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/AppViewModel.kt @@ -141,7 +141,6 @@ constructor( } private suspend fun onLoggerStop() { - logReader.stop() logReader.deleteAndClearLogs() } diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/appearance/language/LanguageScreen.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/appearance/language/LanguageScreen.kt index 131e6cf..1746353 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/appearance/language/LanguageScreen.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/appearance/language/LanguageScreen.kt @@ -50,7 +50,7 @@ fun LanguageScreen(appUiState: AppUiState, appViewModel: AppViewModel) { modifier = Modifier .fillMaxSize().padding(padding) - .padding(horizontal = 24.dp.scaledWidth()) + .padding(horizontal = 24.dp.scaledWidth()), ) { item { Box(modifier = Modifier.padding(top = 24.dp.scaledHeight())) { diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/autotunnel/AutoTunnelScreen.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/autotunnel/AutoTunnelScreen.kt index 1afc971..d6536f9 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/autotunnel/AutoTunnelScreen.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/autotunnel/AutoTunnelScreen.kt @@ -6,7 +6,6 @@ import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.ExperimentalLayoutApi import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding diff --git a/logcatter/build.gradle.kts b/logcatter/build.gradle.kts index c31dce2..a2957c0 100644 --- a/logcatter/build.gradle.kts +++ b/logcatter/build.gradle.kts @@ -49,6 +49,7 @@ dependencies { androidTestImplementation(libs.androidx.junit) androidTestImplementation(libs.androidx.espresso.core) + implementation(libs.androidx.lifecycle.process) // logging implementation(libs.timber) } diff --git a/logcatter/src/main/java/com/zaneschepke/logcatter/LogReader.kt b/logcatter/src/main/java/com/zaneschepke/logcatter/LogReader.kt index 56f44dd..50224df 100644 --- a/logcatter/src/main/java/com/zaneschepke/logcatter/LogReader.kt +++ b/logcatter/src/main/java/com/zaneschepke/logcatter/LogReader.kt @@ -4,8 +4,7 @@ import com.zaneschepke.logcatter.model.LogMessage import kotlinx.coroutines.flow.Flow interface LogReader { - suspend fun start(onLogMessage: ((message: LogMessage) -> Unit)? = null) - fun stop() + fun initialize(onLogMessage: ((message: LogMessage) -> Unit)? = null) fun zipLogFiles(path: String) suspend fun deleteAndClearLogs() val bufferedLogs: Flow diff --git a/logcatter/src/main/java/com/zaneschepke/logcatter/LogcatCollector.kt b/logcatter/src/main/java/com/zaneschepke/logcatter/LogcatReader.kt similarity index 72% rename from logcatter/src/main/java/com/zaneschepke/logcatter/LogcatCollector.kt rename to logcatter/src/main/java/com/zaneschepke/logcatter/LogcatReader.kt index ad5316a..b369e55 100644 --- a/logcatter/src/main/java/com/zaneschepke/logcatter/LogcatCollector.kt +++ b/logcatter/src/main/java/com/zaneschepke/logcatter/LogcatReader.kt @@ -1,26 +1,30 @@ package com.zaneschepke.logcatter -import android.content.Context +import androidx.lifecycle.DefaultLifecycleObserver +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.ProcessLifecycleOwner +import androidx.lifecycle.lifecycleScope import com.zaneschepke.logcatter.model.LogMessage import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.Job import kotlinx.coroutines.channels.BufferOverflow import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.asSharedFlow +import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import timber.log.Timber import java.io.BufferedOutputStream import java.io.BufferedReader import java.io.File -import java.io.FileNotFoundException import java.io.FileOutputStream import java.io.IOException import java.io.InputStreamReader import java.util.zip.ZipEntry import java.util.zip.ZipOutputStream -object LogcatCollector { +object LogcatReader { private const val MAX_FILE_SIZE = 2097152L // 2MB private const val MAX_FOLDER_SIZE = 10485760L // 10MB @@ -40,7 +44,7 @@ object LogcatCollector { var logcatPath = "" } - fun init(maxFileSize: Long = MAX_FILE_SIZE, maxFolderSize: Long = MAX_FOLDER_SIZE, context: Context): LogReader { + fun init(maxFileSize: Long = MAX_FILE_SIZE, maxFolderSize: Long = MAX_FOLDER_SIZE, storageDir: String): LogReader { if (maxFileSize > maxFolderSize) { throw IllegalStateException("maxFileSize must be less than maxFolderSize") } @@ -48,13 +52,11 @@ object LogcatCollector { LogcatHelperInit.maxFileSize = maxFileSize LogcatHelperInit.maxFolderSize = maxFolderSize LogcatHelperInit.pID = android.os.Process.myPid() - context.getExternalFilesDir(null)?.let { - LogcatHelperInit.publicAppDirectory = it.absolutePath - LogcatHelperInit.logcatPath = LogcatHelperInit.publicAppDirectory + File.separator + "logs" - val logDirectory = File(LogcatHelperInit.logcatPath) - if (!logDirectory.exists()) { - logDirectory.mkdir() - } + LogcatHelperInit.publicAppDirectory = storageDir + LogcatHelperInit.logcatPath = LogcatHelperInit.publicAppDirectory + File.separator + "logs" + val logDirectory = File(LogcatHelperInit.logcatPath) + if (!logDirectory.exists()) { + logDirectory.mkdir() } return Logcat } @@ -62,7 +64,12 @@ object LogcatCollector { internal object Logcat : LogReader { - private var logcatReader: LogcatReader? = null + private lateinit var logcatReader: LogcatReader + + override fun initialize(onLogMessage: ((message: LogMessage) -> Unit)?) { + logcatReader = LogcatReader(LogcatHelperInit.pID.toString(), LogcatHelperInit.logcatPath, onLogMessage) + ProcessLifecycleOwner.get().lifecycle.addObserver(logcatReader) + } private fun obfuscator(log: String): String { return findKeyRegex.replace(log, "").let { first -> @@ -72,22 +79,10 @@ object LogcatCollector { }.let { last -> findIpv4AddressRegex.replace(last, "") } } - override suspend fun start(onLogMessage: ((message: LogMessage) -> Unit)?) { - logcatReader ?: run { - logcatReader = LogcatReader(LogcatHelperInit.pID.toString(), LogcatHelperInit.logcatPath, onLogMessage) - } - logcatReader?.run() - } - - override fun stop() { - logcatReader?.stop() - logcatReader = null - } - override fun zipLogFiles(path: String) { - logcatReader?.pause() + logcatReader.cancel() zipAll(path) - logcatReader?.resume() + logcatReader.onCreate(ProcessLifecycleOwner.get()) } private fun zipAll(zipFilePath: String) { @@ -110,10 +105,10 @@ object LogcatCollector { @OptIn(ExperimentalCoroutinesApi::class) override suspend fun deleteAndClearLogs() { withContext(ioDispatcher) { - logcatReader?.pause() + logcatReader.cancel() _bufferedLogs.resetReplayCache() - logcatReader?.deleteAllFiles() - logcatReader?.resume() + logcatReader.deleteAllFiles() + logcatReader.onCreate(ProcessLifecycleOwner.get()) } } @@ -134,57 +129,25 @@ object LogcatCollector { pID: String, private val logcatPath: String, private val callback: ((input: LogMessage) -> Unit)?, - ) { + ) : DefaultLifecycleObserver { private var logcatProc: Process? = null private var reader: BufferedReader? = null - @get:Synchronized @set:Synchronized - private var paused = false - - @get:Synchronized @set:Synchronized - private var stopped = false - private var command = "" - private var clearLogCommand = "" + private val command = "logcat -v epoch | grep \"($pID)\"" + private val clearLogCommand = "logcat -c" + private var logJob: Job? = null private var outputStream: FileOutputStream? = null - init { - try { - outputStream = FileOutputStream(createLogFile(logcatPath)) - } catch (e: FileNotFoundException) { - Timber.e(e) - } - - command = "logcat -v epoch | grep \"($pID)\"" - clearLogCommand = "logcat -c" - } - - fun pause() { - paused = true - } - fun stop() { - stopped = true - } - - fun resume() { - paused = false - } - - fun clear() { - Runtime.getRuntime().exec(clearLogCommand) - } - - suspend fun run() { - withContext(ioDispatcher) { - paused = false - stopped = false - if (outputStream == null) return@withContext + override fun onCreate(owner: LifecycleOwner) { + super.onCreate(owner) + logJob = owner.lifecycleScope.launch(ioDispatcher) { try { + if (outputStream == null) outputStream = createNewLogFileStream() clear() logcatProc = Runtime.getRuntime().exec(command) reader = BufferedReader(InputStreamReader(logcatProc!!.inputStream), 1024) - var line: String? - while (!stopped) { - if (paused) continue + var line: String? = null + while (true) { line = reader?.readLine() if (line.isNullOrEmpty()) continue outputStream?.let { @@ -196,8 +159,8 @@ object LogcatCollector { deleteOldestFile() } line.let { text -> - val obfuscated = obfuscator(text) - it.write((obfuscated + System.lineSeparator()).toByteArray()) + val sanitized = obfuscator(text) + it.write((sanitized + System.lineSeparator()).toByteArray()) try { val logMessage = LogMessage.from(text) _bufferedLogs.tryEmit(logMessage) @@ -214,19 +177,34 @@ object LogcatCollector { } catch (e: IOException) { Timber.e(e) } finally { - logcatProc?.destroy() - logcatProc = null - - try { - reader?.close() - outputStream?.close() - reader = null - outputStream = null - } catch (e: IOException) { - Timber.e(e) - } + reset() } } + logJob?.invokeOnCompletion { + reset() + } + } + + override fun onDestroy(owner: LifecycleOwner) { + super.onDestroy(owner) + logJob?.cancel() + } + + fun cancel() { + logJob?.cancel() + } + + private fun reset() { + logcatProc?.destroy() + logcatProc = null + reader?.close() + outputStream?.close() + reader = null + outputStream = null + } + + fun clear() { + Runtime.getRuntime().exec(clearLogCommand) } private fun getFolderSize(path: String): Long { @@ -266,7 +244,6 @@ object LogcatCollector { directory.listFiles()?.toMutableList()?.run { this.forEach { it.delete() } } - outputStream = createNewLogFileStream() } } }