# How SmsForwarder Handles App Notification Forwarding Using NotificationListenerService

> Discover how SmsForwarder uses NotificationListenerService to forward app notifications. Learn about interception, filtering, and delivery via WorkManager.

- Repository: [pppscn/SmsForwarder](https://github.com/pppscn/SmsForwarder)
- Tags: deep-dive
- Published: 2026-06-22

---

**SmsForwarder implements app notification forwarding by extending Android's `NotificationListenerService` in [`NotificationService.kt`](https://github.com/pppscn/SmsForwarder/blob/main/NotificationService.kt), which intercepts notifications via `onNotificationPosted()`, filters them against user-defined settings, packages the data into `MsgInfo` objects, and dispatches them to `SendWorker` via WorkManager for delivery.**

SmsForwarder is an open-source Android application (pppscn/SmsForwarder) that forwards both SMS and app notifications to various channels like Telegram, WeChat, and email. While SMS handling uses traditional broadcast receivers, app notification forwarding relies on Android's `NotificationListenerService` to capture and process notifications from other applications in real-time.

## Core Implementation in NotificationService.kt

The heart of the notification forwarding system resides in [`NotificationService.kt`](https://github.com/pppscn/SmsForwarder/blob/main/NotificationService.kt), where the service extends `NotificationListenerService` to gain access to the system notification stream.

### Extending NotificationListenerService

The service runs continuously in the background while the app is active, monitoring all posted notifications across the system. When the system broadcasts a new notification, the `onNotificationPosted(StatusBarNotification)` callback triggers the forwarding pipeline.

### The onNotificationPosted() Method

This method serves as the entry point for all notification processing. According to the source code in [`NotificationService.kt`](https://github.com/pppscn/SmsForwarder/blob/main/NotificationService.kt), it performs immediate validation before processing:

1. Checks if the app is in **pure-client mode** (`enablePureClientMode`)
2. Verifies the master **app notification switch** (`enableAppNotify`)
3. Validates the **screen lock state** against `enableNotUserPresent`
4. Extracts the **package name**, **title**, and **text** from the `StatusBarNotification`

Empty notifications (where both title and text are blank) are ignored immediately to prevent processing noise.

## Configuration and Filtering Logic

Before forwarding any notification, the service validates multiple global switches stored in [`SettingUtils.kt`](https://github.com/pppscn/SmsForwarder/blob/main/SettingUtils.kt). These settings provide granular control over the forwarding behavior:

- **`enablePureClientMode`**: When true, the app functions as a pure client and ignores all forwarded notifications.
- **`enableAppNotify`**: The master toggle that enables or disables app notification forwarding entirely.
- **`enableNotUserPresent`**: When enabled, notifications forward only while the screen is unlocked (device is user-present).
- **`cancelExtraAppNotify`**: A newline-separated denylist of package names whose notifications are silently dismissed without forwarding.
- **`enableCancelAppNotify`**: If a forwarding rule matches the incoming message, the original notification is automatically cancelled from the status bar.

## Message Processing and Forwarding Pipeline

Once the settings checks pass, the service extracts notification content and prepares it for forwarding.

### Extracting Notification Content

For Android Lollipop (API 21) and above, the service extracts both standard and large text content to ensure full message capture:

```kotlin
val pkg = sbn?.packageName ?: return
val extras = sbn.notification.extras ?: return
val title = extras.getCharSequence(Notification.EXTRA_TITLE)?.toString() ?: ""
val text = extras.getCharSequence(Notification.EXTRA_TEXT)?.toString() ?: ""

if (title.isBlank() && text.isBlank()) return

```

### Packaging into MsgInfo

The extracted data is encapsulated in a `MsgInfo` object that standardizes the message format across the application:

```kotlin
val msgInfo = MsgInfo(
    type = "app",
    from = sbn.packageName,
    content = text,
    receivedAt = Date(),
    title = title,
    uid = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) sbn.uid else -1
)

```

### Dispatching via WorkManager

The `MsgInfo` is serialized and passed to `SendWorker` via WorkManager, which handles the actual forwarding asynchronously according to user-defined rules:

```kotlin
val work = OneTimeWorkRequestBuilder<SendWorker>()
    .setInputData(workDataOf(Worker.SEND_MSG_INFO to Gson().toJson(msgInfo)))
    .build()
WorkManager.getInstance(applicationContext).enqueue(work)

```

## Notification Cancellation and Advanced Filtering

The service provides automatic notification management based on user preferences.

### Silencing Specific Apps

When `cancelExtraAppNotify` contains package names, the service splits the string by newlines and cancels notifications from those apps immediately:

```kotlin
SettingUtils.cancelExtraAppNotify
    .takeIf { it.isNotEmpty() }
    ?.split("\n")
    ?.forEach { app ->
        if (sbn.packageName == app.trim()) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                cancelNotification(sbn.key)
            } else {
                cancelNotification(sbn.packageName, sbn.tag, sbn.id)
            }
        }
    }

```

### Rule-Based Cancellation

If `enableCancelAppNotify` is active and a forwarding rule matches the notification content, the service calls `cancelNotification()` to remove the original alert from the user's status bar after successful forwarding.

## Service Lifecycle and Resilience

Maintaining a persistent connection to the notification stream requires handling service disconnections gracefully.

### Automatic Rebinding

In [`NotificationService.kt`](https://github.com/pppscn/SmsForwarder/blob/main/NotificationService.kt), the `onListenerDisconnected()` callback triggers `requestRebind()` (available on Android Nougat and above) to automatically re-attach the listener if the system disconnects it, ensuring continuous operation without user intervention.

### Enabling and Disabling the Service

The [`CommonUtils.kt`](https://github.com/pppscn/SmsForwarder/blob/main/CommonUtils.kt) file provides static helpers for UI components to manage the service state:

- **`isNotificationListenerServiceEnabled(context)`**: Checks whether the user has granted notification access permission in system settings.
- **`toggleNotificationListenerService(context)`**: Programmatically opens the system "Notification access" settings page, forcing the user to enable the component if disabled.

These utilities are invoked from `ForegroundService`, [`SettingsFragment.kt`](https://github.com/pppscn/SmsForwarder/blob/main/SettingsFragment.kt), and other UI components whenever the user toggles the "App Notification" switch.

## UI Integration and User Control

The [`SettingsFragment.kt`](https://github.com/pppscn/SmsForwarder/blob/main/SettingsFragment.kt) implements the user-facing controls for notification forwarding. When a user enables the feature, the app verifies the service state:

```kotlin
if (SettingUtils.enableAppNotify && !CommonUtils.isNotificationListenerServiceEnabled(requireContext())) {
    // Prompt the user to enable the listener
    CommonUtils.toggleNotificationListenerService(requireContext())
}

```

This integration ensures that the service cannot be accidentally enabled without the proper system permissions, preventing silent failures.

## Summary

- **SmsForwarder** uses [`NotificationService.kt`](https://github.com/pppscn/SmsForwarder/blob/main/NotificationService.kt) to extend Android's `NotificationListenerService` and intercept system notifications.
- The **forwarding pipeline** consists of: settings validation → content extraction → `MsgInfo` packaging → `SendWorker` dispatch via WorkManager.
- **Five critical settings** in [`SettingUtils.kt`](https://github.com/pppscn/SmsForwarder/blob/main/SettingUtils.kt) control behavior: pure-client mode, master enable switch, screen lock requirements, app-specific denylists, and automatic cancellation.
- The service implements **automatic rebinding** via `requestRebind()` in `onListenerDisconnected()` to survive system disconnections.
- **CommonUtils.kt** provides the bridge between UI components ([`SettingsFragment.kt`](https://github.com/pppscn/SmsForwarder/blob/main/SettingsFragment.kt)) and the system notification access settings.

## Frequently Asked Questions

### What Android permissions are required for notification forwarding?

SmsForwarder requires the **Notification Access** permission, which grants the app permission to read all incoming notifications. This is a system-level permission that users must explicitly enable in Android Settings > Apps > Special access > Notification access. The app cannot forward notifications without this permission, as it relies on `NotificationListenerService` to receive the `StatusBarNotification` objects.

### How does SmsForwarder handle service disconnections or app kills?

The service implements resilience mechanisms in [`NotificationService.kt`](https://github.com/pppscn/SmsForwarder/blob/main/NotificationService.kt). When `onListenerDisconnected()` is triggered, the service automatically calls `requestRebind()` on Android Nougat (API 24) and above to re-establish the connection to the notification stream. Additionally, the `ForegroundService` helps keep the process alive, though the NotificationListener itself is managed by the Android system and will restart if the app is not force-stopped.

### Can users exclude specific apps from notification forwarding?

Yes, users can define a denylist using the `cancelExtraAppNotify` setting, which accepts a newline-separated list of package names. Notifications from these apps are intercepted and immediately cancelled via `cancelNotification()` without being forwarded. Additionally, the `enableCancelAppNotify` setting allows the app to automatically dismiss notifications from forwarded apps if a matching rule is found.

### What happens if the screen is locked when a notification arrives?

Behavior depends on the `enableNotUserPresent` setting in [`SettingUtils.kt`](https://github.com/pppscn/SmsForwarder/blob/main/SettingUtils.kt). When this setting is enabled, the service checks the device state and only forwards notifications while the screen is unlocked (user present). If the device is locked, the notification is ignored. This allows users to filter sensitive notifications that arrive while the device is unattended.