diff --git a/app/src/main/java/com/lagradost/cloudstream3/AcraApplication.kt b/app/src/main/java/com/lagradost/cloudstream3/AcraApplication.kt index 49044e06..85f031b3 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/AcraApplication.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/AcraApplication.kt @@ -4,11 +4,13 @@ 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 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.utils.AppUtils.openBrowser import com.lagradost.cloudstream3.utils.Coroutines.runOnMainThread import com.lagradost.cloudstream3.utils.DataStore.getKey @@ -17,6 +19,7 @@ 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 @@ -24,8 +27,14 @@ 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 @@ -65,7 +74,33 @@ class CustomSenderFactory : ReportSenderFactory { } } +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() + 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 diff --git a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt index 3cf5de18..6c9fadd8 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt @@ -10,7 +10,9 @@ import android.view.KeyEvent import android.view.Menu import android.view.MenuItem import android.view.WindowManager +import android.widget.Toast import androidx.annotation.IdRes +import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatActivity import androidx.core.view.isVisible import androidx.navigation.NavController @@ -88,6 +90,7 @@ import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import java.io.File import java.net.URI +import java.nio.charset.Charset import kotlin.reflect.KClass @@ -455,6 +458,13 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener { override fun onCreate(savedInstanceState: Bundle?) { app.initClient(this) val settingsManager = PreferenceManager.getDefaultSharedPreferences(this) + + val errorFile = filesDir.resolve("last_error") + var lastError: String? = null + if (errorFile.exists() && errorFile.isFile) { + lastError = errorFile.readText(Charset.defaultCharset()) + errorFile.delete() + } val settingsForProvider = SettingsJson() settingsForProvider.enableAdult = settingsManager.getBoolean(getString(R.string.enable_nsfw_on_providers_key), false) @@ -482,26 +492,44 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener { changeStatusBarState(isEmulatorSettings()) - ioSafe { - getKey(USER_SELECTED_HOMEPAGE_API)?.let { homeApi -> - mainPluginsLoadedEvent.invoke(loadSinglePlugin(this@MainActivity, homeApi)) - } ?: run { - mainPluginsLoadedEvent.invoke(false) - } - + if (lastError == null) { ioSafe { - if (settingsManager.getBoolean(getString(R.string.auto_update_plugins_key), true)) { - PluginManager.updateAllOnlinePluginsAndLoadThem(this@MainActivity) - } else { - PluginManager.loadAllOnlinePlugins(this@MainActivity) + getKey(USER_SELECTED_HOMEPAGE_API)?.let { homeApi -> + mainPluginsLoadedEvent.invoke(loadSinglePlugin(this@MainActivity, homeApi)) + } ?: run { + mainPluginsLoadedEvent.invoke(false) + } + + ioSafe { + if (settingsManager.getBoolean(getString(R.string.auto_update_plugins_key), true)) { + PluginManager.updateAllOnlinePluginsAndLoadThem(this@MainActivity) + } else { + PluginManager.loadAllOnlinePlugins(this@MainActivity) + } + } + + ioSafe { + PluginManager.loadAllLocalPlugins(this@MainActivity) } } + } else { + val builder: AlertDialog.Builder = AlertDialog.Builder(this) + builder.setTitle(R.string.safe_mode_title) + builder.setMessage(R.string.safe_mode_description) + builder.apply { + setPositiveButton(R.string.safe_mode_crash_info) { _, _ -> + val tbBuilder: AlertDialog.Builder = AlertDialog.Builder(context) + tbBuilder.setTitle(R.string.safe_mode_title) + tbBuilder.setMessage(lastError) + tbBuilder.show() + } - ioSafe { - PluginManager.loadAllLocalPlugins(this@MainActivity) + setNegativeButton("Ok") { _, _ -> } } + builder.show() } + // ioSafe { // val plugins = // RepositoryParser.getRepoPlugins("https://raw.githubusercontent.com/recloudstream/TestPlugin/master/repo.json") diff --git a/app/src/main/java/com/lagradost/cloudstream3/plugins/PluginManager.kt b/app/src/main/java/com/lagradost/cloudstream3/plugins/PluginManager.kt index 9f283b08..b1fc69e7 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/plugins/PluginManager.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/plugins/PluginManager.kt @@ -127,6 +127,8 @@ object PluginManager { private val LOCAL_PLUGINS_PATH = Environment.getExternalStorageDirectory().absolutePath + "/Cloudstream3/plugins" + public var currentlyLoading: String? = null + // Maps filepath to plugin val plugins: MutableMap = LinkedHashMap() @@ -283,6 +285,7 @@ object PluginManager { private suspend fun loadPlugin(activity: Activity, file: File, data: PluginData): Boolean { val fileName = file.nameWithoutExtension val filePath = file.absolutePath + currentlyLoading = fileName Log.i(TAG, "Loading plugin: $data") return try { @@ -341,6 +344,7 @@ object PluginManager { } pluginInstance.load(activity) Log.i(TAG, "Loaded plugin ${data.internalName} successfully") + currentlyLoading = null true } catch (e: Throwable) { Log.e(TAG, "Failed to load $file: ${Log.getStackTraceString(e)}") @@ -349,6 +353,7 @@ object PluginManager { activity.getString(R.string.plugin_load_fail).format(fileName), Toast.LENGTH_LONG ) + currentlyLoading = null false } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index d0c1d83a..9b09f519 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -613,4 +613,8 @@ Audio tracks Video tracks Apply on Restart + + Safe Mode enabled + An unrecoverable crash occurred and we\'ve automatically disabled all extensions, so you can find and remove the extension which is causing trouble. + View crash info