Ring the Alarm style from a foreground service; roll a passed time forward - #161
Merged
Merged
Conversation
…rward Alarm-style memos arrived as a silent heads-up on an unlocked phone in vibrate mode, with every permission granted. The #52 fix started AlarmActivity from AlarmReceiver, but an alarm receiver gets no background-activity-launch exemption: the start only went through inside the few seconds after the app was on screen, which is exactly when the Settings test ring fired, so the test passed while real alarms were blocked (silently: a blocked startActivity does not throw). - AlarmRingService: a foreground service (type systemExempted) started from AlarmReceiver via NotificationHelper.deliverOnTime. It posts the alert with startForeground and loops AlarmRinger on the alarm stream, locked or not, until answered, swiped, or the 2 minute timeout. - AlarmActivity is now the answer screen; it rings itself only when the service is not ringing (late BootWorker deliveries, refused starts). - Every answer path silences by the same id (AlertIds): notification Done/Snooze, swipe (delete intent), the ring screen, the in-app nudge, and opening the app from the alert. - Manifest: FOREGROUND_SERVICE and FOREGROUND_SERVICE_SYSTEM_EXEMPTED, documented; CLAUDE.md permission list updated. - The test ring now fires after a minute so it runs in the conditions a real alarm does. - Edit sheet: changing the time of a once-only memo to one already gone rolls it to the next occurrence (ringForPickedTime), and a ring moved into the past cannot be saved (onceOnlyRingSavable). LESSONS #65 records the background-start trap; #52 points to it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012c2DoXy9u68tYVUnmziZPy
mapgie
marked this pull request as ready for review
September 25, 2026 17:50
Main added its own LESSONS #65 (failed action vs failed load), so the foreground-service lesson moves to #66 and its references follow. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012c2DoXy9u68tYVUnmziZPy
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
Alarm-style memos arrived as a silent heads-up (Samsung, vibrate mode, every permission row green), while the Settings test ring worked locked or unlocked.
The LESSONS #52 fix started
AlarmActivitydirectly fromAlarmReceiver. An alarm receiver has no background-activity-launch exemption. The start only succeeds within the few seconds after the app was on screen, which is exactly when the 10-second test ring fired. A real memo fires later, the launch is blocked without an exception, and the only sound left is the notification's, on the muted notification stream.What
Ring service
AlarmRingService, a foreground service with typesystemExempted(reserved for exact-alarm holders that keep an alarm ringing).AlarmReceiverstarts it throughNotificationHelper.deliverOnTime. It posts the alert withstartForegroundand loopsAlarmRingeron the alarm stream until the alarm is answered or swiped away, or the 2-minute timeout ends the ring. A timeout leaves the notification in the shade.AlarmActivityis now the answer screen only. It rings itself only when the service isn't ringing, which covers late deliveries fromBootWorkerand a refused service start.AlertIds): the notification's Done and Snooze, swiping it away (new delete intent), the ring screen, the in-app nudge, and opening the app from the alert.FOREGROUND_SERVICEandFOREGROUND_SERVICE_SYSTEM_EXEMPTED, each with a comment. The permission list in CLAUDE.md is updated to match.Test ring
Edit sheet
ringForPickedTime).onceOnlyRingSavable). A memo that has already rung can still be saved as long as its ring is unchanged, so renaming it still works.Docs
Tests
ReminderPickedTimeTest: roll-forward and save rules. All 8 cases also ran locally against the extracted functions.DeliveryStyleTest: alert ids stay distinct between memos and tasks, and keep the values alerts already in the shade were posted under.a11y_check.pyandcheck_changelog_fragment.pyare clean.To verify on device
🤖 Generated with Claude Code
https://claude.ai/code/session_012c2DoXy9u68tYVUnmziZPy