fix(bedtime): keep the slot off the allow-while-idle ration
Android rations allow-while-idle wake-ups per app in doze, and the timer's expiry slot and the alarm's fallback share that ration, so a 07:00 wake-up fire could hold back a timer due at 07:04. The bedtime slot is now a plain exact alarm that doze may defer, which can move a mode boundary but never a ring. A restore of unchanged bedtime settings no longer forces the mode, and the screen skips its rule check while bedtime is off. Refs #1
This commit is contained in:
1 parent
8b182c999b
commit
1a5552a221
4 files changed
+31
-16
No files matched your search
@@ -99,7 +99,12 @@ class BackupEngine @Inject constructor(
|
||||
// with them), so the slot and the Do Not Disturb rule follow.
|
||||
// Its platform calls are guarded already; a failure here is
|
||||
// not a failed import. Nothing to follow if they never changed.
|
||||
if (settingsApplied) runCatching { bedtimeEngine.resync(refreshRule = true) }
|
||||
// Forced only when the bedtime settings really changed, so a
|
||||
// restore of the same schedule leaves a hand-switched mode be.
|
||||
if (settingsApplied) {
|
||||
val changed = previousSettings.bedtime != content.settings.bedtime
|
||||
runCatching { bedtimeEngine.resync(refreshRule = changed) }
|
||||
}
|
||||
}
|
||||
}
|
||||
failure?.let { throw it }
|
||||
|
||||
+13
-9
@@ -15,11 +15,17 @@ import javax.inject.Singleton
|
||||
/**
|
||||
* One wall-clock slot for the schedule's next event. Never `setAlarmClock`:
|
||||
* that slot is the status bar's "next alarm", and bedtime is not an alarm.
|
||||
* Exact while idle where the platform allows it, the same two branches the
|
||||
* timer's slot uses: a wake-up time that ends the mode an hour late, after a
|
||||
* doze maintenance window, is the failure this avoids. It is its own
|
||||
* PendingIntent (its own receiver, action and request code), so arming or
|
||||
* cancelling it can never touch an alarm's or a timer's registration.
|
||||
*
|
||||
* And never *allow-while-idle*. Android rations allow-while-idle wake-ups
|
||||
* per app while the device dozes (one per nine minutes on Android 10 and 11,
|
||||
* and for the inexact fallback without exact-alarm access), and the timer's
|
||||
* expiry slot and the alarm's fallback draw on the same ration. A 07:00
|
||||
* wake-up fire would hold back a timer due at 07:04 or a fallback alarm at
|
||||
* 07:05. So the slot is exact but doze-deferrable: in deep doze it waits for
|
||||
* the next maintenance window or for the next alarm clock to wake the device,
|
||||
* which only moves a mode boundary, never a ring. It is its own PendingIntent
|
||||
* (its own receiver, action and request code), so arming or cancelling it can
|
||||
* never touch an alarm's or a timer's registration.
|
||||
*/
|
||||
@Singleton
|
||||
class AndroidBedtimeScheduler @Inject constructor(
|
||||
@@ -31,12 +37,10 @@ class AndroidBedtimeScheduler @Inject constructor(
|
||||
override fun schedule(at: Instant) {
|
||||
val millis = at.toEpochMilliseconds()
|
||||
val pendingIntent = slotPendingIntent()
|
||||
val exact = runCatching {
|
||||
manager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, millis, pendingIntent)
|
||||
}.isSuccess
|
||||
val exact = runCatching { manager.setExact(AlarmManager.RTC_WAKEUP, millis, pendingIntent) }.isSuccess
|
||||
// Guarded too: bedtime losing its slot is survivable, a crash in the
|
||||
// receiver's process is not.
|
||||
if (!exact) runCatching { manager.setAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, millis, pendingIntent) }
|
||||
if (!exact) runCatching { manager.set(AlarmManager.RTC_WAKEUP, millis, pendingIntent) }
|
||||
}
|
||||
|
||||
override fun cancel() = manager.cancel(slotPendingIntent())
|
||||
|
||||
@@ -54,7 +54,8 @@ class BedtimeViewModel @Inject internal constructor(
|
||||
appScope.launch {
|
||||
// Also when access was granted while this screen was closed and
|
||||
// the system's broadcast did not reach the app: no rule yet.
|
||||
if (changed || (granted && engine.ruleId() == null)) {
|
||||
val enabled = (state.value as? BedtimeUiState.Loaded)?.settings?.enabled == true
|
||||
if (changed || (granted && enabled && engine.ruleId() == null)) {
|
||||
runCatching { engine.resync(refreshRule = true) }
|
||||
}
|
||||
ruleId.value = engine.ruleId()
|
||||
|
||||
+10
-5
@@ -2759,11 +2759,16 @@ engines (`ArchitectureRulesTest` holds it to no `android.*` import):
|
||||
- `BedtimeScheduler`, **one** AlarmManager slot of its own. Its PendingIntent
|
||||
targets `BedtimeReceiver` with its own action and request code, so arming or
|
||||
cancelling it cannot replace or cancel an alarm's or a timer's registration.
|
||||
It is `RTC_WAKEUP`, exact while idle when the platform allows it and
|
||||
`setAndAllowWhileIdle` otherwise, the timer slot's two branches. It never
|
||||
uses `setAlarmClock`: that slot is the status bar's next *alarm*. Alarms
|
||||
themselves use `setAlarmClock`, which the while-idle quota does not touch,
|
||||
and bedtime adds at most three fires a night.
|
||||
It is `RTC_WAKEUP` and `setExact` (plain `set` without exact-alarm access),
|
||||
and deliberately **not** allow-while-idle: Android rations allow-while-idle
|
||||
wake-ups per app in doze (one per nine minutes on Android 10 and 11, and for
|
||||
the inexact fallback without exact-alarm access), and the timer's expiry slot
|
||||
and the alarm's fallback draw on that same ration, so a 07:00 wake-up fire
|
||||
could hold back a timer at 07:04 or a fallback alarm at 07:05. In deep doze
|
||||
the slot therefore waits for a maintenance window or for the next alarm
|
||||
clock to wake the device. That can move a mode boundary, never a ring. It
|
||||
never uses `setAlarmClock` either: that slot is the status bar's next
|
||||
*alarm*.
|
||||
- `BedtimeMode`, the rule. Every call is guarded and a no-op without access.
|
||||
- `BedtimeNotifier`, the reminder, on its own `bedtime_reminder` channel.
|
||||
|
||||
|
||||
Reference in new issue
Block a user