fix(android): apply rollover interval for daily_rollover_* and allow ROLLOVER_ON_FIRE updates
- Bug 1: When the firing run used schedule_id daily_rollover_*, resolve the canonical notify schedule (first enabled with rolloverIntervalMinutes > 0) and use it to read the interval so the next run is current + interval instead of +24h. Add ScheduleHelper.getCanonicalRolloverScheduleBlocking(). - Bug 2: For ROLLOVER_ON_FIRE, do not skip scheduling when an existing PendingIntent is found for the same schedule id: cancel the existing alarm and set the new trigger time so the rollover chain (e.g. 21:10 → 21:20) is updated instead of treated as duplicate.
This commit is contained in:
@@ -2767,6 +2767,24 @@ object ScheduleHelper {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Blocking: first enabled notify schedule with rolloverIntervalMinutes > 0 (canonical for rollover chain).
|
||||
* Used when the firing run has schedule_id = daily_rollover_* so we can still apply the interval.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun getCanonicalRolloverScheduleBlocking(context: Context): Schedule? {
|
||||
return kotlinx.coroutines.runBlocking {
|
||||
try {
|
||||
DailyNotificationDatabase.getDatabase(context).scheduleDao()
|
||||
.getByKindAndEnabled("notify", true)
|
||||
.firstOrNull { it.rolloverIntervalMinutes != null && it.rolloverIntervalMinutes > 0 }
|
||||
} catch (e: Exception) {
|
||||
Log.w("ScheduleHelper", "getCanonicalRolloverScheduleBlocking failed", e)
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Blocking update of schedule next run time (for use from Java Worker after rollover).
|
||||
*/
|
||||
|
||||
@@ -547,12 +547,20 @@ public class DailyNotificationWorker extends Worker {
|
||||
scheduleIdForRollover = notificationId;
|
||||
}
|
||||
}
|
||||
// When firing run used daily_rollover_* id, resolve canonical schedule so we still apply rolloverIntervalMinutes
|
||||
String logicalScheduleIdForRollover = scheduleIdForRollover;
|
||||
if (scheduleIdForRollover != null && scheduleIdForRollover.startsWith("daily_rollover_")) {
|
||||
com.timesafari.dailynotification.Schedule canonical = com.timesafari.dailynotification.ScheduleHelper.getCanonicalRolloverScheduleBlocking(getApplicationContext());
|
||||
if (canonical != null) {
|
||||
logicalScheduleIdForRollover = canonical.getId();
|
||||
}
|
||||
}
|
||||
Integer rolloverMinutes = null;
|
||||
if (scheduleIdForRollover != null && !scheduleIdForRollover.isEmpty()) {
|
||||
com.timesafari.dailynotification.Schedule s = com.timesafari.dailynotification.ScheduleHelper.getScheduleBlocking(getApplicationContext(), scheduleIdForRollover);
|
||||
if (logicalScheduleIdForRollover != null && !logicalScheduleIdForRollover.isEmpty()) {
|
||||
com.timesafari.dailynotification.Schedule s = com.timesafari.dailynotification.ScheduleHelper.getScheduleBlocking(getApplicationContext(), logicalScheduleIdForRollover);
|
||||
if (s != null && s.getRolloverIntervalMinutes() != null && s.getRolloverIntervalMinutes() > 0) {
|
||||
rolloverMinutes = s.getRolloverIntervalMinutes();
|
||||
Log.d(TAG, "DN|ROLLOVER_INTERVAL scheduleId=" + scheduleIdForRollover + " minutes=" + rolloverMinutes);
|
||||
Log.d(TAG, "DN|ROLLOVER_INTERVAL scheduleId=" + logicalScheduleIdForRollover + " minutes=" + rolloverMinutes);
|
||||
}
|
||||
}
|
||||
long nextScheduledTime;
|
||||
|
||||
@@ -193,11 +193,17 @@ class NotifyReceiver : BroadcastReceiver() {
|
||||
}
|
||||
|
||||
if (existingPendingIntent != null) {
|
||||
val triggerTimeStr = java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss", java.util.Locale.US)
|
||||
.format(java.util.Date(triggerAtMillis))
|
||||
Log.w(SCHEDULE_TAG, "Skipping duplicate schedule: id=$stableScheduleId, nextRun=$triggerTimeStr, source=$source")
|
||||
Log.w(SCHEDULE_TAG, "Existing PendingIntent found for requestCode=$requestCode - alarm already scheduled")
|
||||
return
|
||||
if (source == ScheduleSource.ROLLOVER_ON_FIRE) {
|
||||
// Rollover chain: same schedule id, new trigger time — treat as update: cancel then set
|
||||
Log.d(SCHEDULE_TAG, "ROLLOVER_ON_FIRE: cancelling existing alarm for id=$stableScheduleId to set new trigger")
|
||||
alarmManager.cancel(existingPendingIntent)
|
||||
} else {
|
||||
val triggerTimeStr = java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss", java.util.Locale.US)
|
||||
.format(java.util.Date(triggerAtMillis))
|
||||
Log.w(SCHEDULE_TAG, "Skipping duplicate schedule: id=$stableScheduleId, nextRun=$triggerTimeStr, source=$source")
|
||||
Log.w(SCHEDULE_TAG, "Existing PendingIntent found for requestCode=$requestCode - alarm already scheduled")
|
||||
return
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Log.d(SCHEDULE_TAG, "Skipping PendingIntent idempotence (caller just cancelled scheduleId=$stableScheduleId)")
|
||||
|
||||
Reference in New Issue
Block a user