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

77 lines
5.9 KiB
Markdown

# 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
- `PluriWaveAlarmService` becomes 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 `volume` becomes the curve ceiling scalar `s` (`setVolume(s*curve)`); NEVER `setStreamVolume` on any stream.
- Manual `AUDIOFOCUS_GAIN_TRANSIENT` request on `STREAM_ALARM`; abandon on stop.
- Silence the fire notification channel (currently plays `DEFAULT_ALARM_ALERT_URI` on top of the player) → new channel id.
- Kill the handoff: delete `confirmFlutterAudio` / `flutterOwnsRing` and 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 to `STREAM_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 → _v3` resets 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 `_v3` is 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:
1. Kotlin service rebuild (dB curve, manual focus, silent v3 channel, drop `flutterOwnsRing`) — on-device QA gate.
2. Dart deletion + ring-screen rewrite (remove orchestration/override calls; screen → pure UI).
3. 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.