From 0c28ff4b7b9e4a1ddd3528eceaaa5297d45da122 Mon Sep 17 00:00:00 2001 From: Futsch1 <63090558+Futsch1@users.noreply.github.com> Date: Fri, 8 May 2026 16:55:47 +0200 Subject: [PATCH 1/7] Fix a potential crash --- .../java/com/futsch1/medtimer/statistics/ChartsFragment.kt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/futsch1/medtimer/statistics/ChartsFragment.kt b/app/src/main/java/com/futsch1/medtimer/statistics/ChartsFragment.kt index 58f3dc37a..88afc1816 100644 --- a/app/src/main/java/com/futsch1/medtimer/statistics/ChartsFragment.kt +++ b/app/src/main/java/com/futsch1/medtimer/statistics/ChartsFragment.kt @@ -327,7 +327,11 @@ class ChartsFragment : Fragment() { viewModel.setDays(viewModel.uiState.value?.days ?: (arguments?.getInt(DAYS_BUNDLE_KEY) ?: 0)) } - fun setDays(days: Int) = viewModel.setDays(days) + fun setDays(days: Int) { + if (isAdded) { + viewModel.setDays(days) + } + } private inner class DaysSinceEpochFormat : NumberFormat() { override fun format( From 456089caa16b2e93bacb05749adead56107cf7a8 Mon Sep 17 00:00:00 2001 From: Futsch1 <63090558+Futsch1@users.noreply.github.com> Date: Fri, 8 May 2026 16:56:04 +0200 Subject: [PATCH 2/7] Improved logging Only call schedule next reminder via intent --- .../futsch1/medtimer/reminders/NotificationProcessor.kt | 9 +++++++-- .../medtimer/reminders/ReminderNotificationProcessor.kt | 5 ++--- .../reminders/ReminderProcessorBroadcastReceiver.kt | 3 +++ .../ScheduleNextReminderNotificationProcessor.kt | 7 +++---- .../reminders/ShowReminderNotificationProcessor.kt | 7 +++++-- .../notificationData/ReminderNotificationFactory.kt | 1 + 6 files changed, 21 insertions(+), 11 deletions(-) diff --git a/app/src/main/java/com/futsch1/medtimer/reminders/NotificationProcessor.kt b/app/src/main/java/com/futsch1/medtimer/reminders/NotificationProcessor.kt index 45518e9e6..8f8c21534 100644 --- a/app/src/main/java/com/futsch1/medtimer/reminders/NotificationProcessor.kt +++ b/app/src/main/java/com/futsch1/medtimer/reminders/NotificationProcessor.kt @@ -1,6 +1,7 @@ package com.futsch1.medtimer.reminders import android.app.NotificationManager +import android.content.Context import android.util.Log import com.futsch1.medtimer.LogTags import com.futsch1.medtimer.database.ReminderEventRepository @@ -10,9 +11,12 @@ import com.futsch1.medtimer.model.ReminderEvent import com.futsch1.medtimer.model.ReminderType import com.futsch1.medtimer.preferences.PersistentDataDataSource import com.futsch1.medtimer.preferences.PreferencesDataSource +import com.futsch1.medtimer.reminders.ReminderProcessorBroadcastReceiver.Companion.RECEIVER_PERMISSION +import com.futsch1.medtimer.reminders.ReminderProcessorBroadcastReceiver.Companion.requestScheduleNextNotification import com.futsch1.medtimer.reminders.notificationData.ProcessedNotificationData import com.futsch1.medtimer.reminders.notificationData.ReminderNotificationData import com.futsch1.medtimer.reminders.notificationData.ReminderNotificationFactory +import dagger.hilt.android.qualifiers.ApplicationContext import java.time.Instant import javax.inject.Inject @@ -28,9 +32,10 @@ import javax.inject.Inject */ class NotificationProcessor @Inject constructor( + @param:ApplicationContext + private val context: Context, private val alarmProcessor: AlarmProcessor, private val notifications: Notifications, - private val scheduleNextReminderNotificationProcessor: ScheduleNextReminderNotificationProcessor, private val stockHandlingProcessor: StockHandlingProcessor, private val repeatProcessor: RepeatProcessor, private val notificationManager: NotificationManager, @@ -57,7 +62,7 @@ class NotificationProcessor @Inject constructor( setReminderEventStatus(status, reminderEventsToUpdate) // Reschedule since the trigger condition for a linked reminder might have changed - scheduleNextReminderNotificationProcessor.scheduleNextReminder() + requestScheduleNextNotification(context) } fun cancelNotification(notificationId: Int) { diff --git a/app/src/main/java/com/futsch1/medtimer/reminders/ReminderNotificationProcessor.kt b/app/src/main/java/com/futsch1/medtimer/reminders/ReminderNotificationProcessor.kt index 1431f5bfb..f6c166548 100644 --- a/app/src/main/java/com/futsch1/medtimer/reminders/ReminderNotificationProcessor.kt +++ b/app/src/main/java/com/futsch1/medtimer/reminders/ReminderNotificationProcessor.kt @@ -15,6 +15,7 @@ import com.futsch1.medtimer.model.Reminder import com.futsch1.medtimer.model.ReminderEvent import com.futsch1.medtimer.model.ReminderType import com.futsch1.medtimer.preferences.PreferencesDataSource +import com.futsch1.medtimer.reminders.ReminderProcessorBroadcastReceiver.Companion.requestScheduleNextNotification import com.futsch1.medtimer.reminders.notificationData.ReminderNotification import com.futsch1.medtimer.reminders.notificationData.ReminderNotificationData import com.futsch1.medtimer.reminders.notificationData.ReminderNotificationFactory @@ -28,7 +29,6 @@ class ReminderNotificationProcessor @Inject constructor( val notifications: Notifications, val notificationProcessor: NotificationProcessor, val repeatProcessor: RepeatProcessor, - val scheduleNextReminderNotificationProcessor: ScheduleNextReminderNotificationProcessor, private val reminderNotificationFactory: ReminderNotificationFactory, private val reminderEventRepository: ReminderEventRepository, private val preferencesDataSource: PreferencesDataSource @@ -46,8 +46,7 @@ class ReminderNotificationProcessor @Inject constructor( notificationAction(nonTakenReminderNotification) } - val processedEvents = nonTakenReminderNotification.reminderNotificationParts.map { it.reminderEvent } - scheduleNextReminderNotificationProcessor.scheduleNextReminder(processedEvents) + requestScheduleNextNotification(context) return true } diff --git a/app/src/main/java/com/futsch1/medtimer/reminders/ReminderProcessorBroadcastReceiver.kt b/app/src/main/java/com/futsch1/medtimer/reminders/ReminderProcessorBroadcastReceiver.kt index c8415e276..db69093b8 100644 --- a/app/src/main/java/com/futsch1/medtimer/reminders/ReminderProcessorBroadcastReceiver.kt +++ b/app/src/main/java/com/futsch1/medtimer/reminders/ReminderProcessorBroadcastReceiver.kt @@ -3,7 +3,9 @@ package com.futsch1.medtimer.reminders import android.content.BroadcastReceiver import android.content.Context import android.content.Intent +import android.util.Log import com.futsch1.medtimer.ActivityCodes +import com.futsch1.medtimer.LogTags import com.futsch1.medtimer.ProcessorCode import com.futsch1.medtimer.di.ApplicationScope import com.futsch1.medtimer.model.Reminder @@ -61,6 +63,7 @@ class ReminderProcessorBroadcastReceiver : BroadcastReceiver() { val pendingResult = goAsync() applicationScope.launch { try { + Log.d(LogTags.REMINDER, "Received intent $intentAction") when (intentAction) { ProcessorCode.Dismissed -> notificationProcessor.processReminderEventsInNotification( ProcessedNotificationData.fromBundle(intent.extras!!), diff --git a/app/src/main/java/com/futsch1/medtimer/reminders/ScheduleNextReminderNotificationProcessor.kt b/app/src/main/java/com/futsch1/medtimer/reminders/ScheduleNextReminderNotificationProcessor.kt index 89d72260b..c7cf7d38a 100644 --- a/app/src/main/java/com/futsch1/medtimer/reminders/ScheduleNextReminderNotificationProcessor.kt +++ b/app/src/main/java/com/futsch1/medtimer/reminders/ScheduleNextReminderNotificationProcessor.kt @@ -20,12 +20,11 @@ class ScheduleNextReminderNotificationProcessor @Inject constructor( private val preferencesDataSource: PreferencesDataSource ) { - suspend fun scheduleNextReminder(processedEvents: List = emptyList()) { + suspend fun scheduleNextReminder() { val medicines = medicineRepository.getAll() val reminderEvents = reminderEventRepository.getForScheduling(medicines) - val allEvents = (reminderEvents + processedEvents).distinctBy { it.reminderEventId } - - scheduleNextReminderInternal(medicines, allEvents) + Log.d(LogTags.REMINDER, "Schedule next reminders, considering events ${reminderEvents.map { it.reminderEventId }}") + scheduleNextReminderInternal(medicines, reminderEvents) } private fun scheduleNextReminderInternal( diff --git a/app/src/main/java/com/futsch1/medtimer/reminders/ShowReminderNotificationProcessor.kt b/app/src/main/java/com/futsch1/medtimer/reminders/ShowReminderNotificationProcessor.kt index 22a5ad8b5..443a52696 100644 --- a/app/src/main/java/com/futsch1/medtimer/reminders/ShowReminderNotificationProcessor.kt +++ b/app/src/main/java/com/futsch1/medtimer/reminders/ShowReminderNotificationProcessor.kt @@ -1,9 +1,12 @@ package com.futsch1.medtimer.reminders import android.app.NotificationManager +import android.content.Context import android.util.Log import com.futsch1.medtimer.LogTags +import com.futsch1.medtimer.reminders.ReminderProcessorBroadcastReceiver.Companion.requestScheduleNextNotification import com.futsch1.medtimer.reminders.notificationData.ReminderNotificationData +import dagger.hilt.android.qualifiers.ApplicationContext import javax.inject.Inject /** @@ -14,8 +17,8 @@ import javax.inject.Inject * the actual notification scheduling to [AlarmProcessor]. */ class ShowReminderNotificationProcessor @Inject constructor( + @param:ApplicationContext private val context: Context, private val alarmProcessor: AlarmProcessor, - private val scheduleNextReminderNotificationProcessor: ScheduleNextReminderNotificationProcessor, private val notificationProcessor: NotificationProcessor, private val notificationManager: NotificationManager ) { @@ -27,7 +30,7 @@ class ShowReminderNotificationProcessor @Inject constructor( alarmProcessor.setAlarmForReminderNotification(reminderNotificationData) } - scheduleNextReminderNotificationProcessor.scheduleNextReminder() + requestScheduleNextNotification(context) } private suspend fun isNotificationActive(reminderNotificationData: ReminderNotificationData): Boolean { diff --git a/app/src/main/java/com/futsch1/medtimer/reminders/notificationData/ReminderNotificationFactory.kt b/app/src/main/java/com/futsch1/medtimer/reminders/notificationData/ReminderNotificationFactory.kt index fd4e8f19e..e122d4f27 100644 --- a/app/src/main/java/com/futsch1/medtimer/reminders/notificationData/ReminderNotificationFactory.kt +++ b/app/src/main/java/com/futsch1/medtimer/reminders/notificationData/ReminderNotificationFactory.kt @@ -52,6 +52,7 @@ open class ReminderNotificationFactory @Inject constructor( reminderNotificationData.remindInstant.epochSecond, medicine, reminder, reminderEventRepository, timeFormatter ) reminderEvent = reminderEventRepository.create(newEvent.copy(remainingRepeats = numberOfRepeats)) + Log.d(LogTags.REMINDER, "Created reminder event rEID [${reminderEvent.reminderEventId}] for reminder rID [${reminder.id}]") } else { reminderNotificationData.notificationId = reminderEvent.notificationId } From 060d01b5930d7bff7bfe3612379a7b4983170614 Mon Sep 17 00:00:00 2001 From: Futsch1 <63090558+Futsch1@users.noreply.github.com> Date: Fri, 8 May 2026 19:33:48 +0200 Subject: [PATCH 3/7] Fix repeated reminder invocations --- .../medtimer/reminders/AlarmProcessor.kt | 13 +++-- .../reminders/NotificationProcessor.kt | 8 --- .../ReminderNotificationProcessor.kt | 3 -- .../ReminderProcessorBroadcastReceiver.kt | 44 ++++++++++------ ...heduleNextReminderNotificationProcessor.kt | 50 +++++++++++-------- .../ShowReminderNotificationProcessor.kt | 3 -- 6 files changed, 66 insertions(+), 55 deletions(-) diff --git a/app/src/main/java/com/futsch1/medtimer/reminders/AlarmProcessor.kt b/app/src/main/java/com/futsch1/medtimer/reminders/AlarmProcessor.kt index 43d286763..4b5874106 100644 --- a/app/src/main/java/com/futsch1/medtimer/reminders/AlarmProcessor.kt +++ b/app/src/main/java/com/futsch1/medtimer/reminders/AlarmProcessor.kt @@ -24,7 +24,7 @@ class AlarmProcessor @Inject constructor( ) { private val exactReminders: Boolean = preferencesDataSource.preferences.value.exactReminders - fun setAlarmForReminderNotification(scheduledReminderNotificationData: ReminderNotificationData) { + fun setAlarmForReminderNotification(scheduledReminderNotificationData: ReminderNotificationData, sendImmediateBroadcast: Boolean = true): Boolean { val originalInstant = scheduledReminderNotificationData.remindInstant scheduledReminderNotificationData.remindInstant = adjustTimestamp(timeAccess, originalInstant) @@ -49,10 +49,12 @@ class AlarmProcessor @Inject constructor( scheduledReminderNotificationData ) ) - val reminderIntent = getReminderAction(context) - scheduledReminderNotificationData.toIntent(reminderIntent) - context.sendBroadcast(reminderIntent, RECEIVER_PERMISSION) - return + if (sendImmediateBroadcast) { + val reminderIntent = getReminderAction(context) + scheduledReminderNotificationData.toIntent(reminderIntent) + context.sendBroadcast(reminderIntent, RECEIVER_PERMISSION) + } + return true } val pendingIntent = scheduledReminderNotificationData.getPendingIntent(context) @@ -71,6 +73,7 @@ class AlarmProcessor @Inject constructor( ) updateNextReminderWidget() + return false } fun cancelNextReminder() { diff --git a/app/src/main/java/com/futsch1/medtimer/reminders/NotificationProcessor.kt b/app/src/main/java/com/futsch1/medtimer/reminders/NotificationProcessor.kt index 8f8c21534..3a7862ea4 100644 --- a/app/src/main/java/com/futsch1/medtimer/reminders/NotificationProcessor.kt +++ b/app/src/main/java/com/futsch1/medtimer/reminders/NotificationProcessor.kt @@ -1,7 +1,6 @@ package com.futsch1.medtimer.reminders import android.app.NotificationManager -import android.content.Context import android.util.Log import com.futsch1.medtimer.LogTags import com.futsch1.medtimer.database.ReminderEventRepository @@ -11,12 +10,9 @@ import com.futsch1.medtimer.model.ReminderEvent import com.futsch1.medtimer.model.ReminderType import com.futsch1.medtimer.preferences.PersistentDataDataSource import com.futsch1.medtimer.preferences.PreferencesDataSource -import com.futsch1.medtimer.reminders.ReminderProcessorBroadcastReceiver.Companion.RECEIVER_PERMISSION -import com.futsch1.medtimer.reminders.ReminderProcessorBroadcastReceiver.Companion.requestScheduleNextNotification import com.futsch1.medtimer.reminders.notificationData.ProcessedNotificationData import com.futsch1.medtimer.reminders.notificationData.ReminderNotificationData import com.futsch1.medtimer.reminders.notificationData.ReminderNotificationFactory -import dagger.hilt.android.qualifiers.ApplicationContext import java.time.Instant import javax.inject.Inject @@ -32,8 +28,6 @@ import javax.inject.Inject */ class NotificationProcessor @Inject constructor( - @param:ApplicationContext - private val context: Context, private val alarmProcessor: AlarmProcessor, private val notifications: Notifications, private val stockHandlingProcessor: StockHandlingProcessor, @@ -61,8 +55,6 @@ class NotificationProcessor @Inject constructor( setReminderEventStatus(status, reminderEventsToUpdate) - // Reschedule since the trigger condition for a linked reminder might have changed - requestScheduleNextNotification(context) } fun cancelNotification(notificationId: Int) { diff --git a/app/src/main/java/com/futsch1/medtimer/reminders/ReminderNotificationProcessor.kt b/app/src/main/java/com/futsch1/medtimer/reminders/ReminderNotificationProcessor.kt index f6c166548..1f9617531 100644 --- a/app/src/main/java/com/futsch1/medtimer/reminders/ReminderNotificationProcessor.kt +++ b/app/src/main/java/com/futsch1/medtimer/reminders/ReminderNotificationProcessor.kt @@ -15,7 +15,6 @@ import com.futsch1.medtimer.model.Reminder import com.futsch1.medtimer.model.ReminderEvent import com.futsch1.medtimer.model.ReminderType import com.futsch1.medtimer.preferences.PreferencesDataSource -import com.futsch1.medtimer.reminders.ReminderProcessorBroadcastReceiver.Companion.requestScheduleNextNotification import com.futsch1.medtimer.reminders.notificationData.ReminderNotification import com.futsch1.medtimer.reminders.notificationData.ReminderNotificationData import com.futsch1.medtimer.reminders.notificationData.ReminderNotificationFactory @@ -46,8 +45,6 @@ class ReminderNotificationProcessor @Inject constructor( notificationAction(nonTakenReminderNotification) } - requestScheduleNextNotification(context) - return true } diff --git a/app/src/main/java/com/futsch1/medtimer/reminders/ReminderProcessorBroadcastReceiver.kt b/app/src/main/java/com/futsch1/medtimer/reminders/ReminderProcessorBroadcastReceiver.kt index db69093b8..cb8c3153d 100644 --- a/app/src/main/java/com/futsch1/medtimer/reminders/ReminderProcessorBroadcastReceiver.kt +++ b/app/src/main/java/com/futsch1/medtimer/reminders/ReminderProcessorBroadcastReceiver.kt @@ -65,23 +65,37 @@ class ReminderProcessorBroadcastReceiver : BroadcastReceiver() { try { Log.d(LogTags.REMINDER, "Received intent $intentAction") when (intentAction) { - ProcessorCode.Dismissed -> notificationProcessor.processReminderEventsInNotification( - ProcessedNotificationData.fromBundle(intent.extras!!), - ReminderEvent.ReminderStatus.SKIPPED - ) - - ProcessorCode.Taken -> notificationProcessor.processReminderEventsInNotification( - ProcessedNotificationData.fromBundle(intent.extras!!), - ReminderEvent.ReminderStatus.TAKEN - ) - - ProcessorCode.Acknowledged -> notificationProcessor.processReminderEventsInNotification( - ProcessedNotificationData.fromBundle(intent.extras!!), - ReminderEvent.ReminderStatus.ACKNOWLEDGED - ) + ProcessorCode.Dismissed -> { + notificationProcessor.processReminderEventsInNotification( + ProcessedNotificationData.fromBundle(intent.extras!!), + ReminderEvent.ReminderStatus.SKIPPED + ) + scheduleNextReminderNotificationProcessor.scheduleNextReminder() + } + + ProcessorCode.Taken -> { + notificationProcessor.processReminderEventsInNotification( + ProcessedNotificationData.fromBundle(intent.extras!!), + ReminderEvent.ReminderStatus.TAKEN + ) + scheduleNextReminderNotificationProcessor.scheduleNextReminder() + } + + ProcessorCode.Acknowledged -> { + notificationProcessor.processReminderEventsInNotification( + ProcessedNotificationData.fromBundle(intent.extras!!), + ReminderEvent.ReminderStatus.ACKNOWLEDGED + ) + scheduleNextReminderNotificationProcessor.scheduleNextReminder() + } ProcessorCode.Snooze -> processSnooze(intent) - ProcessorCode.Reminder -> reminderNotificationProcessor.processReminders(ReminderNotificationData.fromBundle(intent.extras!!)) + ProcessorCode.Reminder -> { + if (reminderNotificationProcessor.processReminders(ReminderNotificationData.fromBundle(intent.extras!!))) { + scheduleNextReminderNotificationProcessor.scheduleNextReminder() + } + } + ProcessorCode.ShowReminderNotification -> showReminderNotificationProcessor.showReminder( ReminderNotificationData.fromBundle(intent.extras!!) ) diff --git a/app/src/main/java/com/futsch1/medtimer/reminders/ScheduleNextReminderNotificationProcessor.kt b/app/src/main/java/com/futsch1/medtimer/reminders/ScheduleNextReminderNotificationProcessor.kt index c7cf7d38a..9be51687b 100644 --- a/app/src/main/java/com/futsch1/medtimer/reminders/ScheduleNextReminderNotificationProcessor.kt +++ b/app/src/main/java/com/futsch1/medtimer/reminders/ScheduleNextReminderNotificationProcessor.kt @@ -4,47 +4,55 @@ import android.util.Log import com.futsch1.medtimer.LogTags import com.futsch1.medtimer.database.MedicineRepository import com.futsch1.medtimer.database.ReminderEventRepository -import com.futsch1.medtimer.model.Medicine -import com.futsch1.medtimer.model.ReminderEvent -import com.futsch1.medtimer.model.ScheduledReminder import com.futsch1.medtimer.preferences.PreferencesDataSource import com.futsch1.medtimer.reminders.notificationData.ReminderNotificationData import com.futsch1.medtimer.reminders.scheduling.ReminderScheduler +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock import javax.inject.Inject +import javax.inject.Singleton +@Singleton class ScheduleNextReminderNotificationProcessor @Inject constructor( private val alarmProcessor: AlarmProcessor, + private val reminderNotificationProcessor: ReminderNotificationProcessor, private val medicineRepository: MedicineRepository, private val reminderEventRepository: ReminderEventRepository, private val timeAccess: TimeAccess, private val preferencesDataSource: PreferencesDataSource ) { + private val mutex = Mutex() suspend fun scheduleNextReminder() { + mutex.withLock { + while (scheduleNextReminderStep()) { + // loop until a future alarm is set or no reminders remain + } + } + } + + private suspend fun scheduleNextReminderStep(): Boolean { val medicines = medicineRepository.getAll() val reminderEvents = reminderEventRepository.getForScheduling(medicines) Log.d(LogTags.REMINDER, "Schedule next reminders, considering events ${reminderEvents.map { it.reminderEventId }}") - scheduleNextReminderInternal(medicines, reminderEvents) - } - private fun scheduleNextReminderInternal( - medicines: List, - reminderEvents: List - ) { - val reminderScheduler = ReminderScheduler(timeAccess, preferencesDataSource) - val scheduledReminders: List = - reminderScheduler.schedule(medicines, reminderEvents) - if (scheduledReminders.isNotEmpty()) { - val scheduledReminderNotificationData = - ReminderNotificationData.fromScheduledReminders( - if (preferencesDataSource.preferences.value.combineNotifications) scheduledReminders else listOf( - scheduledReminders[0] - ) - ) - alarmProcessor.setAlarmForReminderNotification(scheduledReminderNotificationData) - } else { + val scheduledReminders = ReminderScheduler(timeAccess, preferencesDataSource).schedule(medicines, reminderEvents) + if (scheduledReminders.isEmpty()) { Log.d(LogTags.REMINDER, "No reminders scheduled") alarmProcessor.cancelNextReminder() + return false + } + + val data = ReminderNotificationData.fromScheduledReminders( + if (preferencesDataSource.preferences.value.combineNotifications) scheduledReminders + else listOf(scheduledReminders[0]) + ) + + val isImmediate = alarmProcessor.setAlarmForReminderNotification(data, sendImmediateBroadcast = false) + if (isImmediate) { + reminderNotificationProcessor.processReminders(data) + return true } + return false } } diff --git a/app/src/main/java/com/futsch1/medtimer/reminders/ShowReminderNotificationProcessor.kt b/app/src/main/java/com/futsch1/medtimer/reminders/ShowReminderNotificationProcessor.kt index 443a52696..e4fdbc756 100644 --- a/app/src/main/java/com/futsch1/medtimer/reminders/ShowReminderNotificationProcessor.kt +++ b/app/src/main/java/com/futsch1/medtimer/reminders/ShowReminderNotificationProcessor.kt @@ -4,7 +4,6 @@ import android.app.NotificationManager import android.content.Context import android.util.Log import com.futsch1.medtimer.LogTags -import com.futsch1.medtimer.reminders.ReminderProcessorBroadcastReceiver.Companion.requestScheduleNextNotification import com.futsch1.medtimer.reminders.notificationData.ReminderNotificationData import dagger.hilt.android.qualifiers.ApplicationContext import javax.inject.Inject @@ -29,8 +28,6 @@ class ShowReminderNotificationProcessor @Inject constructor( if (!isNotificationActive(reminderNotificationData)) { alarmProcessor.setAlarmForReminderNotification(reminderNotificationData) } - - requestScheduleNextNotification(context) } private suspend fun isNotificationActive(reminderNotificationData: ReminderNotificationData): Boolean { From c96e35f889d76a642b72e9d2a16ec94dd6ed18b5 Mon Sep 17 00:00:00 2001 From: Futsch1 <63090558+Futsch1@users.noreply.github.com> Date: Fri, 8 May 2026 19:44:04 +0200 Subject: [PATCH 4/7] Simplify reminding logic --- .../location/GeofenceBroadcastReceiver.kt | 11 +++++- .../medtimer/reminders/AlarmProcessor.kt | 9 +++-- .../reminders/LocationSnoozeProcessor.kt | 2 +- .../ReminderProcessorBroadcastReceiver.kt | 2 +- ...heduleNextReminderNotificationProcessor.kt | 39 +++++++------------ .../medtimer/reminders/SnoozeProcessor.kt | 2 +- 6 files changed, 32 insertions(+), 33 deletions(-) diff --git a/app/src/full/java/com/futsch1/medtimer/location/GeofenceBroadcastReceiver.kt b/app/src/full/java/com/futsch1/medtimer/location/GeofenceBroadcastReceiver.kt index 19a227af9..d368c6eda 100644 --- a/app/src/full/java/com/futsch1/medtimer/location/GeofenceBroadcastReceiver.kt +++ b/app/src/full/java/com/futsch1/medtimer/location/GeofenceBroadcastReceiver.kt @@ -5,10 +5,13 @@ import android.content.Context import android.content.Intent import android.util.Log import com.futsch1.medtimer.LogTags +import com.futsch1.medtimer.di.ApplicationScope import com.futsch1.medtimer.reminders.LocationSnoozeProcessor import com.google.android.gms.location.Geofence import com.google.android.gms.location.GeofencingEvent import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch import javax.inject.Inject @AndroidEntryPoint @@ -17,6 +20,10 @@ class GeofenceBroadcastReceiver : BroadcastReceiver() { @Inject lateinit var locationSnoozeProcessor: LocationSnoozeProcessor + @Inject + @ApplicationScope + lateinit var applicationScope: CoroutineScope + override fun onReceive(context: Context, intent: Intent) { val event = GeofencingEvent.fromIntent(intent) ?: return handleGeofencingEvent(event) @@ -28,7 +35,9 @@ class GeofenceBroadcastReceiver : BroadcastReceiver() { return } if (event.geofenceTransition == Geofence.GEOFENCE_TRANSITION_ENTER || event.geofenceTransition == Geofence.GEOFENCE_TRANSITION_DWELL) { - locationSnoozeProcessor.processLocationSnooze() + applicationScope.launch { + locationSnoozeProcessor.processLocationSnooze() + } } } } diff --git a/app/src/main/java/com/futsch1/medtimer/reminders/AlarmProcessor.kt b/app/src/main/java/com/futsch1/medtimer/reminders/AlarmProcessor.kt index 4b5874106..037d139db 100644 --- a/app/src/main/java/com/futsch1/medtimer/reminders/AlarmProcessor.kt +++ b/app/src/main/java/com/futsch1/medtimer/reminders/AlarmProcessor.kt @@ -24,7 +24,7 @@ class AlarmProcessor @Inject constructor( ) { private val exactReminders: Boolean = preferencesDataSource.preferences.value.exactReminders - fun setAlarmForReminderNotification(scheduledReminderNotificationData: ReminderNotificationData, sendImmediateBroadcast: Boolean = true): Boolean { + suspend fun setAlarmForReminderNotification(scheduledReminderNotificationData: ReminderNotificationData, reminderNotificationProcessor: ReminderNotificationProcessor? = null) { val originalInstant = scheduledReminderNotificationData.remindInstant scheduledReminderNotificationData.remindInstant = adjustTimestamp(timeAccess, originalInstant) @@ -49,12 +49,14 @@ class AlarmProcessor @Inject constructor( scheduledReminderNotificationData ) ) - if (sendImmediateBroadcast) { + if (reminderNotificationProcessor == null) { val reminderIntent = getReminderAction(context) scheduledReminderNotificationData.toIntent(reminderIntent) context.sendBroadcast(reminderIntent, RECEIVER_PERMISSION) + } else { + reminderNotificationProcessor.processReminders(scheduledReminderNotificationData) } - return true + return } val pendingIntent = scheduledReminderNotificationData.getPendingIntent(context) @@ -73,7 +75,6 @@ class AlarmProcessor @Inject constructor( ) updateNextReminderWidget() - return false } fun cancelNextReminder() { diff --git a/app/src/main/java/com/futsch1/medtimer/reminders/LocationSnoozeProcessor.kt b/app/src/main/java/com/futsch1/medtimer/reminders/LocationSnoozeProcessor.kt index 41923f6b9..58e5dae7c 100644 --- a/app/src/main/java/com/futsch1/medtimer/reminders/LocationSnoozeProcessor.kt +++ b/app/src/main/java/com/futsch1/medtimer/reminders/LocationSnoozeProcessor.kt @@ -11,7 +11,7 @@ class LocationSnoozeProcessor @Inject constructor( private val persistentDataDataSource: PersistentDataDataSource, private val geofenceRegistrar: GeofenceRegistrar ) { - fun processLocationSnooze() { + suspend fun processLocationSnooze() { val pending = persistentDataDataSource.getPendingLocationSnoozes() Log.d(LogTags.REMINDER, "In home location, restoring ${pending.size} snoozed reminders") for (data in pending) { diff --git a/app/src/main/java/com/futsch1/medtimer/reminders/ReminderProcessorBroadcastReceiver.kt b/app/src/main/java/com/futsch1/medtimer/reminders/ReminderProcessorBroadcastReceiver.kt index cb8c3153d..eaa2e19d3 100644 --- a/app/src/main/java/com/futsch1/medtimer/reminders/ReminderProcessorBroadcastReceiver.kt +++ b/app/src/main/java/com/futsch1/medtimer/reminders/ReminderProcessorBroadcastReceiver.kt @@ -119,7 +119,7 @@ class ReminderProcessorBroadcastReceiver : BroadcastReceiver() { } } - private fun processSnooze(intent: Intent) { + private suspend fun processSnooze(intent: Intent) { snoozeProcessor.processSnooze( ReminderNotificationData.fromBundle(intent.extras!!), intent.getLongExtra(ActivityCodes.EXTRA_SNOOZE_TIME_SECONDS, 0).toDuration(DurationUnit.SECONDS) diff --git a/app/src/main/java/com/futsch1/medtimer/reminders/ScheduleNextReminderNotificationProcessor.kt b/app/src/main/java/com/futsch1/medtimer/reminders/ScheduleNextReminderNotificationProcessor.kt index 9be51687b..d6a90a9fe 100644 --- a/app/src/main/java/com/futsch1/medtimer/reminders/ScheduleNextReminderNotificationProcessor.kt +++ b/app/src/main/java/com/futsch1/medtimer/reminders/ScheduleNextReminderNotificationProcessor.kt @@ -25,34 +25,23 @@ class ScheduleNextReminderNotificationProcessor @Inject constructor( suspend fun scheduleNextReminder() { mutex.withLock { - while (scheduleNextReminderStep()) { - // loop until a future alarm is set or no reminders remain - } - } - } + val medicines = medicineRepository.getAll() + val reminderEvents = reminderEventRepository.getForScheduling(medicines) + Log.d(LogTags.REMINDER, "Schedule next reminders, considering events ${reminderEvents.map { it.reminderEventId }}") - private suspend fun scheduleNextReminderStep(): Boolean { - val medicines = medicineRepository.getAll() - val reminderEvents = reminderEventRepository.getForScheduling(medicines) - Log.d(LogTags.REMINDER, "Schedule next reminders, considering events ${reminderEvents.map { it.reminderEventId }}") - - val scheduledReminders = ReminderScheduler(timeAccess, preferencesDataSource).schedule(medicines, reminderEvents) - if (scheduledReminders.isEmpty()) { - Log.d(LogTags.REMINDER, "No reminders scheduled") - alarmProcessor.cancelNextReminder() - return false - } + val scheduledReminders = ReminderScheduler(timeAccess, preferencesDataSource).schedule(medicines, reminderEvents) + if (scheduledReminders.isEmpty()) { + Log.d(LogTags.REMINDER, "No reminders scheduled") + alarmProcessor.cancelNextReminder() + return + } - val data = ReminderNotificationData.fromScheduledReminders( - if (preferencesDataSource.preferences.value.combineNotifications) scheduledReminders - else listOf(scheduledReminders[0]) - ) + val data = ReminderNotificationData.fromScheduledReminders( + if (preferencesDataSource.preferences.value.combineNotifications) scheduledReminders + else listOf(scheduledReminders[0]) + ) - val isImmediate = alarmProcessor.setAlarmForReminderNotification(data, sendImmediateBroadcast = false) - if (isImmediate) { - reminderNotificationProcessor.processReminders(data) - return true + alarmProcessor.setAlarmForReminderNotification(data, reminderNotificationProcessor) } - return false } } diff --git a/app/src/main/java/com/futsch1/medtimer/reminders/SnoozeProcessor.kt b/app/src/main/java/com/futsch1/medtimer/reminders/SnoozeProcessor.kt index 6a70ae2a9..2c48217c1 100644 --- a/app/src/main/java/com/futsch1/medtimer/reminders/SnoozeProcessor.kt +++ b/app/src/main/java/com/futsch1/medtimer/reminders/SnoozeProcessor.kt @@ -24,7 +24,7 @@ open class SnoozeProcessor @Inject constructor( private val geofenceRegistrar: GeofenceRegistrar ) { - fun processSnooze(reminderNotificationData: ReminderNotificationData, snoozeTime: Duration) { + suspend fun processSnooze(reminderNotificationData: ReminderNotificationData, snoozeTime: Duration) { reminderNotificationData.remindInstant = Instant.now().plusSeconds(snoozeTime.inWholeSeconds) Log.d(LogTags.REMINDER, "Snoozing reminder: $reminderNotificationData") From d3d5cec84717e2cf68cb1f5c6367ebd47b16b94d Mon Sep 17 00:00:00 2001 From: Futsch1 <63090558+Futsch1@users.noreply.github.com> Date: Fri, 8 May 2026 19:50:28 +0200 Subject: [PATCH 5/7] Several crash fixes --- .../medtimer/alarm/ReminderAlarmActivity.kt | 28 +++++++++++++------ .../ReminderProcessorBroadcastReceiver.kt | 5 ++-- 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/app/src/main/java/com/futsch1/medtimer/alarm/ReminderAlarmActivity.kt b/app/src/main/java/com/futsch1/medtimer/alarm/ReminderAlarmActivity.kt index 70df2f942..3f1329282 100644 --- a/app/src/main/java/com/futsch1/medtimer/alarm/ReminderAlarmActivity.kt +++ b/app/src/main/java/com/futsch1/medtimer/alarm/ReminderAlarmActivity.kt @@ -96,14 +96,23 @@ class ReminderAlarmActivity : AppCompatActivity() { } else { this@ReminderAlarmActivity } - mediaPlayer = - MediaPlayer.create( - audioContext, - preferencesDataSource.preferences.value.alarmRingtone ?: Settings.System.DEFAULT_ALARM_ALERT_URI, - null, - AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_ALARM).build(), - 0 - ).apply { isLooping = true } + val tmpMediaPlayer = MediaPlayer.create( + audioContext, + preferencesDataSource.preferences.value.alarmRingtone ?: Settings.System.DEFAULT_ALARM_ALERT_URI, + null, + AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_ALARM).build(), + 0 + ) ?: MediaPlayer.create( + audioContext, + Settings.System.DEFAULT_ALARM_ALERT_URI, + null, + AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_ALARM).build(), + 0 + ) + if (tmpMediaPlayer != null) { + tmpMediaPlayer.isLooping = true + mediaPlayer = tmpMediaPlayer + } } private suspend fun startAlarm() { @@ -136,6 +145,7 @@ class ReminderAlarmActivity : AppCompatActivity() { private fun releaseMediaPlayer() { mediaPlayer?.release() + mediaPlayer = null Log.d("ReminderAlarm", "Released media player") } @@ -178,7 +188,7 @@ class ReminderAlarmActivity : AppCompatActivity() { reminderNotificationData: ReminderNotificationData ): Intent { val intent = Intent(context, ReminderAlarmActivity::class.java).apply { - setFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP) + flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP } reminderNotificationData.toIntent(intent) diff --git a/app/src/main/java/com/futsch1/medtimer/reminders/ReminderProcessorBroadcastReceiver.kt b/app/src/main/java/com/futsch1/medtimer/reminders/ReminderProcessorBroadcastReceiver.kt index eaa2e19d3..379df3c13 100644 --- a/app/src/main/java/com/futsch1/medtimer/reminders/ReminderProcessorBroadcastReceiver.kt +++ b/app/src/main/java/com/futsch1/medtimer/reminders/ReminderProcessorBroadcastReceiver.kt @@ -3,6 +3,7 @@ package com.futsch1.medtimer.reminders import android.content.BroadcastReceiver import android.content.Context import android.content.Intent +import android.os.Bundle import android.util.Log import com.futsch1.medtimer.ActivityCodes import com.futsch1.medtimer.LogTags @@ -91,7 +92,7 @@ class ReminderProcessorBroadcastReceiver : BroadcastReceiver() { ProcessorCode.Snooze -> processSnooze(intent) ProcessorCode.Reminder -> { - if (reminderNotificationProcessor.processReminders(ReminderNotificationData.fromBundle(intent.extras!!))) { + if (reminderNotificationProcessor.processReminders(ReminderNotificationData.fromBundle(intent.extras ?: Bundle()))) { scheduleNextReminderNotificationProcessor.scheduleNextReminder() } } @@ -103,7 +104,7 @@ class ReminderProcessorBroadcastReceiver : BroadcastReceiver() { ProcessorCode.Refill -> processRefill(intent) ProcessorCode.StockHandling -> processStockHandling(intent) ProcessorCode.Schedule -> scheduleNextReminderNotificationProcessor.scheduleNextReminder() - ProcessorCode.LocationSnooze -> snoozeProcessor.processLocationSnooze(ReminderNotificationData.fromBundle(intent.extras!!)) + ProcessorCode.LocationSnooze -> snoozeProcessor.processLocationSnooze(ReminderNotificationData.fromBundle(intent.extras ?: Bundle())) } } finally { pendingResult.finish() From 044718c5b49f5a97a516552b71360eb293b77ffa Mon Sep 17 00:00:00 2001 From: Futsch1 <63090558+Futsch1@users.noreply.github.com> Date: Fri, 8 May 2026 20:12:43 +0200 Subject: [PATCH 6/7] Test fixes --- .gitignore | 3 ++- .../location/LocationSnoozeProcessorTest.kt | 14 ++++++++------ .../ShowReminderNotificationProcessorTest.kt | 15 +++++---------- .../location/GeofenceBroadcastReceiverTest.kt | 10 +++++++--- 4 files changed, 22 insertions(+), 20 deletions(-) diff --git a/.gitignore b/.gitignore index 6d40eb7df..c74c59b1c 100644 --- a/.gitignore +++ b/.gitignore @@ -43,4 +43,5 @@ app/.cifuzz-corpus/ gradle/gradle-daemon-jvm.properties # Kotlin -.kotlin \ No newline at end of file +.kotlin +/caveman.json diff --git a/app/src/test/java/com/futsch1/medtimer/location/LocationSnoozeProcessorTest.kt b/app/src/test/java/com/futsch1/medtimer/location/LocationSnoozeProcessorTest.kt index 44959ec92..4909f826c 100644 --- a/app/src/test/java/com/futsch1/medtimer/location/LocationSnoozeProcessorTest.kt +++ b/app/src/test/java/com/futsch1/medtimer/location/LocationSnoozeProcessorTest.kt @@ -4,10 +4,12 @@ import com.futsch1.medtimer.preferences.PersistentDataDataSource import com.futsch1.medtimer.reminders.AlarmProcessor import com.futsch1.medtimer.reminders.LocationSnoozeProcessor import com.futsch1.medtimer.reminders.notificationData.ReminderNotificationData +import kotlinx.coroutines.runBlocking import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.kotlin.any +import org.mockito.kotlin.isNull import org.mockito.kotlin.mock import org.mockito.kotlin.never import org.mockito.kotlin.times @@ -37,9 +39,9 @@ class LocationSnoozeProcessorTest { fun emptyPendingList() { whenever(persistentDataDataSource.getPendingLocationSnoozes()).thenReturn(emptyList()) - processor.processLocationSnooze() + runBlocking { processor.processLocationSnooze() } - verify(alarmProcessor, never()).setAlarmForReminderNotification(any()) + runBlocking { verify(alarmProcessor, never()).setAlarmForReminderNotification(any(), isNull()) } verify(persistentDataDataSource).clearAllPendingLocationSnoozes() verify(geofenceRegistrar).unregisterHomeGeofence() } @@ -50,9 +52,9 @@ class LocationSnoozeProcessorTest { val data = ReminderNotificationData(futureInstant, listOf(1), mutableListOf(10), 1) whenever(persistentDataDataSource.getPendingLocationSnoozes()).thenReturn(listOf(data)) - processor.processLocationSnooze() + runBlocking { processor.processLocationSnooze() } - verify(alarmProcessor, times(1)).setAlarmForReminderNotification(any()) + runBlocking { verify(alarmProcessor, times(1)).setAlarmForReminderNotification(any(), isNull()) } verify(persistentDataDataSource).clearAllPendingLocationSnoozes() verify(geofenceRegistrar).unregisterHomeGeofence() } @@ -66,9 +68,9 @@ class LocationSnoozeProcessorTest { ) whenever(persistentDataDataSource.getPendingLocationSnoozes()).thenReturn(snoozes) - processor.processLocationSnooze() + runBlocking { processor.processLocationSnooze() } - verify(alarmProcessor, times(3)).setAlarmForReminderNotification(any()) + runBlocking { verify(alarmProcessor, times(3)).setAlarmForReminderNotification(any(), isNull()) } verify(geofenceRegistrar).unregisterHomeGeofence() } } diff --git a/app/src/test/java/com/futsch1/medtimer/processortests/ShowReminderNotificationProcessorTest.kt b/app/src/test/java/com/futsch1/medtimer/processortests/ShowReminderNotificationProcessorTest.kt index 3ba0558bc..50151c98e 100644 --- a/app/src/test/java/com/futsch1/medtimer/processortests/ShowReminderNotificationProcessorTest.kt +++ b/app/src/test/java/com/futsch1/medtimer/processortests/ShowReminderNotificationProcessorTest.kt @@ -34,6 +34,7 @@ import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentMatchers.any import org.mockito.ArgumentMatchers.anyInt +import org.mockito.ArgumentMatchers.anyLong import org.mockito.ArgumentMatchers.eq import org.mockito.Mockito.never import org.mockito.Mockito.times @@ -139,8 +140,6 @@ class ShowReminderNotificationProcessorTest { // The actually requested reminder verify(reminderContext.alarmManagerMock, times(1)).setAndAllowWhileIdle(anyInt(), eq(10_000L), any()) - // And the one for tomorrow - verify(reminderContext.alarmManagerMock, times(1)).setAndAllowWhileIdle(anyInt(), eq(24 * 60 * 60 * 1000L + 10 * 60 * 60 * 1000L), any()) } @Test @@ -160,10 +159,8 @@ class ShowReminderNotificationProcessorTest { verify(reminderContext.notificationManagerFake.mock, never()).cancel(anyInt()) // Never raise a new one verify(reminderContext.notificationManagerFake.mock, never()).notify(anyInt(), any()) - // The actually requested reminder - verify(reminderContext.alarmManagerMock, never()).setAndAllowWhileIdle(anyInt(), eq(10_000L), any()) - // And the one for tomorrow - verify(reminderContext.alarmManagerMock, times(1)).setAndAllowWhileIdle(anyInt(), eq(24 * 60 * 60 * 1000L + 10 * 60 * 60 * 1000L), any()) + // No alarm set when notification is already active + verify(reminderContext.alarmManagerMock, never()).setAndAllowWhileIdle(anyInt(), anyLong(), any()) } @Test @@ -180,13 +177,11 @@ class ShowReminderNotificationProcessorTest { ) } - // Send broadcast (one for the reminder and one for updating the widget) + // Send broadcast for the reminder val broadcastIntents = shadowOf(ApplicationProvider.getApplicationContext()).broadcastIntents - assertEquals(2, broadcastIntents.size) + assertEquals(1, broadcastIntents.size) // The actually requested reminder verify(reminderContext.alarmManagerMock, never()).setAndAllowWhileIdle(anyInt(), eq(10_000L), any()) - // And two times for tomorrow (twice is actually unnecessary, but to reduce complexity, scheduling is called more often) - verify(reminderContext.alarmManagerMock, times(1)).setAndAllowWhileIdle(anyInt(), eq(24 * 60 * 60 * 1000L + 10 * 60 * 60 * 1000L), any()) } @Test diff --git a/app/src/testFull/java/com/futsch1/medtimer/location/GeofenceBroadcastReceiverTest.kt b/app/src/testFull/java/com/futsch1/medtimer/location/GeofenceBroadcastReceiverTest.kt index 6c7b501c9..05591e457 100644 --- a/app/src/testFull/java/com/futsch1/medtimer/location/GeofenceBroadcastReceiverTest.kt +++ b/app/src/testFull/java/com/futsch1/medtimer/location/GeofenceBroadcastReceiverTest.kt @@ -3,6 +3,9 @@ package com.futsch1.medtimer.location import com.futsch1.medtimer.reminders.LocationSnoozeProcessor import com.google.android.gms.location.Geofence import com.google.android.gms.location.GeofencingEvent +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.runBlocking import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -24,6 +27,7 @@ class GeofenceBroadcastReceiverTest { locationSnoozeProcessor = mock() receiver = GeofenceBroadcastReceiver() receiver.locationSnoozeProcessor = locationSnoozeProcessor + receiver.applicationScope = CoroutineScope(Dispatchers.Unconfined) } @Test @@ -34,7 +38,7 @@ class GeofenceBroadcastReceiverTest { receiver.handleGeofencingEvent(event) - verify(locationSnoozeProcessor).processLocationSnooze() + runBlocking { verify(locationSnoozeProcessor).processLocationSnooze() } } @Test @@ -45,7 +49,7 @@ class GeofenceBroadcastReceiverTest { receiver.handleGeofencingEvent(event) - verify(locationSnoozeProcessor, never()).processLocationSnooze() + runBlocking { verify(locationSnoozeProcessor, never()).processLocationSnooze() } } @Test @@ -56,6 +60,6 @@ class GeofenceBroadcastReceiverTest { receiver.handleGeofencingEvent(event) - verify(locationSnoozeProcessor, never()).processLocationSnooze() + runBlocking { verify(locationSnoozeProcessor, never()).processLocationSnooze() } } } From e3f702f83b9415ca2a78a74fa950493f867c5cd5 Mon Sep 17 00:00:00 2001 From: Futsch1 <63090558+Futsch1@users.noreply.github.com> Date: Fri, 8 May 2026 20:14:15 +0200 Subject: [PATCH 7/7] Bump version to v1.22.13 --- app/build.gradle.kts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index ad700a8ef..216f1d67c 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -22,8 +22,8 @@ android { minSdk = 28 multiDexEnabled = true targetSdk = 36 - versionCode = 167 - versionName = "1.22.12" + versionCode = 168 + versionName = "1.22.13" base.archivesName = "MedTimer" // Use this deprecated setting because Android Lint will not pick up androidResources.localeFilters correctly @Suppress("DEPRECATION")