Files
FreeTLab 41b95fed44
Build & Deploy PluriWave / Análisis de código (push) Successful in 38s
Build & Deploy PluriWave / Build APK + AAB release (push) Successful in 1m45s
docs(openspec): archive native-alarm-ring and update the native-alarms spec
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.
2026-07-12 12:36:22 +02:00

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:foregroundServiceType MUST be mediaPlayback|systemExempted
  • AND the runtime startForeground type constants in PluriWaveAlarmService.kt MUST 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) starts PluriWaveAlarmService
  • THEN it MUST start without ForegroundServiceTypeException
  • AND the alarm MUST be audible via the native USAGE_ALARM player

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_MUSIC volume is 0
  • WHEN an alarm fires
  • THEN it MUST be audible via the service's STREAM_ALARM player
  • 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=0 MUST equal s * 10^(-40/20) (~1% of s), rising every 50ms
  • AND volume at t=fadeInSegundos MUST equal exactly s

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 before start(), 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 startAlarm runs
  • THEN AUDIOFOCUS_GAIN_TRANSIENT MUST be requested on STREAM_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 setStreamVolume call 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_v2 already created
  • WHEN the service next ensures its channel
  • THEN _v3 MUST be created and _v2 deleted; 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/posponerAlarma MUST be called
  • AND zero calls MUST reach EstadoRadio.audio or a removed channel method

Scenario: Reduced Android port surface

Testability: static/compile-verifiable (Dart analyzer) + flutter test

  • GIVEN PuertoAlarmasAndroid and its implementations/fakes
  • WHEN inspected after this change
  • THEN confirmarAudioFlutter, forzarVolumenMediaParaAlarma, restaurarVolumenMedia MUST NOT exist

Non-Functional Notes

  • No new user-visible strings; no l10n work required across the 13 supported locales.