# Delta for native-alarms ## ADDED Requirements ### Requirement: Manifest declares alarm-eligible foreground service `PluriWaveAlarmService` in `AndroidManifest.xml` MUST use `foregroundServiceType="mediaPlayback|alarm"` and the app MUST declare `FOREGROUND_SERVICE_ALARM`, so the service is eligible to start on Android 14+ (API 34+) from a broadcast receiver. #### Scenario: Manifest declares required FGS type and permission **Testability**: static/grep-verifiable - GIVEN the built `AndroidManifest.xml` - WHEN the `PluriWaveAlarmService` `` element is inspected - THEN `android:foregroundServiceType` MUST contain `alarm` - AND a `` MUST exist #### Scenario: Native service starts from broadcast context on Android 14+ **Testability**: manual on-device QA (Android 14+; agent cannot install/verify) - GIVEN an alarm is scheduled and the app is not foregrounded - WHEN `PluriWaveAlarmReceiver.onReceive(ACTION_FIRE)` starts `PluriWaveAlarmService` - THEN it MUST start without `ForegroundServiceTypeException` - AND the alarm MUST be audible via the native `USAGE_ALARM` player ### Requirement: Ring-scoped device-volume override While an alarm is ringing, the system MUST override `STREAM_MUSIC` to an audible reference level so the alarm is not silenced by device volume 0, and MUST restore the original captured volume when the ring ends through any exit path. #### Scenario: Alarm is audible when device media volume is 0 **Testability**: manual on-device QA (native volume APIs) - GIVEN device `STREAM_MUSIC` volume is 0 - WHEN an alarm fires and the override captures/raises `STREAM_MUSIC` to an audible level - THEN the alarm MUST be audible for the full ring duration, not only the pre-handoff window #### Scenario: Player fade-in ramps within the overridden level **Testability**: flutter test with fakes - GIVEN the override has set an audible `STREAM_MUSIC` reference level - WHEN the ring's fade-in timer runs - THEN player volume MUST ramp 5% -> `alarma.volumen` over `alarma.fadeInSegundos`, unchanged from today #### Scenario: Dismiss restores the original captured volume **Testability**: flutter test with fakes (Dart->channel call) + manual on-device QA (native restore) - GIVEN the override captured `STREAM_MUSIC` at volume `V` - WHEN the user dismisses the alarm (`_detener()` -> `_silenciarAudio()`) - THEN `STREAM_MUSIC` MUST be restored to exactly `V` #### Scenario: Snooze restores the original captured volume **Testability**: flutter test with fakes (Dart->channel call) + manual on-device QA (native restore) - GIVEN the override captured `STREAM_MUSIC` at volume `V` - WHEN the user snoozes the alarm (`_posponer()` -> `_silenciarAudio()`) - THEN `STREAM_MUSIC` MUST be restored to exactly `V` #### Scenario: Restore is idempotent across double-exit paths **Testability**: flutter test with fakes (single/no-op restore call assertion) + manual on-device QA - GIVEN restore already ran once (`_silenciarAudio()` inside `_detener()`) - WHEN a second exit path also runs restore (e.g. `dispose()` firing after) - THEN the second call MUST NOT throw, MUST NOT re-apply a stale value, and MUST leave `STREAM_MUSIC` unchanged #### Scenario: Normal radio playback never triggers the override **Testability**: flutter test with fakes (channel never invoked outside a ring) + manual on-device QA - GIVEN the user is listening to radio with no alarm ringing - WHEN playback starts, plays, or stops normally - THEN the override MUST NOT be invoked; `STREAM_MUSIC` stays fully governed by device controls #### Scenario: App killed mid-ring — best-effort restore only **Testability**: manual on-device QA; accepted residual gap, not required in automated coverage - GIVEN an alarm is ringing and the override is active - WHEN the app process is killed before any exit path runs - THEN the system SHOULD best-effort restore from service teardown/`onDestroy`, but a residual overridden-volume state MAY occur and is an accepted known gap ### Requirement: Single fade-in driver across native-to-Flutter handoff During handoff from the native `USAGE_ALARM` player to the Flutter media-stream player, only one fade-in ramp MUST drive audible volume at any instant. #### Scenario: No double-ramp interleaving at handoff **Testability**: manual on-device QA (timing-sensitive, cross-process); flutter test with fakes can assert the Dart ramp only runs when native ownership is inactive, but cannot observe native `MediaPlayer.setVolume()` timing - GIVEN the native fade-in is ramping the `USAGE_ALARM` player - WHEN the Flutter player becomes ready and handoff occurs (`confirmarAudioFlutter`) - THEN the native fade-in MUST stop driving audible volume once Flutter takes over; both ramps MUST NOT drive audible volume simultaneously ## Non-Functional Notes - No new user-visible strings; no l10n work required across the 13 supported locales.