Skip to content

Commit 29df906

Browse files
committed
fix: Handle ForegroundServiceDidNotStartInTimeException crash
1 parent 70d9c04 commit 29df906

2 files changed

Lines changed: 45 additions & 6 deletions

File tree

app/src/main/java/eu/darken/capod/App.kt

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
package eu.darken.capod
22

33
import android.app.Application
4+
import android.os.Looper
45
import dagger.hilt.android.HiltAndroidApp
56
import eu.darken.capod.common.coroutine.AppScope
7+
import eu.darken.capod.common.debug.Bugs
68
import eu.darken.capod.common.debug.autoreport.AutomaticBugReporter
79
import eu.darken.capod.common.debug.logging.LogCatLogger
810
import eu.darken.capod.common.debug.logging.Logging
911
import eu.darken.capod.common.debug.logging.asLog
12+
import eu.darken.capod.common.debug.logging.Logging.Priority.ERROR
13+
import eu.darken.capod.common.debug.logging.Logging.Priority.WARN
1014
import eu.darken.capod.common.debug.logging.log
1115
import eu.darken.capod.common.debug.logging.logTag
1216
import eu.darken.capod.common.flow.throttleLatest
@@ -23,6 +27,7 @@ import kotlinx.coroutines.flow.map
2327
import kotlinx.coroutines.flow.onEach
2428
import kotlinx.coroutines.launch
2529
import javax.inject.Inject
30+
import kotlin.system.exitProcess
2631

2732
@HiltAndroidApp
2833
open class App : Application() {
@@ -37,6 +42,20 @@ open class App : Application() {
3742
super.onCreate()
3843
if (BuildConfig.DEBUG) Logging.install(LogCatLogger())
3944

45+
var foregroundExceptionHandled = false
46+
val oldHandler = Thread.getDefaultUncaughtExceptionHandler()
47+
Thread.setDefaultUncaughtExceptionHandler { thread, throwable ->
48+
if (throwable.isForegroundServiceTimingException() && !foregroundExceptionHandled) {
49+
foregroundExceptionHandled = true
50+
log(TAG, WARN) { "Suppressed foreground service timing exception: ${throwable.asLog()}" }
51+
Bugs.report(tag = TAG, "Foreground service timing exception suppressed", exception = throwable)
52+
Looper.loop()
53+
return@setDefaultUncaughtExceptionHandler
54+
}
55+
log(TAG, ERROR) { "UNCAUGHT EXCEPTION: ${throwable.asLog()}" }
56+
if (oldHandler != null) oldHandler.uncaughtException(thread, throwable) else exitProcess(1)
57+
}
58+
4059
autoReporting.setup(this)
4160

4261
log(TAG) { "onCreate() done! ${Exception().asLog()}" }
@@ -64,5 +83,14 @@ open class App : Application() {
6483

6584
companion object {
6685
internal val TAG = logTag("CAP")
86+
87+
private fun Throwable.isForegroundServiceTimingException(): Boolean {
88+
var current: Throwable? = this
89+
while (current != null) {
90+
if (current.javaClass.simpleName == "ForegroundServiceDidNotStartInTimeException") return true
91+
current = current.cause
92+
}
93+
return false
94+
}
6795
}
6896
}

app/src/main/java/eu/darken/capod/monitor/core/worker/MonitorService.kt

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ class MonitorService : Service() {
7979
private var monitoringJob: Job? = null
8080
@Volatile private var monitorGeneration = 0
8181
private var foregroundStartFailed = false
82+
private var injectionComplete = false
8283

8384
@SuppressLint("InlinedApi")
8485
private fun promoteToForeground(notification: Notification): Boolean {
@@ -114,7 +115,12 @@ class MonitorService : Service() {
114115
if (!promoteToForeground(MonitorNotifications.createEarlyNotification(this))) {
115116
foregroundStartFailed = true
116117
stopSelf()
117-
super.onCreate()
118+
try {
119+
super.onCreate()
120+
} catch (e: Exception) {
121+
log(TAG, WARN) { "Hilt DI failed in onCreate() (foreground denied): ${e.asLog()}" }
122+
Bugs.report(tag = TAG, "Hilt DI failed in onCreate() (foreground denied)", exception = e)
123+
}
118124
return
119125
}
120126

@@ -127,6 +133,7 @@ class MonitorService : Service() {
127133
stopSelf()
128134
return
129135
}
136+
injectionComplete = true
130137
log(TAG, VERBOSE) { "onCreate()" }
131138

132139
// Replace early notification with the full one from injected MonitorNotifications.
@@ -302,12 +309,16 @@ class MonitorService : Service() {
302309
log(TAG, VERBOSE) { "onDestroy()" }
303310
monitorScope.cancel("Service destroyed")
304311

305-
if (generalSettings.useExtraMonitorNotification.valueBlocking && !generalSettings.keepConnectedNotificationAfterDisconnect.valueBlocking) {
306-
try {
307-
notificationManager.cancel(MonitorNotifications.NOTIFICATION_ID_CONNECTED)
308-
} catch (e: Exception) {
309-
log(TAG, WARN) { "Failed to cancel connected notification: ${e.message}" }
312+
if (injectionComplete) {
313+
if (generalSettings.useExtraMonitorNotification.valueBlocking && !generalSettings.keepConnectedNotificationAfterDisconnect.valueBlocking) {
314+
try {
315+
notificationManager.cancel(MonitorNotifications.NOTIFICATION_ID_CONNECTED)
316+
} catch (e: Exception) {
317+
log(TAG, WARN) { "Failed to cancel connected notification: ${e.message}" }
318+
}
310319
}
320+
} else {
321+
log(TAG, WARN) { "onDestroy: Skipping notification cleanup, injection was incomplete." }
311322
}
312323

313324
super.onDestroy()

0 commit comments

Comments
 (0)