@@ -0,0 +1,74 @@
1+package soy.iko.opencode
3+import android.content.Context
4+import android.util.Log
6+import java.io.PrintWriter
7+import java.io.StringWriter
8+import java.text.SimpleDateFormat
10+import java.util.Locale
13+ * Lightweight crash reporter that installs as the global uncaught-exception
14+ * handler. Crashes are logged to logcat and persisted to internal storage so
15+ * they survive process death and can be inspected or exported later.
19+ private const val TAG = "opencode-crash"
20+ private const val CRASH_DIR = "crashes"
21+ private const val MAX_REPORTS = 10
23+ private lateinit var appContext: Context
24+ private var previousHandler: Thread.UncaughtExceptionHandler? = null
26+ fun init(context: Context) {
27+ appContext = context.applicationContext
28+ previousHandler = Thread.getDefaultUncaughtExceptionHandler()
29+ Thread.setDefaultUncaughtExceptionHandler { thread, throwable ->
30+ report(throwable, thread.name)
31+ previousHandler?.uncaughtException(thread, throwable)
35+ fun report(throwable: Throwable, source: String = Thread.currentThread().name) {
36+ val stackTrace = stackTraceString(throwable)
37+ Log.e(TAG, "Uncaught exception in [$source]\n$stackTrace", throwable)
38+ persistReport(source, stackTrace)
41+ fun getReports(): List<File> =
42+ crashDir().listFiles()?.sortedByDescending { it.lastModified() } ?: emptyList()
45+ crashDir().listFiles()?.forEach { it.delete() }
48+ private fun persistReport(source: String, stackTrace: String) {
50+ val timestamp = SimpleDateFormat("yyyy-MM-dd_HH-mm-ss", Locale.US).format(Date())
51+ val file = File(crashDir(), "crash_${timestamp}.txt")
52+ file.writeText("Time: $timestamp\nThread: $source\n\n$stackTrace")
54+ } catch (e: Exception) {
55+ Log.e(TAG, "Failed to persist crash report", e)
59+ private fun pruneOldReports() {
60+ val reports = getReports()
61+ if (reports.size > MAX_REPORTS) {
62+ reports.drop(MAX_REPORTS).forEach { it.delete() }
66+ private fun crashDir(): File =
67+ File(appContext.filesDir, CRASH_DIR).apply { mkdirs() }
69+ private fun stackTraceString(throwable: Throwable): String {
70+ val sw = StringWriter()
71+ throwable.printStackTrace(PrintWriter(sw))