feat(reminders): AlarmManagerService and NotificationService#19118
Conversation
|
Important Maintainers: This PR contains Strings changes
|
7c56727 to
75e41bb
Compare
There was a problem hiding this comment.
Pull Request Overview
This PR introduces a complete review reminder notification system with AlarmManagerService for scheduling alarms and NotificationService for firing notifications. The implementation includes proper database schema migration, snoozing functionality, and comprehensive testing.
- AlarmManagerService schedules recurring and snoozed review reminder notifications using AlarmManager
- NotificationService sends actual notification with snooze buttons and handles card count validation
- Database migration system implemented for handling schema changes in ReviewReminder
Reviewed Changes
Copilot reviewed 26 out of 26 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| libanki/src/main/java/com/ichi2/anki/libanki/sched/Scheduler.kt | Adds allDecksCounts() method for getting total card counts across all decks |
| AnkiDroid/src/main/java/com/ichi2/anki/services/AlarmManagerService.kt | New service for scheduling review reminder alarms using AlarmManager |
| AnkiDroid/src/main/java/com/ichi2/anki/services/NotificationService.kt | Enhanced service for firing review reminder notifications with snooze functionality |
| AnkiDroid/src/main/java/com/ichi2/anki/reviewreminders/ReviewRemindersDatabase.kt | Database migration system and deck existence validation |
| AnkiDroid/src/main/java/com/ichi2/anki/reviewreminders/ReviewReminderMigrationSettings.kt | Schema migration configuration and versioning system |
| AnkiDroid/src/main/java/com/ichi2/anki/reviewreminders/ReviewReminder.kt | Updated to implement ReviewReminderSchema interface with renamed ID field |
| AnkiDroid/src/main/java/com/ichi2/anki/services/BootService.kt | Integrates review reminder scheduling on device boot |
| AnkiDroid/src/main/AndroidManifest.xml | Registers AlarmManagerService as broadcast receiver |
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
AnkiDroid/src/main/java/com/ichi2/anki/services/NotificationService.kt
Outdated
Show resolved
Hide resolved
AnkiDroid/src/main/java/com/ichi2/anki/services/NotificationService.kt
Outdated
Show resolved
Hide resolved
AnkiDroid/src/main/java/com/ichi2/anki/services/AlarmManagerService.kt
Outdated
Show resolved
Hide resolved
AnkiDroid/src/main/java/com/ichi2/anki/services/AlarmManagerService.kt
Outdated
Show resolved
Hide resolved
75e41bb to
dd789c2
Compare
58d3247 to
3285df9
Compare
|
Rebased. Now it's only blocked by one PR: #19065 |
3285df9 to
b47b008
Compare
|
Non-functional test file change to make NotificationServiceTest a little simpler. |
6ca25d6 to
496aae3
Compare
|
Rebased on main. |
ce80716 to
a57edf1
Compare
|
GSoC 2025: Review Reminders Renamed the inner field of ReviewReminderId to avoid confusing calls to `reviewReminder.id.id`.
GSoC 2025: Review Reminders This magic number was used for the old notifications system. It's useless now.
David let me know about a better way to retrieve the NotificationManager system service. I realized I used the old method in this test file and I've now fixed it.
450c5c6 to
fc2c0d5
Compare
GSoC 2025: Review Reminders Added logic for review reminder notifications being sent to the user. Alarms for sending notifications are created by AlarmManagerService, and the actual notifications themselves are fired by NotificationService. `scheduleReviewReminderNotification` is the primary part of AlarmManagerService, setting the recurring notifications for a review reminder. `unschedule` and `scheduleAll` methods are also provided. Snoozing is handled by AlarmManagerService via `scheduleSnoozedNotification`. AlarmManagerService must be a BroadcastReceiver so that it can receive snoozing requests via onReceive from PendingIntents created by NotificationService. `sendReviewReminderNotification` in NotificationService does the bulk of the work for sending notifications, filling out content, etc. `fireReviewReminderNotification` is a separate method that handles the actual OS call to fire the notification itself. Added AlarmManagerService as a BroadcastReceiver to the AndroidManifest.xml file. There's a method called `catchAlarmManagerExceptions` which was previously in BootService. It was added to solve some bugs. I've moved it to AlarmManagerService and kept it around to ensure no regressions occur. Marked old functionality in NotificationService as legacy notification code. I had to create a NotificationServiceAction sealed class to mark the kinds of notification requests the NotificationService gets. These different requests must have different actions set, otherwise they collide with each other and interfere. This can cause snoozes to cancel normal notifications, normal notifications to cancel snoozes, etc., hence why I added this sealed class. You might wonder why NotificationService is a BroadcastReceiver. NotificationService must be a BroadcastReceiver because it needs to listen to PendingIntents triggered by AlarmManager alarms, which trigger the onReceive method. Added unit tests for AlarmManagerService and NotificationService.
GSoC 2025: Review Reminders Added calls to set review reminder notifications on device boot-up and app start-up to AnkiDroidApp and BootService. Modifies ScheduleReminders so that it sets alarms for review reminders via AlarmManagerService.
fc2c0d5 to
0e78342
Compare
|
@david-allison Review addressed!
After double-checking what I've written, I'm fairly sure this isn't possible. The only operation that opens the collection in this PR is the act of firing a review reminder itself, and I ensure that happens after the next recurring reminder is scheduled: // Schedule the next instance of this review reminder notification if this is a recurring notification
if (action == NotificationServiceAction.ScheduleRecurringNotifications.actionString) {
Timber.d("Scheduling next review reminder notification")
AlarmManagerService.scheduleReviewReminderNotification(context, reviewReminder)
}
runGloballyWithTimeout(SEND_REVIEW_REMINDER_TIMEOUT) {
// We run this on the global scope for simplicity's sake, as BroadcastReceivers do not have CoroutineScopes.
// Theoretically we could also use an expedited Worker, but AnkiDroid is only allotted a fixed number
// of expedited Worker calls per day, and these expedited calls are also used by the sync service,
// so it's best to conserve them.
try {
sendReviewReminderNotification(context, reviewReminder)
} catch (e: BackendException) {
// This may occur if the collection is blocked, in which case we should fail gracefully
Timber.w(e, "Aborted review reminder notification due to backend exception")
}
}
See the above code snippet. I've tried wrapping the act of sending a review reminder notification in a try-catch block. Am I correct to filter by |
david-allison
left a comment
There was a problem hiding this comment.
Fantastic, thanks so much!
| ) | ||
| ReviewRemindersDatabase.editRemindersForDeck(did1) { mapOf(ReviewReminderId(0) to reviewReminder) } | ||
|
|
||
| CollectionManager.emulatedOpenFailure = CollectionManager.CollectionOpenFailure.LOCKED |
There was a problem hiding this comment.
just to confirm: I believe this throws on access?
If so, perfect! Thanks so much for the test
There was a problem hiding this comment.
Yep, the access throws and the BackendException that results is caught silently by NotificationService. Thanks for the approval! Do we still want logic to retry the notification three or so times if a BackendException occurs, or is that unnecessary?
mikehardy
left a comment
There was a problem hiding this comment.
This looks really good - so I'm sure this has been discussed, but the hard-coded strings for the reminder title and buttons - are those strings going to be extracted for translation or ?
|
Thanks!! I think the original plan with the hard-coded strings was to wait until the feature was almost out the door into an alpha or beta before extracting them out, but if you'd like I can do the extraction now. Your call! If not, feel free to slap this guy into the merge queue -- I think everything's been addressed and if David wants notification retries upon BackendExceptions we can handle that in a follow-up. |
|
@david-allison should we do the strings now or wait? I understand while trialing ideas it doesn't make a lot of sense, but if we are set to merge this (and...I'm set to merge it at least...) then it seems like now is the time for strings to get set up for localization |
|
I think: hold off for a little while longer One of the strings contains "ETA", which we're likely to fully deprecate One of the strings is using the deck name, which may be an issue:
|
|
Okay :-), peeled that to a separate issue so I/we don't lose sight aaaaand 🥁 🥁 queued this for merge. Let's go! |
|
Thanks for the merge and for creating the new issue! I'm a bit swamped these few days but I'll get to patching up the advanced review reminder option PRs (which are unblocked now) and addressing everything in my inbox as soon as possible. Cheers |
|
@ericli3690 my pleasure - only can offer apologies it took this long. Just went through the notifications one as well so it's no longer blocked, I think it can go in pretty quickly - I'm excited to see this stuff merge :-). Cheers |
Added logic for review reminder notifications being sent to the user. Alarms for sending notifications are created by AlarmManagerService, and the actual notifications themselves are fired by NotificationService.
Purpose / Description
scheduleReviewReminderNotificationis the primary part of AlarmManagerService, setting the recurring notifications for a review reminder.unscheduleandscheduleAllmethods are also provided. Snoozing is handled by AlarmManagerService viascheduleSnoozedNotification. AlarmManagerService must be a BroadcastReceiver so that it can receive snoozing requests via onReceive from PendingIntents created by NotificationService.sendReviewReminderNotificationin NotificationService does the bulk of the work for sending notifications, filling out content, etc.fireReviewReminderNotificationis a separate method that handles the actual OS call to fire the notification itself. Added AlarmManagerService as a BroadcastReceiver to the AndroidManifest.xml file.There's a method called
catchAlarmManagerExceptionswhich was previously in BootService. It was added to solve some bugs. I've moved it to AlarmManagerService and kept it around to ensure no regressions occur.Marked old functionality in NotificationService as legacy notification code.
I had to create a NotificationServiceAction sealed class to mark the kinds of notification requests the NotificationService gets. These different requests must have different actions set, otherwise they collide with each other and interfere. This can cause snoozes to cancel normal notifications, normal notifications to cancel snoozes, etc., hence why I added this sealed class.
You might wonder why NotificationService is a BroadcastReceiver. NotificationService must be a BroadcastReceiver because it needs to listen to PendingIntents triggered by AlarmManager alarms, which trigger the onReceive method.
Added unit tests for AlarmManagerService and NotificationService.
Added calls to set review reminder notifications on device boot-up and app start-up to AnkiDroidApp and BootService. Modifies ScheduleReminders so that it sets alarms for review reminders via AlarmManagerService.
Other small changes:
reviewReminder.id.id.UI
Fixes
Approach
How Has This Been Tested?
Learning
It took a long time for me to figure out what parts of the codebase were legacy notification code and could be removed, and which parts I should keep and try to build on top of.
Checklist