forked from geode-sdk/android-launcher
add crash info popup
This commit is contained in:
3 files changed
+149
-45
No files matched your search
@@ -1,9 +1,12 @@
|
|||||||
package com.geode.launcher
|
package com.geode.launcher
|
||||||
|
|
||||||
|
import android.content.ActivityNotFoundException
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.provider.DocumentsContract
|
||||||
import android.text.format.Formatter.formatShortFileSize
|
import android.text.format.Formatter.formatShortFileSize
|
||||||
|
import android.widget.Toast
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
import androidx.activity.enableEdgeToEdge
|
import androidx.activity.enableEdgeToEdge
|
||||||
@@ -17,6 +20,7 @@ import androidx.compose.material.icons.Icons
|
|||||||
import androidx.compose.material.icons.filled.PlayArrow
|
import androidx.compose.material.icons.filled.PlayArrow
|
||||||
import androidx.compose.material.icons.filled.Refresh
|
import androidx.compose.material.icons.filled.Refresh
|
||||||
import androidx.compose.material.icons.filled.Settings
|
import androidx.compose.material.icons.filled.Settings
|
||||||
|
import androidx.compose.material.icons.filled.Share
|
||||||
import androidx.compose.material.icons.filled.Warning
|
import androidx.compose.material.icons.filled.Warning
|
||||||
import androidx.compose.material3.*
|
import androidx.compose.material3.*
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
@@ -63,8 +67,8 @@ import java.net.UnknownHostException
|
|||||||
|
|
||||||
data class LoadFailureInfo(
|
data class LoadFailureInfo(
|
||||||
val title: LaunchUtils.LauncherError,
|
val title: LaunchUtils.LauncherError,
|
||||||
val description: String,
|
val description: String? = null,
|
||||||
val extendedMessage: String?
|
val extendedMessage: String? = null
|
||||||
)
|
)
|
||||||
|
|
||||||
class MainActivity : ComponentActivity() {
|
class MainActivity : ComponentActivity() {
|
||||||
@@ -83,6 +87,12 @@ class MainActivity : ComponentActivity() {
|
|||||||
val returnError = intent.extras?.getSerializable(LaunchUtils.LAUNCHER_KEY_RETURN_ERROR)
|
val returnError = intent.extras?.getSerializable(LaunchUtils.LAUNCHER_KEY_RETURN_ERROR)
|
||||||
as? LaunchUtils.LauncherError
|
as? LaunchUtils.LauncherError
|
||||||
|
|
||||||
|
val loadFailureInfo = if (returnError != null && returnMessage != null) {
|
||||||
|
LoadFailureInfo(returnError, returnMessage, returnExtendedMessage)
|
||||||
|
} else if (LaunchUtils.lastSessionCrashed(this)) {
|
||||||
|
LoadFailureInfo(LaunchUtils.LauncherError.CRASHED)
|
||||||
|
} else { null }
|
||||||
|
|
||||||
setContent {
|
setContent {
|
||||||
val themeOption by PreferenceUtils.useIntPreference(PreferenceUtils.Key.THEME)
|
val themeOption by PreferenceUtils.useIntPreference(PreferenceUtils.Key.THEME)
|
||||||
val theme = Theme.fromInt(themeOption)
|
val theme = Theme.fromInt(themeOption)
|
||||||
@@ -95,10 +105,6 @@ class MainActivity : ComponentActivity() {
|
|||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier.fillMaxSize(),
|
||||||
color = MaterialTheme.colorScheme.background
|
color = MaterialTheme.colorScheme.background
|
||||||
) {
|
) {
|
||||||
val loadFailureInfo = if (returnError != null && returnMessage != null) {
|
|
||||||
LoadFailureInfo(returnError, returnMessage, returnExtendedMessage)
|
|
||||||
} else { null }
|
|
||||||
|
|
||||||
MainScreen(gdInstalled, geodeInstalled, loadFailureInfo)
|
MainScreen(gdInstalled, geodeInstalled, loadFailureInfo)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -416,6 +422,7 @@ fun ErrorInfoBody(failureReason: LaunchUtils.LauncherError, modifier: Modifier =
|
|||||||
LaunchUtils.LauncherError.LINKER_NEEDS_32BIT -> stringResource(R.string.load_failed_abi_error)
|
LaunchUtils.LauncherError.LINKER_NEEDS_32BIT -> stringResource(R.string.load_failed_abi_error)
|
||||||
LaunchUtils.LauncherError.LINKER_FAILED -> stringResource(R.string.load_failed_link_error_description)
|
LaunchUtils.LauncherError.LINKER_FAILED -> stringResource(R.string.load_failed_link_error_description)
|
||||||
LaunchUtils.LauncherError.GENERIC -> stringResource(R.string.load_failed_generic_error_description)
|
LaunchUtils.LauncherError.GENERIC -> stringResource(R.string.load_failed_generic_error_description)
|
||||||
|
LaunchUtils.LauncherError.CRASHED -> stringResource(R.string.load_failed_crashed_description)
|
||||||
}
|
}
|
||||||
|
|
||||||
val recommendations = when (failureReason) {
|
val recommendations = when (failureReason) {
|
||||||
@@ -436,6 +443,10 @@ fun ErrorInfoBody(failureReason: LaunchUtils.LauncherError, modifier: Modifier =
|
|||||||
stringResource(R.string.load_failed_recommendation_update),
|
stringResource(R.string.load_failed_recommendation_update),
|
||||||
stringResource(R.string.load_failed_recommendation_report)
|
stringResource(R.string.load_failed_recommendation_report)
|
||||||
)
|
)
|
||||||
|
LaunchUtils.LauncherError.CRASHED -> listOf(
|
||||||
|
stringResource(R.string.load_failed_recommendation_safe_mode),
|
||||||
|
stringResource(R.string.load_failed_recommendation_report)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
Column(modifier = modifier) {
|
Column(modifier = modifier) {
|
||||||
@@ -454,10 +465,11 @@ fun ErrorInfoTitle(failureReason: LaunchUtils.LauncherError) {
|
|||||||
LaunchUtils.LauncherError.LINKER_NEEDS_32BIT,
|
LaunchUtils.LauncherError.LINKER_NEEDS_32BIT,
|
||||||
LaunchUtils.LauncherError.LINKER_FAILED -> stringResource(R.string.load_failed_link_error)
|
LaunchUtils.LauncherError.LINKER_FAILED -> stringResource(R.string.load_failed_link_error)
|
||||||
LaunchUtils.LauncherError.GENERIC -> stringResource(R.string.load_failed_generic_error)
|
LaunchUtils.LauncherError.GENERIC -> stringResource(R.string.load_failed_generic_error)
|
||||||
|
LaunchUtils.LauncherError.CRASHED -> stringResource(R.string.load_failed_crashed)
|
||||||
}
|
}
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
horizontalArrangement = Arrangement.spacedBy(10.dp),
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
@@ -470,6 +482,39 @@ fun ErrorInfoTitle(failureReason: LaunchUtils.LauncherError) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun onShareCrash(context: Context) {
|
||||||
|
val lastCrash = LaunchUtils.getLastCrash(context)
|
||||||
|
if (lastCrash == null) {
|
||||||
|
Toast.makeText(context, R.string.launcher_error_export_missing, Toast.LENGTH_SHORT).show()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val baseDirectory = LaunchUtils.getBaseDirectory(context)
|
||||||
|
|
||||||
|
val providerLocation = lastCrash.toRelativeString(baseDirectory)
|
||||||
|
val documentsPath = "${UserDirectoryProvider.ROOT}${providerLocation}"
|
||||||
|
|
||||||
|
val uri = DocumentsContract.buildDocumentUri("${context.packageName}.user", documentsPath)
|
||||||
|
|
||||||
|
println("shareCrash: $uri")
|
||||||
|
|
||||||
|
val shareIntent = Intent(Intent.ACTION_SEND).apply {
|
||||||
|
putExtra(Intent.EXTRA_STREAM, uri)
|
||||||
|
type = "application/octet-stream"
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
context.startActivity(shareIntent)
|
||||||
|
} catch (_: ActivityNotFoundException) {
|
||||||
|
Toast.makeText(context, R.string.no_activity_found, Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onShowLogs(context: Context) {
|
||||||
|
val launchIntent = Intent(context, ApplicationLogsActivity::class.java)
|
||||||
|
context.startActivity(launchIntent)
|
||||||
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun ErrorInfoSheet(failureInfo: LoadFailureInfo, onDismiss: () -> Unit) {
|
fun ErrorInfoSheet(failureInfo: LoadFailureInfo, onDismiss: () -> Unit) {
|
||||||
@@ -483,19 +528,21 @@ fun ErrorInfoSheet(failureInfo: LoadFailureInfo, onDismiss: () -> Unit) {
|
|||||||
|
|
||||||
ErrorInfoBody(failureInfo.title)
|
ErrorInfoBody(failureInfo.title)
|
||||||
|
|
||||||
Column(modifier = Modifier.padding(vertical = 8.dp)) {
|
if (failureInfo.description != null) {
|
||||||
Text(
|
Column(modifier = Modifier.padding(vertical = 8.dp)) {
|
||||||
stringResource(R.string.launcher_error_details),
|
Text(
|
||||||
style = Typography.headlineSmall
|
stringResource(R.string.launcher_error_details),
|
||||||
)
|
style = Typography.headlineSmall
|
||||||
|
)
|
||||||
|
|
||||||
Spacer(Modifier.size(8.dp))
|
Spacer(Modifier.size(8.dp))
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
failureInfo.description,
|
failureInfo.description,
|
||||||
fontFamily = FontFamily.Monospace,
|
fontFamily = FontFamily.Monospace,
|
||||||
style = Typography.bodyMedium
|
style = Typography.bodyMedium
|
||||||
)
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val clipboardManager = LocalClipboardManager.current
|
val clipboardManager = LocalClipboardManager.current
|
||||||
@@ -509,28 +556,45 @@ fun ErrorInfoSheet(failureInfo: LoadFailureInfo, onDismiss: () -> Unit) {
|
|||||||
12.dp, alignment = Alignment.CenterHorizontally
|
12.dp, alignment = Alignment.CenterHorizontally
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
Button(onClick = {
|
if (failureInfo.description != null) {
|
||||||
val launchIntent = Intent(context, ApplicationLogsActivity::class.java)
|
Button(onClick = { onShowLogs(context) }) {
|
||||||
context.startActivity(launchIntent)
|
Icon(
|
||||||
}) {
|
painterResource(R.drawable.icon_description),
|
||||||
Icon(
|
contentDescription = null
|
||||||
painterResource(R.drawable.icon_description),
|
)
|
||||||
contentDescription = null
|
Spacer(Modifier.size(ButtonDefaults.IconSpacing))
|
||||||
)
|
Text(stringResource(R.string.launcher_error_view_logs))
|
||||||
Spacer(Modifier.size(ButtonDefaults.IconSpacing))
|
}
|
||||||
Text(stringResource(R.string.launcher_error_view_logs))
|
|
||||||
}
|
|
||||||
|
|
||||||
FilledTonalButton(onClick = {
|
FilledTonalButton(onClick = {
|
||||||
val message = failureInfo.extendedMessage ?: failureInfo.description
|
val message = failureInfo.extendedMessage ?: failureInfo.description
|
||||||
clipboardManager.setText(AnnotatedString(message))
|
clipboardManager.setText(AnnotatedString(message))
|
||||||
}) {
|
}) {
|
||||||
Icon(
|
Icon(
|
||||||
painterResource(R.drawable.icon_content_copy),
|
painterResource(R.drawable.icon_content_copy),
|
||||||
contentDescription = null
|
contentDescription = null
|
||||||
)
|
)
|
||||||
Spacer(Modifier.size(ButtonDefaults.IconSpacing))
|
Spacer(Modifier.size(ButtonDefaults.IconSpacing))
|
||||||
Text(stringResource(R.string.launcher_error_copy))
|
Text(stringResource(R.string.launcher_error_copy))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Button(onClick = { onShareCrash(context) }) {
|
||||||
|
Icon(
|
||||||
|
Icons.Filled.Share,
|
||||||
|
contentDescription = null
|
||||||
|
)
|
||||||
|
Spacer(Modifier.size(ButtonDefaults.IconSpacing))
|
||||||
|
Text(stringResource(R.string.launcher_error_export_crash))
|
||||||
|
}
|
||||||
|
|
||||||
|
FilledTonalButton(onClick = { onShowLogs(context) }) {
|
||||||
|
Icon(
|
||||||
|
painterResource(R.drawable.icon_description),
|
||||||
|
contentDescription = null
|
||||||
|
)
|
||||||
|
Spacer(Modifier.size(ButtonDefaults.IconSpacing))
|
||||||
|
Text(stringResource(R.string.launcher_error_view_logs))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -590,15 +654,20 @@ fun MainScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val hasError = loadFailureInfo != null
|
|
||||||
var showErrorInfo by remember { mutableStateOf(false) }
|
|
||||||
|
|
||||||
val snackbarHostState = remember { SnackbarHostState() }
|
val snackbarHostState = remember { SnackbarHostState() }
|
||||||
|
|
||||||
|
var showErrorInfo by remember { mutableStateOf(false) }
|
||||||
|
val hasError = loadFailureInfo != null
|
||||||
LaunchedEffect(hasError) {
|
LaunchedEffect(hasError) {
|
||||||
if (hasError) {
|
if (loadFailureInfo != null) {
|
||||||
|
val message = if (loadFailureInfo.title == LaunchUtils.LauncherError.CRASHED) {
|
||||||
|
context.getString(R.string.launcher_crashed)
|
||||||
|
} else {
|
||||||
|
context.getString(R.string.launcher_failed_to_load)
|
||||||
|
}
|
||||||
|
|
||||||
val res = snackbarHostState.showSnackbar(
|
val res = snackbarHostState.showSnackbar(
|
||||||
message = context.getString(R.string.launcher_failed_to_load),
|
message = message,
|
||||||
actionLabel = context.getString(R.string.launcher_error_more),
|
actionLabel = context.getString(R.string.launcher_error_more),
|
||||||
duration = SnackbarDuration.Indefinite,
|
duration = SnackbarDuration.Indefinite,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -156,11 +156,40 @@ object LaunchUtils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private const val CRASH_INDICATOR_NAME = "lastSessionDidCrash"
|
||||||
|
|
||||||
|
private fun getCrashDirectory(context: Context): File {
|
||||||
|
val base = getBaseDirectory(context)
|
||||||
|
return File(base, "game/geode/crashlogs/")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getLastCrash(context: Context): File? {
|
||||||
|
val crashDirectory = getCrashDirectory(context)
|
||||||
|
if (!crashDirectory.exists()) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
val children = crashDirectory.listFiles {
|
||||||
|
// ignore indicator files (including old file)
|
||||||
|
_, name -> name != CRASH_INDICATOR_NAME && name != "last-pid"
|
||||||
|
}
|
||||||
|
|
||||||
|
return children?.maxByOrNull { it.lastModified() }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun lastSessionCrashed(context: Context): Boolean {
|
||||||
|
val base = getCrashDirectory(context)
|
||||||
|
val crashIndicatorFile = File(base, CRASH_INDICATOR_NAME)
|
||||||
|
|
||||||
|
return crashIndicatorFile.exists()
|
||||||
|
}
|
||||||
|
|
||||||
enum class LauncherError {
|
enum class LauncherError {
|
||||||
LINKER_NEEDS_64BIT,
|
LINKER_NEEDS_64BIT,
|
||||||
LINKER_NEEDS_32BIT,
|
LINKER_NEEDS_32BIT,
|
||||||
LINKER_FAILED,
|
LINKER_FAILED,
|
||||||
GENERIC
|
GENERIC,
|
||||||
|
CRASHED
|
||||||
}
|
}
|
||||||
|
|
||||||
const val LAUNCHER_KEY_RETURN_ERROR = "return_error"
|
const val LAUNCHER_KEY_RETURN_ERROR = "return_error"
|
||||||
|
|||||||
@@ -33,11 +33,14 @@
|
|||||||
<string name="safe_mode_enable_title">Enable safe mode?</string>
|
<string name="safe_mode_enable_title">Enable safe mode?</string>
|
||||||
<string name="safe_mode_enable_description">Safe mode disables loading of all mods, allowing one to remove problematic mods or diagnose crashes. If you\'re not experiencing issues with Geode, you should probably not be using safe mode.</string>
|
<string name="safe_mode_enable_description">Safe mode disables loading of all mods, allowing one to remove problematic mods or diagnose crashes. If you\'re not experiencing issues with Geode, you should probably not be using safe mode.</string>
|
||||||
<string name="launcher_failed_to_load">Geode failed to load.</string>
|
<string name="launcher_failed_to_load">Geode failed to load.</string>
|
||||||
|
<string name="launcher_crashed">Geode crashed.</string>
|
||||||
<string name="launcher_error_more">Learn more</string>
|
<string name="launcher_error_more">Learn more</string>
|
||||||
<string name="launcher_error_details">Error details</string>
|
<string name="launcher_error_details">Error details</string>
|
||||||
<string name="launcher_error_view_logs">View logs</string>
|
<string name="launcher_error_view_logs">View logs</string>
|
||||||
<string name="launcher_error_copy_error_alt">Copy</string>
|
<string name="launcher_error_copy_error_alt">Copy</string>
|
||||||
<string name="launcher_error_copy">Copy details</string>
|
<string name="launcher_error_copy">Copy details</string>
|
||||||
|
<string name="launcher_error_export_crash">Share crash</string>
|
||||||
|
<string name="launcher_error_export_missing">No crash dump was found!</string>
|
||||||
|
|
||||||
<!-- Updater strings -->
|
<!-- Updater strings -->
|
||||||
<string name="release_fetch_failed">Update failed.\n%1$s</string>
|
<string name="release_fetch_failed">Update failed.\n%1$s</string>
|
||||||
@@ -58,11 +61,14 @@
|
|||||||
<string name="load_failed_abi_error">The architecture of the launcher and Geometry Dash do not match. Try to:</string>
|
<string name="load_failed_abi_error">The architecture of the launcher and Geometry Dash do not match. Try to:</string>
|
||||||
<string name="load_failed_generic_error">Failed to load Geode</string>
|
<string name="load_failed_generic_error">Failed to load Geode</string>
|
||||||
<string name="load_failed_generic_error_description">Uncaught error during game load. Try to:</string>
|
<string name="load_failed_generic_error_description">Uncaught error during game load. Try to:</string>
|
||||||
|
<string name="load_failed_crashed">Geode crashed</string>
|
||||||
|
<string name="load_failed_crashed_description">A crash was reported during the last session. Try to:</string>
|
||||||
<string name="load_failed_recommendation_reinstall">Reinstall Geometry Dash from the Play Store.</string>
|
<string name="load_failed_recommendation_reinstall">Reinstall Geometry Dash from the Play Store.</string>
|
||||||
<string name="load_failed_recommendation_switch_32bit_abi">Install the 32-bit version of the launcher.</string>
|
<string name="load_failed_recommendation_switch_32bit_abi">Install the 32-bit version of the launcher.</string>
|
||||||
<string name="load_failed_recommendation_switch_64bit_abi">Install the non 32-bit version of the launcher.</string>
|
<string name="load_failed_recommendation_switch_64bit_abi">Install the non 32-bit version of the launcher.</string>
|
||||||
<string name="load_failed_recommendation_update">Verify that the latest versions of the launcher and Geometry Dash are installed.</string>
|
<string name="load_failed_recommendation_update">Verify that the latest versions of the launcher and Geometry Dash are installed.</string>
|
||||||
<string name="load_failed_recommendation_report">Report this issue to the developers.</string>
|
<string name="load_failed_recommendation_report">Report this issue to the developers.</string>
|
||||||
|
<string name="load_failed_recommendation_safe_mode">Hold the Launch button to launch in safe mode.</string>
|
||||||
|
|
||||||
<string name="message_box_cancel">Cancel</string>
|
<string name="message_box_cancel">Cancel</string>
|
||||||
<string name="message_box_accept">OK</string>
|
<string name="message_box_accept">OK</string>
|
||||||
|
|||||||
Reference in new issue
Block a user