package com.lagradost.cloudstream3 import android.app.Activity import android.app.Application import android.content.Context import android.content.ContextWrapper import android.content.Intent import android.widget.Toast import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentActivity import com.google.auto.service.AutoService import com.lagradost.cloudstream3.mvvm.normalSafeApiCall import com.lagradost.cloudstream3.mvvm.suspendSafeApiCall import com.lagradost.cloudstream3.plugins.PluginManager import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings import com.lagradost.cloudstream3.utils.AppUtils.openBrowser import com.lagradost.cloudstream3.utils.Coroutines.runOnMainThread import com.lagradost.cloudstream3.utils.DataStore.getKey import com.lagradost.cloudstream3.utils.DataStore.getKeys import com.lagradost.cloudstream3.utils.DataStore.removeKey import com.lagradost.cloudstream3.utils.DataStore.removeKeys import com.lagradost.cloudstream3.utils.DataStore.setKey import kotlinx.coroutines.runBlocking import org.acra.ACRA import org.acra.ReportField import org.acra.config.CoreConfiguration import org.acra.data.CrashReportData import org.acra.data.StringFormat import org.acra.ktx.initAcra import org.acra.sender.ReportSender import org.acra.sender.ReportSenderFactory import java.io.File import java.io.FileNotFoundException import java.io.PrintStream import java.lang.Exception import java.lang.ref.WeakReference import kotlin.concurrent.thread import kotlin.system.exitProcess class CustomReportSender : ReportSender { // Sends all your crashes to google forms override fun send(context: Context, errorContent: CrashReportData) { println("Sending report") val url = "https://docs.google.com/forms/d/e/1FAIpQLSdOlbgCx7NeaxjvEGyEQlqdh2nCvwjm2vwpP1VwW7REj9Ri3Q/formResponse" val data = mapOf( "entry.753293084" to errorContent.toJSON() ) thread { // to not run it on main thread runBlocking { suspendSafeApiCall { app.post(url, data = data) //println("Report response: $post") } } } runOnMainThread { // to run it on main looper normalSafeApiCall { Toast.makeText(context, R.string.acra_report_toast, Toast.LENGTH_SHORT).show() } } } } @AutoService(ReportSenderFactory::class) class CustomSenderFactory : ReportSenderFactory { override fun create(context: Context, config: CoreConfiguration): ReportSender { return CustomReportSender() } override fun enabled(config: CoreConfiguration): Boolean { return true } } class ExceptionHandler(val errorFile: File, val onError: (() -> Unit)) : Thread.UncaughtExceptionHandler { override fun uncaughtException(thread: Thread, error: Throwable) { ACRA.errorReporter.handleException(error) try { PrintStream(errorFile).use { ps -> ps.println(String.format("Currently loading extension: ${PluginManager.currentlyLoading ?: "none"}")) ps.println( String.format( "Fatal exception on thread %s (%d)", thread.name, thread.id ) ) error.printStackTrace(ps) } } catch (ignored: FileNotFoundException) { } try { onError.invoke() } catch (ignored: Exception) { } exitProcess(1) } } class AcraApplication : Application() { override fun onCreate() { super.onCreate() NativeCrashHandler.initCrashHandler() Thread.setDefaultUncaughtExceptionHandler(ExceptionHandler(filesDir.resolve("last_error")) { val intent = context!!.packageManager.getLaunchIntentForPackage(context!!.packageName) startActivity(Intent.makeRestartActivityTask(intent!!.component)) }) } override fun attachBaseContext(base: Context?) { super.attachBaseContext(base) context = base initAcra { //core configuration: buildConfigClass = BuildConfig::class.java reportFormat = StringFormat.JSON reportContent = listOf( ReportField.BUILD_CONFIG, ReportField.USER_CRASH_DATE, ReportField.ANDROID_VERSION, ReportField.PHONE_MODEL, ReportField.STACK_TRACE, ) // removed this due to bug when starting the app, moved it to when it actually crashes //each plugin you chose above can be configured in a block like this: /*toast { text = getString(R.string.acra_report_toast) //opening this block automatically enables the plugin. }*/ } } companion object { /** Use to get activity from Context */ tailrec fun Context.getActivity(): Activity? = this as? Activity ?: (this as? ContextWrapper)?.baseContext?.getActivity() private var _context: WeakReference? = null var context get() = _context?.get() private set(value) { _context = WeakReference(value) } fun getKeyClass(path: String, valueType: Class): T? { return context?.getKey(path, valueType) } fun setKeyClass(path: String, value: T) { context?.setKey(path, value) } fun removeKeys(folder: String): Int? { return context?.removeKeys(folder) } fun setKey(path: String, value: T) { context?.setKey(path, value) } fun setKey(folder: String, path: String, value: T) { context?.setKey(folder, path, value) } inline fun getKey(path: String, defVal: T?): T? { return context?.getKey(path, defVal) } inline fun getKey(path: String): T? { return context?.getKey(path) } inline fun getKey(folder: String, path: String): T? { return context?.getKey(folder, path) } inline fun getKey(folder: String, path: String, defVal: T?): T? { return context?.getKey(folder, path, defVal) } fun getKeys(folder: String): List? { return context?.getKeys(folder) } fun removeKey(folder: String, path: String) { context?.removeKey(folder, path) } fun removeKey(path: String) { context?.removeKey(path) } /** * If fallbackWebview is true and a fragment is supplied then it will open a webview with the url if the browser fails. * */ fun openBrowser(url: String, fallbackWebview: Boolean = false, fragment: Fragment? = null) { context?.openBrowser(url, fallbackWebview, fragment) } /** Will fallback to webview if in TV layout */ fun openBrowser(url: String, activity: FragmentActivity?) { openBrowser( url, isTvSettings(), activity?.supportFragmentManager?.fragments?.lastOrNull() ) } } }