1 package kotlinx.coroutines.android
2 
3 import android.os.*
4 import kotlinx.coroutines.*
5 import java.lang.reflect.*
6 import kotlin.coroutines.*
7 
8 internal class AndroidExceptionPreHandler :
9     AbstractCoroutineContextElement(CoroutineExceptionHandler), CoroutineExceptionHandler
10 {
11     @Volatile
12     private var _preHandler: Any? = this // uninitialized marker
13 
14     // Reflectively lookup pre-handler.
preHandlernull15     private fun preHandler(): Method? {
16         val current = _preHandler
17         if (current !== this) return current as Method?
18         val declared = try {
19             Thread::class.java.getDeclaredMethod("getUncaughtExceptionPreHandler").takeIf {
20                 Modifier.isPublic(it.modifiers) && Modifier.isStatic(it.modifiers)
21             }
22         } catch (e: Throwable) {
23             null /* not found */
24         }
25         _preHandler = declared
26         return declared
27     }
28 
handleExceptionnull29     override fun handleException(context: CoroutineContext, exception: Throwable) {
30         /*
31          * Android Oreo introduced private API for a global pre-handler for uncaught exceptions, to ensure that the
32          * exceptions are logged even if the default uncaught exception handler is replaced by the app. The pre-handler
33          * is invoked from the Thread's private dispatchUncaughtException() method, so our manual invocation of the
34          * Thread's uncaught exception handler bypasses the pre-handler in Android Oreo, and uncaught coroutine
35          * exceptions are not logged. This issue was addressed in Android Pie, which added a check in the default
36          * uncaught exception handler to invoke the pre-handler if it was not invoked already (see
37          * https://android-review.googlesource.com/c/platform/frameworks/base/+/654578/). So the issue is present only
38          * in Android Oreo.
39          *
40          * We're fixing this by manually invoking the pre-handler using reflection, if running on an Android Oreo SDK
41          * version (26 and 27).
42          */
43         if (Build.VERSION.SDK_INT in 26..27) {
44             (preHandler()?.invoke(null) as? Thread.UncaughtExceptionHandler)
45                 ?.uncaughtException(Thread.currentThread(), exception)
46         }
47     }
48 }
49