Files
pluriwave/openspec/changes/archive/2026-07-11-alarm-volume-ramp-restore/proposal.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

4.8 KiB

Proposal: Alarm Volume Ramp & Device-Volume Immunity

Intent

An alarm must ring regardless of device media volume. Today the alarm hands off within 1-3s from the volume-immune native USAGE_ALARM player to the Flutter media-stream player, which is fully governed by media volume — so media volume 0 = silent alarm for nearly the whole ring. A manifest bug (systemExempted instead of alarm, no FOREGROUND_SERVICE_ALARM) can also silently kill the native start on Android 14+. Device-volume immunity for the full ring was never actually built; it must be built now.

Scope

In Scope

  • Manifest fix: PluriWaveAlarmServiceforegroundServiceType="mediaPlayback|alarm" + declare FOREGROUND_SERVICE_ALARM. Restores already-approved design (app-quality-and-native-alarms D1.1 / S1-R1).
  • Ring-scoped media-volume override (Kotlin-owned): on ring start capture STREAM_MUSIC volume, force an audible reference level; player ramps 5% → alarma.volumen (existing Dart ramp); restore original volume on EVERY exit (dismiss, snooze, dispose, service teardown, best-effort on kill).
  • Fade-in dedup: gate native + Dart ramps so only one drives audible volume across handoff.

Out of Scope

  • Full native-only audio for the whole ring (exploration option b) — future follow-up.
  • Single-sourcing the fade-in constant/curve shared by Kotlin+Dart — noted as debt.

Capabilities

New Capabilities

  • None.

Modified Capabilities

  • native-alarms: alarm ring MUST be immune to device media volume for its full duration (not just the pre-handoff window); FGS type/permission corrected; fade-in single-driver across handoff.

Approach

Override lives in Kotlin (MainActivity/PluriWaveAlarmService via a new method on the existing pluriwave/alarm_scheduler MethodChannel) because AudioManager.setStreamVolume has no Flutter plugin here and MainActivity already owns audio channels. Dart drives the ring lifecycle; pantalla_alarma_sonando.dart calls capture+override at ring start and restore from the already-centralized exit points (_silenciarAudio → dismiss/snooze; dispose). Restore must be idempotent and provably scoped: normal radio listening and phone-call ducking (ServicioAudioSession, S3-R1) stay untouched — the override only fires while a ring is active. Existing 5%→target Dart ramp is kept; the native ramp is gated to avoid a second audible driver at handoff.

Affected Areas

Area Impact Description
android/.../AndroidManifest.xml:57 Modified FGS type mediaPlayback|alarm + FOREGROUND_SERVICE_ALARM permission
android/.../MainActivity.kt Modified New alarm_scheduler methods: capture/override/restore STREAM_MUSIC
android/.../PluriWaveAlarmService.kt Modified Restore-on-teardown safety net; gate native fade-in vs Dart
lib/pantallas/pantalla_alarma_sonando.dart Modified Invoke override at start; restore in _silenciarAudio/dispose
lib/servicios/servicio_alarmas_android.dart Modified Dart wrapper for the new channel methods
lib/app.dart Modified Coordinate override with early _prearrancarAudioAlarma

Risks

Risk Likelihood Mitigation
Kotlin/manifest not compilable by agent High On-device verify on Android 14+ is a MANDATORY human gate (CC-R1/R2)
App killed mid-ring leaves volume overridden Med Best-effort restore in service teardown + onDestroy; document residual gap
Override leaks into normal radio playback Med Scope strictly to ring; idempotent restore; assert ducking path untouched
Double fade-in still audible at handoff Low Single-driver gate; test the handoff window

Rollback Plan

Per-slice and independent. Revert the manifest line to restore prior FGS type. Behind a guard so restore always runs even if override failed (no worse than today). If regressions appear in normal playback, disable the override call in pantalla_alarma_sonando.dart — manifest fix stands alone.

Dependencies

  • Android 14+ device for the mandatory on-device verification of the manifest fix.
  • Confirm from user whether app-quality-and-native-alarms Slice 1 (manifest) was ever verified on a real device (apply-progress deviation note is missing).

Success Criteria

  • Manifest declares mediaPlayback|alarm + FOREGROUND_SERVICE_ALARM; native service starts on Android 14+.
  • With device media volume at 0, the alarm still rings and ramps 5% → alarma.volumen.
  • Original media volume is restored after dismiss, snooze, and dispose.
  • Normal radio listening and phone-call ducking are unaffected.
  • Only one fade-in driver is audible across the native→Flutter handoff.
  • No new user-visible strings (no l10n across 13 locales required).