Skip to content

AlarmKit critical alarms: configured sound, backfill-proof, and visible authorization state - #2520

Open
ps2 wants to merge 5 commits into
next-devfrom
fix/alarmkit-configured-sound
Open

ps2 wants to merge 5 commits into
next-devfrom
fix/alarmkit-configured-sound

Conversation

@ps2

@ps2 ps2 commented Sep 16, 2026

Copy link
Copy Markdown
Collaborator

Fixes #2481. Five commits: the AlarmKit alarm plays the configured sound, a CGM backfill can no longer retract a fresh alert, and the AlarmKit authorization state becomes visible in iOS Permissions.

Background

Libre 3+ glucose alarms on next-dev come from Loop's own GlucoseAlertManager, which stamps the configured sound onto the alert and issues it at .critical. The notification path honours that — UserNotificationAlertScheduler uses criticalSoundNamed for the copy in Library/Sounds.

Builds without the Critical Alerts entitlement (every DIY build; criticalAlertsEnabled: false in the reporter's Loop Report) can't deliver a true Critical Alert, so AlertManager is meant to raise these through AlarmKit on iOS 26, with the in-process CriticalAlertAudioPlayer as fallback.

Why the reporter hears the default tone

AlarmKit authorization is requested during onboarding and again on every launch (launchHomeScreen), and both are no-ops once the user has decided. A user who never sees a prompt has already allowed or dismissed it — and if it's not allowed, scheduleAlarm() returns false and the AlarmKit path never runs. On such a build the only sound left is the downgraded criticalSoundNamed notification, which iOS plays with the system default alert tone; that is the reporter's "Radial". Users in that state need Settings → Loop → Allow Alarms.

1. Play the configured sound on the AlarmKit alarm

Once authorized, the alarm was scheduled with no sound and would play AlarmKit's default tone regardless of the Loop setting — the comment said the .caf files "aren't guaranteed AlarmKit-compatible". They are: afinfo shows every bundled sound is IMA4 .caf, 0.8–8 s, at the bundle root, which is the same constraint a notification sound has. Pass sound: .named(filename). .vibrate has no filename and stays .default.

2. Don't re-evaluate alerts from a backfill older than the last reading

Found while testing: CGMs deliver the live reading and then backfill, each as its own .newData batch, and evaluate(samples:) ran on both. With a G7:

22:58:59  didRead         glucose 86  ts 89595  → urgent low issued
22:58:59  didReadBackfill glucose 86  ts 89591  → urgent low retracted 22:59:00

Retraction calls criticalAlertAudioPlayer.stop(), so the alarm audio was cut off one second after it started. Skip any batch whose newest sample is not newer than the last evaluated reading; backfill still reaches the glucose store.

3. Show AlarmKit authorization under iOS Permissions

Without the entitlement, whether the user has allowed alarms was invisible — worse, the permissions screen showed a "Critical Alerts: On" row that the checker never populates on those builds, so it read On regardless. A user who dismissed the prompt had no way to learn their urgent low would fire silently.

The checker now reports AlarmManager's authorization state as a new flag; the screen shows an Alarms row where the entitlement is absent, keeps a Critical Alerts: Not Available row beneath it, and adds a How to request Critical Alerts link (a placeholder for the loopdocs walkthrough — one URL constant to swap), and the iOS Permissions entry gets the warning badge when alarms aren't allowed. The checker already re-checks on foreground, so it updates on return from Settings. Alarms being off is also added to requiresRiskMitigation, so it raises the red status banner and the unsafe-permissions modal with its own "Turn On Alarms" text — on these builds it's the audible channel, so it's as unsafe as Critical Alerts being off is elsewhere.

Testing

Builds clean. Confirmed on hardware — iPhone 16 Pro, iOS 26, a DIY build without the entitlement (criticalAlertsEnabled: false), AlarmKit allowed:

  • With Urgent Low set to "Honk" and the "next reading as low" test armed, the AlarmKit alarm fired playing Honk and persisted until stopped. Before this branch the same test went silent within a second (the backfill retraction).
  • So the bundled .caf files are AlarmKit-playable, and the caution in the old comment is retired.
  • The iOS Permissions screen shows Alarms: On, Critical Alerts: Not Available, and the request link.

Still open, and not addressed here: whether a downgraded criticalSoundNamed notification should instead use UNNotificationSound(named:) so the visual notification also carries the chosen sound. On these builds that would be a second channel sounding alongside the alarm.

Builds without the Critical Alerts entitlement raise urgent glucose alarms
through AlarmKit on iOS 26, and the alarm was scheduled with no sound, so
it always played AlarmKit's default tone regardless of the sound chosen in
Loop. The notification path had been passing the configured sound all
along; it just isn't what makes the noise on those builds.

Pass the alert's sound by name. The bundled alarm sounds are IMA4 .caf
files under 30 seconds at the bundle root, which are the same constraints
a notification sound has, so AlarmKit can play them directly.
@ps2 ps2 changed the title Play the configured alert sound on AlarmKit critical alarms Make AlarmKit critical alarms authorized, audible with the configured sound, and immune to backfill retraction Sep 16, 2026
…reading

CGMs deliver the live reading and then backfill the gap behind it, each as
its own batch. evaluate() re-ran on the backfill, which carries a sample no
newer than the one just evaluated, and re-decided the alert from it. Seen
with the G7: a live reading raised an urgent low at 22:58:59 and the
backfill of the same period retracted it at 22:59:00, which also stopped
the in-process alarm audio one second after it started.

Skip any batch whose newest sample is not newer than the last evaluated
reading. Backfill still reaches the glucose store; it just cannot override
an alert decision made on a newer reading.

(cherry picked from commit 125a01b)
@ps2 ps2 changed the title Make AlarmKit critical alarms authorized, audible with the configured sound, and immune to backfill retraction Play the configured sound on AlarmKit critical alarms, and don't let backfill retract them Sep 16, 2026
@ps2
ps2 force-pushed the fix/alarmkit-configured-sound branch from 0dbd3c0 to 6072aed Compare September 16, 2026 04:20
…ritical Alerts

On builds without the Critical Alerts entitlement, AlarmKit is the audible
channel for urgent alarms, and whether the user has allowed alarms was not
visible anywhere: the permissions screen showed a "Critical Alerts: On" row
that the checker never populates on those builds, so it read On regardless.
A user who dismissed the authorization prompt had no way to see that their
urgent low would fire silently.

Report AlarmManager's authorization state through the permissions checker
as a new flag, show it as an "Alarms" row in place of the Critical Alerts
row where the entitlement is absent, and badge the iOS Permissions entry
when alarms are not allowed. The checker already re-checks on foreground,
so the row updates on return from Settings. Not added to
requiresRiskMitigation, so it does not raise the unsafe-permissions modal.
@ps2 ps2 changed the title Play the configured sound on AlarmKit critical alarms, and don't let backfill retract them AlarmKit critical alarms: configured sound, backfill-proof, and visible authorization state Sep 16, 2026
Replacing the row with Alarms hid the fact that Critical Alerts are absent
altogether. Show it as "Not Available" beneath the Alarms row, with a link
to instructions for requesting the entitlement from Apple. The link is a
placeholder for a loopdocs page; the URL is a single constant to swap.
@ps2
ps2 requested review from Camji55 and marionbarker September 16, 2026 04:43
…tical Alerts

On those builds AlarmKit is how an urgent low makes a sound, so alarms being
off is as unsafe as Critical Alerts being off is elsewhere. Add it to the
risk-mitigation set so it raises the status banner and the unsafe-permissions
modal, with its own text and alert identifier. It takes precedence over the
notification flags in the mapping, since it only exists where the entitlement
is absent. The badge on the iOS Permissions entry now comes from showWarning
like the other flags.
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.

1 participant