fix: Handle ForegroundServiceDidNotStartInTimeException crash

This commit is contained in:
darken
2026-04-14 11:24:49 +02:00
committed by Matthias Urhahn
parent 70d9c047dc
commit 29df90628f
2 changed files with 45 additions and 6 deletions
+28
View File
@@ -1,12 +1,16 @@
package eu.darken.capod
import android.app.Application
import android.os.Looper
import dagger.hilt.android.HiltAndroidApp
import eu.darken.capod.common.coroutine.AppScope
import eu.darken.capod.common.debug.Bugs
import eu.darken.capod.common.debug.autoreport.AutomaticBugReporter
import eu.darken.capod.common.debug.logging.LogCatLogger
import eu.darken.capod.common.debug.logging.Logging
import eu.darken.capod.common.debug.logging.asLog
import eu.darken.capod.common.debug.logging.Logging.Priority.ERROR
import eu.darken.capod.common.debug.logging.Logging.Priority.WARN
import eu.darken.capod.common.debug.logging.log
import eu.darken.capod.common.debug.logging.logTag
import eu.darken.capod.common.flow.throttleLatest
@@ -23,6 +27,7 @@ import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import javax.inject.Inject
import kotlin.system.exitProcess
@HiltAndroidApp
open class App : Application() {
@@ -37,6 +42,20 @@ open class App : Application() {
super.onCreate()
if (BuildConfig.DEBUG) Logging.install(LogCatLogger())
var foregroundExceptionHandled = false
val oldHandler = Thread.getDefaultUncaughtExceptionHandler()
Thread.setDefaultUncaughtExceptionHandler { thread, throwable ->
if (throwable.isForegroundServiceTimingException() && !foregroundExceptionHandled) {
foregroundExceptionHandled = true
log(TAG, WARN) { "Suppressed foreground service timing exception: ${throwable.asLog()}" }
Bugs.report(tag = TAG, "Foreground service timing exception suppressed", exception = throwable)
Looper.loop()
return@setDefaultUncaughtExceptionHandler
}
log(TAG, ERROR) { "UNCAUGHT EXCEPTION: ${throwable.asLog()}" }
if (oldHandler != null) oldHandler.uncaughtException(thread, throwable) else exitProcess(1)
}
autoReporting.setup(this)
log(TAG) { "onCreate() done! ${Exception().asLog()}" }
@@ -64,5 +83,14 @@ open class App : Application() {
companion object {
internal val TAG = logTag("CAP")
private fun Throwable.isForegroundServiceTimingException(): Boolean {
var current: Throwable? = this
while (current != null) {
if (current.javaClass.simpleName == "ForegroundServiceDidNotStartInTimeException") return true
current = current.cause
}
return false
}
}
}
@@ -79,6 +79,7 @@ class MonitorService : Service() {
private var monitoringJob: Job? = null
@Volatile private var monitorGeneration = 0
private var foregroundStartFailed = false
private var injectionComplete = false
@SuppressLint("InlinedApi")
private fun promoteToForeground(notification: Notification): Boolean {
@@ -114,7 +115,12 @@ class MonitorService : Service() {
if (!promoteToForeground(MonitorNotifications.createEarlyNotification(this))) {
foregroundStartFailed = true
stopSelf()
super.onCreate()
try {
super.onCreate()
} catch (e: Exception) {
log(TAG, WARN) { "Hilt DI failed in onCreate() (foreground denied): ${e.asLog()}" }
Bugs.report(tag = TAG, "Hilt DI failed in onCreate() (foreground denied)", exception = e)
}
return
}
@@ -127,6 +133,7 @@ class MonitorService : Service() {
stopSelf()
return
}
injectionComplete = true
log(TAG, VERBOSE) { "onCreate()" }
// Replace early notification with the full one from injected MonitorNotifications.
@@ -302,12 +309,16 @@ class MonitorService : Service() {
log(TAG, VERBOSE) { "onDestroy()" }
monitorScope.cancel("Service destroyed")
if (generalSettings.useExtraMonitorNotification.valueBlocking && !generalSettings.keepConnectedNotificationAfterDisconnect.valueBlocking) {
try {
notificationManager.cancel(MonitorNotifications.NOTIFICATION_ID_CONNECTED)
} catch (e: Exception) {
log(TAG, WARN) { "Failed to cancel connected notification: ${e.message}" }
if (injectionComplete) {
if (generalSettings.useExtraMonitorNotification.valueBlocking && !generalSettings.keepConnectedNotificationAfterDisconnect.valueBlocking) {
try {
notificationManager.cancel(MonitorNotifications.NOTIFICATION_ID_CONNECTED)
} catch (e: Exception) {
log(TAG, WARN) { "Failed to cancel connected notification: ${e.message}" }
}
}
} else {
log(TAG, WARN) { "onDestroy: Skipping notification cleanup, injection was incomplete." }
}
super.onDestroy()