Fix crashes in CoroutineIntentService

This commit is contained in:
Koitharu
2022-10-01 09:28:11 +03:00
parent 23239f1fec
commit 4af8e73303
6 changed files with 68 additions and 11 deletions

View File

@@ -4,11 +4,13 @@ import android.app.Service
import android.content.Intent
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext
import org.koitharu.kotatsu.utils.ext.printStackTraceDebug
abstract class CoroutineIntentService : BaseService() {
@@ -21,11 +23,13 @@ abstract class CoroutineIntentService : BaseService() {
return Service.START_REDELIVER_INTENT
}
private fun launchCoroutine(intent: Intent?, startId: Int) = lifecycleScope.launch {
private fun launchCoroutine(intent: Intent?, startId: Int) = lifecycleScope.launch(errorHandler(startId)) {
mutex.withLock {
try {
withContext(dispatcher) {
processIntent(intent)
if (intent != null) {
withContext(dispatcher) {
processIntent(startId, intent)
}
}
} finally {
stopSelf(startId)
@@ -33,5 +37,12 @@ abstract class CoroutineIntentService : BaseService() {
}
}
protected abstract suspend fun processIntent(intent: Intent?)
protected abstract suspend fun processIntent(startId: Int, intent: Intent)
protected abstract fun onError(startId: Int, error: Throwable)
private fun errorHandler(startId: Int) = CoroutineExceptionHandler { _, throwable ->
throwable.printStackTraceDebug()
onError(startId, throwable)
}
}

View File

@@ -16,7 +16,12 @@ import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.lifecycleScope
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.transformWhile
import kotlinx.coroutines.launch
import org.koitharu.kotatsu.BuildConfig
import org.koitharu.kotatsu.R
@@ -85,7 +90,9 @@ class DownloadService : BaseService() {
override fun onDestroy() {
unregisterReceiver(controlReceiver)
wakeLock.release()
if (wakeLock.isHeld) {
wakeLock.release()
}
isRunning = false
super.onDestroy()
}
@@ -169,6 +176,7 @@ class DownloadService : BaseService() {
val cancelId = intent.getIntExtra(EXTRA_CANCEL_ID, 0)
jobs[cancelId]?.cancel()
}
ACTION_DOWNLOAD_RESUME -> {
val cancelId = intent.getIntExtra(EXTRA_CANCEL_ID, 0)
jobs[cancelId]?.resume()

View File

@@ -23,7 +23,12 @@ import org.koitharu.kotatsu.download.ui.service.DownloadService
import org.koitharu.kotatsu.local.domain.importer.MangaImporter
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.utils.PendingIntentCompat
import org.koitharu.kotatsu.utils.ext.*
import org.koitharu.kotatsu.utils.ext.asArrayList
import org.koitharu.kotatsu.utils.ext.getDisplayMessage
import org.koitharu.kotatsu.utils.ext.printStackTraceDebug
import org.koitharu.kotatsu.utils.ext.referer
import org.koitharu.kotatsu.utils.ext.report
import org.koitharu.kotatsu.utils.ext.toBitmapOrNull
import javax.inject.Inject
@AndroidEntryPoint
@@ -48,8 +53,8 @@ class ImportService : CoroutineIntentService() {
super.onDestroy()
}
override suspend fun processIntent(intent: Intent?) {
val uris = intent?.getParcelableArrayListExtra<Uri>(EXTRA_URIS)
override suspend fun processIntent(startId: Int, intent: Intent) {
val uris = intent.getParcelableArrayListExtra<Uri>(EXTRA_URIS)
if (uris.isNullOrEmpty()) {
return
}
@@ -69,6 +74,10 @@ class ImportService : CoroutineIntentService() {
ServiceCompat.stopForeground(this, ServiceCompat.STOP_FOREGROUND_REMOVE)
}
override fun onError(startId: Int, error: Throwable) {
error.report(null)
}
private suspend fun importImpl(uri: Uri): Manga {
val importer = importerFactory.create(uri)
return importer.import(uri)

View File

@@ -15,6 +15,7 @@ import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga
import org.koitharu.kotatsu.download.ui.service.DownloadService
import org.koitharu.kotatsu.local.domain.LocalMangaRepository
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.utils.ext.getDisplayMessage
import org.koitharu.kotatsu.utils.ext.getParcelableExtraCompat
import javax.inject.Inject
@@ -34,8 +35,8 @@ class LocalChaptersRemoveService : CoroutineIntentService() {
super.onDestroy()
}
override suspend fun processIntent(intent: Intent?) {
val manga = intent?.getParcelableExtraCompat<ParcelableManga>(EXTRA_MANGA)?.manga ?: return
override suspend fun processIntent(startId: Int, intent: Intent) {
val manga = intent.getParcelableExtraCompat<ParcelableManga>(EXTRA_MANGA)?.manga ?: return
val chaptersIds = intent.getLongArrayExtra(EXTRA_CHAPTERS_IDS)?.toSet() ?: return
startForeground()
val mangaWithChapters = localMangaRepository.getDetails(manga)
@@ -47,6 +48,21 @@ class LocalChaptersRemoveService : CoroutineIntentService() {
ServiceCompat.stopForeground(this, ServiceCompat.STOP_FOREGROUND_REMOVE)
}
override fun onError(startId: Int, error: Throwable) {
val notification = NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle(getString(R.string.error_occurred))
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setDefaults(0)
.setColor(ContextCompat.getColor(this, R.color.blue_primary_dark))
.setSilent(true)
.setContentText(error.getDisplayMessage(resources))
.setSmallIcon(android.R.drawable.stat_notify_error)
.setAutoCancel(true)
.build()
val nm = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
nm.notify(NOTIFICATION_ID + startId, notification)
}
private fun startForeground() {
val title = getString(R.string.local_manga_processing)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {

View File

@@ -5,7 +5,9 @@ import android.content.res.Resources
import androidx.collection.arraySetOf
import kotlinx.coroutines.CancellationException
import okio.FileNotFoundException
import okio.IOException
import org.acra.ktx.sendWithAcra
import org.json.JSONException
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.exceptions.CaughtException
import org.koitharu.kotatsu.core.exceptions.CloudFlareProtectedException
@@ -20,6 +22,8 @@ import org.koitharu.kotatsu.parsers.exception.ParseException
import java.net.SocketTimeoutException
import java.net.UnknownHostException
private const val MSG_NO_SPACE_LEFT = "No space left on device"
fun Throwable.getDisplayMessage(resources: Resources): String = when (this) {
is AuthRequiredException -> resources.getString(R.string.auth_required)
is CloudFlareProtectedException -> resources.getString(R.string.captcha_required)
@@ -41,9 +45,16 @@ fun Throwable.getDisplayMessage(resources: Resources): String = when (this) {
is WrongPasswordException -> resources.getString(R.string.wrong_password)
is NotFoundException -> resources.getString(R.string.not_found_404)
is IOException -> getDisplayMessage(message, resources) ?: localizedMessage
else -> localizedMessage
} ?: resources.getString(R.string.error_occurred)
private fun getDisplayMessage(msg: String?, resources: Resources): String? = when {
msg.isNullOrEmpty() -> null
msg.contains(MSG_NO_SPACE_LEFT) -> resources.getString(R.string.error_no_space_left)
else -> null
}
fun Throwable.isReportable(): Boolean {
return this is Error || this.javaClass in reportableExceptions
}
@@ -55,6 +66,7 @@ fun Throwable.report(message: String?) {
private val reportableExceptions = arraySetOf<Class<*>>(
ParseException::class.java,
JSONException::class.java,
RuntimeException::class.java,
IllegalStateException::class.java,
IllegalArgumentException::class.java,

View File

@@ -388,4 +388,5 @@
<string name="color_correction_hint">The chosen color settings will be remembered for this manga</string>
<string name="text_unsaved_changes_prompt">You have unsaved changes, do you want to save or discard them?</string>
<string name="discard">Discard</string>
<string name="error_no_space_left">No space left on device</string>
</resources>