From 86d3f3bc61136c5032f9381eb9f06f4b71709b84 Mon Sep 17 00:00:00 2001 From: darken Date: Fri, 13 Feb 2026 13:56:10 +0100 Subject: [PATCH] fix: Call startForeground() before Hilt DI to prevent timing crash Move startForeground() before super.onCreate() in MonitorService to avoid ForegroundServiceDidNotStartInTimeException when Hilt DI is slow on backgrounded cold starts. An early minimal notification is shown immediately, then replaced with the full one after DI completes. --- .../monitor/core/worker/MonitorService.kt | 17 +++++++++ .../capod/monitor/ui/MonitorNotifications.kt | 37 +++++++++++++++---- 2 files changed, 47 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/eu/darken/capod/monitor/core/worker/MonitorService.kt b/app/src/main/java/eu/darken/capod/monitor/core/worker/MonitorService.kt index 1098c332..e51b7e7b 100644 --- a/app/src/main/java/eu/darken/capod/monitor/core/worker/MonitorService.kt +++ b/app/src/main/java/eu/darken/capod/monitor/core/worker/MonitorService.kt @@ -73,9 +73,26 @@ class MonitorService : Service() { @SuppressLint("InlinedApi") override fun onCreate() { + // Promote to foreground BEFORE Hilt DI (triggered by super.onCreate()) to avoid + // ForegroundServiceDidNotStartInTimeException when DI is slow on backgrounded cold starts. + MonitorNotifications.ensureChannel(this) + val earlyNotification = MonitorNotifications.createEarlyNotification(this) + if (hasApiLevel(29)) { + startForeground( + MonitorNotifications.NOTIFICATION_ID, + earlyNotification, + ServiceInfo.FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE, + ) + } else { + startForeground(MonitorNotifications.NOTIFICATION_ID, earlyNotification) + } + super.onCreate() log(TAG, VERBOSE) { "onCreate()" } + // Replace early notification with the full one from injected MonitorNotifications. + // Second startForeground() with the same ID updates the notification in place and is + // preferred over notify() for robust foreground state on OEM variants. val notification = notifications.getStartupNotification() if (hasApiLevel(29)) { startForeground( diff --git a/app/src/main/java/eu/darken/capod/monitor/ui/MonitorNotifications.kt b/app/src/main/java/eu/darken/capod/monitor/ui/MonitorNotifications.kt index ed6c7668..bba29803 100644 --- a/app/src/main/java/eu/darken/capod/monitor/ui/MonitorNotifications.kt +++ b/app/src/main/java/eu/darken/capod/monitor/ui/MonitorNotifications.kt @@ -37,11 +37,7 @@ class MonitorNotifications @Inject constructor( private val builder: NotificationCompat.Builder init { - NotificationChannel( - NOTIFICATION_CHANNEL_ID, - context.getString(R.string.notification_channel_device_status_label), - NotificationManager.IMPORTANCE_LOW - ).run { notificationManager.createNotificationChannel(this) } + ensureChannel(context) NotificationChannel( NOTIFICATION_CHANNEL_ID_CONNECTED, context.getString(R.string.notification_channel_device_status_connected_label), @@ -51,7 +47,7 @@ class MonitorNotifications @Inject constructor( val openIntent = Intent(context, MainActivity::class.java) val openPi = PendingIntent.getActivity( context, - 0, + PENDING_INTENT_REQUEST_CODE, openIntent, PendingIntentCompat.FLAG_IMMUTABLE ) @@ -164,10 +160,37 @@ class MonitorNotifications @Inject constructor( companion object { val TAG = logTag("Monitor", "Notifications") - private val NOTIFICATION_CHANNEL_ID = "${BuildConfigWrap.APPLICATION_ID}.notification.channel.device.status" + internal val NOTIFICATION_CHANNEL_ID = "${BuildConfigWrap.APPLICATION_ID}.notification.channel.device.status" private val NOTIFICATION_CHANNEL_ID_CONNECTED = "${BuildConfigWrap.APPLICATION_ID}.notification.channel.device.status.connected" internal const val NOTIFICATION_ID = 1 internal const val NOTIFICATION_ID_CONNECTED = 2 + private const val PENDING_INTENT_REQUEST_CODE = 0 + + fun ensureChannel(context: Context) { + val nm = context.getSystemService(NotificationManager::class.java) + nm.createNotificationChannel( + NotificationChannel( + NOTIFICATION_CHANNEL_ID, + context.getString(R.string.notification_channel_device_status_label), + NotificationManager.IMPORTANCE_LOW, + ) + ) + } + + fun createEarlyNotification(context: Context): Notification { + val openPi = PendingIntent.getActivity( + context, PENDING_INTENT_REQUEST_CODE, + Intent(context, MainActivity::class.java), + PendingIntentCompat.FLAG_IMMUTABLE, + ) + return NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID) + .setContentIntent(openPi) + .setSmallIcon(R.drawable.devic_earbuds_generic_both) + .setContentTitle(context.getString(R.string.app_name)) + .setPriority(NotificationCompat.PRIORITY_LOW) + .setOngoing(true) + .build() + } } }