SmsForwarder Battery Monitoring and Charging State Trigger System: A Deep Dive into the Source Code
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. 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:
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, 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:
TASK_CONDITION_BATTERY– Triggered when battery percentage crosses user-defined thresholdsTASK_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:
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) 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 contains the core comparison logic. It validates whether the current TaskUtils.batteryInfo satisfies the user-defined constraints by comparing:
- Current percentage against
BatterySetting.levelMinandBatterySetting.levelMax - Current status against
BatterySetting.status - Threshold crossing direction (using
levelOldvslevelNew)
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 handles POST requests to /battery/query:
@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 flagsChargeSetting: Specifies target charging states and plug typesBatteryInfo: 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
BatteryReceiverto capture Android'sACTION_BATTERY_CHANGEDbroadcasts and extract battery data viaBatteryUtils. - 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 whenenableApiBatteryQueryis 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.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →