fix(alarm): honor Dart's next-occurrence verdict on fresh schedule calls
The system ran two independent next-occurrence engines: Dart computes proximaProgramable (what the UI shows) and sends it as triggerAtMillis, but the native scheduleAlarm discarded it and recomputed from hour/minute/weekdays. Two engines over the same data WILL diverge — observed on-device: Dart said "today 22:48", the native weekday scan armed next Friday, and the alarm silently never rang at its hour while snooze (which bypasses recomputation and obeys a timestamp) always worked. That asymmetry was the user-visible "saving an alarm breaks, snoozing works" split. Fresh channel calls now arm exactly the trigger Dart sent whenever it is in the future or within the shared 90s imminence window; the native recompute remains as the fallback for stale triggers and for autonomous re-arms with no fresh Dart data (onAlarmFired's next occurrence, boot/persisted reschedules). Snooze preservation is untouched: a live native snooze still short-circuits through the compute path. Also logs the weekdays/trigger/lastHandled payload on every schedule call so day-convention divergences are diagnosable from logcat.
This commit is contained in:
@@ -75,14 +75,49 @@ class AlarmScheduler(private val context: Context) {
|
||||
fadeInSegundos = fadeInSegundos.coerceIn(0, 60),
|
||||
timezoneId = TimeZone.getDefault().id
|
||||
)
|
||||
return scheduleSpec(spec, persistOnSuccess = true)
|
||||
}
|
||||
|
||||
private fun scheduleSpec(spec: NativeAlarmSpec, persistOnSuccess: Boolean): Boolean {
|
||||
val nextTrigger = computeNextTriggerMillis(spec)
|
||||
Log.d(
|
||||
tag,
|
||||
"alarm.schedule id=${spec.id} title=${spec.title} trigger=$nextTrigger type=${spec.scheduleType} snooze=${spec.snoozeUntilMillis} canExact=${canScheduleExactAlarms()}"
|
||||
"alarm.channel spec id=$id weekdays=$weekdays type=${spec.scheduleType} " +
|
||||
"dartTrigger=$triggerAtMillis lastHandled=$lastHandledAtMillis"
|
||||
)
|
||||
// Dart is the single source of truth for WHEN the next occurrence is:
|
||||
// the trigger it sends is exactly what the user sees in the UI, so a
|
||||
// fresh channel call must arm THAT instant, never a native recompute
|
||||
// that can disagree with it (two independent next-occurrence engines
|
||||
// WILL diverge someday — observed on-device: Dart said "today", the
|
||||
// native weekday scan armed next week, and the alarm silently never
|
||||
// rang). The native computation remains ONLY for autonomous re-arms
|
||||
// where no fresh Dart data exists (onAlarmFired's next occurrence,
|
||||
// boot/persisted reschedules) via scheduleSpec's own recompute.
|
||||
return scheduleSpec(spec, persistOnSuccess = true, trustDartTrigger = true)
|
||||
}
|
||||
|
||||
private fun scheduleSpec(
|
||||
spec: NativeAlarmSpec,
|
||||
persistOnSuccess: Boolean,
|
||||
trustDartTrigger: Boolean = false
|
||||
): Boolean {
|
||||
// A fresh channel call carries Dart's own next-occurrence verdict —
|
||||
// the one the UI shows. Honor it whenever it is still in the future
|
||||
// (or just-passed within the shared 90s imminence window, matching
|
||||
// Dart's toleranciaDisparoInminente: AlarmManager fires a past
|
||||
// trigger immediately, which is exactly the contract). Only when the
|
||||
// Dart trigger is genuinely stale (e.g. a resync replaying an old
|
||||
// stored value long past) does the native recompute take over as the
|
||||
// fallback engine.
|
||||
val now = System.currentTimeMillis()
|
||||
val nextTrigger =
|
||||
if (trustDartTrigger &&
|
||||
spec.snoozeUntilMillis == null &&
|
||||
spec.triggerAtMillis > now - IMMINENT_TOLERANCE_MILLIS
|
||||
) {
|
||||
spec.triggerAtMillis
|
||||
} else {
|
||||
computeNextTriggerMillis(spec)
|
||||
}
|
||||
Log.d(
|
||||
tag,
|
||||
"alarm.schedule id=${spec.id} title=${spec.title} trigger=$nextTrigger type=${spec.scheduleType} snooze=${spec.snoozeUntilMillis} trustDart=$trustDartTrigger canExact=${canScheduleExactAlarms()}"
|
||||
)
|
||||
if (nextTrigger == null) {
|
||||
Log.d(tag, "alarm.schedule no next trigger id=${spec.id}")
|
||||
|
||||
Reference in New Issue
Block a user