Close the SDD cycle: verified pass with warnings (0 critical), slice 1 cancelled with SDK evidence, slices 2-3 shipped, post-verify dispose fix landed. The native-alarms capability spec is promoted to openspec/specs/ with the corrected FGS requirement. Phase 5 on-device QA remains the pending human gate.
4.2 KiB
Exploration: alarm-volume-ramp-restore
Current State — dual-track handoff architecture (by design)
Alarm audio is a deliberate handoff (documented in app-quality-and-native-alarms/design.md Decision 1.5, docs/alarmas-pantalla-apagada.md):
- Native track:
PluriWaveAlarmReceiver(ACTION_FIRE)→PluriWaveAlarmService.ktplays viaMediaPlayerwithAudioAttributes.USAGE_ALARM(L350-354) — Android ALARM stream, immune to media-volume-0 by OS design. Has a working fade-in (startFadeIn,FADE_IN_START_FRACTION = 0.05f, 250ms steps). - Flutter track:
app.dart_prearrancarAudioAlarma()starts the regular radio player (PluriWaveAudioHandler/just_audio) at volume 0.05;pantalla_alarma_sonando.dart_iniciarFadeIn()ramps it 0.05 →alarma.volumen. UsesAudioSessionConfiguration.music()— normal media session, fully subject to device media volume. - Handoff: when the Flutter player reaches
reproduciendo,confirmarAudioFlutter→MainActivity→PluriWaveAlarmService.stop(). Native audio torn down; Flutter media-stream player is the sole source for the rest of the ring.
Grep-verified: zero occurrences of setStreamVolume/AudioManager volume-override anywhere in the codebase, ever — the app has never programmatically overridden device stream volume. The "used to ignore device volume" impression comes from the native track's brief immune window, not a regressed capability.
Root Causes
A — architectural: steady-state alarm audio is the Flutter media-stream player within 1-3s of firing. Media volume 0 → silent alarm (symptom 2).
B — manifest bug (verified): AndroidManifest.xml:55-58 declares foregroundServiceType="mediaPlayback|systemExempted" and NO FOREGROUND_SERVICE_ALARM permission — contradicting the approved design (app-quality-and-native-alarms design.md Decision 1.1, spec S1-R1 CRITICAL, which mandates mediaPlayback|alarm + permission to prevent ForegroundServiceTypeException on Android 14+ when starting from a broadcast receiver). apply-progress.md marks T-S1-03/04 done with "DEVIATION (see below)" but no Batch-1 deviation section exists. On Android 14+ this plausibly kills the native service start silently — removing even the brief volume-immune window.
C — double fade-in: native Kotlin ramp and Dart ramp run independently and can interleave at handoff → audible jump/reset (likely the real cause of "feels broken", symptom 1).
Recommendation (sequenced, one change)
- Manifest fix: restore
mediaPlayback|alarm+FOREGROUND_SERVICE_ALARMper the already-approved design. Near-zero risk. - Ring-scoped volume immunity (option c): at ring start, capture current media-stream volume; force it to an audible reference; ramp PLAYER volume 5% →
alarma.volumen; restore original stream volume on stop/snooze/dismiss. Scoped strictly to the ring — must not touch normal radio listening. - Fade-in dedup: gate so only one ramp drives audible volume across the handoff.
Deferred (flagged as follow-up, not bundled): option (b) — native-only audio for the whole ring (Flutter screen as pure UI). Architecturally purest, but a large rewrite across pantalla_alarma_sonando.dart, PluriWaveAlarmService.kt, and the MethodChannel surface.
Affected Areas
AndroidManifest.xml:56-58— FGS type + missing permission (CRITICAL on API 34+)PluriWaveAlarmService.kt— native fade-in OK for its scope; handoff teardownlib/servicios/servicio_audio.dart— steady-state alarm source, no alarm-stream routinglib/pantallas/pantalla_alarma_sonando.dart— Dart ramp; double-ramp at handofflib/app.dart—_prearrancarAudioAlarma()shrinks the immune windowapp-quality-and-native-alarms/apply-progress.md— missing Batch-1 deviation note (doc gap)
Risks
- Kotlin/manifest edits cannot be compiled by agents (
flutter buildforbidden) — mandatory on-device verification by the user (this exact gap is how the manifest regression slipped through) - Any audio-session/volume change must be provably scoped to the ring and reverted — must not regress phone-call ducking (
ServicioAudioSession, S3-R1) or normal listening - Native and Dart fade-ins duplicate the same algorithm/constants — flag for single-sourcing to prevent drift