Apply-stage SDK verification (javap on android-34/35/36 platform jars plus api-versions.xml) proved FOREGROUND_SERVICE_TYPE_ALARM and the FOREGROUND_SERVICE_ALARM permission are fictional constants. The existing mediaPlayback|systemExempted declaration is the documented correct pattern for an alarm app holding exact-alarm permissions, so slice 1 ships no code and root cause B is withdrawn. Spec, design, and tasks amended with the evidence; volume-override and fade-dedup slices proceed unaffected.
5.5 KiB
Delta for native-alarms
ADDED Requirements
Requirement: Manifest declares alarm-eligible foreground service
CORRECTED (2026-07-11, apply-stage SDK verification): the originally specified foregroundServiceType="mediaPlayback|alarm" + FOREGROUND_SERVICE_ALARM permission are FICTIONAL — neither constant exists anywhere in the Android SDK (verified via javap -constants on android-34/35/36 platform jars and api-versions.xml; 13 real FGS types, no alarm variant). The existing declaration foregroundServiceType="mediaPlayback|systemExempted" is the documented correct pattern for an alarm-clock app holding exact-alarm permissions that starts a foreground service from an AlarmManager broadcast on API 34+. The prior app-quality-and-native-alarms deviation (T-S1-03/04) that kept systemExempted was right. No manifest or runtime change ships in this change; the requirement is re-scoped to the service-start behavior below.
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:foregroundServiceTypeMUST bemediaPlayback|systemExempted - AND the runtime
startForegroundtype constants inPluriWaveAlarmService.ktMUST 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)startsPluriWaveAlarmService - THEN it MUST start without
ForegroundServiceTypeException - AND the alarm MUST be audible via the native
USAGE_ALARMplayer
Requirement: Ring-scoped device-volume override
While an alarm is ringing, the system MUST override STREAM_MUSIC to an audible reference level so the alarm is not silenced by device volume 0, and MUST restore the original captured volume when the ring ends through any exit path.
Scenario: Alarm is audible when device media volume is 0
Testability: manual on-device QA (native volume APIs)
- GIVEN device
STREAM_MUSICvolume is 0 - WHEN an alarm fires and the override captures/raises
STREAM_MUSICto an audible level - THEN the alarm MUST be audible for the full ring duration, not only the pre-handoff window
Scenario: Player fade-in ramps within the overridden level
Testability: flutter test with fakes
- GIVEN the override has set an audible
STREAM_MUSICreference level - WHEN the ring's fade-in timer runs
- THEN player volume MUST ramp 5% ->
alarma.volumenoveralarma.fadeInSegundos, unchanged from today
Scenario: Dismiss restores the original captured volume
Testability: flutter test with fakes (Dart->channel call) + manual on-device QA (native restore)
- GIVEN the override captured
STREAM_MUSICat volumeV - WHEN the user dismisses the alarm (
_detener()->_silenciarAudio()) - THEN
STREAM_MUSICMUST be restored to exactlyV
Scenario: Snooze restores the original captured volume
Testability: flutter test with fakes (Dart->channel call) + manual on-device QA (native restore)
- GIVEN the override captured
STREAM_MUSICat volumeV - WHEN the user snoozes the alarm (
_posponer()->_silenciarAudio()) - THEN
STREAM_MUSICMUST be restored to exactlyV
Scenario: Restore is idempotent across double-exit paths
Testability: flutter test with fakes (single/no-op restore call assertion) + manual on-device QA
- GIVEN restore already ran once (
_silenciarAudio()inside_detener()) - WHEN a second exit path also runs restore (e.g.
dispose()firing after) - THEN the second call MUST NOT throw, MUST NOT re-apply a stale value, and MUST leave
STREAM_MUSICunchanged
Scenario: Normal radio playback never triggers the override
Testability: flutter test with fakes (channel never invoked outside a ring) + manual on-device QA
- GIVEN the user is listening to radio with no alarm ringing
- WHEN playback starts, plays, or stops normally
- THEN the override MUST NOT be invoked;
STREAM_MUSICstays fully governed by device controls
Scenario: App killed mid-ring — best-effort restore only
Testability: manual on-device QA; accepted residual gap, not required in automated coverage
- GIVEN an alarm is ringing and the override is active
- WHEN the app process is killed before any exit path runs
- THEN the system SHOULD best-effort restore from service teardown/
onDestroy, but a residual overridden-volume state MAY occur and is an accepted known gap
Requirement: Single fade-in driver across native-to-Flutter handoff
During handoff from the native USAGE_ALARM player to the Flutter media-stream player, only one fade-in ramp MUST drive audible volume at any instant.
Scenario: No double-ramp interleaving at handoff
Testability: manual on-device QA (timing-sensitive, cross-process); flutter test with fakes can assert the Dart ramp only runs when native ownership is inactive, but cannot observe native MediaPlayer.setVolume() timing
- GIVEN the native fade-in is ramping the
USAGE_ALARMplayer - WHEN the Flutter player becomes ready and handoff occurs (
confirmarAudioFlutter) - THEN the native fade-in MUST stop driving audible volume once Flutter takes over; both ramps MUST NOT drive audible volume simultaneously
Non-Functional Notes
- No new user-visible strings; no l10n work required across the 13 supported locales.