Files
pluriwave/openspec/changes/archive/2026-07-11-alarm-volume-ramp-restore/explore.md
T
FreeTLab efbf289f6b
Build & Deploy PluriWave / Análisis de código (push) Successful in 39s
Build & Deploy PluriWave / Build APK + AAB release (push) Successful in 1m48s
docs(openspec): archive alarm-volume-ramp-restore and promote native-alarms spec
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.
2026-07-11 10:32:07 +02:00

41 lines
4.2 KiB
Markdown

# 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`):
1. **Native track**: `PluriWaveAlarmReceiver(ACTION_FIRE)``PluriWaveAlarmService.kt` plays via `MediaPlayer` with `AudioAttributes.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).
2. **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`. Uses `AudioSessionConfiguration.music()` — normal media session, fully subject to device media volume.
3. **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)
1. **Manifest fix**: restore `mediaPlayback|alarm` + `FOREGROUND_SERVICE_ALARM` per the already-approved design. Near-zero risk.
2. **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.
3. **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 teardown
- `lib/servicios/servicio_audio.dart` — steady-state alarm source, no alarm-stream routing
- `lib/pantallas/pantalla_alarma_sonando.dart` — Dart ramp; double-ramp at handoff
- `lib/app.dart``_prearrancarAudioAlarma()` shrinks the immune window
- `app-quality-and-native-alarms/apply-progress.md` — missing Batch-1 deviation note (doc gap)
## Risks
- Kotlin/manifest edits cannot be compiled by agents (`flutter build` forbidden) — 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