fix: ring a snooze that is delivered late
A snooze stays pending for the ring window after it comes due, but the fire was rejected after the two-minute grace and re-registered at the same past instant, so the alarm looped on wakeups and never rang after a reboot or a late delivery.
This commit is contained in:
@@ -234,9 +234,15 @@ class AlarmEngine @Inject constructor(
|
||||
private suspend fun onFireLocked(alarmId: Long, scheduledFor: Instant): FireOutcome {
|
||||
val now = wallClock.now()
|
||||
val alarm = alarms.find(alarmId)
|
||||
val state = states.state(alarmId)
|
||||
val isSnoozeFire = state.snoozedUntil != null && state.snoozedUntil == scheduledFor
|
||||
// A snooze is kept for as long as the resolver keeps it pending, so one
|
||||
// delivered late (after a reboot, or by Doze) still rings. Rejecting it
|
||||
// here would only re-register the same past instant and fire again.
|
||||
val lateBy = if (isSnoozeFire) AlarmRing.AUTO_SILENCE_AFTER else AlarmRing.FIRE_GRACE
|
||||
// Deleted, switched off, or a PendingIntent so stale the moment has gone:
|
||||
// re-resolve the slot and stay quiet.
|
||||
if (alarm == null || !alarm.enabled || now - scheduledFor > AlarmRing.FIRE_GRACE) {
|
||||
if (alarm == null || !alarm.enabled || now - scheduledFor > lateBy) {
|
||||
rescheduleLocked()
|
||||
return FireOutcome.Ignored
|
||||
}
|
||||
@@ -249,8 +255,6 @@ class AlarmEngine @Inject constructor(
|
||||
states.save(other.copy(ringingSince = null, snoozedUntil = null, snoozeCount = 0))
|
||||
}
|
||||
|
||||
val state = states.state(alarmId)
|
||||
val isSnoozeFire = state.snoozedUntil != null && state.snoozedUntil == scheduledFor
|
||||
val opened = if (isSnoozeFire) {
|
||||
// The cycle is the same one: the count and the watermark carry over.
|
||||
state.copy(snoozedUntil = null)
|
||||
|
||||
@@ -363,6 +363,40 @@ class AlarmEngineTest {
|
||||
assertThat(states.state(1L).snoozeCount).isEqualTo(1)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `a snooze delivered late after a reboot still rings`(@TempDir dir: Path) = runTest {
|
||||
givenAlarm(repeatDays = RepeatDays.EVERY_DAY)
|
||||
val snoozeAt = sevenToday + 9.minutes
|
||||
states.save(
|
||||
ringState(snoozedUntil = snoozeAt, snoozeCount = 1, handledOccurrence = sevenToday),
|
||||
)
|
||||
wallClock.instant = snoozeAt + 4.minutes
|
||||
val engine = engine(dir)
|
||||
|
||||
val outcome = engine.onFire(alarmId = 1L, scheduledFor = snoozeAt)
|
||||
|
||||
assertThat(outcome).isEqualTo(FireOutcome.Ringing(1L))
|
||||
assertThat(states.state(1L).snoozeCount).isEqualTo(1)
|
||||
assertThat(scheduler.next?.fireAt).isEqualTo(sevenTomorrow)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `a snooze delivered after its ring window is ignored`(@TempDir dir: Path) = runTest {
|
||||
givenAlarm(repeatDays = RepeatDays.EVERY_DAY)
|
||||
val snoozeAt = sevenToday + 9.minutes
|
||||
states.save(
|
||||
ringState(snoozedUntil = snoozeAt, snoozeCount = 1, handledOccurrence = sevenToday),
|
||||
)
|
||||
wallClock.instant = snoozeAt + 11.minutes
|
||||
val engine = engine(dir)
|
||||
|
||||
val outcome = engine.onFire(alarmId = 1L, scheduledFor = snoozeAt)
|
||||
|
||||
assertThat(outcome).isEqualTo(FireOutcome.Ignored)
|
||||
assertThat(ring.events).isEmpty()
|
||||
assertThat(scheduler.next?.fireAt).isEqualTo(sevenTomorrow)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `the ringing alarm never takes the next-alarm slot from another one`(@TempDir dir: Path) = runTest {
|
||||
givenAlarm(id = 1L, hour = 7, repeatDays = RepeatDays.EVERY_DAY)
|
||||
|
||||
Reference in New Issue
Block a user