17 KiB
Notification System
Table of Contents
- Overview
- NtfManager Abstract Class
- Android Notification Manager
- Desktop Notification Manager
- Android Background Messaging
- Notification Privacy
- Source Files
Executive Summary
SimpleX Chat uses platform-specific notification strategies. The common NtfManager abstract class defines the notification contract with shared helper methods for message, contact, and call notifications. Android implements a full notification system with channels, grouped summaries, full-screen call intents, and a foreground service (SimplexService) or periodic WorkManager tasks for background message fetching. Desktop uses the TwoSlices library (with OS-native fallbacks) for system notifications. Notification privacy is controlled via NotificationPreviewMode (MESSAGE, CONTACT, HIDDEN).
1. Overview
Notifications serve three purposes in SimpleX Chat:
- Message notifications -- alert users to new messages when the app is not focused on the relevant chat.
- Call notifications -- high-priority alerts for incoming WebRTC calls, with full-screen intent support on Android for lock-screen scenarios.
- Contact events -- notifications for contact connection and contact request events.
The architecture uses an abstract NtfManager in common code with platform-specific actual implementations. On Android, background message delivery requires a foreground service or periodic WorkManager tasks since SimpleX does not use push notifications (no Firebase/APNs dependency for privacy).
2. NtfManager Abstract Class
NtfManager.kt (139 lines, commonMain)
The global ntfManager instance is declared at line 17 and initialized by each platform at startup.
Concrete methods
| Method | Line | Description |
|---|---|---|
notifyContactConnected |
L20 | Displays "contact connected" notification for a Contact |
notifyContactRequestReceived |
L27 | Shows contact request notification with an "Accept" action button |
notifyMessageReceived |
L38 | Conditionally shows message notification based on ntfsEnabled, showNotification, and whether user is viewing that chat |
acceptContactRequestAction |
L51 | Accepts a contact request from a notification action |
openChatAction |
L59 | Opens a specific chat from a notification tap, switching user if needed |
showChatsAction |
L74 | Opens the chat list, switching user if needed |
acceptCallAction |
L88 | Accepts a call invitation from a notification action |
Abstract methods
| Method | Line | Description |
|---|---|---|
notifyCallInvitation |
L98 | Show call notification; returns true if notification was shown |
displayNotification |
L102 | Display a message notification with optional image and action buttons |
cancelCallNotification |
L103 | Cancel the active call notification |
hasNotificationsForChat |
L99 | Check if notifications exist for a given chat |
cancelNotificationsForChat |
L100 | Cancel all notifications for a specific chat |
cancelNotificationsForUser |
L101 | Cancel all notifications for a user profile |
cancelAllNotifications |
L104 | Cancel all notifications |
showMessage |
L105 | Show a simple title+text notification |
androidCreateNtfChannelsMaybeShowAlert |
L107 | Android-only: create notification channels (triggers permission prompt on Android 13+) |
Private helpers
awaitChatStartedIfNeeded(line 109): Waits up to 30 seconds for chat initialization (handles database decryption delay).hideSecrets(line 122): ReplacesFormat.Secretformatted text with"..."in notification previews.
3. Android Notification Manager
NtfManager.android.kt (331 lines)
Implemented as a Kotlin object (singleton) in the Android module.
Notification channels
| Channel | Constant | Importance | Purpose |
|---|---|---|---|
| Messages | MessageChannel (chat.simplex.app.MESSAGE_NOTIFICATION) |
HIGH | All chat message notifications |
| Calls | CallChannel (chat.simplex.app.CALL_NOTIFICATION_2) |
HIGH | Incoming call alerts with custom ringtone and vibration |
Channel creation happens in createNtfChannelsMaybeShowAlert() (line 298). Old channel IDs (CALL_NOTIFICATION, CALL_NOTIFICATION_1, LOCK_SCREEN_CALL_NOTIFICATION) are explicitly deleted.
displayNotification (messages)
- Uses
NotificationCompat.BuilderwithMessageChannel. - Groups notifications using
MessageGroupwithGROUP_ALERT_CHILDRENbehavior. - Applies rate limiting: silent mode if notification for the same
(userId, chatId)was shown within 30 seconds (msgNtfTimeoutMs). - Creates a group summary notification (line 142) with
setGroupSummary(true). - Content intent uses
TaskStackBuilderfor proper back stack. - Supports
NotificationAction.ACCEPT_CONTACT_REQUESTaction buttons viaNtfActionReceiverbroadcast receiver.
notifyCallInvitation
- Returns
false(no notification) if app is in foreground -- in-app alert is used instead. - Lock screen / screen off: Uses
setFullScreenIntentwith aPendingIntenttoCallActivity, plusVISIBILITY_PUBLIC. - Foreground / unlocked: Uses regular notification with Accept/Reject action buttons and a custom ringtone (
ring_onceraw resource). - Notification flags include
FLAG_INSISTENTfor repeating sound and vibration. - Call notification channel vibration pattern:
[250, 250, 0, 2600]ms.
Cancel operations
| Method | Line | Description |
|---|---|---|
cancelNotificationsForChat |
L75 | Cancels by chatId.hashCode(), cleans up group summary if no children remain |
cancelNotificationsForUser |
L88 | Iterates and cancels all notifications for a given userId |
cancelCallNotification |
L261 | Cancels the singleton call notification (CallNotificationId = -1) |
cancelAllNotifications |
L265 | Cancels all via NotificationManager.cancelAll() |
NtfActionReceiver
Line 311: A BroadcastReceiver that handles notification action intents:
ACCEPT_CONTACT_REQUEST-- callsntfManager.acceptContactRequestAction()RejectCallAction-- callscallManager.endCall()on the invitation
4. Desktop Notification Manager
NtfManager.desktop.kt (193 lines)
Implemented as a Kotlin object using the TwoSlices library (Toast builder API) for cross-platform desktop notifications.
displayNotification
- Suppresses if
!user.showNotifications. - Respects
NotificationPreviewModefor title and content. - Calls
displayNotificationViaLib()(line 114) which builds aToastwith title, content, icon, action buttons, and default action. - Icon images are written to a temporary PNG file via
prepareIconPath()(line 150). - Default action on click opens the relevant chat via
openChatAction().
notifyCallInvitation
- Returns
falseif the SimpleX window is focused (in-app alert used instead). - Creates a notification with Accept and Reject action buttons.
- Default click action opens the chat.
OS-native fallbacks
Line 162: The displayNotification private method dispatches based on desktopPlatform:
| Platform | Method |
|---|---|
| Linux | notify-send command with optional -i icon |
| Windows | SystemTray with TrayIcon.displayMessage() |
| macOS | osascript -e 'display notification ...' |
Notification tracking
Previous notifications are tracked in prevNtfs: ArrayList<Pair<Pair<Long, ChatId>, Slice>> with a Mutex for thread safety. Cancel operations remove entries from this list.
5. Android Background Messaging
5.1 SimplexService.kt (734 lines)
A foreground Service that keeps the app process alive for continuous message receiving. This is SimpleX's privacy-preserving alternative to push notifications.
Service lifecycle:
startService()(line 128): Waits for database migration, validates DB status, saves service state as STARTED. WakeLock acquisition is commented out -- the app relies on battery optimization whitelisting instead.onDestroy()(line 87): Releases wakelocks, saves state as STOPPED, sends broadcast toAutoRestartReceiverif allowed.onTaskRemoved()(line 211): Schedules restart viaAlarmManagerwhen the app is swiped from recents.
Notification:
- Channel:
SIMPLEX_SERVICE_NOTIFICATIONwithIMPORTANCE_LOWand badge disabled (line 165). - Shows a persistent notification with a "Hide notification" action that opens channel settings.
- Service ID:
6789.
Restart mechanisms:
| Receiver | Line | Trigger |
|---|---|---|
StartReceiver |
L234 | Device boot (BOOT_COMPLETED) |
AutoRestartReceiver |
L253 | Service destruction |
AppUpdateReceiver |
L261 | App update (MY_PACKAGE_REPLACED) |
ServiceStartWorker |
L283 | WorkManager one-time task |
Battery optimization:
isBackgroundAllowed()(line 681): Checks bothisIgnoringBatteryOptimizationsand!isBackgroundRestricted.showBackgroundServiceNoticeIfNeeded()(line 430): Shows alerts guiding users to disable battery optimization or background restriction. Includes Xiaomi-specific guidance.disableNotifications()(line 722): Switches mode to OFF, disables receivers, cancels workers.
5.2 MessagesFetcherWorker.kt (100 lines)
A CoroutineWorker used in PERIODIC notification mode as an alternative to the persistent foreground service:
scheduleWork()(line 18): Schedules aOneTimeWorkRequestwith a default 600-second (10 minute) initial delay and 60-second duration. RequiresNetworkType.CONNECTEDconstraint.doWork()(line 53): Skips ifSimplexServiceis already running. Initializes chat controller if needed (self-destruct mode). Waits for DB migration. Runs for up todurationSecseconds, polling every 5 seconds until no messages have been received for 10 seconds (WAIT_AFTER_LAST_MESSAGE).- Self-rescheduling: Always calls
reschedule()at the end (creating a chain of one-time tasks that simulate periodic execution).
5.3 Notification modes
Defined in SimpleXAPI.kt:
enum class NotificationsMode {
OFF, // No background message fetching
PERIODIC, // WorkManager periodic tasks (MessagesFetcherWorker)
SERVICE; // Persistent foreground service (SimplexService)
}
Default is SERVICE. The requiresIgnoringBattery property is an Android extension property (defined in Extensions.kt, not on the enum itself) whose value depends on the SDK version: SERVICE requires ignoring battery optimizations since SDK S (API 31), PERIODIC since SDK M (API 23).
6. Notification Privacy
Defined in ChatModel.kt:
enum class NotificationPreviewMode {
MESSAGE, // Show sender name and message text
CONTACT, // Show sender name, generic "new message" text
HIDDEN; // Show "Somebody" as sender, generic "new message" text
}
Privacy mode affects:
- Notification title:
HIDDENuses"Somebody"instead of contact name. - Notification content: Only
MESSAGEmode shows actual message text. - Large icon:
HIDDENuses the app icon instead of the contact's profile image. - Call notifications:
HIDDENhides the caller's name and profile image.
Both Android and Desktop implementations check appPreferences.notificationPreviewMode.get() before constructing notification content.
7. Source Files
| File | Path | Lines | Description |
|---|---|---|---|
NtfManager.kt |
common/src/commonMain/.../platform/NtfManager.kt |
139 | Abstract notification manager with shared logic |
NtfManager.android.kt |
android/src/main/java/.../model/NtfManager.android.kt |
331 | Android notification channels, groups, call intents |
NtfManager.desktop.kt |
common/src/desktopMain/.../model/NtfManager.desktop.kt |
193 | Desktop notifications via TwoSlices/OS-native |
SimplexService.kt |
android/src/main/java/.../SimplexService.kt |
734 | Android foreground service for background messaging |
MessagesFetcherWorker.kt |
android/src/main/java/.../MessagesFetcherWorker.kt |
100 | WorkManager periodic message fetcher |
ChatModel.kt |
common/src/commonMain/.../model/ChatModel.kt |
-- | NotificationPreviewMode enum (L4823) |
SimpleXAPI.kt |
common/src/commonMain/.../model/SimpleXAPI.kt |
-- | NotificationsMode enum (L7739) |