feat(alarm): the ringing service, and never degrading to silence

A systemExempted foreground service holds the ring: audio focus, USAGE_ALARM,
the volume ramp, vibration, and the ringtone URI. It goes foreground
synchronously before it reads anything from the database, because the platform
kills a service that reaches startForeground late — including on the path where
there turns out to be no session to ring.

The chain that matters is the one that never ends in silence. A full-screen
intent when canUseFullScreenIntent() allows it; a high-priority heads-up
notification that still rings when it does not; the device's default alarm
ringtone when the configured URI will not open; and forced vibration when no
audio source will play at all.

Preparing a MediaPlayer is done off the main thread — a synchronous prepare at
the moment the alarm fires is an ANR — and a player is released rather than
orphaned on every failure path, so a dismissal cannot be outlived by the ring
it cancelled.

The ring screen here is plumbing and a placeholder: window flags to show over
the lockscreen and turn the screen on. Its UI is M4.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-11 16:05:25 +02:00
co-authored by Claude Opus 5
parent dd9a920c28
commit bf511b47d7
10 changed files with 887 additions and 11 deletions
+10
View File
@@ -2,6 +2,16 @@
<string name="app_name">Clockula</string>
<string name="app_tagline">A modern Material 3 Expressive clock app.\nComing into bloom.</string>
<!-- Alarms (M3): the notification channels and the ring actions -->
<string name="channel_alarm_ring_name">Ringing alarms</string>
<string name="channel_alarm_ring_description">The full-screen alarm while it is ringing. Turning this off does not silence an alarm.</string>
<string name="channel_alarm_snoozed_name">Snoozed alarms</string>
<string name="channel_alarm_snoozed_description">A quiet reminder that an alarm is snoozed, and when it comes back.</string>
<string name="alarm_notification_title">Alarm</string>
<string name="alarm_action_snooze">Snooze</string>
<string name="alarm_action_dismiss">Dismiss</string>
<string name="alarm_snoozed_until">Snoozed until %1$s</string>
<!-- Crash reporting (floret-kit core-crash) -->
<string name="crash_report_issue_title">Crash report</string>
<string name="report_issue_url" translatable="false">https://codeberg.org/jlmakiola/clockula/issues/new</string>