# How SmsForwarder Implements Dual SIM Card Detection and Handling: A Complete Technical Guide

> Discover how SmsForwarder uses a three-layer architecture to detect and handle dual SIM cards, ensuring accurate message routing.

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

---

**SmsForwarder detects and manages dual SIM cards through a three-layer architecture involving system broadcast receivers, shared preference tracking, and rule-based filtering to ensure messages are routed through the correct SIM slot.**

The open-source **SmsForwarder** repository (`pppscn/SmsForwarder`) provides a robust SMS forwarding solution for Android devices with multiple SIM slots. Understanding its dual SIM card detection and handling mechanism reveals how the app maintains reliable message processing across different hardware configurations. This implementation combines Android system broadcasts, centralized state management, and user-configurable rules to handle the complexities of dual SIM operations.

## System-Level SIM State Detection

The first layer of detection relies on **`SimStateReceiver`**, which listens for the Android system broadcast `android.intent.action.SIM_STATE_CHANGED`. When the system notifies the app of a SIM state change, the receiver performs several critical operations.

It reads the current state from **`TelephonyManager`**, checking for states like `SIM_STATE_READY` or `SIM_STATE_ABSENT` to determine physical SIM presence and readiness. According to the source code in [`SimStateReceiver.kt`](https://github.com/pppscn/SmsForwarder/blob/main/SimStateReceiver.kt) (lines 31-52), the receiver then updates a shared preference flag named `simState` through **`TaskUtils`**, where the value `5` indicates ready status and `1` indicates removed.

When a SIM becomes ready, the receiver enforces a **5-second delay** (`DELAY_TIME_AFTER_SIM_READY`) before triggering any dependent tasks. This delay, implemented in [`SimStateReceiver.kt`](https://github.com/pppscn/SmsForwarder/blob/main/SimStateReceiver.kt) (lines 54-66), ensures the SIM is fully initialized and network registration is complete before the app attempts to use the slot for forwarding operations.

## Data SIM Slot Tracking

Beyond simple presence detection, SmsForwarder tracks which SIM slot currently carries the active data connection. The **`TaskUtils`** class maintains the `dataSimSlot` value in shared preferences (lines 136-137), storing the index of the SIM currently assigned for mobile data.

This value updates dynamically through **`NetworkChangeReceiver`**, which queries the system whenever network connectivity changes. When a new network is detected, the receiver calls `TelephonyManager.getDataSimSlotIndex()` and persists the result via `TaskUtils` (as shown in [`NetworkChangeReceiver.kt`](https://github.com/pppscn/SmsForwarder/blob/main/NetworkChangeReceiver.kt), lines 64-71). This creates a real-time mapping of which physical slot serves as the primary data connection, separate from the voice/SMS slot.

## Rule-Based SIM Slot Filtering

The final layer applies user-defined rules during message processing. When **`SendWorker`** evaluates forwarding tasks, it checks whether the incoming message matches the configured SIM slot requirements defined in **[`Constants.kt`](https://github.com/pppscn/SmsForwarder/blob/main/Constants.kt)** (lines 125-127).

The app supports three slot selection constants:
- **`CHECK_SIM_SLOT_1`** for SIM 1 only
- **`CHECK_SIM_SLOT_2`** for SIM 2 only  
- **`CHECK_SIM_SLOT_ALL`** for any active SIM

During execution, `SendWorker` constructs a target identifier string by converting the message's `simSlot` index (adding 1 to convert from 0-based to 1-based) to `"SIM1"` or `"SIM2"` format. It compares this against the rule's `simSlot` field (as implemented in [`SendWorker.kt`](https://github.com/pppscn/SmsForwarder/blob/main/SendWorker.kt), lines 52-53 and 152-154). If the rule specifies a particular slot and the message originates from a different one, the worker skips the task and prevents forwarding through the wrong channel.

## Implementation Code Examples

The following Kotlin snippets demonstrate how to implement the same dual SIM detection patterns used throughout the SmsForwarder codebase.

### Listening for SIM State Changes

Mirror the `SimStateReceiver` implementation to detect SIM insertion and removal:

```kotlin
val filter = IntentFilter(TelephonyManager.ACTION_SIM_STATE_CHANGED)
context.registerReceiver(object : BroadcastReceiver() {
    override fun onReceive(ctx: Context, intent: Intent) {
        val tm = ctx.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
        when (tm.simState) {
            TelephonyManager.SIM_STATE_READY -> {
                // SIM is ready - schedule delayed work
                Handler(Looper.getMainLooper()).postDelayed({
                    // Trigger task condition check
                }, 5_000L)   // DELAY_TIME_AFTER_SIM_READY
            }
            TelephonyManager.SIM_STATE_ABSENT -> {
                // SIM removed - update shared prefs
            }
        }
    }
}, filter)

```

### Reading Stored SIM State

Access the centralized state values maintained by `TaskUtils`:

```kotlin
val simReady = SharedPreference(SP_SIM_STATE, 0).get()   // 5 = ready, 1 = removed
val dataSlot = SharedPreference(SP_DATA_SIM_SLOT, 0).get()

```

### Applying SIM Slot Filters

Validate SIM slot requirements before executing forwarding tasks, matching the logic in `SendWorker`:

```kotlin
val targetSlot = "SIM${msgInfo.simSlot + 1}"           // e.g., "SIM1"
if (ruleSetting.simSlot != CHECK_SIM_SLOT_ALL && targetSlot != ruleSetting.simSlot) {
    // Skip this message - rule requires different SIM
    return
}
// Proceed to send via the chosen Subscription ID

```

## Summary

SmsForwarder's dual SIM detection and handling mechanism combines three distinct architectural layers to ensure reliable operation:

- **System broadcasts** (`SimStateReceiver`) detect physical SIM changes and enforce initialization delays
- **Shared preference tracking** (`TaskUtils`) maintains current state for both SIM readiness and active data slot selection
- **Rule enforcement** (`SendWorker`) compares message origin against user-configured slot filters using constants from `NetworkChangeReceiver` and [`Constants.kt`](https://github.com/pppscn/SmsForwarder/blob/main/Constants.kt)

## Frequently Asked Questions

### How does SmsForwarder detect when a SIM card is inserted or removed?

The app registers **`SimStateReceiver`** to listen for `android.intent.action.SIM_STATE_CHANGED` broadcasts from the Android system. When triggered, the receiver queries `TelephonyManager` for the current SIM state and updates shared preferences accordingly, storing `5` for ready states and `1` for absent states.

### What is the purpose of the 5-second delay after SIM becomes ready?

The **`DELAY_TIME_AFTER_SIM_READY`** delay (5 seconds) ensures the SIM card completes network registration and initialization before SmsForwarder attempts to use it for forwarding operations. This prevents failed sends that would occur if the app tried to transmit immediately when the SIM transitions to ready state.

### How does the app determine which SIM slot is currently active for data?

**`NetworkChangeReceiver`** calls `TelephonyManager.getDataSimSlotIndex()` whenever network connectivity changes occur. It stores this 0-based index in shared preferences via `TaskUtils.setDataSimSlot()`, allowing the system to track which physical slot currently handles mobile data separate from voice/SMS operations.

### Can users filter forwarded messages based on specific SIM slots?

Yes. The forwarding rules support **`CHECK_SIM_SLOT_1`**, **`CHECK_SIM_SLOT_2`**, and **`CHECK_SIM_SLOT_ALL`** constants defined in [`Constants.kt`](https://github.com/pppscn/SmsForwarder/blob/main/Constants.kt). When `SendWorker` processes a message, it constructs a slot identifier string from `msgInfo.simSlot` and compares it against the rule's requirement, skipping the task if the origin slot does not match the configured filter.