From 9cb4be371318a1c6f810a036c0e2af5c8571b944 Mon Sep 17 00:00:00 2001 From: naurakrbna Date: Sun, 15 Mar 2026 00:16:07 +0700 Subject: [PATCH] fix: handle nonfatal review reminder deserialization errors on startup When review reminder data stored in SharedPreferences cannot be deserialized (e.g. due to a schema mismatch), catch the exception and notify the user via a toast instead of crashing the app. Closes #20168 Fixes #20163 Co-Authored-By: Claude Sonnet 4.6 --- .../anki/services/AlarmManagerService.kt | 21 ++++++++++++++++++- AnkiDroid/src/main/res/values/03-dialogs.xml | 1 + .../anki/services/AlarmManagerServiceTest.kt | 16 ++++++++++++++ 3 files changed, 37 insertions(+), 1 deletion(-) diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/services/AlarmManagerService.kt b/AnkiDroid/src/main/java/com/ichi2/anki/services/AlarmManagerService.kt index d8244de8060d..956c7b8343a0 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/services/AlarmManagerService.kt +++ b/AnkiDroid/src/main/java/com/ichi2/anki/services/AlarmManagerService.kt @@ -30,6 +30,7 @@ import com.ichi2.anki.common.time.TimeManager import com.ichi2.anki.reviewreminders.ReviewReminder import com.ichi2.anki.reviewreminders.ReviewRemindersDatabase import com.ichi2.anki.showThemedToast +import kotlinx.serialization.SerializationException import timber.log.Timber import java.util.Calendar import kotlin.time.Duration @@ -223,7 +224,25 @@ class AlarmManagerService : BroadcastReceiver() { private fun scheduleAllEnabledReviewReminderNotifications(context: Context) { Timber.d("scheduleAllEnabledReviewReminderNotifications") val allReviewRemindersAsMap = - ReviewRemindersDatabase.getAllAppWideReminders() + ReviewRemindersDatabase.getAllDeckSpecificReminders() + try { + ReviewRemindersDatabase.getAllAppWideReminders() + ReviewRemindersDatabase.getAllDeckSpecificReminders() + } catch (e: SerializationException) { + Timber.w(e, "Failed to read review reminders from storage") + try { + showThemedToast(context, context.getString(R.string.boot_service_review_reminders_corrupt), false) + } catch (toastException: Exception) { + Timber.w(toastException, "Failed to show review reminders corrupt toast") + } + return + } catch (e: IllegalArgumentException) { + Timber.w(e, "Failed to read review reminders from storage") + try { + showThemedToast(context, context.getString(R.string.boot_service_review_reminders_corrupt), false) + } catch (toastException: Exception) { + Timber.w(toastException, "Failed to show review reminders corrupt toast") + } + return + } val enabledReviewReminders = allReviewRemindersAsMap.values.filter { it.enabled } for (reviewReminder in enabledReviewReminders) { scheduleReviewReminderNotification(context, reviewReminder) diff --git a/AnkiDroid/src/main/res/values/03-dialogs.xml b/AnkiDroid/src/main/res/values/03-dialogs.xml index f2173c5cc6ed..473b50848ef9 100644 --- a/AnkiDroid/src/main/res/values/03-dialogs.xml +++ b/AnkiDroid/src/main/res/values/03-dialogs.xml @@ -152,6 +152,7 @@ Failed to schedule reminders Too many reminders scheduled. Some will not appear + Review reminder data could not be read. Some reminders may not appear Set Language diff --git a/AnkiDroid/src/test/java/com/ichi2/anki/services/AlarmManagerServiceTest.kt b/AnkiDroid/src/test/java/com/ichi2/anki/services/AlarmManagerServiceTest.kt index 4e5bd96d08e8..f20e35cd6481 100644 --- a/AnkiDroid/src/test/java/com/ichi2/anki/services/AlarmManagerServiceTest.kt +++ b/AnkiDroid/src/test/java/com/ichi2/anki/services/AlarmManagerServiceTest.kt @@ -34,6 +34,7 @@ import com.ichi2.anki.reviewreminders.ReviewReminder import com.ichi2.anki.reviewreminders.ReviewReminderId import com.ichi2.anki.reviewreminders.ReviewReminderTime import com.ichi2.anki.reviewreminders.ReviewRemindersDatabase +import com.ichi2.anki.showThemedToast import io.mockk.every import io.mockk.mockk import io.mockk.mockkStatic @@ -159,6 +160,21 @@ class AlarmManagerServiceTest : RobolectricTest() { verify(exactly = 3) { alarmManager.setWindow(AlarmManager.RTC_WAKEUP, any(), 10.minutes.inWholeMilliseconds, any()) } } + @Test + fun `scheduleAllNotifications does not crash and shows toast when review reminder data is corrupt`() { + mockkStatic("com.ichi2.anki.UIUtilsKt") + every { showThemedToast(any(), any(), any()) } returns Unit + + ReviewRemindersDatabase.remindersSharedPrefs.edit { + putString(ReviewRemindersDatabase.APP_WIDE_KEY, "not valid json at all") + } + + AlarmManagerService.scheduleAllNotifications(context) + + verify(exactly = 0) { alarmManager.setWindow(any(), any(), any(), any()) } + verify(exactly = 1) { showThemedToast(any(), any(), false) } + } + @Test fun `onReceive schedules snoozed notification and cancels clicked notification`() { val extras = mockk()