Files
pluriwave/openspec/changes/alarm-volume-ramp-restore/explore.md
T
FreeTLab 747738d20a
Build & Deploy PluriWave / Análisis de código (push) Successful in 35s
Build & Deploy PluriWave / Build APK + AAB release (push) Successful in 1m38s
docs(openspec): add SDD artifact trails for bt-device-identity and alarm-volume-ramp-restore
In-progress artifact sets from the current SDD cycles: exploration,
proposal, spec, design, tasks, and verify reports as produced so far.
Also drops a leftover working copy of eq-device-disconnect-revert
whose contents were already committed under changes/archive/.
2026-07-11 00:56:22 +02:00

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):

  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, confirmarAudioFlutterMainActivityPluriWaveAlarmService.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