# SmsForwarder Battery Monitoring and Charging State Trigger System: A Deep Dive into the Source Code

> Explore SmsForwarder's battery monitoring and charging state trigger system. Learn how it uses Android BroadcastReceiver and WorkManager for automated actions based on custom conditions.

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

---

**SmsForwarder monitors battery levels and charging states using Android's BroadcastReceiver and WorkManager architecture to evaluate user-defined conditions and trigger automated actions.**

The **SmsForwarder** repository (pppscn/SmsForwarder) implements a robust background system that tracks device power status to execute user-configured forwarding tasks. This article examines the complete **battery monitoring and charging state trigger system** as implemented in the Kotlin source code, from system broadcast reception to condition evaluation and HTTP API exposure.

## System Architecture Overview

The battery monitoring system follows a decoupled architecture that separates lightweight broadcast handling from heavy task processing. The flow moves from system broadcasts through dedicated receivers, then to WorkManager workers for asynchronous condition evaluation.

The data flow follows this path:

```

System Broadcast → BatteryReceiver → BatteryWorker → ConditionUtils → ActionWorker

```

This design ensures reliable trigger execution even when the app is in the background or the device is idle, leveraging Android's `WorkManager` for guaranteed task scheduling.

## Capturing Battery Events with BatteryReceiver

