1616
1717package com.duckduckgo.app.global
1818
19+ import com.duckduckgo.app.browser.BuildConfig
1920import com.duckduckgo.app.global.exception.UncaughtExceptionRepository
2021import com.duckduckgo.app.global.exception.UncaughtExceptionSource
2122import com.duckduckgo.app.statistics.store.OfflinePixelCountDataStore
2223import kotlinx.coroutines.Dispatchers
2324import kotlinx.coroutines.GlobalScope
2425import kotlinx.coroutines.NonCancellable
2526import kotlinx.coroutines.launch
27+ import java.io.InterruptedIOException
2628
2729class AlertingUncaughtExceptionHandler (
2830 private val originalHandler : Thread .UncaughtExceptionHandler ,
2931 private val offlinePixelCountDataStore : OfflinePixelCountDataStore ,
3032 private val uncaughtExceptionRepository : UncaughtExceptionRepository
3133) : Thread.UncaughtExceptionHandler {
3234
33- override fun uncaughtException (t : Thread ? , originalException : Throwable ? ) {
35+ override fun uncaughtException (thread : Thread ? , originalException : Throwable ? ) {
3436
37+ if (shouldRecordExceptionAndCrashApp(originalException)) {
38+ recordExceptionAndAllowCrash(thread, originalException)
39+ return
40+ }
41+
42+ if (shouldCrashApp()) {
43+ originalHandler.uncaughtException(thread, originalException)
44+ }
45+
46+ }
47+
48+ /* *
49+ * Some exceptions happen due to lifecycle issues, such as our sync service being forced to stop.
50+ * In such cases, we don't need to alert about the exception as they are exceptions essentially signalling that work was interrupted.
51+ * Examples of this would be if the internet was lost during the sync,
52+ * or when two or more sync operations are scheduled to run at the same time; one would run and the rest would be interrupted.
53+ */
54+ private fun shouldRecordExceptionAndCrashApp (exception : Throwable ? ): Boolean {
55+ return when (exception) {
56+ is InterruptedException , is InterruptedIOException -> false
57+ else -> true
58+ }
59+ }
60+
61+ /* *
62+ * 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
63+ */
64+ private fun shouldCrashApp (): Boolean = BuildConfig .DEBUG
65+
66+ private fun recordExceptionAndAllowCrash (thread : Thread ? , originalException : Throwable ? ) {
3567 GlobalScope .launch(Dispatchers .IO + NonCancellable ) {
3668 uncaughtExceptionRepository.recordUncaughtException(originalException, UncaughtExceptionSource .GLOBAL )
3769 offlinePixelCountDataStore.applicationCrashCount + = 1
3870
3971 // wait until the exception has been fully processed before propagating exception
40- originalHandler.uncaughtException(t , originalException)
72+ originalHandler.uncaughtException(thread , originalException)
4173 }
4274 }
4375}
0 commit comments