How to Configure the Silent Period (Do Not Disturb) Time in SmsForwarder

SmsForwarder stores silent period hours as integers (0-23) in SharedPreferences via SettingUtils, enforces them in SendWorker and SendUtils, and disables the feature when start and end times are equal.

SmsForwarder implements a configurable silent period—also known as Do Not Disturb (DND)—that blocks message forwarding during user-defined hours. This feature operates at both global and per-rule levels, utilizing a three-layer architecture spanning UI components, SharedPreferences storage, and background worker enforcement. Understanding how these components interact ensures reliable suppression of unwanted notifications during rest periods.

Architecture Overview: Three Layers of Silent Period Management

The silent period implementation spans three distinct architectural layers in the SmsForwarder codebase. The UI layer collects input through time picker dialogs in SettingsFragment.kt and RulesEditFragment.kt. The persistence layer stores values using SharedPreferences keys defined in Constants.kt and accessed via delegated properties in SettingUtils.kt. Finally, the runtime layer enforces restrictions in SendWorker.kt for global settings and SendUtils.kt for rule-specific logic.

Configuring Silent Period Hours in the User Interface

Users define the silent window by selecting start and end hours (0-23) through the application's settings interface, defined in app/src/main/res/layout/fragment_settings.xml.

Global Configuration in SettingsFragment

In app/src/main/kotlin/cn/ppps/forwarder/fragment/SettingsFragment.kt, the btn_silent_period button triggers a TimeOptionPicker dialog. When users confirm their selection, the system writes the chosen indices to SettingUtils and updates the tv_silent_period TextView:

// From SettingsFragment.kt
R.id.btn_silent_period -> {
    TimeOptionPicker.Builder()
        .setTitleText(getString(R.string.select_time_period))
        .setSelectOptions(SettingUtils.silentPeriodStart, SettingUtils.silentPeriodEnd)
        .build<Any>()
        .also { picker -> picker.setOnOptionSelectListener { opt1, opt2 ->
            SettingUtils.silentPeriodStart = opt1
            SettingUtils.silentPeriodEnd   = opt2
            binding!!.tvSilentPeriod.text = "${mTimeOption[opt1]} ~ ${mTimeOption[opt2]}"
        } }
}

Per-Rule Configuration

Individual forwarding rules can override global settings via RulesEditFragment.kt, which stores values in the Rule entity database columns: silent_period_start, silent_period_end, and silent_day_of_week.

Data Persistence with SharedPreferences

The application persists configuration using Android's SharedPreferences through a delegated property pattern in SettingUtils.kt.

In app/src/main/kotlin/cn/ppps/forwarder/utils/Constants.kt, the keys are defined as SP_SILENT_PERIOD_START and SP_SILENT_PERIOD_END. The SettingUtils object maps these to integer properties:

// From SettingUtils.kt
var silentPeriodStart: Int by SharedPreference(SP_SILENT_PERIOD_START, 0)
var silentPeriodEnd:   Int by SharedPreference(SP_SILENT_PERIOD_END, 0)

When silentPeriodStart equals silentPeriodEnd, the feature is effectively disabled, allowing messages to forward at any time.

Runtime Enforcement in Workers

Forwarding workers validate the current time against the configured window before processing messages.

Global Silent Period Check

In app/src/main/kotlin/cn/ppps/forwarder/workers/SendWorker.kt, the worker retrieves stored hours and calls DataProvider.isCurrentTimeInPeriod():

// From SendWorker.kt
Log.d(TAG, "silentPeriodStart = ${SettingUtils.silentPeriodStart}, silentPeriodEnd = ${SettingUtils.silentPeriodEnd}")
if (SettingUtils.silentPeriodStart != SettingUtils.silentPeriodEnd) {
    val isSilent = DataProvider.isCurrentTimeInPeriod(
        SettingUtils.silentPeriodStart, SettingUtils.silentPeriodEnd)
    if (isSilent) {
        updateLogs(logId, 0, getString(R.string.silent_time_period))
        return Result.success()
    }
}

Per-Rule Validation

For rule-specific enforcement, SendUtils.kt evaluates both the time window and an optional silent_day_of_week CSV field to restrict DND to specific weekdays.

Understanding the Time Window Logic

The silent period supports two operational modes:

  • Standard Window: When end > start, the period operates within the same calendar day (e.g., 09:00 to 17:00).
  • Midnight Spanning: When end < start, the window crosses midnight (e.g., 22:00 to 06:00 covers 22:00-23:59 and 00:00-06:00).

The DataProvider.isCurrentTimeInPeriod() utility handles these calculations by comparing the current hour against the configured boundaries.

Implementing Manual Silent Period Checks

Developers can replicate the enforcement logic using standard Calendar operations:

fun shouldForwardNow(): Boolean {
    val now = Calendar.getInstance()
    val hour = now.get(Calendar.HOUR_OF_DAY)

    val start = SettingUtils.silentPeriodStart
    val end   = SettingUtils.silentPeriodEnd

    // Disabled when times are equal
    if (start == end) return true

    return if (end > start) {
        hour < start || hour >= end      // Outside same-day window
    } else {
        hour < start && hour >= end      // Outside midnight-spanning window
    }
}

This implementation mirrors the logic found in SendWorker.kt and supports both standard and overnight configurations.

Summary

  • SmsForwarder implements silent period functionality through UI components in SettingsFragment.kt and RulesEditFragment.kt using TimeOptionPicker dialogs defined in fragment_settings.xml.
  • Configuration persists via SharedPreferences keys SP_SILENT_PERIOD_START and SP_SILENT_PERIOD_END, accessed through delegated properties in SettingUtils.kt.
  • Runtime enforcement occurs in SendWorker.kt and SendUtils.kt using DataProvider.isCurrentTimeInPeriod() to validate the current hour against the stored window.
  • The feature disables automatically when start and end times are identical, and supports midnight-spanning windows when the end time is less than the start time.
  • Per-rule configurations can include weekday restrictions via the silent_day_of_week database column in the Rule entity.

Frequently Asked Questions

What happens when the silent period start and end times are the same?

When silentPeriodStart equals silentPeriodEnd, SmsForwarder interprets this as a disabled state and allows message forwarding at all hours. This check appears in SendWorker.kt before invoking the time validation logic, effectively bypassing the DND feature.

How does SmsForwarder handle silent periods that cross midnight?

The DataProvider.isCurrentTimeInPeriod() method correctly handles overnight windows. When the end time is less than the start time (e.g., 22:00 to 06:00), the logic treats the period as spanning two calendar days, blocking forwarding from 22:00-23:59 and 00:00-06:00 while allowing it during 06:00-22:00.

Can different forwarding rules have different silent periods?

Yes. While global settings apply to all messages by default, individual rules defined in Rule.kt can specify custom silent_period_start, silent_period_end, and silent_day_of_week values. The SendUtils.kt file evaluates these rule-specific settings independently during per-rule forwarding logic.

Where are the silent period preference keys defined?

The SharedPreferences keys SP_SILENT_PERIOD_START and SP_SILENT_PERIOD_END are defined as constants in app/src/main/kotlin/cn/ppps/forwarder/utils/Constants.kt and consumed by the delegated properties in SettingUtils.kt.

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 →