diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index bd23ec1..d8c8cc1 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -167,6 +167,16 @@ android:stopWithTask="false" tools:node="merge" /> + + + + + + + + + + + { + 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) } } diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/receiver/BootReceiver.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/receiver/BootReceiver.kt index aef58bf..592d490 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/receiver/BootReceiver.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/receiver/BootReceiver.kt @@ -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) { diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/foreground/ServiceManager.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/foreground/ServiceManager.kt index e12e1ad..5f20da3 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/foreground/ServiceManager.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/foreground/ServiceManager.kt @@ -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, + ) + } } diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/foreground/TunnelBackgroundService.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/foreground/TunnelBackgroundService.kt new file mode 100644 index 0000000..fb94715 --- /dev/null +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/foreground/TunnelBackgroundService.kt @@ -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 = "", + ) + } +} diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/shortcut/ShortcutsActivity.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/shortcut/ShortcutsActivity.kt index d75c219..d61ea71 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/shortcut/ShortcutsActivity.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/shortcut/ShortcutsActivity.kt @@ -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 } } diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tile/TunnelControlTile.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tile/TunnelControlTile.kt index cbbb85b..0da6a01 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tile/TunnelControlTile.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tile/TunnelControlTile.kt @@ -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() diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/TunnelService.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/TunnelService.kt index c89193c..d40af70 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/TunnelService.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/TunnelService.kt @@ -14,4 +14,7 @@ interface TunnelService : Tunnel, org.amnezia.awg.backend.Tunnel { suspend fun runningTunnelNames(): Set suspend fun getState(): TunnelState + + fun cancelStatsJob() + fun startStatsJob() } diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/WireGuardTunnel.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/WireGuardTunnel.kt index ffd8612..27f20cc 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/WireGuardTunnel.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/WireGuardTunnel.kt @@ -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 @@ -153,6 +152,14 @@ constructor( } } + override fun cancelStatsJob() { + statsJob?.cancel() + } + + override fun startStatsJob() { + statsJob = startTunnelStatisticsJob() + } + override fun getName(): String { return _vpnState.value.tunnelConfig?.name ?: "" } @@ -164,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() } } diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/MainActivity.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/MainActivity.kt index 6ae564a..071e1ab 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/MainActivity.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/MainActivity.kt @@ -39,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 @@ -65,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) @@ -77,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() val appUiState by appViewModel.appUiState.collectAsStateWithLifecycle() @@ -241,4 +230,9 @@ class MainActivity : AppCompatActivity() { } } } + + override fun onDestroy() { + super.onDestroy() + tunnelService.cancelStatsJob() + } } diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/SplashActivity.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/SplashActivity.kt index a12c09f..8d2d946 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/SplashActivity.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/SplashActivity.kt @@ -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 @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() diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/navigation/BottomNavBar.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/navigation/BottomNavBar.kt index ed63db0..1fe5b0e 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/navigation/BottomNavBar.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/navigation/BottomNavBar.kt @@ -22,7 +22,7 @@ fun BottomNavBar(navController: NavController, bottomNavItems: List - 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) @@ -300,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 @@ -379,7 +344,7 @@ fun SettingsScreen( .focusRequester(focusRequester) }, ) - AnimatedVisibility(visible = uiState.settings.isTunnelOnWifiEnabled) { + if (uiState.settings.isTunnelOnWifiEnabled) { Column { FlowRow( modifier = diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/SettingsViewModel.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/SettingsViewModel.kt index 1077500..97e7fd9 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/SettingsViewModel.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/SettingsViewModel.kt @@ -248,4 +248,13 @@ constructor( onSuccess() } } + + suspend fun exportAllConfigs(): Result { + return kotlin.runCatching { + val tunnels = appDataRepository.tunnels.getAll() + val wgFiles = fileUtils.createWgFiles(tunnels) + val amFiles = fileUtils.createAmFiles(tunnels) + onExportTunnels(wgFiles + amFiles) + } + } } diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/util/Constants.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/util/Constants.kt index 1983ac9..af73f52 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/util/Constants.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/util/Constants.kt @@ -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" diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/util/FileUtils.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/util/FileUtils.kt index e9f4a39..23fdc38 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/util/FileUtils.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/util/FileUtils.kt @@ -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 { + 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 { + 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 { return withContext(ioDispatcher) { try { diff --git a/buildSrc/src/main/kotlin/Constants.kt b/buildSrc/src/main/kotlin/Constants.kt index a038ce2..92c8a10 100644 --- a/buildSrc/src/main/kotlin/Constants.kt +++ b/buildSrc/src/main/kotlin/Constants.kt @@ -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 = 35001 + const val VERSION_CODE = 35100 const val TARGET_SDK = 34 const val MIN_SDK = 26 const val APP_ID = "com.zaneschepke.wireguardautotunnel" diff --git a/fastlane/metadata/android/en-US/changelogs/35100.txt b/fastlane/metadata/android/en-US/changelogs/35100.txt new file mode 100644 index 0000000..0216661 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/35100.txt @@ -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 diff --git a/logcatter/src/main/java/com/zaneschepke/logcatter/LocalLogCollector.kt b/logcatter/src/main/java/com/zaneschepke/logcatter/LocalLogCollector.kt index a92c53d..83d17ec 100644 --- a/logcatter/src/main/java/com/zaneschepke/logcatter/LocalLogCollector.kt +++ b/logcatter/src/main/java/com/zaneschepke/logcatter/LocalLogCollector.kt @@ -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() diff --git a/logcatter/src/main/java/com/zaneschepke/logcatter/LogcatUtil.kt b/logcatter/src/main/java/com/zaneschepke/logcatter/LogcatUtil.kt index c15f2ad..645b165 100644 --- a/logcatter/src/main/java/com/zaneschepke/logcatter/LogcatUtil.kt +++ b/logcatter/src/main/java/com/zaneschepke/logcatter/LogcatUtil.kt @@ -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, "") } } - override fun run() { + fun run() { if (outputStream == null) return try { clear()