diff --git a/app/src/main/java/com/duckduckgo/app/global/AlertingUncaughtExceptionHandler.kt b/app/src/main/java/com/duckduckgo/app/global/AlertingUncaughtExceptionHandler.kt index f6744f45fc34..d8c3217ce6f1 100644 --- a/app/src/main/java/com/duckduckgo/app/global/AlertingUncaughtExceptionHandler.kt +++ b/app/src/main/java/com/duckduckgo/app/global/AlertingUncaughtExceptionHandler.kt @@ -16,6 +16,7 @@ package com.duckduckgo.app.global +import com.duckduckgo.app.browser.BuildConfig import com.duckduckgo.app.global.exception.UncaughtExceptionRepository import com.duckduckgo.app.global.exception.UncaughtExceptionSource import com.duckduckgo.app.statistics.store.OfflinePixelCountDataStore @@ -23,6 +24,7 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.NonCancellable import kotlinx.coroutines.launch +import java.io.InterruptedIOException class AlertingUncaughtExceptionHandler( private val originalHandler: Thread.UncaughtExceptionHandler, @@ -30,14 +32,44 @@ class AlertingUncaughtExceptionHandler( private val uncaughtExceptionRepository: UncaughtExceptionRepository ) : Thread.UncaughtExceptionHandler { - override fun uncaughtException(t: Thread?, originalException: Throwable?) { + override fun uncaughtException(thread: Thread?, originalException: Throwable?) { + if (shouldRecordExceptionAndCrashApp(originalException)) { + recordExceptionAndAllowCrash(thread, originalException) + return + } + + if (shouldCrashApp()) { + originalHandler.uncaughtException(thread, originalException) + } + + } + + /** + * Some exceptions happen due to lifecycle issues, such as our sync service being forced to stop. + * In such cases, we don't need to alert about the exception as they are exceptions essentially signalling that work was interrupted. + * Examples of this would be if the internet was lost during the sync, + * or when two or more sync operations are scheduled to run at the same time; one would run and the rest would be interrupted. + */ + private fun shouldRecordExceptionAndCrashApp(exception: Throwable?): Boolean { + return when (exception) { + is InterruptedException, is InterruptedIOException -> false + else -> true + } + } + + /** + * If the exception is one we don't report on, we still want to see a crash when we're in DEBUG builds for safety we aren't ignoring important issues + */ + private fun shouldCrashApp(): Boolean = BuildConfig.DEBUG + + private fun recordExceptionAndAllowCrash(thread: Thread?, originalException: Throwable?) { GlobalScope.launch(Dispatchers.IO + NonCancellable) { uncaughtExceptionRepository.recordUncaughtException(originalException, UncaughtExceptionSource.GLOBAL) offlinePixelCountDataStore.applicationCrashCount += 1 // wait until the exception has been fully processed before propagating exception - originalHandler.uncaughtException(t, originalException) + originalHandler.uncaughtException(thread, originalException) } } } \ No newline at end of file