forked from geode-sdk/android-launcher
add support for text based crash dumps
This commit is contained in:
7 files changed
+53
-22
No files matched your search
@@ -103,7 +103,7 @@
|
|||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
<activity
|
<activity
|
||||||
android:name=".preferences.GeodeLogViewActivity"
|
android:name=".preferences.TextViewActivity"
|
||||||
android:exported="false"
|
android:exported="false"
|
||||||
android:theme="@style/Theme.GeodeLauncher">
|
android:theme="@style/Theme.GeodeLauncher">
|
||||||
</activity>
|
</activity>
|
||||||
|
|||||||
@@ -13,7 +13,8 @@ import kotlin.time.Instant
|
|||||||
|
|
||||||
data class CrashDump(
|
data class CrashDump(
|
||||||
val filename: String,
|
val filename: String,
|
||||||
val lastModified: Instant
|
val lastModified: Instant,
|
||||||
|
val fullPath: String,
|
||||||
)
|
)
|
||||||
|
|
||||||
class CrashViewModel(crashDirectory: File) : BaseDirectoryViewModel<CrashDump>(crashDirectory) {
|
class CrashViewModel(crashDirectory: File) : BaseDirectoryViewModel<CrashDump>(crashDirectory) {
|
||||||
@@ -41,16 +42,19 @@ class CrashViewModel(crashDirectory: File) : BaseDirectoryViewModel<CrashDump>(c
|
|||||||
|
|
||||||
val children = crashDirectory.listFiles {
|
val children = crashDirectory.listFiles {
|
||||||
// ignore indicator files (including old file)
|
// ignore indicator files (including old file)
|
||||||
_, name -> name != LaunchUtils.CRASH_INDICATOR_NAME && name != "last-pid"
|
_, name -> !LaunchUtils.CRASH_INDICATOR_NAMES.contains(name)
|
||||||
} ?: return emptyList()
|
} ?: return emptyList()
|
||||||
|
|
||||||
hasIndicator = File(crashDirectory, LaunchUtils.CRASH_INDICATOR_NAME).exists()
|
hasIndicator = LaunchUtils.CRASH_INDICATOR_NAMES.any {
|
||||||
|
File(crashDirectory, it).exists()
|
||||||
|
}
|
||||||
|
|
||||||
return children
|
return children
|
||||||
.map {
|
.map {
|
||||||
CrashDump(
|
CrashDump(
|
||||||
filename = it.name,
|
filename = it.name,
|
||||||
lastModified = Instant.fromEpochMilliseconds(it.lastModified())
|
lastModified = Instant.fromEpochMilliseconds(it.lastModified()),
|
||||||
|
fullPath = it.path,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
.sortedByDescending { it.lastModified }
|
.sortedByDescending { it.lastModified }
|
||||||
@@ -58,7 +62,12 @@ class CrashViewModel(crashDirectory: File) : BaseDirectoryViewModel<CrashDump>(c
|
|||||||
|
|
||||||
fun clearIndicator() {
|
fun clearIndicator() {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
File(baseDirectory, LaunchUtils.CRASH_INDICATOR_NAME).delete()
|
LaunchUtils.CRASH_INDICATOR_NAMES.forEach {
|
||||||
|
val crashFile = File(baseDirectory, it)
|
||||||
|
if (crashFile.exists()) {
|
||||||
|
crashFile.delete()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
hasIndicator = false
|
hasIndicator = false
|
||||||
}
|
}
|
||||||
@@ -66,7 +75,8 @@ class CrashViewModel(crashDirectory: File) : BaseDirectoryViewModel<CrashDump>(c
|
|||||||
|
|
||||||
fun createIndicator() {
|
fun createIndicator() {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
File(baseDirectory, LaunchUtils.CRASH_INDICATOR_NAME).createNewFile()
|
val crashFile = File(baseDirectory, LaunchUtils.CRASH_INDICATOR_NAMES.first())
|
||||||
|
crashFile.createNewFile()
|
||||||
|
|
||||||
hasIndicator = true
|
hasIndicator = true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ data class GeodeLog(
|
|||||||
val filename: String,
|
val filename: String,
|
||||||
val lastModified: Instant,
|
val lastModified: Instant,
|
||||||
val fileSize: Long,
|
val fileSize: Long,
|
||||||
|
val fullPath: String,
|
||||||
)
|
)
|
||||||
|
|
||||||
class GeodeLogsViewModel(crashDirectory: File) : BaseDirectoryViewModel<GeodeLog>(crashDirectory) {
|
class GeodeLogsViewModel(crashDirectory: File) : BaseDirectoryViewModel<GeodeLog>(crashDirectory) {
|
||||||
@@ -43,7 +44,8 @@ class GeodeLogsViewModel(crashDirectory: File) : BaseDirectoryViewModel<GeodeLog
|
|||||||
GeodeLog(
|
GeodeLog(
|
||||||
filename = it.name,
|
filename = it.name,
|
||||||
lastModified = Instant.fromEpochMilliseconds(it.lastModified()),
|
lastModified = Instant.fromEpochMilliseconds(it.lastModified()),
|
||||||
fileSize = it.length()
|
fileSize = it.length(),
|
||||||
|
fullPath = it.path,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
.sortedByDescending { it.lastModified }
|
.sortedByDescending { it.lastModified }
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import androidx.activity.OnBackPressedDispatcher
|
|||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
import androidx.activity.enableEdgeToEdge
|
import androidx.activity.enableEdgeToEdge
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
@@ -110,11 +111,20 @@ fun shareCrash(context: Context, filename: String) {
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun CrashCard(crashDump: CrashDump, crashViewModel: CrashViewModel, modifier: Modifier = Modifier) {
|
fun CrashCard(crashDump: CrashDump, crashViewModel: CrashViewModel, modifier: Modifier = Modifier) {
|
||||||
|
val isReadable = crashDump.filename.endsWith(".txt")
|
||||||
|
val context = LocalContext.current
|
||||||
|
|
||||||
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier
|
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(vertical = 1.dp, horizontal = 8.dp)
|
.padding(vertical = 1.dp, horizontal = 8.dp)
|
||||||
.height(64.dp)
|
.height(64.dp)
|
||||||
.then(modifier)
|
.then(modifier)
|
||||||
|
.clickable(isReadable) {
|
||||||
|
val launchIntent = Intent(context, TextViewActivity::class.java).run {
|
||||||
|
putExtra(TextViewActivity.EXTRA_LOG_VIEW_FILENAME, crashDump.fullPath)
|
||||||
|
}
|
||||||
|
context.startActivity(launchIntent)
|
||||||
|
}
|
||||||
.background(MaterialTheme.colorScheme.surfaceContainerLow)
|
.background(MaterialTheme.colorScheme.surfaceContainerLow)
|
||||||
.padding(vertical = 8.dp, horizontal = 8.dp)
|
.padding(vertical = 8.dp, horizontal = 8.dp)
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -117,8 +117,8 @@ fun GeodeLogCard(geodeLog: GeodeLog, geodeLogsViewModel: GeodeLogsViewModel, mod
|
|||||||
.height(64.dp)
|
.height(64.dp)
|
||||||
.then(modifier)
|
.then(modifier)
|
||||||
.clickable {
|
.clickable {
|
||||||
val launchIntent = Intent(context, GeodeLogViewActivity::class.java).run {
|
val launchIntent = Intent(context, TextViewActivity::class.java).run {
|
||||||
putExtra(GeodeLogViewActivity.EXTRA_LOG_VIEW_FILENAME, geodeLog.filename)
|
putExtra(TextViewActivity.EXTRA_LOG_VIEW_FILENAME, geodeLog.fullPath)
|
||||||
}
|
}
|
||||||
context.startActivity(launchIntent)
|
context.startActivity(launchIntent)
|
||||||
}
|
}
|
||||||
|
|||||||
+16
-7
@@ -12,7 +12,6 @@ import androidx.compose.foundation.layout.fillMaxSize
|
|||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.safeDrawingPadding
|
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.foundation.text.selection.SelectionContainer
|
import androidx.compose.foundation.text.selection.SelectionContainer
|
||||||
@@ -40,18 +39,19 @@ import androidx.compose.ui.res.stringResource
|
|||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
|
||||||
import com.geode.launcher.R
|
import com.geode.launcher.R
|
||||||
import com.geode.launcher.log.GeodeLogsViewModel
|
|
||||||
import com.geode.launcher.ui.theme.GeodeLauncherTheme
|
import com.geode.launcher.ui.theme.GeodeLauncherTheme
|
||||||
import com.geode.launcher.ui.theme.LocalTheme
|
import com.geode.launcher.ui.theme.LocalTheme
|
||||||
import com.geode.launcher.ui.theme.Theme
|
import com.geode.launcher.ui.theme.Theme
|
||||||
import com.geode.launcher.ui.theme.Typography
|
import com.geode.launcher.ui.theme.Typography
|
||||||
import com.geode.launcher.ui.theme.robotoMonoFamily
|
import com.geode.launcher.ui.theme.robotoMonoFamily
|
||||||
import com.geode.launcher.utils.PreferenceUtils
|
import com.geode.launcher.utils.PreferenceUtils
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
|
||||||
class GeodeLogViewActivity : ComponentActivity() {
|
class TextViewActivity : ComponentActivity() {
|
||||||
companion object {
|
companion object {
|
||||||
const val EXTRA_LOG_VIEW_FILENAME = "EXTRA_LOG_FILENAME"
|
const val EXTRA_LOG_VIEW_FILENAME = "EXTRA_LOG_FILENAME"
|
||||||
}
|
}
|
||||||
@@ -95,14 +95,23 @@ class GeodeLogViewActivity : ComponentActivity() {
|
|||||||
fun GeodeLogView(
|
fun GeodeLogView(
|
||||||
onBackPressedDispatcher: OnBackPressedDispatcher?,
|
onBackPressedDispatcher: OnBackPressedDispatcher?,
|
||||||
filename: String,
|
filename: String,
|
||||||
geodeLogsViewModel: GeodeLogsViewModel = viewModel(factory = GeodeLogsViewModel.Factory)
|
|
||||||
) {
|
) {
|
||||||
var loadingFile by remember { mutableStateOf(true) }
|
var loadingFile by remember { mutableStateOf(true) }
|
||||||
var description by remember { mutableStateOf(emptyList<String>()) }
|
var description by remember { mutableStateOf(emptyList<String>()) }
|
||||||
|
|
||||||
|
val toRead = File(filename)
|
||||||
|
|
||||||
LaunchedEffect(filename) {
|
LaunchedEffect(filename) {
|
||||||
loadingFile = true
|
loadingFile = true
|
||||||
description = geodeLogsViewModel.getFileText(filename)
|
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
if (!toRead.isFile) {
|
||||||
|
description = listOf("File does not exist!")
|
||||||
|
} else {
|
||||||
|
description = toRead.readLines()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
loadingFile = false
|
loadingFile = false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,7 +132,7 @@ fun GeodeLogView(
|
|||||||
},
|
},
|
||||||
title = {
|
title = {
|
||||||
Text(
|
Text(
|
||||||
filename,
|
toRead.name,
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
overflow = TextOverflow.Ellipsis
|
overflow = TextOverflow.Ellipsis
|
||||||
)
|
)
|
||||||
@@ -73,7 +73,7 @@ object LaunchUtils {
|
|||||||
return File(getBaseDirectory(context), "save")
|
return File(getBaseDirectory(context), "save")
|
||||||
}
|
}
|
||||||
|
|
||||||
const val CRASH_INDICATOR_NAME = "lastSessionDidCrash"
|
val CRASH_INDICATOR_NAMES = setOf("lastSessionDidCrash", "last-pid", "last-crashed")
|
||||||
|
|
||||||
fun getCrashDirectory(context: Context): File {
|
fun getCrashDirectory(context: Context): File {
|
||||||
val base = getBaseDirectory(context)
|
val base = getBaseDirectory(context)
|
||||||
@@ -93,7 +93,7 @@ object LaunchUtils {
|
|||||||
|
|
||||||
val children = crashDirectory.listFiles {
|
val children = crashDirectory.listFiles {
|
||||||
// ignore indicator files (including old file)
|
// ignore indicator files (including old file)
|
||||||
_, name -> name != CRASH_INDICATOR_NAME && name != "last-pid"
|
_, name -> !CRASH_INDICATOR_NAMES.contains(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
return children?.maxByOrNull { it.lastModified() }
|
return children?.maxByOrNull { it.lastModified() }
|
||||||
@@ -101,9 +101,9 @@ object LaunchUtils {
|
|||||||
|
|
||||||
fun lastSessionCrashed(context: Context): Boolean {
|
fun lastSessionCrashed(context: Context): Boolean {
|
||||||
val base = getCrashDirectory(context)
|
val base = getCrashDirectory(context)
|
||||||
val crashIndicatorFile = File(base, CRASH_INDICATOR_NAME)
|
return CRASH_INDICATOR_NAMES.any {
|
||||||
|
File(base, it).exists()
|
||||||
return crashIndicatorFile.exists()
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class LauncherError : Serializable {
|
enum class LauncherError : Serializable {
|
||||||
|
|||||||
Reference in new issue
Block a user