ARTICLE AD BOX
I have an Android app that receives FCM messages and posts notifications on two channels:
A generic/default channel (IMPORTANCE_DEFAULT)
A critical channel (IMPORTANCE_HIGH, setBypassDnd(true))
Critical notifications should alert the user even when Do Not Disturb (DND) is enabled (when the user has allowed it)
When two notifications (generic + critical) arrive very close together:
Sometimes only one notification sound is played
Sometimes the critical one does not sound during DND
Behavior varies by timing and device
Does Android guarantee separate sounds when two notifications arrive at nearly the same time?
Is there a supported way to ensure the critical notification reliably alerts during DND without modifying global audio state?
Is this expected behavior of the Android notification system?
Below are some code snippets:
@HiltAndroidApp class MyApp : Application() { override fun onCreate() { super.onCreate() initNotification() } private fun initNotification() { FirebaseApp.initializeApp(this) val audioAttributes = AudioAttributes.Builder() .setUsage(AudioAttributes.USAGE_NOTIFICATION) .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) .build() val defaultSoundUri = ("android.resource://" + this.packageName + "/" + R.raw.default_notification).toUri() val channel = NotificationChannel( getString(R.string.new_default_notification_channel_id), getString(R.string.default_notification_channel_name), NotificationManager.IMPORTANCE_DEFAULT ).apply { setSound(defaultSoundUri, audioAttributes) } val criticalAudioAttributes = AudioAttributes.Builder() .setUsage(AudioAttributes.USAGE_ALARM) .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) .build() val criticalSoundUri = ("android.resource://" + this.packageName + "/" + R.raw.criticalalert).toUri() val criticalAlertsChannel = NotificationChannel( /* id = */ getString(R.string.critical_notification_channel_id), /* name = */ getString(R.string.critical_notification_channel_name), /* importance = */ NotificationManager.IMPORTANCE_HIGH ).apply { setSound(criticalSoundUri, criticalAudioAttributes) enableVibration(true) vibrationPattern = longArrayOf(0, 250, 250, 250) description = getString(R.string.critical_notification_channel_desc) setBypassDnd(true) } val notificationManager: NotificationManager = getSystemService( NOTIFICATION_SERVICE ) as NotificationManager notificationManager.createNotificationChannel(criticalAlertsChannel) notificationManager.createNotificationChannel(channel) } } @AndroidEntryPoint class MessagingService : FirebaseMessagingService() { override fun onMessageReceived(message: RemoteMessage) { val messageData = MessageData(message.data) val overrideDoNotDisturb = (messageData.isCritical == "1" && notificationManager.isNotificationPolicyAccessGranted) val notificationId = Random.nextInt(from = 1, until = 1000) val pendingIntent = messageData.alert?.let { rawAlert -> val alert = json.decodeFromString<Alert>(rawAlert) when (messageData.alertType) { ALERT_TYPE_INCIDENT -> getIncidentIntent(notificationId, alert.id) else -> null } } ?: safeLet(messageData.title, messageData.body) { title, body -> getGenericNotificationIntent(notificationId, title, body, messageData.url) } val channelId = if (overrideDoNotDisturb) { getString(R.string.critical_notification_channel_id) } else { getString(R.string.new_default_notification_channel_id) } val builder = NotificationCompat.Builder(this, channelId) .setSmallIcon(R.drawable.ic_alert_sa_notification) .setColor(ContextCompat.getColor(this, R.color.primary)) .setContentTitle(messageData.title) .setContentText(messageData.body) .setStyle(NotificationCompat.BigTextStyle().bigText(messageData.body)) .setContentIntent(pendingIntent) .setAutoCancel(true) .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) if (overrideDoNotDisturb) { builder.setPriority(NotificationCompat.PRIORITY_MAX) builder.setCategory(NotificationCompat.CATEGORY_ALARM) val audioManager = getSystemService(AUDIO_SERVICE) as AudioManager audioManager.ringerMode = AudioManager.RINGER_MODE_NORMAL audioManager.setStreamVolume( AudioManager.STREAM_NOTIFICATION, audioManager.getStreamMaxVolume(AudioManager.STREAM_NOTIFICATION), AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE ) notificationManager.notify(notificationId, builder.build()) } else { // Set a default priority for regular notifications builder.setPriority(NotificationCompat.PRIORITY_DEFAULT) // Delay regular notification to allow time for the critical alerts to play first Handler(Looper.getMainLooper()).postDelayed({ notificationManager.notify(notificationId, builder.build()) }, REGULAR_NOTIFICATION_DELAY) } } }