# Delta for native-alarms Ring audio moves from the Dart/native handoff + `STREAM_MUSIC` override to one native owner on `STREAM_ALARM` for the whole ring. System volumes are never written; nothing needs restoring. ## ADDED Requirements ### Requirement: Sole native ring-audio ownership `PluriWaveAlarmService` MUST be the only audio source for the whole ring, on `STREAM_ALARM` via its own `MediaPlayer`. No Dart player or second audible source MUST play. #### Scenario: Media volume 0 does not silence the ring **Testability**: on-device QA (audio routing) + flutter test with fakes - GIVEN device `STREAM_MUSIC` volume is 0 - WHEN an alarm fires - THEN it MUST be audible via the service's `STREAM_ALARM` player - AND no Dart audio path (`EstadoRadio`) MUST run #### Scenario: Fire notification posts with no sound **Testability**: code-inspection + on-device QA - GIVEN channel `pluriwave_alarm_fire_v3` - WHEN the ringing notification posts - THEN it MUST carry no sound; the player MUST be the only audible source #### Scenario: Three-stage fallback preserved, single fade anchor **Testability**: code-inspection + on-device QA - GIVEN the primary station fails within its timeout - WHEN the service advances station -> fallback station -> WAV - THEN whichever stage starts MUST get the same fade curve, anchored at ring start ### Requirement: Exponential dB fade-in ceiling With `fadeInSegundos > 0`: `gainDb = fraction*40-40`, `curve = 10^(gainDb/20)` on a 50ms tick, `setVolume(s * curve)`, `s` = per-alarm `volumen` as ceiling. With `fadeInSegundos == 0`: start directly at `s`. #### Scenario: Fade rises exponentially to the ceiling **Testability**: code-inspection (curve formula) - GIVEN `fadeInSegundos > 0`, `volumen = s` - WHEN the ring starts - THEN volume at `t=0` MUST equal `s * 10^(-40/20)` (~1% of `s`), rising every 50ms - AND volume at `t=fadeInSegundos` MUST equal exactly `s` #### Scenario: No-fade path starts pop-free **Testability**: code-inspection + on-device QA - GIVEN `fadeInSegundos == 0` - WHEN the ring starts - THEN `setVolume(s)` MUST be applied before `start()`, with no pop ### Requirement: Manual transient focus; no system volume writes The service MUST request `AUDIOFOCUS_GAIN_TRANSIENT` on `STREAM_ALARM` and abandon it at ring end. `setStreamVolume` MUST NOT be called on any stream, ever. #### Scenario: Focus requested at start, abandoned at end **Testability**: code-inspection + on-device QA (`dumpsys audio`) - GIVEN the ring is about to start - WHEN `startAlarm` runs - THEN `AUDIOFOCUS_GAIN_TRANSIENT` MUST be requested on `STREAM_ALARM` - AND WHEN the ring ends THEN focus MUST be abandoned #### Scenario: Dismiss, snooze, and back tear down with nothing to restore **Testability**: on-device QA + flutter test with fakes - GIVEN an alarm is ringing - WHEN the user dismisses, snoozes, or triggers back - THEN the player MUST stop, focus MUST be abandoned, and foreground removed - AND no `setStreamVolume` call MUST have occurred for this ring ### Requirement: Notification channel migration v2 -> v3 The service MUST create `pluriwave_alarm_fire_v3` (no sound) and delete `pluriwave_alarm_fire_v2` exactly once, guarded by a migration flag (same pattern as v1->v2). #### Scenario: v2 deleted exactly once on upgrade **Testability**: code-inspection (migration guard) + on-device QA - GIVEN a device with `pluriwave_alarm_fire_v2` already created - WHEN the service next ensures its channel - THEN `_v3` MUST be created and `_v2` deleted; a re-run MUST be a no-op ### Requirement: Ring screen is pure UI `PantallaAlarmaSonando` MUST only call `EstadoAlarmas.finalizarEjecucion`/`posponerAlarma` from Stop/Snooze/back. It MUST NOT control any audio player or volume/handoff channel. `PuertoAlarmasAndroid` MUST NOT expose `confirmarAudioFlutter`, `forzarVolumenMediaParaAlarma`, or `restaurarVolumenMedia`. #### Scenario: Buttons and back call only EstadoAlarmas **Testability**: flutter test with fakes - GIVEN the ringing screen is displayed - WHEN Stop, a Snooze option, or back is triggered - THEN only `finalizarEjecucion`/`posponerAlarma` MUST be called - AND zero calls MUST reach `EstadoRadio.audio` or a removed channel method #### Scenario: Reduced Android port surface **Testability**: static/compile-verifiable (Dart analyzer) + flutter test - GIVEN `PuertoAlarmasAndroid` and its implementations/fakes - WHEN inspected after this change - THEN `confirmarAudioFlutter`, `forzarVolumenMediaParaAlarma`, `restaurarVolumenMedia` MUST NOT exist ## REMOVED Requirements ### Requirement: Ring-scoped device-volume override (Reason: `STREAM_MUSIC` is never overridden now — native rings exclusively on `STREAM_ALARM`, independent of the media-volume knob. Replaced by "Sole native ring-audio ownership" and "Manual transient focus; no system volume writes" above.) ### Requirement: Single fade-in driver across native-to-Flutter handoff (Reason: the handoff no longer exists; native owns the fade for the whole ring, so only one ramp ever runs. Replaced by "Exponential dB fade-in ceiling" above.) ## Non-Functional Notes - No new user-visible strings; no l10n work required across the 13 supported locales.