The same pass the server had, on the Kotlin side: comments restating what the code says are gone, and the ones recording a measurement, a constraint or an incident are kept but cut to a few lines each. 6540 comment lines to 5674, and 920 lines off the app. Two doc comments had drifted onto the item above the one they describe -- `contextAfter`'s onto `sessionWorking` in Events.kt, and `UsageMonitor`'s equivalent on the server was fixed in the previous commit. Each is back on its own item, which is the only non-comment line this diff moves. The comments are reflowed to the column limit at their own indentation: several were written wide, and ktfmt re-wrapped them into lines holding a single orphan word. `/tmp` script, not kept -- ktfmt is idempotent over the result, which is the check. Left alone deliberately: this codebase's remaining comment density is high because the comments carry things the code cannot say -- what a null means, what a number was measured against, which bug a guard exists for. Of the 238 one-line doc comments in the app, five were pure restatement of the name and were removed; the rest each say something the signature does not. ktfmtFormat, compileDebugKotlin, lintDebug and testDebugUnitTest pass; cargo test (127), clippy --all-targets and fmt still clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
64 lines
2.6 KiB
Kotlin
64 lines
2.6 KiB
Kotlin
package com.example.aiapp
|
|
|
|
import android.content.Context
|
|
import java.io.File
|
|
import java.io.PrintWriter
|
|
import java.io.StringWriter
|
|
import java.text.SimpleDateFormat
|
|
import java.util.Date
|
|
import java.util.Locale
|
|
|
|
/**
|
|
* The last crash, kept so the debug button can hand it over.
|
|
*
|
|
* The alternative is asking somebody to reproduce a crash with the phone plugged into a computer
|
|
* and `logcat` running, which is the one thing nobody has set up at the moment it happens. This
|
|
* costs one file write on a process that is already dying, and it turns "it crashes when I open
|
|
* that chat" into the frame it crashed in.
|
|
*
|
|
* Kept until it is read rather than cleared on the next launch: the app restarts before anybody can
|
|
* ask about it, so a log that lives for one session is a log that is never read.
|
|
*/
|
|
private const val CRASH_FILE = "last-crash.txt"
|
|
|
|
/**
|
|
* How much of a stack is kept.
|
|
*
|
|
* This is pasted into a conversation, so it has a budget like any other output written for a
|
|
* reader. The top of a stack is what identifies a crash and the bottom is framework plumbing.
|
|
*/
|
|
private const val CRASH_LIMIT = 4000
|
|
|
|
/**
|
|
* Records uncaught exceptions, then lets the platform do what it was going to do.
|
|
*
|
|
* Chained rather than replacing: the default handler is what shows the "app has stopped" dialog and
|
|
* ends the process, and an app that swallows that instead sits there in an unknown state.
|
|
*/
|
|
fun installCrashLog(context: Context) {
|
|
val app = context.applicationContext
|
|
val previous = Thread.getDefaultUncaughtExceptionHandler()
|
|
Thread.setDefaultUncaughtExceptionHandler { thread, error ->
|
|
runCatching { File(app.filesDir, CRASH_FILE).writeText(describe(thread, error)) }
|
|
previous?.uncaughtException(thread, error)
|
|
}
|
|
}
|
|
|
|
private fun describe(thread: Thread, error: Throwable): String {
|
|
val when_ = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US).format(Date())
|
|
val stack = StringWriter().also { error.printStackTrace(PrintWriter(it)) }.toString()
|
|
val kept =
|
|
if (stack.length <= CRASH_LIMIT) stack
|
|
else stack.take(CRASH_LIMIT) + "\n ... ${stack.length - CRASH_LIMIT} more characters"
|
|
return "$when_ on thread ${thread.name}\n$kept"
|
|
}
|
|
|
|
/** The last crash, or null if there has not been one since it was last read. */
|
|
fun lastCrash(context: Context): String? =
|
|
File(context.applicationContext.filesDir, CRASH_FILE).takeIf { it.exists() }?.readText()
|
|
|
|
/** Forgets the last crash, once somebody has taken a copy of it. */
|
|
fun clearCrash(context: Context) {
|
|
File(context.applicationContext.filesDir, CRASH_FILE).delete()
|
|
}
|