diff --git a/app/src/androidTest/java/com/nononsenseapps/feeder/util/BugReportKTest.kt b/app/src/androidTest/java/com/nononsenseapps/feeder/util/BugReportKTest.kt index 7c2c2d312..956b5639e 100644 --- a/app/src/androidTest/java/com/nononsenseapps/feeder/util/BugReportKTest.kt +++ b/app/src/androidTest/java/com/nononsenseapps/feeder/util/BugReportKTest.kt @@ -4,72 +4,50 @@ import android.content.Intent.ACTION_SENDTO import android.content.Intent.ACTION_VIEW import android.content.Intent.EXTRA_EMAIL import android.content.Intent.EXTRA_SUBJECT +import android.content.Intent.EXTRA_TEXT import android.net.Uri -import android.os.Build import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.MediumTest -import com.nononsenseapps.feeder.BuildConfig -import org.junit.Assert.assertEquals +import kotlin.test.assertEquals +import kotlin.test.assertTrue import org.junit.Test import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) @MediumTest class BugReportKTest { - @Test - fun bodyContainsAndroidInformation() { - assertEquals( - """ - ${BuildConfig.APPLICATION_ID} (flavor ${BuildConfig.BUILD_TYPE.ifBlank { "None" }}) - version ${BuildConfig.VERSION_NAME} (code ${BuildConfig.VERSION_CODE}) - on Android ${Build.VERSION.RELEASE} (SDK-${Build.VERSION.SDK_INT}) - on a Tablet? No - - Describe your issue and how to reproduce it below: - """.trimIndent(), - emailBody(false), - ) - } @Test - fun bodyContainsAndroidInformationAsTablet() { - assertEquals( - """ - ${BuildConfig.APPLICATION_ID} (flavor ${BuildConfig.BUILD_TYPE.ifBlank { "None" }}) - version ${BuildConfig.VERSION_NAME} (code ${BuildConfig.VERSION_CODE}) - on Android ${Build.VERSION.RELEASE} (SDK-${Build.VERSION.SDK_INT}) - on a Tablet? Yes - - Describe your issue and how to reproduce it below: - """.trimIndent(), - emailBody(true), - ) - } + fun bugIntentIsCorrect() { + val intent = emailBugReportIntent() - @Test - fun subjectIsSensible() { - assertEquals( - "Bug report for Feeder", - emailSubject(), - ) - } + assertEquals(ACTION_SENDTO, intent.action) + assertEquals(Uri.parse("mailto:feeder@nononsenseapps.com"), intent.data) + assertEquals("Bug report for Feeder", intent.getStringExtra(EXTRA_SUBJECT)) + assertEquals("feeder@nononsenseapps.com", intent.getStringExtra(EXTRA_EMAIL)) - @Test - fun emailAddressIsCorrect() { - assertEquals( - "feeder@nononsenseapps.com", - emailReportAddress(), - ) + assertTrue(intent.getStringExtra(EXTRA_TEXT)) { + "Application: " in intent.getStringExtra(EXTRA_TEXT)!! + } } @Test - fun emailIntentIsCorrect() { - val intent = emailBugReportIntent() - - assertEquals(ACTION_SENDTO, intent.action) - assertEquals(Uri.parse("mailto:${emailReportAddress()}"), intent.data) - assertEquals(emailSubject(), intent.getStringExtra(EXTRA_SUBJECT)) - assertEquals(emailReportAddress(), intent.getStringExtra(EXTRA_EMAIL)) + fun crashIntentIsCorrect() { + try { + @Suppress("DIVISION_BY_ZERO") + 1 / 0 + } catch (e: Exception) { + val intent = emailCrashReportIntent(e) + + assertEquals(ACTION_SENDTO, intent.action) + assertEquals(Uri.parse("mailto:crashes@nononsenseapps.com"), intent.data) + assertEquals("Crash report for Feeder", intent.getStringExtra(EXTRA_SUBJECT)) + assertEquals("crashes@nononsenseapps.com", intent.getStringExtra(EXTRA_EMAIL)) + + assertTrue(intent.getStringExtra(EXTRA_TEXT)) { + "java.lang.ArithmeticException: divide by zero" in intent.getStringExtra(EXTRA_TEXT)!! + } + } } @Test diff --git a/app/src/main/java/com/nononsenseapps/feeder/ui/ActivityExceptionHandler.kt b/app/src/main/java/com/nononsenseapps/feeder/ui/ActivityExceptionHandler.kt new file mode 100644 index 000000000..6168fe7d2 --- /dev/null +++ b/app/src/main/java/com/nononsenseapps/feeder/ui/ActivityExceptionHandler.kt @@ -0,0 +1,28 @@ +package com.nononsenseapps.feeder.ui + +import android.app.Activity +import android.content.Intent +import android.util.Log +import com.nononsenseapps.feeder.util.emailCrashReportIntent +import kotlin.system.exitProcess + +fun Activity.installExceptionHandler() { + val mainHandler = Thread.getDefaultUncaughtExceptionHandler() + Thread.setDefaultUncaughtExceptionHandler { thread, throwable -> + try { + Log.w("FEEDER_PANIC", "Trying to report unhandled exception", throwable) + val intent = emailCrashReportIntent(throwable) + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + startActivity(intent) + } catch (e: Exception) { + e.printStackTrace() + } finally { + // Necessary to handle the error or the process will freeze + if (mainHandler != null) { + mainHandler.uncaughtException(thread, throwable) + } else { + exitProcess(1) + } + } + } +} diff --git a/app/src/main/java/com/nononsenseapps/feeder/ui/AddFeedFromShareActivity.kt b/app/src/main/java/com/nononsenseapps/feeder/ui/AddFeedFromShareActivity.kt index f564f5177..4e4ca4910 100644 --- a/app/src/main/java/com/nononsenseapps/feeder/ui/AddFeedFromShareActivity.kt +++ b/app/src/main/java/com/nononsenseapps/feeder/ui/AddFeedFromShareActivity.kt @@ -26,6 +26,8 @@ class AddFeedFromShareActivity : DIAwareComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + installExceptionHandler() + WindowCompat.setDecorFitsSystemWindows(window, false) val initialFeedUrl = diff --git a/app/src/main/java/com/nononsenseapps/feeder/ui/ImportOMPLFileActivity.kt b/app/src/main/java/com/nononsenseapps/feeder/ui/ImportOMPLFileActivity.kt index 6d250f3d6..77b0852c9 100644 --- a/app/src/main/java/com/nononsenseapps/feeder/ui/ImportOMPLFileActivity.kt +++ b/app/src/main/java/com/nononsenseapps/feeder/ui/ImportOMPLFileActivity.kt @@ -26,6 +26,8 @@ class ImportOMPLFileActivity : DIAwareComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + installExceptionHandler() + WindowCompat.setDecorFitsSystemWindows(window, false) val uri = intent.data diff --git a/app/src/main/java/com/nononsenseapps/feeder/ui/MainActivity.kt b/app/src/main/java/com/nononsenseapps/feeder/ui/MainActivity.kt index 73d0aea04..0d51c3dc3 100644 --- a/app/src/main/java/com/nononsenseapps/feeder/ui/MainActivity.kt +++ b/app/src/main/java/com/nononsenseapps/feeder/ui/MainActivity.kt @@ -62,6 +62,8 @@ class MainActivity : DIAwareComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + installExceptionHandler() + mainActivityViewModel.ensurePeriodicSyncConfigured() WindowCompat.setDecorFitsSystemWindows(window, false) diff --git a/app/src/main/java/com/nononsenseapps/feeder/ui/ManageSettingsActivity.kt b/app/src/main/java/com/nononsenseapps/feeder/ui/ManageSettingsActivity.kt index 6464c7a78..cdf309236 100644 --- a/app/src/main/java/com/nononsenseapps/feeder/ui/ManageSettingsActivity.kt +++ b/app/src/main/java/com/nononsenseapps/feeder/ui/ManageSettingsActivity.kt @@ -18,6 +18,8 @@ class ManageSettingsActivity : DIAwareComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + installExceptionHandler() + WindowCompat.setDecorFitsSystemWindows(window, false) setContent { diff --git a/app/src/main/java/com/nononsenseapps/feeder/ui/OpenLinkInDefaultActivity.kt b/app/src/main/java/com/nononsenseapps/feeder/ui/OpenLinkInDefaultActivity.kt index a5cce7deb..b54f21063 100644 --- a/app/src/main/java/com/nononsenseapps/feeder/ui/OpenLinkInDefaultActivity.kt +++ b/app/src/main/java/com/nononsenseapps/feeder/ui/OpenLinkInDefaultActivity.kt @@ -28,6 +28,8 @@ class OpenLinkInDefaultActivity : DIAwareComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + installExceptionHandler() + intent?.let { intent -> val uri = intent.data diff --git a/app/src/main/java/com/nononsenseapps/feeder/util/BugReport.kt b/app/src/main/java/com/nononsenseapps/feeder/util/BugReport.kt index c6ea44b16..5febf8652 100644 --- a/app/src/main/java/com/nononsenseapps/feeder/util/BugReport.kt +++ b/app/src/main/java/com/nononsenseapps/feeder/util/BugReport.kt @@ -9,30 +9,48 @@ import android.net.Uri import android.os.Build import com.nononsenseapps.feeder.BuildConfig -internal fun emailSubject(): String = "Bug report for Feeder" +private fun deviceInfoBlock(): String = """ + Application: ${BuildConfig.APPLICATION_ID} (flavor ${BuildConfig.BUILD_TYPE.ifBlank { "None" }}) + Version: ${BuildConfig.VERSION_NAME} (code ${BuildConfig.VERSION_CODE}) + Android: ${Build.VERSION.RELEASE} (SDK ${Build.VERSION.SDK_INT}) + Device: ${Build.MANUFACTURER} ${Build.MODEL} +""".trimIndent() -internal fun emailBody(isTablet: Boolean): String = +private fun bugBody(): String = """ - ${BuildConfig.APPLICATION_ID} (flavor ${BuildConfig.BUILD_TYPE.ifBlank { "None" }}) - version ${BuildConfig.VERSION_NAME} (code ${BuildConfig.VERSION_CODE}) - on Android ${Build.VERSION.RELEASE} (SDK-${Build.VERSION.SDK_INT}) - on a Tablet? ${ - if (isTablet) { - "Yes" - } else { - "No" - } - } - - Describe your issue and how to reproduce it below: + ${deviceInfoBlock()} + + Hello. + + I'd like to report an issue: """.trimIndent() -internal fun emailReportAddress(): String = "feeder@nononsenseapps.com" +private const val emailReportAddress: String = "feeder@nononsenseapps.com" -fun emailBugReportIntent(): Intent = Intent(ACTION_SENDTO).also { - it.putExtra(EXTRA_SUBJECT, emailSubject()) - it.putExtra(EXTRA_EMAIL, emailReportAddress()) - it.data = Uri.parse("mailto:${emailReportAddress()}") +fun emailBugReportIntent(): Intent = Intent(ACTION_SENDTO).apply { + data = Uri.parse("mailto:$emailReportAddress") + putExtra(EXTRA_SUBJECT, "Bug report for Feeder") + putExtra(EXTRA_EMAIL, emailReportAddress) + putExtra(Intent.EXTRA_TEXT, bugBody()) +} + +private const val crashReportAddress: String = "crashes@nononsenseapps.com" + +private fun crashBody(throwable: Throwable): String = + """ + ${deviceInfoBlock()} + + Unhandled exception: + + ${throwable.stackTraceToString()} + """.trimIndent() + +fun emailCrashReportIntent(throwable: Throwable): Intent = Intent(ACTION_SENDTO).apply { + data = Uri.parse("mailto:$crashReportAddress") + putExtra(EXTRA_SUBJECT, "Crash report for Feeder") + putExtra(EXTRA_EMAIL, crashReportAddress) + putExtra(Intent.EXTRA_TEXT, crashBody(throwable)) + addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) } fun openGitlabIssues(): Intent = Intent(ACTION_VIEW).also {