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

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 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 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:

// 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:

// 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 allows selecting specific events:

// 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 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 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), 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.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →