# Bluetooth and Network State Change Triggers in SmsForwarder: A Complete Guide

> Discover Bluetooth and network state change triggers in SmsForwarder. Learn how this guide details event handling for seamless message forwarding based on device and connectivity status.

- Repository: [pppscn/SmsForwarder](https://github.com/pppscn/SmsForwarder)
- Tags: how-to-guide
- Published: 2026-06-22

---

**SmsForwarder listens to nine distinct Bluetooth broadcast intents and three network-related intents via dedicated receivers, queuing background workers to execute user-defined forwarding rules when device or connectivity states change.**

The open-source Android app **SmsForwarder** (pppscn/SmsForwarder) extends beyond SMS forwarding by monitoring system state changes. Developers and power users can configure automated tasks triggered by specific Bluetooth and network events, with the logic implemented in Kotlin broadcast receivers that interface with WorkManager for background processing.

## Bluetooth State Change Triggers

The `BluetoothReceiver` class in [`app/src/main/kotlin/cn/ppps/forwarder/receiver/BluetoothReceiver.kt`](https://github.com/pppscn/SmsForwarder/blob/main/app/src/main/kotlin/cn/ppps/forwarder/receiver/BluetoothReceiver.kt) registers for nine specific Android broadcast intents. These intents are parsed in a `when (intent.action)` block that dispatches to specialized handler methods.

### Device Discovery Events

When scanning for nearby devices, the receiver captures two key actions:

- **`BluetoothDevice.ACTION_FOUND`** – Dispatched when a new device is discovered during an active scan. The handler extracts device details and updates `TaskUtils` state.
- **`BluetoothAdapter.ACTION_DISCOVERY_FINISHED`** – Signals that the scan cycle has completed. This triggers `handleDiscoveryFinished()` to process accumulated results.

### Adapter State Changes

System-level Bluetooth status changes fire these intents:

- **`BluetoothAdapter.ACTION_STATE_CHANGED`** – Indicates the adapter is turning on/off or transitioning between states (OFF, TURNING_ON, ON, TURNING_OFF).
- **`BluetoothAdapter.ACTION_SCAN_MODE_CHANGED`** – Reports changes in discoverability (SCAN_MODE_NONE, SCAN_MODE_CONNECTABLE, SCAN_MODE_CONNECTABLE_DISCOVERABLE).
- **`BluetoothAdapter.ACTION_LOCAL_NAME_CHANGED`** – Fires when the device's broadcast Bluetooth name is modified.

### Connection and Bonding Events

Physical layer and pairing status updates include:

- **`BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED`** – State changes for active connections (STATE_DISCONNECTED, STATE_CONNECTING, STATE_CONNECTED, STATE_DISCONNECTING).
- **`BluetoothDevice.ACTION_BOND_STATE_CHANGED`** – Tracks pairing progression (BOND_NONE, BOND_BONDING, BOND_BONDED).
- **`BluetoothDevice.ACTION_ACL_CONNECTED`** – Low-level ACL (Asynchronous Connection-oriented Logical) link established.
- **`BluetoothDevice.ACTION_ACL_DISCONNECTED`** – ACL link terminated.

Each intent routes to a dedicated handler (e.g., `handleActionFound()`, `handleStateChanged()`, `handleAclConnected()`), which updates the global task state and conditionally enqueues a `BluetoothWorker` via WorkManager.

## Network State Change Triggers

The `NetworkChangeReceiver` in [`app/src/main/kotlin/cn/ppps/forwarder/receiver/NetworkChangeReceiver.kt`](https://github.com/pppscn/SmsForwarder/blob/main/app/src/main/kotlin/cn/ppps/forwarder/receiver/NetworkChangeReceiver.kt) monitors connectivity changes through three primary intents, parsed in its `onReceive()` method.

### Connectivity Manager Events

- **`ConnectivityManager.CONNECTIVITY_ACTION`** – The legacy but still-supported broadcast indicating general network availability changes. The receiver extracts `NetworkInfo` and validates actual state changes before proceeding.

### Wi-Fi State Changes

Wi-Fi specific monitoring uses two distinct actions:

- **`WifiManager.WIFI_STATE_CHANGED_ACTION`** – Tracks the Wi-Fi adapter hardware state (WIFI_STATE_DISABLED, WIFI_STATE_ENABLED, etc.).
- **`WifiManager.NETWORK_STATE_CHANGED_ACTION`** – Reports connection state changes to specific access points (CONNECTED, DISCONNECTED).

When these intents fire, the receiver updates `TaskUtils.networkState`, `TaskUtils.dataSimSlot`, and `TaskUtils.wifiSsid`. If the state actually changed, it schedules a `NetworkWorker` with a delay defined by `DELAY_TIME_AFTER_SIM_READY` to prevent rapid-fire executions during connectivity flapping.

## How Receivers Process Triggers

Both receivers follow a similar execution pattern. First, they validate the intent action and extract relevant parcelable extras. Then they update the singleton `TaskUtils` state repository, which holds current values for `networkState`, `discoveredDevices`, and bonding status.

Finally, when user-defined conditions match the trigger criteria, the receivers construct `OneTimeWorkRequest` instances:

```kotlin
// From BluetoothReceiver.kt - enqueuing work when conditions match
val request = OneTimeWorkRequestBuilder<BluetoothWorker>()
    .setInputData(workDataOf(TaskWorker.CONDITION_TYPE to TASK_CONDITION_BLUETOOTH))
    .build()
WorkManager.getInstance(context).enqueue(request)

```

The `NetworkChangeReceiver` adds a mandatory delay to accommodate SIM card initialization:

```kotlin
// From NetworkChangeReceiver.kt - delayed execution pattern
val request = OneTimeWorkRequestBuilder<NetworkWorker>()
    .setInitialDelay(DELAY_TIME_AFTER_SIM_READY, TimeUnit.MILLISECONDS)
    .setInputData(workDataOf(TaskWorker.CONDITION_TYPE to TASK_CONDITION_NETWORK))
    .build()
WorkManager.getInstance(context).enqueue(request)

```

## Configuring Custom Conditions

Users define which specific triggers activate forwarding rules through data classes stored in the task configuration. The `BluetoothSetting` entity in [`app/src/main/kotlin/cn/ppps/forwarder/entity/condition/BluetoothSetting.kt`](https://github.com/pppscn/SmsForwarder/blob/main/app/src/main/kotlin/cn/ppps/forwarder/entity/condition/BluetoothSetting.kt) allows selecting specific events:

```kotlin
// Example: Configure triggers for connection events only
val setting = BluetoothSetting(
    event = listOf(
        BluetoothAdapter.ACTION_STATE_CHANGED,
        BluetoothDevice.ACTION_ACL_CONNECTED,
        BluetoothDevice.ACTION_ACL_DISCONNECTED
    )
)
val json = Gson().toJson(setting) // Stored in task condition JSON

```

Similarly, `NetworkSetting` in [`app/src/main/kotlin/cn/ppps/forwarder/entity/condition/NetworkSetting.kt`](https://github.com/pppscn/SmsForwarder/blob/main/app/src/main/kotlin/cn/ppps/forwarder/entity/condition/NetworkSetting.kt) defines network-specific parameters such as required SSID patterns or connection types.

## Summary

- **SmsForwarder** registers `BluetoothReceiver` for nine distinct Bluetooth intents including device discovery, adapter state, and ACL connection changes.
- Network monitoring occurs through `NetworkChangeReceiver`, which listens to connectivity changes and Wi-Fi state broadcasts.
- Both receivers update `TaskUtils` global state before queuing `BluetoothWorker` or `NetworkWorker` tasks via WorkManager.
- The `NetworkChangeReceiver` specifically implements a delay mechanism (`DELAY_TIME_AFTER_SIM_READY`) to stabilize task execution after connectivity changes.
- Task conditions are defined through `BluetoothSetting` and `NetworkSetting` data classes, allowing granular control over which specific state changes trigger forwarding rules.

## Frequently Asked Questions

### What is the difference between ACTION_ACL_CONNECTED and ACTION_CONNECTION_STATE_CHANGED?

**ACTION_ACL_CONNECTED** fires at the low-level Bluetooth protocol layer when the Asynchronous Connection-oriented Logical link is established, while **ACTION_CONNECTION_STATE_CHANGED** reports higher-level profile connection states (such as A2DP or HSP profile connections). SmsForwarder processes both in [`BluetoothReceiver.kt`](https://github.com/pppscn/SmsForwarder/blob/main/BluetoothReceiver.kt) to ensure comprehensive coverage of physical and logical connection states.

### Why does the NetworkChangeReceiver use a delay before enqueueing work?

The receiver waits for `DELAY_TIME_AFTER_SIM_READY` milliseconds (typically several seconds) to prevent multiple rapid executions during network handshakes or when the device switches between mobile data and Wi-Fi. This debouncing ensures `TaskUtils` contains stable state values before `NetworkWorker` processes the condition.

### Can I trigger forwarding rules only when specific Bluetooth devices are discovered?

Yes. The `BluetoothSetting` data class supports filtering by device names or MAC addresses. When `BluetoothDevice.ACTION_FOUND` fires, the receiver compares the discovered device against your configured criteria in `TaskUtils` before determining whether to enqueue the `BluetoothWorker`.

### Where are the current Bluetooth and network states stored during execution?

Global state is maintained in `TaskUtils` ([`app/src/main/kotlin/cn/ppps/forwarder/utils/task/TaskUtils.kt`](https://github.com/pppscn/SmsForwarder/blob/main/app/src/main/kotlin/cn/ppps/forwarder/utils/task/TaskUtils.kt)), which exposes properties like `networkState`, `wifiSsid`, `dataSimSlot`, and discovered device lists. Both receivers read from and write to this singleton, allowing workers to access the triggering context when executing background tasks.