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.
5.9 KiB
Proposal: Native Alarm Ring
Intent
The alarm ring has failed 5 consecutive on-device iterations under the media-stream/handoff design (Dart runs a ramp, hands off to native, overrides STREAM_MUSIC, restores on dismiss). Root cause: split ring-audio ownership between Dart and native fighting the alarm FGS. This change rebuilds the ring native-only to AOSP DeskClock's exact AsyncRingtonePlayer pattern — the pattern that qualifies the app as a legitimate Play Store alarm clock. The current PluriWaveAlarmService is already ~80% there; the rebuild mostly DELETES accidental complexity (~500 lines).
Scope
In Scope
PluriWaveAlarmServicebecomes the SOLE ring-audio owner for the entire ring:- Replace linear-amplitude fade with DeskClock exponential dB curve @50ms (
gainDb = f*40-40,curve = 10^(gainDb/20)), anchored at ring start. - Per-alarm
volumebecomes the curve ceiling scalars(setVolume(s*curve)); NEVERsetStreamVolumeon any stream. - Manual
AUDIOFOCUS_GAIN_TRANSIENTrequest onSTREAM_ALARM; abandon on stop. - Silence the fire notification channel (currently plays
DEFAULT_ALARM_ALERT_URIon top of the player) → new channel id.
- Replace linear-amplitude fade with DeskClock exponential dB curve @50ms (
- Kill the handoff: delete
confirmFlutterAudio/flutterOwnsRingand the entire media-stream override surface (Dart + MethodChannel + Kotlin override/restore). - Ring screen → pure UI: keep buttons →
EstadoAlarmas.finalizarEjecucion/posponerAlarma, PopScope back=Stop, navigation/SnackBar semantics. - Delete dead code + dead tests; rewrite ring-screen tests as pure-UI; KEEP the "EstadoRadio never touches override channel" regression test as decoupling proof.
- Keep MediaPlayer for playback (already pop-free, already handles stations). ExoPlayer explicitly rejected now (unresolved start-pop #2752).
Out of Scope (→ follow-up change oem-reliability-guidance)
- In-app permission-status surface, MIUI/OEM autostart guidance + deep link, receiver-level
PARTIAL_WAKE_LOCK.
Do-Not-Touch (hard constraint from exploration)
Scheduling stack (AlarmScheduler, boot, ServicioAlarmas/ProgramacionAlarmas, EstadoAlarmas CRUD/snooze/skip/vacations), receiver ACTION_FIRE ordering, wake-lock/FGS-before-audio ordering, snooze 5-entry-point truth-ownership, pre-notice + snooze-countdown chains, radio/EQ/sleep-timer/recording/BT. Only their incidental use by the ring is removed.
Capabilities
New Capabilities
None.
Modified Capabilities
native-alarms: ring-audio requirements change — single native owner for the full ring; exponential dB fade; per-alarm volume as player-scalar ceiling relative toSTREAM_ALARM; manual transient alarm focus; silent notification channel; handoff + media-override requirements REMOVED; dismiss restores nothing by construction.
Approach
Converge the existing native service onto DeskClock's AsyncRingtonePlayer mechanism and delete the Dart/channel/Kotlin override layer. Native plays until stop/snooze; Dart only reflects state and drives stop/snooze through existing EstadoAlarmas methods. Intent-extra wiring (incl. EXTRA_FADE_IN_SECONDS) is already verified end to end.
Affected Areas
| Area | Impact | Description |
|---|---|---|
PluriWaveAlarmService.kt |
Modified | dB curve, volume ceiling, manual focus, silent v3 channel, drop flutterOwnsRing |
MainActivity.kt |
Removed | override/restore methods + companion state + channel handlers |
app.dart / pantalla_alarma_sonando.dart |
Removed/Modified | delete ring-audio orchestration; screen → pure UI |
PuertoAlarmasAndroid / servicio_alarmas_android.dart |
Removed | drop confirmarAudioFlutter / forzar / restaurar surface |
| tests | Removed/Rewritten | ramp/handoff/override groups deleted; ring-screen tests → pure-UI; keep decoupling test |
Risks
| Risk | Likelihood | Mitigation |
|---|---|---|
Silencing channel needs new id (Android locks channel sound at creation; editing _v2 is a no-op) |
High | New pluriwave_alarm_fire_v3 (setSound(null), keep IMPORTANCE_HIGH for FSI) + channels_migrated_v3 guard deleting _v2 — reuse proven _v2 migration pattern |
| Kotlin not agent-compilable | High | On-device QA is the gate: audible curve, volume ceiling, no pop, dismiss/snooze/back, screen-off delivery |
| Ring screen visual regression | Low | Behavior unchanged; keep assertions, rewrite only setup |
Migration Notes
- Channel id bump
_v2 → _v3resets user-visible settings for the ringing channel ONLY (pre-notice channel untouched); note in release notes. Same guarded one-time deletion already shipped for_v2. Vibration handling on_v3is a design decision. - No data migration: scheduling persistence and alarm model unchanged.
Rollback
Single-change revert restores the prior commit; no schema/persistence change, no data loss. A reverted _v3 is harmless (service recreates _v2).
Review Workload Estimate
Net DELETION (~500 lines removed). Total changed lines exceed the 400-line budget (spread across Kotlin + Dart + tests) → chained/stacked slices recommended. Suggested 3 work-unit split:
- Kotlin service rebuild (dB curve, manual focus, silent v3 channel, drop
flutterOwnsRing) — on-device QA gate. - Dart deletion + ring-screen rewrite (remove orchestration/override calls; screen → pure UI).
- Test rewrite (delete dead groups; ring-screen pure-UI tests; keep decoupling regression test).
Dependencies
None blocking. oem-reliability-guidance is a downstream follow-up, not a prerequisite.
Success Criteria
- Native service is the only audio source during ring; notification channel is silent.
- Fade is an audible exponential dB curve capped at the per-alarm volume ceiling; no start pop.
- Dismiss/snooze/back work via EstadoAlarmas; no system volume ever written; nothing to restore.
- Handoff + media-override code and tests fully removed; decoupling regression test passes.
- Screen-off delivery + ring verified on device (POCO X7 Pro) after Autostart enabled.