Notification Capture Service
Relevant source files
The following files were used as context for generating this wiki page:
- app/src/main/kotlin/com/fathiraz/flowbell/data/services/NotificationListenerService.kt
- app/src/main/kotlin/com/fathiraz/flowbell/data/services/WebhookService.kt
- app/src/main/kotlin/com/fathiraz/flowbell/data/mappers/NotificationMapper.kt
- app/src/main/kotlin/com/fathiraz/flowbell/data/local/database/dao/NotificationQueueDao.kt
- app/src/main/AndroidManifest.xml
- README.md
Introduction
The Notification Capture Service is the core engine of FlowBell, responsible for intercepting, processing, and forwarding Android system notifications to user-defined webhooks. It operates as a background service leveraging Android's NotificationListenerService API to observe incoming alerts from specific allowed applications.
The service acts as a bridge, enriching raw notification metadata with device information, applying keyword filters, and managing a local queue for batch delivery. This ensures that notification events are reliably delivered even during network instability, while maintaining operational visibility through a history and statistics system stored in a local Room database.
Sources: README.md:43-46, app/src/main/kotlin/com/fathiraz/flowbell/data/services/NotificationListenerService.kt:53-65
Architecture and Core Components
The service architecture is built on a producer-consumer model where the Android system produces notifications and the NotificationListenerService consumes them for batch processing.
Key Components
| Component | Description |
|---|---|
NotificationListenerService | The primary entry point that receives StatusBarNotification objects from the OS. |
NotificationMapper | Responsible for converting raw notification data into domain entities and Webhook payloads. |
NotificationFilterUtil | Applies global and per-app keyword filters to determine if a notification should be forwarded. |
NotificationQueueDao | Manages the persistence of notification logs and statuses (PENDING, SENT, FAILED) in the Room database. |
HttpRequestUtils | Handles the physical transmission of JSON payloads to the configured webhook URL. |
Sources: app/src/main/kotlin/com/fathiraz/flowbell/data/services/NotificationListenerService.kt:57-80, app/src/main/kotlin/com/fathiraz/flowbell/data/mappers/NotificationMapper.kt:18-25, app/src/main/kotlin/com/fathiraz/flowbell/data/local/database/dao/NotificationQueueDao.kt:12-20
Service Data Flow
The following diagram illustrates the lifecycle of a notification from interception to webhook delivery.
The system uses a 5-second interval for batch processing to balance real-time delivery with device efficiency.
Sources: app/src/main/kotlin/com/fathiraz/flowbell/data/services/NotificationListenerService.kt:111-131, app/src/main/kotlin/com/fathiraz/flowbell/data/services/NotificationListenerService.kt:143-165
Capture and Processing Logic
Notification Interception
The service overrides onNotificationPosted(sbn: StatusBarNotification) to capture events. It immediately generates a unique identifier using the package name, notification ID, and post time to prevent processing duplicates.
val notificationId = "${sbn.packageName}:${sbn.id}:${sbn.postTime}"
if (processedNotifications.contains(notificationId)) return
Sources: app/src/main/kotlin/com/fathiraz/flowbell/data/services/NotificationListenerService.kt:145-155
Filtering and Validation
Before a notification is added to the batch, it undergoes three checks:
- Content Validation: Notifications with empty titles or text are skipped.
- App Forwarding Status: The
AppPreferencesRepositoryis queried to see if the user has enabled forwarding for that specific package. - Keyword Filtering: The
NotificationFilterUtilchecks the content against global and app-specific keyword lists.
Sources: app/src/main/kotlin/com/fathiraz/flowbell/data/services/NotificationListenerService.kt:175-207
Data Extraction
The service extracts metadata to create a NotificationData object. This includes the application name (derived via PackageManager), notification extras (Title, Text, SubText), and priority flags.
Sources: app/src/main/kotlin/com/fathiraz/flowbell/data/services/NotificationListenerService.kt:218-245
Webhook Delivery and Payload Structure
Batch Processing
Notifications are offered to a ConcurrentLinkedQueue. A coroutine-based loop drains this queue every 5 seconds. If a webhook URL is configured, the service iterates through the batch, sending each notification as an individual HTTP POST request.
Sources: app/src/main/kotlin/com/fathiraz/flowbell/data/services/NotificationListenerService.kt:111-131, app/src/main/kotlin/com/fathiraz/flowbell/data/services/NotificationListenerService.kt:320-335
Sequence Diagram: Webhook Transmission
The interaction between the listener service and the remote endpoint is handled as follows:
Payload Schema
The NotificationMapper constructs a JSON payload containing several nested objects:
- app: Package name and display name.
- notification: Title, text, priority, and clearable status.
- device: Hardware metadata (model, manufacturer, Android version).
- security: Nonce and algorithm identifiers for future signature implementation.
Sources: app/src/main/kotlin/com/fathiraz/flowbell/data/mappers/NotificationMapper.kt:85-115, app/src/main/kotlin/com/fathiraz/flowbell/data/services/NotificationListenerService.kt:454-490
Persistence and Queue Management
All intercepted notifications are logged in the notification_queue table via the NotificationQueueDao. This allows the application to track delivery success rates and provides a history for the user.
| Field | Type | Description |
|---|---|---|
status | String | Current state: PENDING, SENT, FAILED, or PROCESSING. |
retryCount | Int | Number of delivery attempts made. |
lastAttemptAt | Long | Timestamp of the most recent transmission attempt. |
errorMessage | String | Details of the failure if the status is FAILED. |
Sources: app/src/main/kotlin/com/fathiraz/flowbell/data/local/database/dao/NotificationQueueDao.kt:20-60
Cleanup Operations
To prevent database bloat, the DAO includes methods for cleaning up old records based on a cutoff time:
cleanupOldSentNotifications: Removes successfully sent logs.cleanupFailedNotifications: Removes logs that have exceeded the maximum retry limit (default 3).
Sources: app/src/main/kotlin/com/fathiraz/flowbell/data/local/database/dao/NotificationQueueDao.kt:125-145
Security and System Integration
The service must be declared in the AndroidManifest.xml with the BIND_NOTIFICATION_LISTENER_SERVICE permission to allow the Android system to bind to it.
<service
android:name=".NotificationListenerService"
android:exported="true"
android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
<intent-filter>
<action android:name="android.service.notification.NotificationListenerService" />
</intent-filter>
</service>
Sources: app/src/main/AndroidManifest.xml:35-42
Security Signatures
The WebhookService includes a placeholder for generating HMAC-SHA256 signatures for payloads, intended to allow the receiving webhook to verify the authenticity of the notification data.
Sources: app/src/main/kotlin/com/fathiraz/flowbell/data/services/WebhookService.kt:82-105
Conclusion
The Notification Capture Service provides a robust framework for bridging mobile notifications to the web. By combining real-time interception with batched processing and local persistence, it ensures high reliability and minimal battery impact. Developers can extend this service by modifying the NotificationMapper for custom payload structures or by adjusting the batching logic in NotificationListenerService.