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() + } } }