Skip to content

Commit 387f84c

Browse files
authored
Merge pull request #25 from oxters168/custom-crash-handler
Custom crash handler
2 parents ab92bb6 + 16521d1 commit 387f84c

17 files changed

+394
-119
lines changed

app/src/main/AndroidManifest.xml

+2-1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
1212
<uses-permission android:name="android.permission.INTERNET" />
1313
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
14+
<uses-permission android:name="android.permission.READ_LOGS" />
1415

1516
<application
1617
android:name=".PluviaApp"
@@ -52,4 +53,4 @@
5253
android:foregroundServiceType="dataSync" />
5354
</application>
5455

55-
</manifest>
56+
</manifest>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
package com.OxGames.Pluvia
2+
3+
import android.content.Context
4+
import java.io.File
5+
import java.io.PrintWriter
6+
import java.io.StringWriter
7+
import java.text.SimpleDateFormat
8+
import java.util.Date
9+
import java.util.Locale
10+
11+
class CrashHandler(
12+
private val context: Context,
13+
private val defaultHandler: Thread.UncaughtExceptionHandler?,
14+
) : Thread.UncaughtExceptionHandler {
15+
16+
companion object {
17+
private const val LOG_CAT_COUNT = 150
18+
private const val CRASH_FILE_HISTORY_COUNT = 1
19+
20+
fun initialize(context: Context) {
21+
val defaultHandler = Thread.getDefaultUncaughtExceptionHandler()
22+
val crashHandler = CrashHandler(context.applicationContext, defaultHandler)
23+
Thread.setDefaultUncaughtExceptionHandler(crashHandler)
24+
}
25+
}
26+
27+
private val crashFileDir by lazy {
28+
File(context.getExternalFilesDir(null), "crash_logs").apply {
29+
if (!exists()) mkdirs()
30+
}
31+
}
32+
33+
private val recentLogcat: String
34+
get() = try {
35+
val process = Runtime.getRuntime().exec("logcat -d -t $LOG_CAT_COUNT --pid=${android.os.Process.myPid()}")
36+
process.inputStream.bufferedReader().use { it.readText() }
37+
} catch (e: Exception) {
38+
"Failed to retrieve logcat: ${e.message}"
39+
}
40+
41+
private val cleanupOldCrashFiles: () -> Unit = {
42+
crashFileDir.listFiles()?.let { files ->
43+
if (files.size > CRASH_FILE_HISTORY_COUNT) {
44+
files.sortByDescending { it.lastModified() }
45+
files.drop(CRASH_FILE_HISTORY_COUNT).forEach { it.delete() }
46+
}
47+
}
48+
}
49+
50+
override fun uncaughtException(thread: Thread, throwable: Throwable) {
51+
PrefManager.recentlyCrashed = true
52+
53+
saveCrashToFile(throwable)
54+
defaultHandler?.uncaughtException(thread, throwable)
55+
}
56+
57+
private fun saveCrashToFile(throwable: Throwable) {
58+
try {
59+
val stackTrace = StringWriter().apply {
60+
val pw = PrintWriter(this)
61+
throwable.printStackTrace(pw)
62+
}.toString()
63+
64+
val timestamp = SimpleDateFormat("yyyy-MM-dd_HH-mm-ss", Locale.getDefault()).format(Date())
65+
66+
val crashReport = buildString {
67+
appendLine("Timestamp: $timestamp")
68+
appendLine("Exception: ${throwable.javaClass.name}")
69+
appendLine("Message: ${throwable.message}")
70+
appendLine()
71+
appendLine("Stack Trace:")
72+
appendLine(stackTrace)
73+
appendLine()
74+
appendLine("Device Information:")
75+
appendLine("Model: ${android.os.Build.MODEL}")
76+
appendLine("Android Version: ${android.os.Build.VERSION.RELEASE}")
77+
appendLine("App Version: ${context.packageManager.getPackageInfo(context.packageName, 0).versionName}")
78+
appendLine()
79+
appendLine("Logcat:")
80+
appendLine("----------------------------------------")
81+
appendLine(recentLogcat)
82+
}
83+
84+
File(crashFileDir, "pluvia_crash_$timestamp.txt").writeText(crashReport)
85+
86+
cleanupOldCrashFiles()
87+
} catch (e: Exception) {
88+
defaultHandler?.uncaughtException(Thread.currentThread(), throwable)
89+
}
90+
}
91+
}

app/src/main/java/com/OxGames/Pluvia/NotificationHelper.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ class NotificationHelper(private val context: Context) {
7272
context,
7373
0,
7474
stopIntent,
75-
PendingIntent.FLAG_IMMUTABLE
75+
PendingIntent.FLAG_IMMUTABLE,
7676
)
7777

7878
return NotificationCompat.Builder(context, CHANNEL_ID)

app/src/main/java/com/OxGames/Pluvia/PluviaApp.kt

+3
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@ class PluviaApp : SplitCompatApplication() {
3030
Timber.plant(ReleaseTree())
3131
}
3232

