130 lines
4.5 KiB
Kotlin
130 lines
4.5 KiB
Kotlin
@file:JvmName("FirebaseCrashReporterKt")
|
|
|
|
package co.electriccoin.zcash.crash.android.internal.firebase
|
|
|
|
import android.content.Context
|
|
import androidx.annotation.AnyThread
|
|
import co.electriccoin.zcash.crash.android.R
|
|
import co.electriccoin.zcash.crash.android.internal.CrashReporter
|
|
import co.electriccoin.zcash.spackle.EmulatorWtfUtil
|
|
import co.electriccoin.zcash.spackle.FirebaseTestLabUtil
|
|
import co.electriccoin.zcash.spackle.SuspendingLazy
|
|
import co.electriccoin.zcash.spackle.Twig
|
|
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
|
import com.google.firebase.installations.FirebaseInstallations
|
|
import kotlinx.coroutines.CoroutineScope
|
|
import kotlinx.coroutines.Deferred
|
|
import kotlinx.coroutines.Dispatchers
|
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
|
import kotlinx.coroutines.SupervisorJob
|
|
import kotlinx.coroutines.async
|
|
|
|
/**
|
|
* Registers an exception handler with Firebase Crashlytics.
|
|
*/
|
|
internal class FirebaseCrashReporter(
|
|
context: Context
|
|
) : CrashReporter {
|
|
|
|
@OptIn(kotlinx.coroutines.DelicateCoroutinesApi::class)
|
|
private val analyticsScope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
|
|
|
|
private val initFirebaseJob: Deferred<CrashReporter?> = analyticsScope.async {
|
|
FirebaseCrashReporterImpl.getInstance(context)
|
|
}
|
|
|
|
@AnyThread
|
|
override fun reportCaughtException(exception: Throwable) {
|
|
initFirebaseJob.invokeOnCompletionWithResult {
|
|
it?.reportCaughtException(exception)
|
|
}
|
|
}
|
|
|
|
override fun enable() {
|
|
initFirebaseJob.invokeOnCompletionWithResult {
|
|
it?.enable()
|
|
}
|
|
}
|
|
|
|
override fun disableAndDelete() {
|
|
initFirebaseJob.invokeOnCompletionWithResult {
|
|
it?.disableAndDelete()
|
|
}
|
|
}
|
|
}
|
|
|
|
@OptIn(ExperimentalCoroutinesApi::class)
|
|
private fun <T> Deferred<T>.invokeOnCompletionWithResult(handler: (T) -> Unit) {
|
|
invokeOnCompletion {
|
|
handler(this.getCompleted())
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Registers an exception handler with Firebase Crashlytics.
|
|
*/
|
|
private class FirebaseCrashReporterImpl(
|
|
private val firebaseCrashlytics: FirebaseCrashlytics,
|
|
private val firebaseInstallations: FirebaseInstallations
|
|
) : CrashReporter {
|
|
|
|
@AnyThread
|
|
override fun reportCaughtException(exception: Throwable) {
|
|
firebaseCrashlytics.recordException(exception)
|
|
}
|
|
|
|
override fun enable() {
|
|
firebaseCrashlytics.setCrashlyticsCollectionEnabled(true)
|
|
}
|
|
|
|
override fun disableAndDelete() {
|
|
firebaseCrashlytics.setCrashlyticsCollectionEnabled(false)
|
|
firebaseCrashlytics.deleteUnsentReports()
|
|
firebaseInstallations.delete()
|
|
}
|
|
|
|
companion object {
|
|
/*
|
|
* Note there is a tradeoff with the suspending implementation. In order to avoid disk IO
|
|
* on the main thread, there is a brief timing gap during application startup where very
|
|
* early crashes may be missed. This is a tradeoff we are willing to make in order to avoid
|
|
* ANRs.
|
|
*/
|
|
private val lazyWithArgument = SuspendingLazy<Context, CrashReporter?> {
|
|
if (it.resources.getBoolean(R.bool.co_electriccoin_zcash_crash_is_firebase_enabled)) {
|
|
|
|
// Workaround for disk IO on main thread in Firebase initialization
|
|
val firebaseApp = FirebaseAppCache.getFirebaseApp(it)
|
|
if (firebaseApp == null) {
|
|
Twig.warn { "Unable to initialize Crashlytics. FirebaseApp is null" }
|
|
return@SuspendingLazy null
|
|
}
|
|
|
|
val firebaseInstallations = FirebaseInstallations.getInstance(firebaseApp)
|
|
val firebaseCrashlytics = FirebaseCrashlytics.getInstance().apply {
|
|
setCustomKey(
|
|
CrashlyticsUserProperties.IS_TEST,
|
|
EmulatorWtfUtil.isEmulatorWtf(it) || FirebaseTestLabUtil.isFirebaseTestLab(it)
|
|
)
|
|
}
|
|
|
|
FirebaseCrashReporterImpl(firebaseCrashlytics, firebaseInstallations)
|
|
} else {
|
|
Twig.warn { "Unable to initialize Crashlytics. Configure API keys in the app module" }
|
|
null
|
|
}
|
|
}
|
|
|
|
suspend fun getInstance(context: Context): CrashReporter? {
|
|
return lazyWithArgument.getInstance(context)
|
|
}
|
|
}
|
|
}
|
|
|
|
internal object CrashlyticsUserProperties {
|
|
/**
|
|
* Flags a crash as occurring in a test environment. Set automatically to detect Firebase Test Lab and emulator.wtf
|
|
*/
|
|
const val IS_TEST = "is_test" // $NON-NLS
|
|
}
|