The entry point resides in [`app/src/main/kotlin/cn/ppps/forwarder/receiver/BatteryReceiver.kt`](https://github.com/pppscn/SmsForwarder/blob/main/app/src/main/kotlin/cn/ppps/forwarder/receiver/BatteryReceiver.kt). This `BroadcastReceiver` registers for the system action `ACTION_BATTERY_CHANGED`, which Android fires whenever battery level, charge status, or plug type changes.

When triggered, the receiver extracts battery data via `BatteryUtils.getBatteryInfo(intent)` and immediately schedules a one-off `BatteryWorker`:

```kotlin
class BatteryReceiver : BroadcastReceiver() {
    override fun onReceive(context: Context, intent: Intent) {
        val batteryInfo = BatteryUtils.getBatteryInfo(intent).toString()
        val levelNew = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0)
        val levelOld = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1)
        val statusNew = intent.getIntExtra(BatteryManager.EXTRA_STATUS, -1)

        val data = Data.Builder()
            .putInt("status", statusNew)
            .putInt("level_new", levelNew)
            .putInt("level_old", levelOld)
            .build()
        val request = OneTimeWorkRequestBuilder<BatteryWorker>()
            .setInputData(data)
            .build()
        WorkManager.getInstance(context).enqueue(request)
    }
}

```

The receiver captures both the current level and the previous cached value, enabling the system to detect threshold crossings accurately.

## Processing Battery Data with BatteryWorker

Located in [`app/src/main/kotlin/cn/ppps/forwarder/workers/BatteryWorker.kt`](https://github.com/pppscn/SmsForwarder/blob/main/app/src/main/kotlin/cn/ppps/forwarder/workers/BatteryWorker.kt), the `BatteryWorker` class extends `CoroutineWorker` and handles the heavy lifting of condition evaluation. It receives raw battery values through `WorkManager`'s input `Data` object.

The worker distinguishes between two condition types:

1. **`TASK_CONDITION_BATTERY`** – Triggered when battery percentage crosses user-defined thresholds
2. **`TASK_CONDITION_CHARGE`** – Triggered when charging state changes (charging, discharging, not-charging, full, unknown)

### Handling Battery Level Thresholds

For battery percentage monitoring, the worker retrieves tasks of type `TASK_CONDITION_BATTERY` and parses the stored JSON condition into a `BatterySetting` object:

```kotlin
when (inputData.getInt(TaskWorker.CONDITION_TYPE, -1)) {
    TASK_CONDITION_BATTERY -> {
        val status = inputData.getInt("status", -1)
        val levelNew = inputData.getInt("level_new", -1)
        val levelOld = inputData.getInt("level_old", -1)

        Core.task.getByType(TASK_CONDITION_BATTERY).forEach { task ->
            val setting = Gson().fromJson(
                task.conditions, Array<TaskSetting>::class.java
            ).first().setting.let {
                Gson().fromJson(it, BatterySetting::class.java)
            }
            val msg = setting.getMsg(status, levelNew, levelOld, TaskUtils.batteryInfo)
            if (msg.isNotEmpty() && ConditionUtils.checkCondition(task.id, ...)) {
                // Enqueue ActionWorker to execute user tasks
            }
        }
        Result.success()
    }
}

```

The `BatterySetting` class (defined in [`app/src/main/kotlin/cn/ppps/forwarder/entity/condition/BatterySetting.kt`](https://github.com/pppscn/SmsForwarder/blob/main/app/src/main/kotlin/cn/ppps/forwarder/entity/condition/BatterySetting.kt)) encapsulates properties such as `status`, `levelMin`, `levelMax`, and `keepReminding`.

### Detecting Charging State Changes

For charging state triggers, the worker evaluates `TASK_CONDITION_CHARGE` tasks against the current `BatteryManager` status constants. The `ChargeSetting` data class tracks the specific charging states (plugged in, unplugged, full) that should activate the trigger.

## Condition Evaluation Logic

The `ConditionUtils` class in [`app/src/main/kotlin/cn/ppps/forwarder/utils/ConditionUtils.kt`](https://github.com/pppscn/SmsForwarder/blob/main/app/src/main/kotlin/cn/ppps/forwarder/utils/ConditionUtils.kt) contains the core comparison logic. It validates whether the current `TaskUtils.batteryInfo` satisfies the user-defined constraints by comparing:

- Current percentage against `BatterySetting.levelMin` and `BatterySetting.levelMax`
- Current status against `BatterySetting.status`
- Threshold crossing direction (using `levelOld` vs `levelNew`)

When conditions match, the utility builds a `MsgInfo` object and enqueues an `ActionWorker` to perform the configured actions, such as sending HTTP requests, notifications, or SMS messages.

## Optional HTTP API for Battery Queries

When the configuration flag `enableApiBatteryQuery` is enabled, SmsForwarder exposes an HTTP endpoint through its internal server. The `BatteryController` in [`app/src/main/kotlin/cn/ppps/forwarder/server/controller/BatteryController.kt`](https://github.com/pppscn/SmsForwarder/blob/main/app/src/main/kotlin/cn/ppps/forwarder/server/controller/BatteryController.kt) handles POST requests to `/battery/query`:

```kotlin
@RestController
class BatteryController {
    @PostMapping("/battery/query")
    fun query(@RequestBody bean: BaseRequest<EmptyData>): BatteryInfo {
        val intent = /* get last broadcast Intent stored by BatteryReceiver */
        return BatteryUtils.getBatteryInfo(intent)
    }
}

```

This endpoint returns the latest `BatteryInfo` obtained from `BatteryUtils`, allowing external systems to query the device power status without accessing Android APIs directly.

## Configuration and Data Models

The system uses several key data classes to persist user settings:

- **`BatterySetting`**: Defines percentage thresholds (`levelMin`, `levelMax`), required status, and reminder flags
- **`ChargeSetting`**: Specifies target charging states and plug types
- **`BatteryInfo`**: Runtime data class holding current level, voltage, temperature, and status

Tasks are stored in the local database via the `Core.task` repository, with conditions serialized as JSON arrays containing the setting objects.

## Summary

- **SmsForwarder** uses `BatteryReceiver` to capture Android's `ACTION_BATTERY_CHANGED` broadcasts and extract battery data via `BatteryUtils`.
- **WorkManager** decouples event reception from processing through `BatteryWorker`, ensuring reliable background execution.
- The system supports **two trigger types**: percentage thresholds (`TASK_CONDITION_BATTERY`) and charging state changes (`TASK_CONDITION_CHARGE`).
- **ConditionUtils** evaluates user-defined constraints against real-time battery data before dispatching to `ActionWorker`.
- An optional **HTTP API** (`/battery/query`) exposes battery information when `enableApiBatteryQuery` is enabled in settings.

## Frequently Asked Questions

### How does SmsForwarder detect battery level changes without draining the battery?

SmsForwarder uses Android's standard `BroadcastReceiver` mechanism rather than polling. The `BatteryReceiver` only activates when the system broadcasts `ACTION_BATTERY_CHANGED`, which occurs when the battery level changes by 1% or when charging state changes. This passive approach consumes negligible power compared to active monitoring loops.

### What is the difference between battery level triggers and charging state triggers?

**Battery level triggers** (`TASK_CONDITION_BATTERY`) fire when the percentage crosses user-defined minimum or maximum thresholds (for example, dropping below 15%). **Charging state triggers** (`TASK_CONDITION_CHARGE`) fire when the power connection status changes (such as plugging in, unplugging, or reaching full charge). The `BatteryWorker` handles these as separate condition types with distinct evaluation logic.

### Can I query the current battery status via HTTP when using the API server?

Yes. When the `enableApiBatteryQuery` configuration flag is enabled, the internal HTTP server exposes a `POST` endpoint at `/battery/query`. The `BatteryController` returns the latest `BatteryInfo` object extracted from the most recent battery broadcast, allowing remote monitoring of device power levels.

### Where does SmsForwarder store the battery condition settings?

User-defined battery conditions are stored as JSON within the `conditions` field of the `TaskSetting` database entity. The `BatteryWorker` deserializes this JSON into `BatterySetting` or `ChargeSetting` objects using Gson during task evaluation. These settings include thresholds, status requirements, and the `keepReminding` flag for persistent alerts.