diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/notification/NotificationReceiver.kt b/app/src/main/java/eu/kanade/tachiyomi/data/notification/NotificationReceiver.kt
index 148849531f..eca72803a9 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/data/notification/NotificationReceiver.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/data/notification/NotificationReceiver.kt
@@ -244,6 +244,8 @@ class NotificationReceiver : BroadcastReceiver() {
// Called to launch send intent.
private const val ACTION_SHARE_BACKUP = "$ID.$NAME.SEND_BACKUP"
+ private const val ACTION_SHARE_CRASH_LOG = "$ID.$NAME.SEND_CRASH_LOG"
+
// Called to cancel library update.
private const val ACTION_CANCEL_LIBRARY_UPDATE = "$ID.$NAME.CANCEL_LIBRARY_UPDATE"
@@ -563,6 +565,23 @@ class NotificationReceiver : BroadcastReceiver() {
return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
}
+ /**
+ * Returns [PendingIntent] that starts a share activity for a crash log dump file.
+ *
+ * @param context context of application
+ * @param uri uri of file
+ * @param notificationId id of notification
+ * @return [PendingIntent]
+ */
+ internal fun shareCrashLogPendingBroadcast(context: Context, uri: Uri, notificationId: Int): PendingIntent {
+ val intent = Intent(context, NotificationReceiver::class.java).apply {
+ action = ACTION_SHARE_CRASH_LOG
+ putExtra(EXTRA_URI, uri)
+ putExtra(EXTRA_NOTIFICATION_ID, notificationId)
+ }
+ return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
+ }
+
/**
* Returns [PendingIntent] that cancels a backup restore job.
*
diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/notification/Notifications.kt b/app/src/main/java/eu/kanade/tachiyomi/data/notification/Notifications.kt
index ef9771ae19..e2c70d7433 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/data/notification/Notifications.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/data/notification/Notifications.kt
@@ -55,6 +55,12 @@ object Notifications {
const val ID_BACKUP_PROGRESS = -502
const val ID_BACKUP_COMPLETE = -503
+ /**
+ * Notification channel used for crash log file sharing.
+ */
+ const val CHANNEL_CRASH_LOGS = "crash_logs_channel"
+ const val ID_CRASH_LOGS = -601
+
/**
* Creates the notification channels introduced in Android Oreo.
*
@@ -113,7 +119,12 @@ object Notifications {
group = GROUP_BACKUP_RESTORE
setShowBadge(false)
setSound(null, null)
- }
+ },
+ NotificationChannel(
+ CHANNEL_CRASH_LOGS,
+ context.getString(R.string.crash_logs),
+ NotificationManager.IMPORTANCE_HIGH
+ )
)
context.notificationManager.createNotificationChannels(channels)
}
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAdvancedController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAdvancedController.kt
index 8da020d7bc..fac983d8b8 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAdvancedController.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAdvancedController.kt
@@ -25,6 +25,7 @@ import eu.kanade.tachiyomi.data.preference.PreferenceKeys
import eu.kanade.tachiyomi.network.NetworkHelper
import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.ui.base.controller.DialogController
+import eu.kanade.tachiyomi.util.CrashLogUtil
import eu.kanade.tachiyomi.util.system.launchUI
import eu.kanade.tachiyomi.util.system.toast
import kotlinx.coroutines.CoroutineStart
@@ -59,6 +60,37 @@ class SettingsAdvancedController : SettingsController() {
summaryRes = R.string.helps_fix_bugs
defaultValue = true
}
+
+ preference {
+ key = "dump_crash_logs"
+ titleRes = R.string.dump_crash_logs
+ summaryRes = R.string.saves_error_logs
+
+ onClick {
+ CrashLogUtil(context).dumpLogs()
+ }
+ }
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ val pm = context.getSystemService(Context.POWER_SERVICE) as? PowerManager?
+ if (pm != null) preference {
+ key = "disable_batt_opt"
+ titleRes = R.string.disable_battery_optimization
+ summaryRes = R.string.disable_if_issues_with_updating
+
+ onClick {
+ val packageName: String = context.packageName
+ if (!pm.isIgnoringBatteryOptimizations(packageName)) {
+ val intent = Intent().apply {
+ action = Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS
+ data = "package:$packageName".toUri()
+ }
+ startActivity(intent)
+ } else {
+ context.toast(R.string.battery_optimization_disabled)
+ }
+ }
+ }
+ }
preferenceCategory {
titleRes = R.string.data_management
preference {
@@ -158,30 +190,6 @@ class SettingsAdvancedController : SettingsController() {
onClick { LibraryUpdateService.start(context, target = Target.TRACKING) }
}
}
-
- preferenceCategory {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
- val pm = context.getSystemService(Context.POWER_SERVICE) as? PowerManager?
- if (pm != null) preference {
- key = "disable_batt_opt"
- titleRes = R.string.disable_battery_optimization
- summaryRes = R.string.disable_if_issues_with_updating
-
- onClick {
- val packageName: String = context.packageName
- if (!pm.isIgnoringBatteryOptimizations(packageName)) {
- val intent = Intent().apply {
- action = Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS
- data = "package:$packageName".toUri()
- }
- startActivity(intent)
- } else {
- context.toast(R.string.battery_optimization_disabled)
- }
- }
- }
- }
- }
}
class CleanupDownloadsDialogController() : DialogController() {
diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/CrashLogUtil.kt b/app/src/main/java/eu/kanade/tachiyomi/util/CrashLogUtil.kt
new file mode 100644
index 0000000000..c9f33f5221
--- /dev/null
+++ b/app/src/main/java/eu/kanade/tachiyomi/util/CrashLogUtil.kt
@@ -0,0 +1,56 @@
+package eu.kanade.tachiyomi.util
+
+import android.content.Context
+import android.net.Uri
+import eu.kanade.tachiyomi.R
+import eu.kanade.tachiyomi.data.notification.NotificationReceiver
+import eu.kanade.tachiyomi.data.notification.Notifications
+import eu.kanade.tachiyomi.util.storage.getUriCompat
+import eu.kanade.tachiyomi.util.system.createFileInCacheDir
+import eu.kanade.tachiyomi.util.system.notificationBuilder
+import eu.kanade.tachiyomi.util.system.notificationManager
+import eu.kanade.tachiyomi.util.system.toast
+import java.io.IOException
+
+class CrashLogUtil(private val context: Context) {
+
+ private val notificationBuilder = context.notificationBuilder(Notifications.CHANNEL_CRASH_LOGS) {
+ setSmallIcon(R.drawable.ic_tachi)
+ }
+
+ fun dumpLogs() {
+ try {
+ val file = context.createFileInCacheDir("tachiyomi_crash_logs.txt")
+ Runtime.getRuntime().exec("logcat *:E -d -f ${file.absolutePath}")
+
+ showNotification(file.getUriCompat(context))
+ } catch (e: IOException) {
+ context.toast("Failed to get logs")
+ }
+ }
+
+ private fun showNotification(uri: Uri) {
+ context.notificationManager.cancel(Notifications.ID_CRASH_LOGS)
+
+ with(notificationBuilder) {
+ setContentTitle(context.getString(R.string.crash_log_saved))
+
+ // Clear old actions if they exist
+ clearActions()
+
+ addAction(
+ R.drawable.ic_bug_report_24dp,
+ context.getString(R.string.open_log),
+ NotificationReceiver.openErrorLogPendingActivity(context, uri)
+ )
+
+ addAction(
+ R.drawable.ic_share_24dp,
+ context.getString(R.string.share),
+ NotificationReceiver.shareCrashLogPendingBroadcast(context, uri, Notifications.ID_CRASH_LOGS)
+ )
+
+ context.notificationManager.notify(Notifications.ID_CRASH_LOGS, build())
+ }
+ }
+}
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index c8d2866c6c..1816e3c985 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -601,6 +601,10 @@
Clear chapter cache
Data Management
+ Crash logs
+ Dump crash logs
+ Saves error logs to a file for sharing with the developers
+ Crash logs saved
Network
DNS over HTTPS (Cloudflare)
Requires app restart to take effect