Fix crashes in CoroutineIntentService
This commit is contained in:
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user