fix(alarms): apply silence-after mid-ring and close missed one-shots

Review fixes for the silence-after setting.

Changing the alarm's silence-after now re-measures a ring in progress at
once, through AlarmEngine.onSilenceAfterChanged: past the new window it
closes as missed, inside it the backstop moves. onAutoSilence also re-arms
instead of closing a ring that is still inside the current window, so a
backstop armed for a shorter window cannot end a ring early.

A cycle the resolver closes as missed (a ring past its window, or a
snooze that never fired) now disables a one-shot alarm, as the engine's
own close does. A short window made that path much easier to reach, and
the alarm was being armed for the same time the next day.

A late snooze is kept for the window or ten minutes, whichever is longer,
so a short window never makes a promised snooze easier to lose than in v1.

A timer that has gone quiet stays quiet when the setting is raised: the
lapsed anchor moves back past the hard stop. The timer's heads-up is now
keyed to the session anchor rather than the window's end, so editing the
setting mid-ring no longer counts as a new session and alerts again.

A negative stored value now reads as the default instead of Never. The
picker offers a stored value that matches no choice, and the wake-lock
timeout is one shared constant.

Refs #17
This commit is contained in:
makiolaj committed 2026-10-21 19:04:08 +02:00
1 parent 412e54db09
commit 1c323d5b4c
20 files changed
+397 -88

No files matched your search

+30 -14
View File
@@ -1163,26 +1163,37 @@ Never), **10 minutes** by default, AOSP DeskClock's default and v1's fixed
value, so an upgrade changes nothing. Snoozing keeps it open.
The window is read from `ClockDefaults.alarmSilenceAfter` everywhere a ring is
measured: the resolver's ringing and late-snooze checks, the backstop `onFire`
arms, how late a snooze fire may arrive, and whether a ring interrupted by a
reboot is resumed. `AlarmResolver.resolve` takes it without a default, so no
caller can measure against a window the user did not choose.
measured: the resolver's ringing check, the backstop `onFire` arms, and whether
a ring interrupted by a reboot is resumed. `AlarmResolver.resolve` takes it
without a default, so no caller can measure against a window the user did not
choose. How late a snooze may come due and still ring is the window **or
`AlarmRing.SNOOZE_GRACE` (v1's ten minutes), whichever is longer**: a short
"Silence after" must not make a promised snooze easier to lose to a reboot.
**"Never" is an hour.** The backstop is also what stops a phone left at home
from ringing until its battery is flat, what bounds the ring services' wake
locks, and what keeps a reboot hours later from resuming a ring nobody was
there for. So "Never" maps to `SilenceAfter.HARD_STOP` (60 minutes), longer
than any finite choice, and the settings hint says so in words. Stored values
are clamped to 0..60 on read, on write and from a backup, so no value can make
a ring outlast it.
go through `SilenceAfter.sanitise` on read, on write and from a backup: over 60
is 60, so no value can make a ring outlast the hard stop, and a negative one is
the default rather than the clamp's floor, which would be "Never".
A ring started under one window keeps the backstop it was armed with. When the
window is shortened mid-ring, the next re-resolution closes the ring once it is
past the new window, and `persist` then stops the ring service and cancels the
backstop with it: storage says nothing rings, so nothing may. The same applies
to any ring the resolver closes (a clock jumped forward, a ringing alarm
switched off), which earlier only cleared `ringingSince` and left the sound to
the backstop.
Changing the setting mid-ring applies at once. The settings screen calls
`AlarmEngine.onSilenceAfterChanged()`, which measures the ring in progress
against the new window from when it *started*: past it, the cycle closes as
missed through `closeCycleLocked`; inside it, the backstop moves to the new
end. As a second line, `onAutoSilence` re-arms rather than closes a ring that
is still inside the current window, so a backstop armed for a shorter one can
never end a ring early.
Whenever the resolver closes a ring (a clock jumped forward, a ringing alarm
switched off, a reboot outlasting the window), `persist` stops the ring service
and cancels the backstop: storage says nothing rings, so nothing may. Earlier
that path only cleared `ringingSince` and left the sound to the backstop. And a
cycle the resolver closes as missed (a ring past its window, or a snooze that
never fired) **disables a one-shot alarm**, exactly as `closeCycleLocked`
does; falling through would have armed it for the same time tomorrow.
A **non-repeating alarm is disabled when its cycle closes, never when it opens**.
Disabling it at the start would clear its own snooze and the alarm would vanish
@@ -1924,7 +1935,12 @@ The window is the timer's own **"Silence after"** setting
default: the same choices as the alarm's and the same one-hour "Never", but a
separate value, because coupling the two would make one number answer two
questions. `TimerRingPolicy` reads it from the defaults it already takes, so a
change applies on the service's next pass. Unlike an alarm's, a timer's
change applies on the service's next pass. A session that has gone quiet stays
quiet: on auto-silence the policy moves the stored anchor back past
`SilenceAfter.HARD_STOP`, so raising the setting afterwards cannot start it
ringing again. The service's one heads-up is keyed to the session's anchor
(`Sound.openedAt`) rather than to the window's end, which an edit can move
without making a new session. Unlike an alarm's, a timer's
**auto-silence stops the noise and leaves the row `EXPIRED`.** An auto-silenced
alarm is a *missed* alarm and its cycle closes; an auto-silenced timer has still
finished, and the user coming back must be able to see that and add a minute