33+
// Init our custom crash handler.
34+
CrashHandler.initialize(this)
35+
3336
// Init our datastore preferences.
3437
PrefManager.init(this)
3538
}

app/src/main/java/com/OxGames/Pluvia/PrefManager.kt

+8
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,14 @@ object PrefManager {
7373
// }
7474
// }
7575

76+
/* Recent Crash Flag */
77+
private val RECENTLY_CRASHED = booleanPreferencesKey("recently_crashed")
78+
var recentlyCrashed: Boolean
79+
get() = getPref(RECENTLY_CRASHED, false)
80+
set(value) {
81+
setPref(RECENTLY_CRASHED, value)
82+
}
83+
7684
/* Login Info */
7785
private val CELL_ID = intPreferencesKey("cell_id")
7886
var cellId: Int

app/src/main/java/com/OxGames/Pluvia/SteamService.kt

+1-2
Original file line numberDiff line numberDiff line change
@@ -362,7 +362,7 @@ class SteamService : Service(), IChallengeUrlChanged {
362362
SplitInstallSessionStatus.INSTALLING,
363363
SplitInstallSessionStatus.DOWNLOADED,
364364
SplitInstallSessionStatus.DOWNLOADING,
365-
-> {
365+
-> {
366366
if (!isActive) {
367367
splitManager.cancelInstall(moduleInstallSessionId)
368368
break
@@ -1724,7 +1724,6 @@ class SteamService : Service(), IChallengeUrlChanged {
17241724
}
17251725

17261726
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
1727-
17281727
// Notification intents
17291728
when (intent?.action) {
17301729
NotificationHelper.ACTION_EXIT -> {

app/src/main/java/com/OxGames/Pluvia/enums/OS.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package com.OxGames.Pluvia.enums
22

3-
import timber.log.Timber
43
import java.util.EnumSet
4+
import timber.log.Timber
55

66
enum class OS {
77
windows,

app/src/main/java/com/OxGames/Pluvia/enums/PathType.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ package com.OxGames.Pluvia.enums
33
import android.content.Context
44
import com.OxGames.Pluvia.SteamService
55
import com.winlator.xenvironment.ImageFs
6-
import timber.log.Timber
76
import java.nio.file.Paths
7+
import timber.log.Timber
88

99
enum class PathType {
1010
GameInstall,

app/src/main/java/com/OxGames/Pluvia/ui/PluviaMain.kt

+42-2
Original file line numberDiff line numberDiff line change
@@ -89,13 +89,14 @@ fun PluviaMain(
8989
mutableStateOf(MessageDialogState(false))
9090
}
9191
var annoyingDialogShown by rememberSaveable { mutableStateOf(false) }
92+
var hasCrashedRecently by rememberSaveable { mutableStateOf(PrefManager.recentlyCrashed) }
9293

9394
val setLoadingDialogVisible: (Boolean) -> Unit = { loadingDialogVisible = it }
9495
val setLoadingProgress: (Float) -> Unit = { loadingProgress = it }
9596
val setMessageDialogState: (MessageDialogState) -> Unit = { msgDialogState = it }
9697

9798
LaunchedEffect(navController) {
98-
Timber.i( "navController changed")
99+
Timber.i("navController changed")
99100
if (!hasLaunched) {
100101
hasLaunched = true
101102
Timber.i("Creating on destination changed listener")
@@ -175,7 +176,23 @@ fun PluviaMain(
175176
// TODO: add preference for first screen on login
176177
Timber.i("Navigating to library")
177178
navController.navigate(PluviaScreen.Home.name)
178-
if (!(PrefManager.tipped || BuildConfig.GOLD) && !annoyingDialogShown) {
179+
180+
// If a crash happen, lets not ask for a tip yet.
181+
// Instead, ask the user to contribute their issues to be addressed.
182+
if (!annoyingDialogShown && hasCrashedRecently) {
183+
annoyingDialogShown = true
184+
msgDialogState = MessageDialogState(
185+
visible = true,
186+
type = DialogType.CRASH,
187+
title = "Recent Crash",
188+
message = "Sorry about that!\n" +
189+
"It would be nice to know about the recent issue you've had.\n" +
190+
"You can view and export the most recent crash log in the app's settings " +
191+
"and attach it as a Github issue in the project's repository.\n" +
192+
"Link to the Github repo is also in settings!",
193+
confirmBtnText = "OK",
194+
)
195+
} else if (!(PrefManager.tipped || BuildConfig.GOLD) && !annoyingDialogShown) {
179196
annoyingDialogShown = true
180197
msgDialogState = MessageDialogState(
181198
visible = true,
@@ -266,6 +283,7 @@ fun PluviaMain(
266283
msgDialogState = MessageDialogState(visible = false)
267284
}
268285
}
286+
269287
DialogType.SYNC_CONFLICT -> {
270288
onConfirmClick = {
271289
preLaunchApp(
@@ -295,6 +313,7 @@ fun PluviaMain(
295313
msgDialogState = MessageDialogState(false)
296314
}
297315
}
316+
298317
DialogType.SYNC_FAIL -> {
299318
onDismissClick = {
300319
setMessageDialogState(MessageDialogState(false))
@@ -304,6 +323,7 @@ fun PluviaMain(
304323
}
305324
onConfirmClick = null
306325
}
326+
307327
DialogType.PENDING_UPLOAD_IN_PROGRESS -> {
308328
onDismissClick = {
309329
setMessageDialogState(MessageDialogState(false))
@@ -313,6 +333,7 @@ fun PluviaMain(
313333
}
314334
onConfirmClick = null
315335
}
336+
316337
DialogType.PENDING_UPLOAD -> {
317338
onConfirmClick = {
318339
setMessageDialogState(MessageDialogState(false))
@@ -333,6 +354,7 @@ fun PluviaMain(
333354
setMessageDialogState(MessageDialogState(false))
334355
}
335356
}
357+
336358
DialogType.APP_SESSION_ACTIVE -> {
337359
onConfirmClick = {
338360
setMessageDialogState(MessageDialogState(false))
@@ -353,6 +375,7 @@ fun PluviaMain(
353375
setMessageDialogState(MessageDialogState(false))
354376
}
355377
}
378+
356379
DialogType.APP_SESSION_SUSPENDED -> {
357380
onDismissClick = {
358381
setMessageDialogState(MessageDialogState(false))
@@ -362,6 +385,7 @@ fun PluviaMain(
362385
}
363386
onConfirmClick = null
364387
}
388+
365389
DialogType.PENDING_OPERATION_NONE -> {
366390
onDismissClick = {
367391
setMessageDialogState(MessageDialogState(false))
@@ -371,6 +395,7 @@ fun PluviaMain(
371395
}
372396
onConfirmClick = null
373397
}
398+
374399
DialogType.MULTIPLE_PENDING_OPERATIONS -> {
375400
onDismissClick = {
376401
setMessageDialogState(MessageDialogState(false))
@@ -380,6 +405,21 @@ fun PluviaMain(
380405
}
381406
onConfirmClick = null
382407
}
408+
409+
DialogType.CRASH -> {
410+
onDismissClick = null
411+
onDismissRequest = {
412+
PrefManager.recentlyCrashed = false
413+
hasCrashedRecently = false
414+
setMessageDialogState(MessageDialogState(false))
415+
}
416+
onConfirmClick = {
417+
PrefManager.recentlyCrashed = false
418+
hasCrashedRecently = false
419+
setMessageDialogState(MessageDialogState(false))
420+
}
421+
}
422+
383423
else -> {
384424
onDismissRequest = null
385425
onDismissClick = null

app/src/main/java/com/OxGames/Pluvia/ui/component/dialog/state/MessageDialogState.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ data class MessageDialogState(
3737
title = savedMap["title"] as String?,
3838
message = savedMap["message"] as String?,
3939
)
40-
}
40+
},
4141
)
4242
}
4343
}

app/src/main/java/com/OxGames/Pluvia/ui/enums/DialogType.kt

+1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.OxGames.Pluvia.ui.enums
22

33
enum class DialogType {
4+
CRASH,
45
SUPPORT,
56
SYNC_CONFLICT,
67
SYNC_FAIL,

app/src/main/java/com/OxGames/Pluvia/ui/model/UserLoginViewModel.kt

+2-1
Original file line numberDiff line numberDiff line change
@@ -180,16 +180,17 @@ class UserLoginViewModel : ViewModel() {
180180

181181
fun onRetry() {
182182
_loginState.update { currentState ->
183-
currentState.copy(isSteamConnected = SteamService.isConnected)
184183
if (SteamService.isLoggedIn) {
185184
// TODO: Can this be handled better?
186185
// We're already logged in when 'onRetry' is called on 'init'. Show loading screen.
187186
currentState.copy(
187+
isSteamConnected = SteamService.isConnected,
188188
isLoggingIn = true,
189189
loginResult = LoginResult.Success,
190190
)
191191
} else {
192192
currentState.copy(
193+
isSteamConnected = SteamService.isConnected,
193194
isLoggingIn = SteamService.isLoggingIn,
194195
attemptCount = currentState.attemptCount.plus(1),
195196
isQrFailed = false,

app/src/main/java/com/OxGames/Pluvia/ui/screen/library/HomeLibraryAppScreen.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,11 @@ import androidx.compose.material3.Text
3232
import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo
3333
import androidx.compose.runtime.Composable
3434
import androidx.compose.runtime.DisposableEffect
35+
import androidx.compose.runtime.getValue
3536
import androidx.compose.runtime.mutableFloatStateOf
3637
import androidx.compose.runtime.mutableStateOf
3738
import androidx.compose.runtime.remember
3839
import androidx.compose.runtime.saveable.rememberSaveable
39-
import androidx.compose.runtime.getValue
4040
import androidx.compose.runtime.setValue
4141
import androidx.compose.ui.Alignment
4242
import androidx.compose.ui.Modifier

0 commit comments

Comments
 (0)