Improve error reporting from notifications
This commit is contained in:
@@ -19,6 +19,7 @@ import org.koitharu.kotatsu.R
|
|||||||
import org.koitharu.kotatsu.alternatives.domain.AutoFixUseCase
|
import org.koitharu.kotatsu.alternatives.domain.AutoFixUseCase
|
||||||
import org.koitharu.kotatsu.core.ErrorReporterReceiver
|
import org.koitharu.kotatsu.core.ErrorReporterReceiver
|
||||||
import org.koitharu.kotatsu.core.model.getTitle
|
import org.koitharu.kotatsu.core.model.getTitle
|
||||||
|
import org.koitharu.kotatsu.core.model.isNsfw
|
||||||
import org.koitharu.kotatsu.core.nav.AppRouter
|
import org.koitharu.kotatsu.core.nav.AppRouter
|
||||||
import org.koitharu.kotatsu.core.ui.CoroutineIntentService
|
import org.koitharu.kotatsu.core.ui.CoroutineIntentService
|
||||||
import org.koitharu.kotatsu.core.util.ext.checkNotificationPermission
|
import org.koitharu.kotatsu.core.util.ext.checkNotificationPermission
|
||||||
@@ -58,7 +59,7 @@ class AutoFixService : CoroutineIntentService() {
|
|||||||
autoFixUseCase.invoke(mangaId)
|
autoFixUseCase.invoke(mangaId)
|
||||||
}
|
}
|
||||||
if (applicationContext.checkNotificationPermission(CHANNEL_ID)) {
|
if (applicationContext.checkNotificationPermission(CHANNEL_ID)) {
|
||||||
val notification = buildNotification(result)
|
val notification = buildNotification(startId, result)
|
||||||
notificationManager.notify(TAG, startId, notification)
|
notificationManager.notify(TAG, startId, notification)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -67,7 +68,7 @@ class AutoFixService : CoroutineIntentService() {
|
|||||||
|
|
||||||
override fun IntentJobContext.onError(error: Throwable) {
|
override fun IntentJobContext.onError(error: Throwable) {
|
||||||
if (applicationContext.checkNotificationPermission(CHANNEL_ID)) {
|
if (applicationContext.checkNotificationPermission(CHANNEL_ID)) {
|
||||||
val notification = runBlocking { buildNotification(Result.failure(error)) }
|
val notification = runBlocking { buildNotification(startId, Result.failure(error)) }
|
||||||
notificationManager.notify(TAG, startId, notification)
|
notificationManager.notify(TAG, startId, notification)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -108,7 +109,7 @@ class AutoFixService : CoroutineIntentService() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun buildNotification(result: Result<Pair<Manga, Manga?>>): Notification {
|
private suspend fun buildNotification(startId: Int, result: Result<Pair<Manga, Manga?>>): Notification {
|
||||||
val notification = NotificationCompat.Builder(applicationContext, CHANNEL_ID)
|
val notification = NotificationCompat.Builder(applicationContext, CHANNEL_ID)
|
||||||
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
|
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
|
||||||
.setDefaults(0)
|
.setDefaults(0)
|
||||||
@@ -135,7 +136,11 @@ class AutoFixService : CoroutineIntentService() {
|
|||||||
false,
|
false,
|
||||||
),
|
),
|
||||||
).setVisibility(
|
).setVisibility(
|
||||||
if (replacement.isNsfw) NotificationCompat.VISIBILITY_SECRET else NotificationCompat.VISIBILITY_PUBLIC,
|
if (replacement.isNsfw()) {
|
||||||
|
NotificationCompat.VISIBILITY_SECRET
|
||||||
|
} else {
|
||||||
|
NotificationCompat.VISIBILITY_PUBLIC
|
||||||
|
},
|
||||||
)
|
)
|
||||||
notification
|
notification
|
||||||
.setContentTitle(applicationContext.getString(R.string.fixed))
|
.setContentTitle(applicationContext.getString(R.string.fixed))
|
||||||
@@ -165,12 +170,13 @@ class AutoFixService : CoroutineIntentService() {
|
|||||||
error.getDisplayMessage(applicationContext.resources)
|
error.getDisplayMessage(applicationContext.resources)
|
||||||
},
|
},
|
||||||
).setSmallIcon(android.R.drawable.stat_notify_error)
|
).setSmallIcon(android.R.drawable.stat_notify_error)
|
||||||
ErrorReporterReceiver.getPendingIntent(applicationContext, error)?.let { reportIntent ->
|
ErrorReporterReceiver.getNotificationAction(
|
||||||
notification.addAction(
|
context = applicationContext,
|
||||||
R.drawable.ic_alert_outline,
|
e = error,
|
||||||
applicationContext.getString(R.string.report),
|
notificationId = startId,
|
||||||
reportIntent,
|
notificationTag = TAG,
|
||||||
)
|
)?.let { action ->
|
||||||
|
notification.addAction(action)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return notification.build()
|
return notification.build()
|
||||||
|
|||||||
@@ -183,8 +183,10 @@ class BackupRepository @Inject constructor(
|
|||||||
data.onStart {
|
data.onStart {
|
||||||
putNextEntry(ZipEntry(section.entryName))
|
putNextEntry(ZipEntry(section.entryName))
|
||||||
write("[")
|
write("[")
|
||||||
}.onCompletion {
|
}.onCompletion { error ->
|
||||||
write("]")
|
if (error == null) {
|
||||||
|
write("]")
|
||||||
|
}
|
||||||
closeEntry()
|
closeEntry()
|
||||||
flush()
|
flush()
|
||||||
}.collectIndexed { index, value ->
|
}.collectIndexed { index, value ->
|
||||||
|
|||||||
@@ -84,13 +84,9 @@ abstract class BaseBackupRestoreService : CoroutineIntentService() {
|
|||||||
.setBigText(title, message)
|
.setBigText(title, message)
|
||||||
.setSmallIcon(android.R.drawable.stat_notify_error)
|
.setSmallIcon(android.R.drawable.stat_notify_error)
|
||||||
result.failures.firstNotNullOfOrNull { error ->
|
result.failures.firstNotNullOfOrNull { error ->
|
||||||
ErrorReporterReceiver.getPendingIntent(applicationContext, error)
|
ErrorReporterReceiver.getNotificationAction(applicationContext, error, startId, notificationTag)
|
||||||
}?.let { reportIntent ->
|
}?.let { action ->
|
||||||
notification.addAction(
|
notification.addAction(action)
|
||||||
R.drawable.ic_alert_outline,
|
|
||||||
applicationContext.getString(R.string.report),
|
|
||||||
reportIntent,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import android.widget.Toast
|
|||||||
import androidx.annotation.CheckResult
|
import androidx.annotation.CheckResult
|
||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.documentfile.provider.DocumentFile
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.cancelAndJoin
|
import kotlinx.coroutines.cancelAndJoin
|
||||||
@@ -61,8 +62,17 @@ class BackupService : BaseBackupRestoreService() {
|
|||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
ZipOutputStream(contentResolver.openOutputStream(destination)).use { output ->
|
try {
|
||||||
repository.createBackup(output, progress)
|
ZipOutputStream(contentResolver.openOutputStream(destination)).use { output ->
|
||||||
|
repository.createBackup(output, progress)
|
||||||
|
}
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
try {
|
||||||
|
DocumentFile.fromSingleUri(applicationContext, destination)?.delete()
|
||||||
|
} catch (e2: Throwable) {
|
||||||
|
e.addSuppressed(e2)
|
||||||
|
}
|
||||||
|
throw e
|
||||||
}
|
}
|
||||||
progressUpdateJob?.cancelAndJoin()
|
progressUpdateJob?.cancelAndJoin()
|
||||||
contentResolver.notifyChange(destination, null)
|
contentResolver.notifyChange(destination, null)
|
||||||
|
|||||||
@@ -5,9 +5,12 @@ import android.content.BroadcastReceiver
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.BadParcelableException
|
import android.os.BadParcelableException
|
||||||
|
import androidx.core.app.NotificationCompat
|
||||||
|
import androidx.core.app.NotificationManagerCompat
|
||||||
import androidx.core.app.PendingIntentCompat
|
import androidx.core.app.PendingIntentCompat
|
||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
import org.koitharu.kotatsu.BuildConfig
|
import org.koitharu.kotatsu.BuildConfig
|
||||||
|
import org.koitharu.kotatsu.R
|
||||||
import org.koitharu.kotatsu.core.nav.AppRouter
|
import org.koitharu.kotatsu.core.nav.AppRouter
|
||||||
import org.koitharu.kotatsu.core.util.ext.getSerializableExtraCompat
|
import org.koitharu.kotatsu.core.util.ext.getSerializableExtraCompat
|
||||||
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
|
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
|
||||||
@@ -17,18 +20,58 @@ class ErrorReporterReceiver : BroadcastReceiver() {
|
|||||||
|
|
||||||
override fun onReceive(context: Context?, intent: Intent?) {
|
override fun onReceive(context: Context?, intent: Intent?) {
|
||||||
val e = intent?.getSerializableExtraCompat<Throwable>(AppRouter.KEY_ERROR) ?: return
|
val e = intent?.getSerializableExtraCompat<Throwable>(AppRouter.KEY_ERROR) ?: return
|
||||||
|
val notificationId = intent.getIntExtra(EXTRA_NOTIFICATION_ID, 0)
|
||||||
|
if (notificationId != 0 && context != null) {
|
||||||
|
val notificationTag = intent.getStringExtra(EXTRA_NOTIFICATION_TAG)
|
||||||
|
NotificationManagerCompat.from(context).cancel(notificationTag, notificationId)
|
||||||
|
}
|
||||||
e.report()
|
e.report()
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
private const val ACTION_REPORT = "${BuildConfig.APPLICATION_ID}.action.REPORT_ERROR"
|
private const val ACTION_REPORT = "${BuildConfig.APPLICATION_ID}.action.REPORT_ERROR"
|
||||||
|
private const val EXTRA_NOTIFICATION_ID = "notify.id"
|
||||||
|
private const val EXTRA_NOTIFICATION_TAG = "notify.tag"
|
||||||
|
|
||||||
fun getPendingIntent(context: Context, e: Throwable): PendingIntent? = try {
|
fun getPendingIntent(context: Context, e: Throwable): PendingIntent? = getPendingIntentInternal(
|
||||||
|
context = context,
|
||||||
|
e = e,
|
||||||
|
notificationId = 0,
|
||||||
|
notificationTag = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
fun getNotificationAction(
|
||||||
|
context: Context,
|
||||||
|
e: Throwable,
|
||||||
|
notificationId: Int,
|
||||||
|
notificationTag: String?,
|
||||||
|
): NotificationCompat.Action? {
|
||||||
|
val intent = getPendingIntentInternal(
|
||||||
|
context = context,
|
||||||
|
e = e,
|
||||||
|
notificationId = notificationId,
|
||||||
|
notificationTag = notificationTag,
|
||||||
|
) ?: return null
|
||||||
|
return NotificationCompat.Action(
|
||||||
|
R.drawable.ic_alert_outline,
|
||||||
|
context.getString(R.string.report),
|
||||||
|
intent,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getPendingIntentInternal(
|
||||||
|
context: Context,
|
||||||
|
e: Throwable,
|
||||||
|
notificationId: Int,
|
||||||
|
notificationTag: String?,
|
||||||
|
): PendingIntent? = try {
|
||||||
val intent = Intent(context, ErrorReporterReceiver::class.java)
|
val intent = Intent(context, ErrorReporterReceiver::class.java)
|
||||||
intent.setAction(ACTION_REPORT)
|
intent.setAction(ACTION_REPORT)
|
||||||
intent.setData("err://${e.hashCode()}".toUri())
|
intent.setData("err://${e.hashCode()}".toUri())
|
||||||
intent.putExtra(AppRouter.KEY_ERROR, e)
|
intent.putExtra(AppRouter.KEY_ERROR, e)
|
||||||
|
intent.putExtra(EXTRA_NOTIFICATION_ID, notificationId)
|
||||||
|
intent.putExtra(EXTRA_NOTIFICATION_TAG, notificationTag)
|
||||||
PendingIntentCompat.getBroadcast(context, 0, intent, 0, false)
|
PendingIntentCompat.getBroadcast(context, 0, intent, 0, false)
|
||||||
} catch (e: BadParcelableException) {
|
} catch (e: BadParcelableException) {
|
||||||
e.printStackTraceDebug()
|
e.printStackTraceDebug()
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import dagger.hilt.android.AndroidEntryPoint
|
|||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import org.koitharu.kotatsu.R
|
import org.koitharu.kotatsu.R
|
||||||
import org.koitharu.kotatsu.core.ErrorReporterReceiver
|
import org.koitharu.kotatsu.core.ErrorReporterReceiver
|
||||||
|
import org.koitharu.kotatsu.core.model.isNsfw
|
||||||
import org.koitharu.kotatsu.core.nav.AppRouter
|
import org.koitharu.kotatsu.core.nav.AppRouter
|
||||||
import org.koitharu.kotatsu.core.ui.CoroutineIntentService
|
import org.koitharu.kotatsu.core.ui.CoroutineIntentService
|
||||||
import org.koitharu.kotatsu.core.util.ext.checkNotificationPermission
|
import org.koitharu.kotatsu.core.util.ext.checkNotificationPermission
|
||||||
@@ -57,7 +58,7 @@ class ImportService : CoroutineIntentService() {
|
|||||||
importer.import(uri).manga
|
importer.import(uri).manga
|
||||||
}
|
}
|
||||||
if (applicationContext.checkNotificationPermission(CHANNEL_ID)) {
|
if (applicationContext.checkNotificationPermission(CHANNEL_ID)) {
|
||||||
val notification = buildNotification(result)
|
val notification = buildNotification(startId, result)
|
||||||
notificationManager.notify(TAG, startId, notification)
|
notificationManager.notify(TAG, startId, notification)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -65,7 +66,7 @@ class ImportService : CoroutineIntentService() {
|
|||||||
|
|
||||||
override fun IntentJobContext.onError(error: Throwable) {
|
override fun IntentJobContext.onError(error: Throwable) {
|
||||||
if (applicationContext.checkNotificationPermission(CHANNEL_ID)) {
|
if (applicationContext.checkNotificationPermission(CHANNEL_ID)) {
|
||||||
val notification = runBlocking { buildNotification(Result.failure(error)) }
|
val notification = runBlocking { buildNotification(startId, Result.failure(error)) }
|
||||||
notificationManager.notify(TAG, startId, notification)
|
notificationManager.notify(TAG, startId, notification)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -101,7 +102,7 @@ class ImportService : CoroutineIntentService() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun buildNotification(result: Result<Manga>): Notification {
|
private suspend fun buildNotification(startId: Int, result: Result<Manga>): Notification {
|
||||||
val notification = NotificationCompat.Builder(applicationContext, CHANNEL_ID)
|
val notification = NotificationCompat.Builder(applicationContext, CHANNEL_ID)
|
||||||
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
|
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
|
||||||
.setDefaults(0)
|
.setDefaults(0)
|
||||||
@@ -127,7 +128,7 @@ class ImportService : CoroutineIntentService() {
|
|||||||
false,
|
false,
|
||||||
),
|
),
|
||||||
).setVisibility(
|
).setVisibility(
|
||||||
if (manga.isNsfw) NotificationCompat.VISIBILITY_SECRET else NotificationCompat.VISIBILITY_PUBLIC,
|
if (manga.isNsfw()) NotificationCompat.VISIBILITY_SECRET else NotificationCompat.VISIBILITY_PUBLIC,
|
||||||
)
|
)
|
||||||
notification.setContentTitle(applicationContext.getString(R.string.import_completed))
|
notification.setContentTitle(applicationContext.getString(R.string.import_completed))
|
||||||
.setContentText(applicationContext.getString(R.string.import_completed_hint))
|
.setContentText(applicationContext.getString(R.string.import_completed_hint))
|
||||||
@@ -138,12 +139,13 @@ class ImportService : CoroutineIntentService() {
|
|||||||
notification.setContentTitle(applicationContext.getString(R.string.error_occurred))
|
notification.setContentTitle(applicationContext.getString(R.string.error_occurred))
|
||||||
.setContentText(error.getDisplayMessage(applicationContext.resources))
|
.setContentText(error.getDisplayMessage(applicationContext.resources))
|
||||||
.setSmallIcon(android.R.drawable.stat_notify_error)
|
.setSmallIcon(android.R.drawable.stat_notify_error)
|
||||||
ErrorReporterReceiver.getPendingIntent(applicationContext, error)?.let { reportIntent ->
|
ErrorReporterReceiver.getNotificationAction(
|
||||||
notification.addAction(
|
context = applicationContext,
|
||||||
R.drawable.ic_alert_outline,
|
e = error,
|
||||||
applicationContext.getString(R.string.report),
|
notificationId = startId,
|
||||||
reportIntent,
|
notificationTag = TAG,
|
||||||
)
|
)?.let { action ->
|
||||||
|
notification.addAction(action)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return notification.build()
|
return notification.build()
|
||||||
|
|||||||
Reference in New Issue
Block a user