fix(notify): eliminate duplicate alarm scheduling and fix test harness counting

Centralize all notification alarm scheduling through NotifyReceiver.scheduleExactNotification()
with idempotence checks to prevent duplicate alarms. Implement one-alarm policy using
setAlarmClock() only. Fix test harness alarm counting to deduplicate by Alarm handle.

Plugin Changes:
- Add ScheduleSource enum to track scheduling paths (INITIAL_SETUP, ROLLOVER_ON_FIRE, etc.)
- Add DB-level idempotence check before scheduling (prevents logical duplicates)
- Add explicit alarm cancellation before scheduling (safety net)
- Implement one-alarm policy: use setAlarmClock() only, no setExact* fallbacks for same event
- Add deep logging for all AlarmManager calls (variant, requestCode, pendingIntentHash)
- Update all rollover paths (DailyNotificationReceiver, DailyNotificationWorker) to use
  centralized function with ROLLOVER_ON_FIRE source
- Add @JvmStatic annotation to scheduleExactNotification for Java interop

Test Harness Changes:
- Fix get_plugin_alarm_count() to deduplicate by Alarm handle (prevents double-counting
  same alarm in main list and "Next wake from idle" section)
- Update TEST 0 messaging: treat 0 alarms as race condition (inconclusive, not failure)
- Make post-rollover check the authoritative assertion point (only fails on >1 or 0 alarms)
- Remove redundant "Found 0 alarms - test may not be accurate" messages

This fixes the duplicate alarm bug where two distinct AlarmManager entries were created
for the same daily notification, violating the "one notification per day" contract.
This commit is contained in:
Matthew Raymer
2025-12-01 10:09:54 +00:00
parent ba8f98db65
commit fc2f64bae3
13 changed files with 880 additions and 186 deletions

View File

@@ -52,12 +52,18 @@ test1_boot_future_alarms() {
substep "Step 2: Verify alarms are scheduled"
show_alarms
local before_count
before_count="$(count_alarms)"
info "Alarm count before reboot: $before_count"
local before_count system_before
before_count="$(get_plugin_alarm_count)"
system_before="$(get_system_alarm_count)"
info "Plugin alarms before reboot: $before_count (expected: 1)"
info "System/other alarms: $system_before (for context)"
if [[ "$before_count" -eq 0 ]]; then
warn "No alarms found before reboot; TEST 1 may not be meaningful."
warn "No plugin alarms found before reboot; TEST 1 may not be meaningful."
elif [[ "$before_count" -eq 1 ]]; then
ok "Single plugin alarm confirmed (one per day)"
else
warn "Found $before_count plugin alarms (expected: 1)"
fi
pause
@@ -96,9 +102,11 @@ test1_boot_future_alarms() {
substep "Step 5: Verify alarms were recreated"
show_alarms
local after_count
after_count="$(count_alarms)"
info "Alarm count after boot: $after_count"
local after_count system_after
after_count="$(get_plugin_alarm_count)"
system_after="$(get_system_alarm_count)"
info "Plugin alarms after boot: $after_count (expected: 1)"
info "System/other alarms: $system_after (for context)"
if [[ "$scenario" == "$BOOT_SCENARIO_VALUE" && "$rescheduled" -gt 0 ]]; then
ok "TEST 1 PASSED: Boot recovery detected and alarms rescheduled (scenario=$scenario, rescheduled=$rescheduled)."
@@ -276,12 +284,18 @@ test4_silent_boot_recovery() {
substep "Step 2: Verify alarms are scheduled"
show_alarms
local before_count
before_count="$(count_alarms)"
info "Alarm count before reboot: $before_count"
local before_count system_before
before_count="$(get_plugin_alarm_count)"
system_before="$(get_system_alarm_count)"
info "Plugin alarms before reboot: $before_count (expected: 1)"
info "System/other alarms: $system_before (for context)"
if [[ "$before_count" -eq 0 ]]; then
warn "No alarms found; TEST 4 may not be meaningful."
warn "No plugin alarms found; TEST 4 may not be meaningful."
elif [[ "$before_count" -eq 1 ]]; then
ok "Single plugin alarm confirmed (one per day)"
else
warn "Found $before_count plugin alarms (expected: 1)"
fi
pause
@@ -317,9 +331,11 @@ test4_silent_boot_recovery() {
substep "Step 5: Verify alarms were recreated (without opening app)"
show_alarms
local after_count
after_count="$(count_alarms)"
info "Alarm count after boot (app never opened): $after_count"
local after_count system_after
after_count="$(get_plugin_alarm_count)"
system_after="$(get_system_alarm_count)"
info "Plugin alarms after boot (app never opened): $after_count (expected: 1)"
info "System/other alarms: $system_after (for context)"
if [[ "$after_count" -gt 0 && "$rescheduled" -gt 0 ]]; then
ok "TEST 4 PASSED: Boot recovery occurred silently and alarms were recreated (rescheduled=$rescheduled) without app launch."