Close the SDD cycle for the ring architecture replacement: verified with one critical (channel silence by omission) fixed and re-checked before archive, delta merged into the main native-alarms spec (2 requirements removed, 5 added), artifacts archived byte-for-byte. Phase 3 on-device QA (9 items) remains the mandatory human gate.
6.5 KiB
Native Alarms Specification
Purpose
Native (Kotlin) foreground-service behavior for ringing alarms: reliable start from a broadcast context on Android 14+ via the correct foreground-service-type declaration, and sole native ring-audio ownership — PluriWaveAlarmService is the only audio source for the entire ring, on STREAM_ALARM via its own MediaPlayer, with an exponential dB fade-in ceiling, manually requested/abandoned transient audio focus, and a silent notification channel. System volumes are never written, so nothing ever needs restoring; the Flutter ringing screen is pure UI that only drives Stop/Snooze through EstadoAlarmas.
Requirements
Requirement: Manifest declares alarm-eligible foreground service
foregroundServiceType="mediaPlayback|systemExempted" on the PluriWaveAlarmService <service> element in AndroidManifest.xml is the correct, verified declaration for an alarm-clock app holding exact-alarm permissions that starts a foreground service from an AlarmManager broadcast on API 34+. FOREGROUND_SERVICE_TYPE_ALARM and android.permission.FOREGROUND_SERVICE_ALARM do NOT exist anywhere in the Android SDK (verified via javap -constants on android-34/35/36 platform jars and a full api-versions.xml sweep — 13 real FGS types, no alarm variant); do not attempt to introduce them.
Scenario: Manifest keeps the verified-correct FGS declaration
Testability: static/grep-verifiable
- GIVEN the built
AndroidManifest.xml - WHEN the
PluriWaveAlarmService<service>element is inspected - THEN
android:foregroundServiceTypeMUST bemediaPlayback|systemExempted - AND the runtime
startForegroundtype constants inPluriWaveAlarmService.ktMUST match the manifest declaration
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)startsPluriWaveAlarmService - THEN it MUST start without
ForegroundServiceTypeException - AND the alarm MUST be audible via the native
USAGE_ALARMplayer
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_MUSICvolume is 0 - WHEN an alarm fires
- THEN it MUST be audible via the service's
STREAM_ALARMplayer - 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=0MUST equals * 10^(-40/20)(~1% ofs), rising every 50ms - AND volume at
t=fadeInSegundosMUST equal exactlys
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 beforestart(), 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
startAlarmruns - THEN
AUDIOFOCUS_GAIN_TRANSIENTMUST be requested onSTREAM_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
setStreamVolumecall 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_v2already created - WHEN the service next ensures its channel
- THEN
_v3MUST be created and_v2deleted; 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/posponerAlarmaMUST be called - AND zero calls MUST reach
EstadoRadio.audioor a removed channel method
Scenario: Reduced Android port surface
Testability: static/compile-verifiable (Dart analyzer) + flutter test
- GIVEN
PuertoAlarmasAndroidand its implementations/fakes - WHEN inspected after this change
- THEN
confirmarAudioFlutter,forzarVolumenMediaParaAlarma,restaurarVolumenMediaMUST NOT exist
Non-Functional Notes
- No new user-visible strings; no l10n work required across the 13 supported locales.