Skip to content

Re-arm alarms on every app start, and report when Android has dropped them - #3

Merged
TargetCrafter merged 1 commit into
mainfrom
fix/alarm-schedule-reliability
Sep 3, 2026
Merged

TargetCrafter merged 1 commit into
mainfrom
fix/alarm-schedule-reliability

Conversation

@TargetCrafter

Copy link
Copy Markdown
Owner

The gap

An alarm set 10 hours ahead didn't ring; one set minutes ahead did. That difference points at the schedule being lost, not the ring path failing — the ring path demonstrably works.

AlarmManager entries survive ordinary process death, but not a force-stop — and force-stopping idle apps is exactly what OEM battery managers do. Nothing notifies an app that it happened.

rescheduleAll() was only ever called from BootReceiver. So once the OS dropped this app's alarms, they stayed dropped until the next reboot, while the alarm list went on showing them as enabled. A silent inconsistency whose only symptom is not waking up.

Changes

Re-arm on every process start. HaAlarmClockApp.onCreate now re-arms alarms and timers, not just BootReceiver. Rescheduling replaces the existing entry, so this is an idempotent repair pass that any run of the app performs.

Make a dropped schedule visible. New ScheduleHealth compares the app's next trigger against AlarmManager.getNextAlarmClock() — the same source as the status-bar alarm icon — and Settings shows the result with a Re-arm button. That API reports the next alarm clock across all apps, so it's conclusive in only two directions; when another app has a sooner alarm the check says so rather than guessing.

The honest limit

An app cannot make itself force-stop-proof. Once Android force-stops it, the alarms are cancelled and nothing belonging to it runs again — no service, no receiver, no WorkManager job, no watchdog — until something launches it. Everything above improves the odds and makes failure visible. None of it makes ringing certain.

What does close the gap is redundancy off the phone, which this project is unusually well placed for: Home Assistant already knows the next alarm time and whether the phone reported ringing. The README now carries a backup automation that fires when the alarm time passes with no ring reported — a second alarm on hardware the phone's battery manager has no say over.

Not verified

Uncompiled here, as usual — ./gradlew assembleDebug first.


Generated by Claude Code

… them

Third report of an alarm set hours ahead not ringing while one set
minutes ahead works. That difference points at the schedule being lost
rather than the ring path failing, and there was a real gap behind it.

AlarmManager entries survive ordinary process death but not a
force-stop, and force-stopping idle apps is what OEM battery managers
do. Nothing tells an app it happened. rescheduleAll() was only ever
called from BootReceiver, so a dropped schedule stayed dropped until the
next reboot while the alarm list went on showing the alarm as enabled -
a silent inconsistency whose only symptom is not waking up.

HaAlarmClockApp.onCreate now re-arms everything on every process start.
Rescheduling is idempotent, so this is just a repair pass that any run
of the app performs.

Adds ScheduleHealth, which compares the app's next trigger against
AlarmManager.getNextAlarmClock() - the same source as the status-bar
alarm icon - and surfaces the result in Settings with a Re-arm button,
so a dropped schedule is visible instead of silent. That API reports the
next alarm clock across all apps, so it is conclusive in only two
directions; when another app has a sooner alarm the check reports that
rather than guessing.

Also documents the limit honestly: an app cannot make itself
force-stop-proof, and once Android force-stops it nothing of its own
runs again until something launches it. The README now describes the
redundancy that does close that gap - a Home Assistant backup automation
firing when the alarm time passes with no ring reported, on hardware the
phone's battery manager has no say over.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01C7x8v5c5Lu29PBWy1LdfCe
@TargetCrafter
TargetCrafter merged commit 0e3980c into main Sep 3, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants