# Alarm Pre-Notice Countdown Specification ## Purpose True per-minute live countdown for the 30-minute alarm pre-notice notification, mirroring the proven snooze-countdown repeating-alarm pattern. Replaces the current frozen, single-shot pre-notice ("30 min" forever) with a self-healing chain that updates every minute (29, 28, ... 1) until the real alarm fires. ## Requirements ### Requirement: First Pre-Notice Post The system MUST post the initial pre-notice notification at `triggerAtMillis - 30min` (T-30min), unchanged from current behavior. #### Scenario: First post at T-30min - GIVEN an alarm scheduled to fire at time T - WHEN the system clock reaches T-30min - THEN an exact alarm fires `ACTION_PRE_NOTICE` - AND a notification showing "30 min" remaining is posted using `notificationIdForAlarm(id)` ### Requirement: Per-Minute Tick Re-Arm After posting a pre-notice notification, the system MUST re-arm itself to fire again at the next minute boundary, reusing `ACTION_PRE_NOTICE` for both the first post and every subsequent tick (no separate action constant). #### Scenario: Tick re-arms next minute - GIVEN `ACTION_PRE_NOTICE` has just fired and posted a notification with remaining minutes `R` where `R > 1` - WHEN the post completes - THEN `AlarmScheduler.armNextPreNoticeCountdownTick(id, R)` arms a new exact alarm at `triggerAtMillis - (R - 1) * 60_000L` - AND the new alarm uses requestCode slot 9 #### Scenario: Tick updates notification content - GIVEN the tick chain is active for alarm `id` - WHEN a re-armed `ACTION_PRE_NOTICE` fires at a later minute boundary - THEN the notification at `notificationIdForAlarm(id)` is updated (not duplicated) to show the new remaining-minutes value ### Requirement: Self-Healing Minute Computation Each tick MUST compute remaining minutes from the current wall-clock time relative to `triggerAtMillis`, using `ceilMinutes()`, rather than decrementing a stored counter. #### Scenario: Normal tick sequence - GIVEN consecutive ticks fire close to their scheduled minute boundaries - WHEN each tick computes remaining minutes via `ceilMinutes(triggerAtMillis - now)` - THEN the displayed sequence is 29, 28, 27, ... 1 with no manual decrement state #### Scenario: Missed tick self-heals by jumping, not crashing - GIVEN the OS delays or coalesces a scheduled tick (e.g. Doze quota) so the receiver fires late - WHEN the delayed tick recomputes remaining minutes from wall clock - THEN the displayed countdown jumps forward to the correct current value (e.g. skips from 15 to 12) instead of crashing, looping, or showing a stale/negative value ### Requirement: Self-Stop at Final Minute The tick chain MUST stop re-arming once computed remaining minutes is `<= 1`; the final minute is left to the real fire alarm, not a tick. #### Scenario: Chain stops before final minute - GIVEN a tick fires and computes remaining minutes `R <= 1` - WHEN the tick finishes posting/updating the notification - THEN no further `armNextPreNoticeCountdownTick` call is made - AND the alarm's existing `setAlarmClock` fire alarm remains the sole next trigger ### Requirement: Consistent Rounding via ceilMinutes The system MUST use `ceilMinutes()` for pre-notice remaining-minutes computation, replacing the prior floor-based `computeRemainingMinutes()`, for consistency with the snooze-countdown chain. #### Scenario: Rounding matches snooze countdown - GIVEN identical time-remaining deltas for a pre-notice tick and a snooze-countdown tick - WHEN both compute their displayed minute value - THEN both use `ceilMinutes()` and produce the same rounding result for equivalent inputs ### Requirement: Tick Chain Cancellation The system MUST tear down the pending pre-notice tick alarm at all of the following sites: `cancelAlarm()`, the `scheduleSpec` no-next-trigger branch, the snooze-transition branch, `ACTION_SKIP_NEXT`, and `ACTION_POSTPONE_NEXT`. No site may leave an orphaned repeating alarm. #### Scenario: Full alarm cancellation tears down tick chain - GIVEN a pre-notice tick chain is active for alarm `id` - WHEN `cancelAlarm(id)` is called - THEN the pending pre-notice tick `PendingIntent` (slot 9) is cancelled - AND no further `ACTION_PRE_NOTICE` ticks fire for `id` #### Scenario: No-next-trigger reschedule cancels tick chain - GIVEN a pre-notice tick chain is active for alarm `id` - WHEN `scheduleSpec` recomputes and finds no next trigger time for `id` - THEN the pending pre-notice tick is cancelled in the same branch that already cancels the single-shot pre-notice and snooze-countdown pendings #### Scenario: Snooze transition cancels pre-notice tick chain - GIVEN a pre-notice tick chain is active for alarm `id` - WHEN the user snoozes the alarm, transitioning it into snooze-countdown mode - THEN the pre-notice tick chain is cancelled - AND no pre-notice notification or alarm remains pending while snooze-countdown is active #### Scenario: Skip-next action cancels tick chain - GIVEN a pre-notice tick chain is active for alarm `id` - WHEN the user taps "Skip" on the pre-notice notification, triggering `ACTION_SKIP_NEXT` - THEN the pending pre-notice tick alarm for `id` is cancelled - AND no further pre-notice ticks fire for the skipped occurrence #### Scenario: Postpone-next action cancels tick chain - GIVEN a pre-notice tick chain is active for alarm `id` - WHEN the user taps "Postpone" on the pre-notice notification, triggering `ACTION_POSTPONE_NEXT` - THEN the pending pre-notice tick alarm for `id` is cancelled - AND no further pre-notice ticks fire for the postponed occurrence ### Requirement: Notification ID Reuse and Mutual Exclusivity with Snooze The pre-notice tick chain MUST reuse the same notification ID (`notificationIdForAlarm(id)`) as snooze-countdown, and the two chains MUST remain mutually exclusive in time for the same alarm `id`. #### Scenario: Pre-notice and snooze-countdown never run concurrently - GIVEN alarm `id` has an active pre-notice tick chain - WHEN the alarm is not snoozed - THEN no snooze-countdown chain is scheduled for `id` concurrently, preserving the existing `scheduleSpec` branch invariant on `snoozeUntilMillis` #### Scenario: Notification updates in place, no duplicate - GIVEN a pre-notice tick posts an update for alarm `id` - WHEN the notification ID matches a previously posted pre-notice or snooze-countdown notification for the same `id` - THEN the system tray shows a single updated notification, not a duplicate entry