mirror of
https://github.com/koreader/koreader.git
synced 2025-12-13 20:36:53 +01:00
AutoSuspend: Handle standby scheduling in the same manner as suspend/shutdown (#8985)
Specifically, don't forcibly unschedule/schedule on every input event, instead, let the scheduled task figure out if the deadline came to pass or not ;). c.f., https://github.com/koreader/koreader/pull/8970#issuecomment-1092775830 Besides getting rid of some overhead, this allows proper scheduling after a task that would have blocked for longer than the standby timeout.
This commit is contained in:
@@ -776,12 +776,13 @@ function Kobo:standby(max_duration)
|
||||
local TimeVal = require("ui/timeval")
|
||||
local standby_time_tv = TimeVal:boottime_or_realtime_coarse()
|
||||
|
||||
logger.info("Kobo suspend: asking to enter standby . . .")
|
||||
local ret = writeToSys("standby", "/sys/power/state")
|
||||
|
||||
self.last_standby_tv = TimeVal:boottime_or_realtime_coarse() - standby_time_tv
|
||||
self.total_standby_tv = self.total_standby_tv + self.last_standby_tv
|
||||
|
||||
logger.info("Kobo suspend: asked the kernel to put subsystems to standby, ret:", ret)
|
||||
logger.info("Kobo suspend: zZz zZz zZz zZz? Write syscall returned: ", ret)
|
||||
|
||||
if max_duration then
|
||||
self.wakeup_mgr:removeTask(nil, nil, dummy)
|
||||
|
||||
@@ -56,32 +56,31 @@ function AutoSuspend:_schedule(shutdown_only)
|
||||
return
|
||||
end
|
||||
|
||||
local delay_suspend, delay_shutdown
|
||||
|
||||
local suspend_delay, shutdown_delay
|
||||
if PluginShare.pause_auto_suspend or Device.powerd:isCharging() then
|
||||
delay_suspend = self.auto_suspend_timeout_seconds
|
||||
delay_shutdown = self.autoshutdown_timeout_seconds
|
||||
suspend_delay = self.auto_suspend_timeout_seconds
|
||||
shutdown_delay = self.autoshutdown_timeout_seconds
|
||||
else
|
||||
local now_tv = UIManager:getElapsedTimeSinceBoot()
|
||||
delay_suspend = (self.last_action_tv - now_tv):tonumber() + self.auto_suspend_timeout_seconds
|
||||
delay_shutdown = (self.last_action_tv - now_tv):tonumber() + self.autoshutdown_timeout_seconds
|
||||
suspend_delay = self.auto_suspend_timeout_seconds - (now_tv - self.last_action_tv):tonumber()
|
||||
shutdown_delay = self.autoshutdown_timeout_seconds - (now_tv - self.last_action_tv):tonumber()
|
||||
end
|
||||
|
||||
-- Try to shutdown first, as we may have been woken up from suspend just for the sole purpose of doing that.
|
||||
if self:_enabledShutdown() and delay_shutdown <= 0 then
|
||||
if self:_enabledShutdown() and shutdown_delay <= 0 then
|
||||
logger.dbg("AutoSuspend: initiating shutdown")
|
||||
UIManager:poweroff_action()
|
||||
elseif self:_enabled() and delay_suspend <= 0 and not shutdown_only then
|
||||
elseif self:_enabled() and suspend_delay <= 0 and not shutdown_only then
|
||||
logger.dbg("AutoSuspend: will suspend the device")
|
||||
UIManager:suspend()
|
||||
else
|
||||
if self:_enabled() and not shutdown_only then
|
||||
logger.dbg("AutoSuspend: scheduling next suspend check in", delay_suspend)
|
||||
UIManager:scheduleIn(delay_suspend, self.task)
|
||||
logger.dbg("AutoSuspend: scheduling next suspend check in", suspend_delay)
|
||||
UIManager:scheduleIn(suspend_delay, self.task)
|
||||
end
|
||||
if self:_enabledShutdown() then
|
||||
logger.dbg("AutoSuspend: scheduling next shutdown check in", delay_shutdown)
|
||||
UIManager:scheduleIn(delay_shutdown, self.task)
|
||||
logger.dbg("AutoSuspend: scheduling next shutdown check in", shutdown_delay)
|
||||
UIManager:scheduleIn(shutdown_delay, self.task)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -96,11 +95,19 @@ end
|
||||
function AutoSuspend:_start()
|
||||
if self:_enabled() or self:_enabledShutdown() then
|
||||
self.last_action_tv = UIManager:getElapsedTimeSinceBoot()
|
||||
logger.dbg("AutoSuspend: start at", self.last_action_tv:tonumber())
|
||||
logger.dbg("AutoSuspend: start (suspend/shutdown) at", self.last_action_tv:tonumber())
|
||||
self:_schedule()
|
||||
end
|
||||
end
|
||||
|
||||
function AutoSuspend:_start_standby()
|
||||
if self:_enabledStandby() then
|
||||
self.last_action_tv = UIManager:getElapsedTimeSinceBoot()
|
||||
logger.dbg("AutoSuspend: start (standby) at", self.last_action_tv:tonumber())
|
||||
self:_schedule_standby()
|
||||
end
|
||||
end
|
||||
|
||||
-- Variant that only re-engages the shutdown timer for onUnexpectedWakeupLimit
|
||||
function AutoSuspend:_restart()
|
||||
if self:_enabledShutdown() then
|
||||
@@ -113,6 +120,7 @@ end
|
||||
function AutoSuspend:init()
|
||||
logger.dbg("AutoSuspend: init")
|
||||
if Device:isPocketBook() and not Device:canSuspend() then return end
|
||||
|
||||
self.autoshutdown_timeout_seconds = G_reader_settings:readSetting("autoshutdown_timeout_seconds",
|
||||
default_autoshutdown_timeout_seconds)
|
||||
self.auto_suspend_timeout_seconds = G_reader_settings:readSetting("auto_suspend_timeout_seconds",
|
||||
@@ -133,11 +141,11 @@ function AutoSuspend:init()
|
||||
self:_schedule(shutdown_only)
|
||||
end
|
||||
self.standby_task = function()
|
||||
self:allowStandby()
|
||||
self:_schedule_standby()
|
||||
end
|
||||
|
||||
self:_start()
|
||||
self:_reschedule_standby()
|
||||
self:_start_standby()
|
||||
|
||||
-- self.ui is nil in the testsuite
|
||||
if not self.ui or not self.ui.menu then return end
|
||||
@@ -148,11 +156,10 @@ end
|
||||
function AutoSuspend:onCloseWidget()
|
||||
logger.dbg("AutoSuspend: onCloseWidget")
|
||||
if Device:isPocketBook() and not Device:canSuspend() then return end
|
||||
|
||||
self:_unschedule()
|
||||
self.task = nil
|
||||
|
||||
if not Device:canStandby() then return end
|
||||
|
||||
self:_unschedule_standby()
|
||||
self.standby_task = nil
|
||||
end
|
||||
@@ -160,10 +167,6 @@ end
|
||||
function AutoSuspend:onInputEvent()
|
||||
logger.dbg("AutoSuspend: onInputEvent")
|
||||
self.last_action_tv = UIManager:getElapsedTimeSinceBoot()
|
||||
|
||||
-- NOTE: The fact that we run this on *this* event ensures we don't have to handle the standby scheduling
|
||||
-- at all in setSuspendShutdownTimes ;).
|
||||
self:_reschedule_standby()
|
||||
end
|
||||
|
||||
function AutoSuspend:_unschedule_standby()
|
||||
@@ -177,20 +180,56 @@ function AutoSuspend:_unschedule_standby()
|
||||
end
|
||||
end
|
||||
|
||||
function AutoSuspend:_reschedule_standby()
|
||||
if not Device:canStandby() then return end
|
||||
function AutoSuspend:_schedule_standby()
|
||||
-- Start the long list of conditions in which we do *NOT* want to go into standby ;).
|
||||
if not Device:canStandby() then
|
||||
-- NOTE: This partly duplicates what `_enabledStandby` does,
|
||||
-- but it's here to avoid logging noise on devices that can't even standby ;).
|
||||
return
|
||||
end
|
||||
|
||||
-- We may have just disabled the feature, so unschedule before checking it.
|
||||
self:_unschedule_standby()
|
||||
-- Don't even schedule standby if we haven't set a proper timeout yet.
|
||||
if not self:_enabledStandby() then
|
||||
logger.dbg("AutoSuspend: No timeout set, no standby")
|
||||
return
|
||||
end
|
||||
|
||||
if not self:_enabledStandby() then return end
|
||||
-- When we're in a state where entering suspend is undesirable, we simply postpone the check by the full delay.
|
||||
local standby_delay = self.auto_standby_timeout_seconds
|
||||
if NetworkMgr:isWifiOn() then
|
||||
-- Don't enter standby if wifi is on, as this will break in fun and interesting ways (from Wi-Fi issues to kernel deadlocks).
|
||||
--logger.dbg("AutoSuspend: WiFi is on, delaying standby")
|
||||
elseif Device.powerd:isCharging() and not Device:canPowerSaveWhileCharging() then
|
||||
-- Don't enter standby when charging on devices where charging prevents entering low power states.
|
||||
--logger.dbg("AutoSuspend: charging, delaying standby")
|
||||
else
|
||||
local now_tv = UIManager:getElapsedTimeSinceBoot()
|
||||
standby_delay = self.auto_standby_timeout_seconds - (now_tv - self.last_action_tv):tonumber()
|
||||
|
||||
logger.dbg("AutoSuspend: schedule autoStandby in", self.auto_standby_timeout_seconds)
|
||||
UIManager:scheduleIn(self.auto_standby_timeout_seconds, self.standby_task)
|
||||
self.is_standby_scheduled = true
|
||||
-- If we somehow blow past the deadline on the first call of a scheduling cycle,
|
||||
-- make sure we don't go straight to allowStandby, as we haven't called preventStandby yet...
|
||||
-- (This shouldn't really ever happen, unless something is going seriously wrong somewhere).
|
||||
if not self.is_standby_scheduled and standby_delay <= 0 then
|
||||
standby_delay = 0.001
|
||||
end
|
||||
end
|
||||
|
||||
-- Prevent standby until our scheduled allowStandby
|
||||
self:preventStandby()
|
||||
if standby_delay <= 0 then
|
||||
-- We blew the deadline, tell UIManager we're ready to enter standby
|
||||
self:allowStandby()
|
||||
else
|
||||
-- Reschedule standby for the full or remaining delay
|
||||
-- NOTE: This is fairly chatty, given the low delays, but really helpful nonetheless... :/
|
||||
logger.dbg("AutoSuspend: scheduling next standby check in", standby_delay)
|
||||
UIManager:scheduleIn(standby_delay, self.standby_task)
|
||||
|
||||
-- Prevent standby until we actually blow the deadline
|
||||
if not self.is_standby_scheduled then
|
||||
self:preventStandby()
|
||||
end
|
||||
|
||||
self.is_standby_scheduled = true
|
||||
end
|
||||
end
|
||||
|
||||
function AutoSuspend:preventStandby()
|
||||
@@ -198,16 +237,16 @@ function AutoSuspend:preventStandby()
|
||||
UIManager:preventStandby()
|
||||
end
|
||||
|
||||
-- NOTE: This is the scheduled task that should trip the UIManager state to standby
|
||||
-- NOTE: This is what our scheduled task runs to trip the UIManager state to standby
|
||||
function AutoSuspend:allowStandby()
|
||||
logger.dbg("AutoSuspend:allowStandby")
|
||||
logger.dbg("AutoSuspend: allowStandby")
|
||||
-- Tell UIManager that we now allow standby.
|
||||
UIManager:allowStandby()
|
||||
|
||||
-- This is necessary for wakeup from standby, as the deadline for receiving input events
|
||||
-- is calculated from the time to the next scheduled function.
|
||||
-- Make sure this function comes soon, as the time for going to standby after a scheduled wakeup
|
||||
-- is prolonged by the given time. Any time between 0.500 and 0.001 seconds would go.
|
||||
-- is prolonged by the given time. Any time between 0.500 and 0.001 seconds should do.
|
||||
-- Let's call it deadline_guard.
|
||||
UIManager:scheduleIn(0.100, function() end)
|
||||
|
||||
@@ -234,11 +273,12 @@ function AutoSuspend:onResume()
|
||||
-- Unschedule in case we tripped onUnexpectedWakeupLimit first...
|
||||
self:_unschedule()
|
||||
self:_start()
|
||||
self:_reschedule_standby()
|
||||
self:_unschedule_standby()
|
||||
self:_start_standby()
|
||||
end
|
||||
|
||||
function AutoSuspend:onLeaveStandby()
|
||||
self:_reschedule_standby()
|
||||
self:_start_standby()
|
||||
end
|
||||
|
||||
function AutoSuspend:onUnexpectedWakeupLimit()
|
||||
@@ -251,15 +291,18 @@ end
|
||||
-- 2 ... display day:hour
|
||||
-- 1 ... display hour:min
|
||||
-- else ... display min:sec
|
||||
function AutoSuspend:setSuspendShutdownTimes(touchmenu_instance, title, info, setting,
|
||||
function AutoSuspend:pickTimeoutValue(touchmenu_instance, title, info, setting,
|
||||
default_value, range, time_scale)
|
||||
-- Attention if is_day_hour then time.hour stands for days and time.min for hours
|
||||
-- NOTE: if is_day_hour then time.hour stands for days and time.min for hours
|
||||
|
||||
local InfoMessage = require("ui/widget/infomessage")
|
||||
local DateTimeWidget = require("ui/widget/datetimewidget")
|
||||
|
||||
local setting_val = self[setting] > 0 and self[setting] or default_value
|
||||
|
||||
-- Standby uses a different scheduled task than suspend/shutdown
|
||||
local is_standby = setting == "auto_standby_timeout_seconds"
|
||||
|
||||
local left_val
|
||||
if time_scale == 2 then
|
||||
left_val = math.floor(setting_val / (24*3600))
|
||||
@@ -301,8 +344,13 @@ function AutoSuspend:setSuspendShutdownTimes(touchmenu_instance, title, info, se
|
||||
end
|
||||
self[setting] = Math.clamp(self[setting], range[1], range[2])
|
||||
G_reader_settings:saveSetting(setting, self[setting])
|
||||
self:_unschedule()
|
||||
self:_start()
|
||||
if is_standby then
|
||||
self:_unschedule_standby()
|
||||
self:_start_standby()
|
||||
else
|
||||
self:_unschedule()
|
||||
self:_start()
|
||||
end
|
||||
if touchmenu_instance then touchmenu_instance:updateItems() end
|
||||
local time_string = util.secondsToClockDuration("modern", self[setting],
|
||||
time_scale == 2 or time_scale == 1, true, true)
|
||||
@@ -337,7 +385,11 @@ function AutoSuspend:setSuspendShutdownTimes(touchmenu_instance, title, info, se
|
||||
extra_callback = function(this)
|
||||
self[setting] = -1 -- disable with a negative time/number
|
||||
G_reader_settings:saveSetting(setting, -1)
|
||||
self:_unschedule()
|
||||
if is_standby then
|
||||
self:_unschedule_standby()
|
||||
else
|
||||
self:_unschedule()
|
||||
end
|
||||
if touchmenu_instance then touchmenu_instance:updateItems() end
|
||||
UIManager:show(InfoMessage:new{
|
||||
text = T(_("%1: disabled"), title),
|
||||
@@ -370,7 +422,7 @@ function AutoSuspend:addToMainMenu(menu_items)
|
||||
-- 60 sec (1') is the minimum and 24*3600 sec (1day) is the maximum suspend time.
|
||||
-- A suspend time of one day seems to be excessive.
|
||||
-- But or battery testing it might give some sense.
|
||||
self:setSuspendShutdownTimes(touchmenu_instance,
|
||||
self:pickTimeoutValue(touchmenu_instance,
|
||||
_("Timeout for autosuspend"), _("Enter time in hours and minutes."),
|
||||
"auto_suspend_timeout_seconds", default_auto_suspend_timeout_seconds,
|
||||
{60, 24*3600}, 1)
|
||||
@@ -397,7 +449,7 @@ function AutoSuspend:addToMainMenu(menu_items)
|
||||
-- Minimum time has to be big enough, to avoid start-stop death scenarious.
|
||||
-- Maximum more than four weeks seems a bit excessive if you want to enable authoshutdown,
|
||||
-- even if the battery can last up to three months.
|
||||
self:setSuspendShutdownTimes(touchmenu_instance,
|
||||
self:pickTimeoutValue(touchmenu_instance,
|
||||
_("Timeout for autoshutdown"), _("Enter time in days and hours."),
|
||||
"autoshutdown_timeout_seconds", default_autoshutdown_timeout_seconds,
|
||||
{5*60, 28*24*3600}, 2)
|
||||
@@ -432,7 +484,7 @@ Upon user input, the device needs a certain amount of time to wake up. Generally
|
||||
-- We need a minimum time, so that scheduled function have a chance to execute.
|
||||
-- A standby time of 15 min seem excessive.
|
||||
-- But or battery testing it might give some sense.
|
||||
self:setSuspendShutdownTimes(touchmenu_instance,
|
||||
self:pickTimeoutValue(touchmenu_instance,
|
||||
_("Timeout for autostandby"), _("Enter time in minutes and seconds."),
|
||||
"auto_standby_timeout_seconds", default_auto_standby_timeout_seconds,
|
||||
{default_auto_standby_timeout_seconds, 15*60}, 0)
|
||||
@@ -448,30 +500,6 @@ function AutoSuspend:onAllowStandby()
|
||||
-- This piggy-backs minimally on the UI framework implemented for the PocketBook autostandby plugin,
|
||||
-- see its own AllowStandby handler for more details.
|
||||
|
||||
-- Start the long list of conditions in which we do *NOT* want to go into standby ;).
|
||||
if not Device:canStandby() then
|
||||
return
|
||||
end
|
||||
|
||||
-- Don't enter standby if we haven't set a proper timeout yet.
|
||||
if not self:_enabledStandby() then
|
||||
logger.dbg("AutoSuspend: No timeout set, no standby")
|
||||
return
|
||||
end
|
||||
|
||||
-- Don't enter standby if wifi is on, as this will break in fun and interesting ways (from Wi-Fi issues to kernel deadlocks).
|
||||
if NetworkMgr:isWifiOn() then
|
||||
logger.dbg("AutoSuspend: WiFi is on, no standby")
|
||||
return
|
||||
end
|
||||
|
||||
-- Don't enter standby when charging on devices where charging prevents entering low power states.
|
||||
if Device.powerd:isCharging() and not Device:canPowerSaveWhileCharging() then
|
||||
logger.dbg("AutoSuspend: charging, no standby")
|
||||
return
|
||||
end
|
||||
|
||||
-- Do the thing!
|
||||
local wake_in = math.huge
|
||||
-- The next scheduled function should be our deadline_guard (c.f., `AutoSuspend:allowStandby`).
|
||||
-- Wake up before the second next scheduled function executes (e.g. footer update, suspend ...)
|
||||
@@ -484,18 +512,18 @@ function AutoSuspend:onAllowStandby()
|
||||
|
||||
if wake_in > 3 then -- don't go into standby, if scheduled wakeup is in less than 3 secs
|
||||
UIManager:broadcastEvent(Event:new("EnterStandby"))
|
||||
logger.dbg("AutoSuspend: going to standby and wake in " .. wake_in .. "s zZzzZzZzzzzZZZzZZZz")
|
||||
logger.dbg("AutoSuspend: entering standby with a wakeup alarm in", wake_in, "s")
|
||||
|
||||
-- This obviously needs a matching implementation in Device, the canonical one being Kobo.
|
||||
Device:standby(wake_in)
|
||||
|
||||
logger.dbg("AutoSuspend: leaving standby after " .. Device.last_standby_tv:tonumber() .. " s")
|
||||
logger.dbg("AutoSuspend: left standby after", Device.last_standby_tv:tonumber(), "s")
|
||||
|
||||
UIManager:broadcastEvent(Event:new("LeaveStandby"))
|
||||
self:_unschedule() -- unschedule suspend and shutdown, as the realtime clock has ticked
|
||||
self:_schedule() -- reschedule suspend and shutdown with the new time
|
||||
self:_start() -- reschedule suspend and shutdown with the new time
|
||||
end
|
||||
-- Don't do a `self:_reschedule_standby()` here, as this will interfere with suspend.
|
||||
-- We don't reschedule standby here, as this will interfere with suspend.
|
||||
-- Leave that to `onLeaveStandby`.
|
||||
end
|
||||
|
||||
|
||||
Reference in New Issue
Block a user