Severity: MEDIUM
Summary
The per-conversation notification stacking introduced in #180 rebuilds each conversation's MessagingStyle by re-extracting the entire prior history from the currently-posted notification and appending the new message. The extracted history is never trimmed, so for a busy conversation the rebuilt notification accumulates an ever-growing list of MessagingStyle.Message entries that are re-serialized across the Binder boundary on every single post.
Evidence
app/src/main/java/dev/ipf/darkmatter/notifications/LocalNotificationPresenter.kt
private fun messagingStyle(...): NotificationCompat.MessagingStyle {
...
val style = existingMessagingStyle(content.notificationTag)
?: NotificationCompat.MessagingStyle(self)
...
style.addMessage(content.body, update.timestampMs, sender) // appended; nothing trimmed
return style
}
private fun existingMessagingStyle(tag: String): NotificationCompat.MessagingStyle? {
val manager = context.getSystemService(NotificationManager::class.java) ?: return null
val existing =
runCatching { manager.activeNotifications } // Binder IPC into NotificationManagerService
.getOrNull()
?.firstOrNull { it.tag == tag && it.id == MESSAGE_NOTIFICATION_ID }
?: return null
return NotificationCompat.MessagingStyle.extractMessagingStyleFromNotification(existing.notification)
}
Every incoming message:
- Calls
manager.activeNotifications — a Binder IPC into NotificationManagerService — on each post.
- Re-extracts the full prior
MessagingStyle and appends, so the message list grows without bound for the life of the (un-dismissed) notification.
Impact
- On a high-traffic conversation this is O(n) work and an increasingly large parcel per notification. Once the accumulated message list (each entry carrying body text + a
Person) exceeds the ~1 MB Binder limit, notify() throws TransactionTooLargeException and the notification is silently dropped — the user stops getting notifications for an active conversation.
- Steady per-message IPC + re-serialization cost on the notification path.
Suggested fix
Cap the retained history when rebuilding — the system only renders ~6–7 lines, so keep only the last N messages (e.g. trim the extracted list, or build a fresh MessagingStyle seeded with a bounded recent set) rather than re-extracting and re-appending the unbounded prior history.
Validation
Post many messages (e.g. 200+) to one conversation without dismissing the notification; confirm the notification keeps updating and the parcel size stays bounded (no TransactionTooLargeException in logcat).
Severity: MEDIUM
Summary
The per-conversation notification stacking introduced in #180 rebuilds each conversation's
MessagingStyleby re-extracting the entire prior history from the currently-posted notification and appending the new message. The extracted history is never trimmed, so for a busy conversation the rebuilt notification accumulates an ever-growing list ofMessagingStyle.Messageentries that are re-serialized across the Binder boundary on every single post.Evidence
app/src/main/java/dev/ipf/darkmatter/notifications/LocalNotificationPresenter.ktEvery incoming message:
manager.activeNotifications— a Binder IPC intoNotificationManagerService— on each post.MessagingStyleand appends, so the message list grows without bound for the life of the (un-dismissed) notification.Impact
Person) exceeds the ~1 MB Binder limit,notify()throwsTransactionTooLargeExceptionand the notification is silently dropped — the user stops getting notifications for an active conversation.Suggested fix
Cap the retained history when rebuilding — the system only renders ~6–7 lines, so keep only the last N messages (e.g. trim the extracted list, or build a fresh
MessagingStyleseeded with a bounded recent set) rather than re-extracting and re-appending the unbounded prior history.Validation
Post many messages (e.g. 200+) to one conversation without dismissing the notification; confirm the notification keeps updating and the parcel size stays bounded (no
TransactionTooLargeExceptionin logcat).