diff --git a/app/src/main/kotlin/dev/meloda/fast/presentation/MainActivity.kt b/app/src/main/kotlin/dev/meloda/fast/presentation/MainActivity.kt index e51070cc..5e8e9fef 100644 --- a/app/src/main/kotlin/dev/meloda/fast/presentation/MainActivity.kt +++ b/app/src/main/kotlin/dev/meloda/fast/presentation/MainActivity.kt @@ -137,7 +137,8 @@ class MainActivity : AppCompatActivity() { } } - LaunchedEffect(longPollStateToApply) { + LifecycleResumeEffect(longPollStateToApply) { + Log.d("LongPollMainActivity", "longPollStateToApply: $longPollStateToApply") if (longPollStateToApply != LongPollState.Background) { if (longPollStateToApply.isLaunched() && longPollCurrentState.isLaunched() && longPollCurrentState != longPollStateToApply @@ -151,6 +152,8 @@ class MainActivity : AppCompatActivity() { inBackground = longPollStateToApply == LongPollState.Background ) } + + onPauseOrDispose {} } val sendOnline by userSettings.sendOnlineStatus.collectAsStateWithLifecycle() @@ -299,12 +302,19 @@ class MainActivity : AppCompatActivity() { } } + private val longPollingServiceIntent by lazy { + Intent(this, LongPollingService::class.java) + } + private val onlineServiceIntent by lazy { + Intent(this, OnlineService::class.java) + } + private fun toggleLongPollService( enable: Boolean, inBackground: Boolean = AppSettings.Experimental.longPollInBackground ) { if (enable) { - val longPollIntent = Intent(this, LongPollingService::class.java) + val longPollIntent = longPollingServiceIntent if (inBackground) { ContextCompat.startForegroundService(this, longPollIntent) @@ -312,15 +322,15 @@ class MainActivity : AppCompatActivity() { startService(longPollIntent) } } else { - stopService(Intent(this, LongPollingService::class.java)) + stopService(longPollingServiceIntent) } } private fun toggleOnlineService(enable: Boolean) { if (enable) { - startService(Intent(this, OnlineService::class.java)) + startService(onlineServiceIntent) } else { - stopService(Intent(this, OnlineService::class.java)) + stopService(onlineServiceIntent) } } diff --git a/app/src/main/kotlin/dev/meloda/fast/service/longpolling/LongPollingService.kt b/app/src/main/kotlin/dev/meloda/fast/service/longpolling/LongPollingService.kt index 354c1f14..d6bf0dce 100644 --- a/app/src/main/kotlin/dev/meloda/fast/service/longpolling/LongPollingService.kt +++ b/app/src/main/kotlin/dev/meloda/fast/service/longpolling/LongPollingService.kt @@ -30,11 +30,13 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.delay import kotlinx.coroutines.launch import org.koin.android.ext.android.inject import kotlin.coroutines.CoroutineContext import kotlin.coroutines.resume import kotlin.coroutines.suspendCoroutine +import kotlin.time.Duration.Companion.seconds class LongPollingService : Service() { @@ -42,22 +44,11 @@ class LongPollingService : Service() { private val job = SupervisorJob() - private val exceptionHandler = CoroutineExceptionHandler { _, throwable -> - Log.e(TAG, "error: $throwable") - - if (throwable !is NoAccessTokenException) { - throwable.printStackTrace() + private val exceptionHandler = + CoroutineExceptionHandler { _, throwable -> + handleError(throwable) } - if (throwable is LongPollException) { - // TODO: 23-Mar-25, Danil Nikolaev: restart LongPoll - return@CoroutineExceptionHandler - } - - longPollController.updateCurrentState(LongPollState.Exception) - longPollController.setStateToApply(LongPollState.Exception) - } - private val coroutineContext: CoroutineContext get() = Dispatchers.IO + job + exceptionHandler @@ -68,6 +59,8 @@ class LongPollingService : Service() { private var currentJob: Job? = null + private val inBackground get() = AppSettings.Experimental.longPollInBackground + override fun onCreate() { super.onCreate() Log.d(STATE_TAG, "onCreate()") @@ -81,21 +74,12 @@ class LongPollingService : Service() { override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { if (startId > 1) return START_STICKY - val inBackground = AppSettings.Experimental.longPollInBackground - Log.d( STATE_TAG, "onStartCommand: asForeground: $inBackground; flags: $flags; startId: $startId;\ninstance: $this" ) - if (currentJob != null) { - currentJob?.cancel() - currentJob = null - } - - coroutineScope.launch { - currentJob = startPolling().also { it.join() } - } + startJob() val openCategorySettingsIntent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS) @@ -113,11 +97,6 @@ class LongPollingService : Service() { PendingIntent.FLAG_IMMUTABLE ) - longPollController.updateCurrentState( - if (inBackground) LongPollState.Background - else LongPollState.InApp - ) - if (inBackground) { val notification = NotificationsUtils.createNotification( @@ -139,17 +118,33 @@ class LongPollingService : Service() { return START_STICKY } + private fun startJob() { + if (currentJob != null) { + currentJob?.cancel() + currentJob = null + } + + coroutineScope.launch { + currentJob = startPolling().also { it.join() } + } + } + private fun startPolling(): Job { if (job.isCompleted || job.isCancelled) { - Log.d(STATE_TAG, "job is completed or cancelled") + Log.d(STATE_TAG, "Job is completed or cancelled") throw Exception("Job is over") } - Log.d(STATE_TAG, "job started") + Log.d(STATE_TAG, "Starting job...") return coroutineScope.launch(coroutineContext) { + longPollController.updateCurrentState( + if (inBackground) LongPollState.Background + else LongPollState.InApp + ) + if (UserConfig.accessToken.isEmpty()) { - throw NoAccessTokenException + throw NoAccessTokenException() } var serverInfo = getServerInfo() @@ -251,10 +246,24 @@ class LongPollingService : Service() { } } + private fun handleError(throwable: Throwable) { + Log.e(TAG, "error: $throwable") + + if (throwable !is NoAccessTokenException) { + throwable.printStackTrace() + } + + coroutineScope.launch { + delay(5.seconds) + startJob() + } + + longPollController.updateCurrentState(LongPollState.Exception) + } + override fun onDestroy() { Log.d(STATE_TAG, "onDestroy") longPollController.updateCurrentState(LongPollState.Stopped) - updatesParser.clearListeners() try { AppSettings.edit { putBoolean(KEY_LONG_POLL_WAS_DESTROYED, true) } job.cancel() @@ -281,4 +290,4 @@ class LongPollingService : Service() { } private data class LongPollException(override val message: String) : Throwable() -private data object NoAccessTokenException : Throwable() +private class NoAccessTokenException : Throwable()