2
0
mirror of https://github.com/koreader/koreader synced 2024-11-16 06:12:56 +00:00
koreader/plugins/autosuspend.koplugin/main.lua
NiLuJe e7acec1526 ReaderUI: Saner FM/RD lifecycle
* Ensure that going from one to the other tears down the former and
    its plugins before instantiating the latter and its plugins.

UIManager: Unify Event sending & broadcasting
  * Make the two behave the same way (walk the widget stack from top to
    bottom), and properly handle the window stack shrinking shrinking
    *and* growing.
    Previously, broadcasting happened bottom-to-top and didn't really
    handle the list shrinking/growing, while sending only handled the list
    shrinking by a single element, and hopefully that element being the one
    the event was just sent to.

These two items combined allowed us to optimize suboptimal
refresh behavior with Menu and other Menu classes when
opening/closing a document.
e.g., the "opening document" Notification is now properly regional,
and the "open last doc" option no longer flashes like a crazy person
anymore.

Plugins: Allow optimizing Menu refresh with custom menus, too.

Requires moving Menu's close_callback *after* onMenuSelect, which, eh,
probably makes sense, and is probably harmless in the grand scheme of
things.
2021-05-05 20:37:33 +02:00

241 lines
9.2 KiB
Lua

local Device = require("device")
if not Device:isCervantes() and
not Device:isKindle() and
not Device:isKobo() and
not Device:isRemarkable() and
not Device:isSDL() and
not Device:isSonyPRSTUX() and
not Device:isPocketBook() then
return { disabled = true, }
end
local PluginShare = require("pluginshare")
local TimeVal = require("ui/timeval")
local UIManager = require("ui/uimanager")
local WidgetContainer = require("ui/widget/container/widgetcontainer")
local logger = require("logger")
local _ = require("gettext")
local T = require("ffi/util").template
local default_autoshutdown_timeout_seconds = 3*24*60*60
local default_auto_suspend_timeout_seconds = 60*60
local AutoSuspend = WidgetContainer:new{
name = "autosuspend",
is_doc_only = false,
autoshutdown_timeout_seconds = default_autoshutdown_timeout_seconds,
auto_suspend_timeout_seconds = default_auto_suspend_timeout_seconds,
last_action_tv = TimeVal.zero,
standby_prevented = false,
task = nil,
}
function AutoSuspend:_enabled()
return self.auto_suspend_timeout_seconds > 0
end
function AutoSuspend:_enabledShutdown()
return Device:canPowerOff() and self.autoshutdown_timeout_seconds > 0
end
function AutoSuspend:_schedule(shutdown_only)
if not self:_enabled() and (Device:canPowerOff() and not self:_enabledShutdown()) then
logger.dbg("AutoSuspend:_schedule is disabled")
return
end
local delay_suspend, delay_shutdown
if PluginShare.pause_auto_suspend or Device.standby_prevented or Device.powerd:isCharging() then
delay_suspend = self.auto_suspend_timeout_seconds
delay_shutdown = self.autoshutdown_timeout_seconds
else
local now_tv = UIManager:getTime()
delay_suspend = self.last_action_tv + TimeVal:new{ sec = self.auto_suspend_timeout_seconds, usec = 0 } - now_tv
delay_suspend = delay_suspend:tonumber()
delay_shutdown = self.last_action_tv + TimeVal:new{ sec = self.autoshutdown_timeout_seconds, usec = 0 } - now_tv
delay_shutdown = delay_shutdown:tonumber()
end
-- Try to shutdown first, as we may have been woken up from suspend just for the sole purpose of doing that.
if delay_shutdown <= 0 then
logger.dbg("AutoSuspend: initiating shutdown")
UIManager:poweroff_action()
elseif delay_suspend <= 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)
end
if self:_enabledShutdown() then
logger.dbg("AutoSuspend: scheduling next shutdown check in", delay_shutdown)
UIManager:scheduleIn(delay_shutdown, self.task)
end
end
end
function AutoSuspend:_unschedule()
if self.task then
logger.dbg("AutoSuspend: unschedule")
UIManager:unschedule(self.task)
end
end
function AutoSuspend:_start()
if self:_enabled() or self:_enabledShutdown() then
local now_tv = UIManager:getTime()
logger.dbg("AutoSuspend: start at", now_tv:tonumber())
self.last_action_tv = now_tv
self:_schedule()
end
end
-- Variant that only re-engages the shutdown timer for onUnexpectedWakeupLimit
function AutoSuspend:_restart()
if self:_enabledShutdown() then
local now_tv = UIManager:getTime()
logger.dbg("AutoSuspend: restart at", now_tv:tonumber())
self.last_action_tv = now_tv
self:_schedule(true)
end
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") or default_autoshutdown_timeout_seconds
self.auto_suspend_timeout_seconds = G_reader_settings:readSetting("auto_suspend_timeout_seconds") or default_auto_suspend_timeout_seconds
UIManager.event_hook:registerWidget("InputEvent", self)
-- We need an instance-specific function reference to schedule, because in some rare cases,
-- we may instantiate a new plugin instance *before* tearing down the old one.
self.task = function(shutdown_only)
self:_schedule(shutdown_only)
end
self:_start()
-- self.ui is nil in the testsuite
if not self.ui or not self.ui.menu then return end
self.ui.menu:registerToMainMenu(self)
end
-- For event_hook automagic deregistration purposes
function AutoSuspend:onCloseWidget()
logger.dbg("AutoSuspend: onCloseWidget")
if Device:isPocketBook() and not Device:canSuspend() then return end
self:_unschedule()
self.task = nil
end
function AutoSuspend:onInputEvent()
logger.dbg("AutoSuspend: onInputEvent")
self.last_action_tv = UIManager:getTime()
end
function AutoSuspend:onSuspend()
logger.dbg("AutoSuspend: onSuspend")
-- We do not want auto suspend procedure to waste battery during suspend. So let's unschedule it
-- when suspending and restart it after resume.
self:_unschedule()
if self:_enabledShutdown() and Device.wakeup_mgr then
Device.wakeup_mgr:addTask(self.autoshutdown_timeout_seconds, UIManager.poweroff_action)
end
end
function AutoSuspend:onResume()
logger.dbg("AutoSuspend: onResume")
if self:_enabledShutdown() and Device.wakeup_mgr then
Device.wakeup_mgr:removeTask(nil, nil, UIManager.poweroff_action)
end
-- Unschedule in case we tripped onUnexpectedWakeupLimit first...
self:_unschedule()
self:_start()
end
function AutoSuspend:onUnexpectedWakeupLimit()
logger.dbg("AutoSuspend: onUnexpectedWakeupLimit")
-- Only re-engage the *shutdown* schedule to avoid doing the same dance indefinitely.
self:_restart()
end
function AutoSuspend:onAllowStandby()
self.standby_prevented = false
end
function AutoSuspend:onPreventStandby()
self.standby_prevented = true
end
function AutoSuspend:addToMainMenu(menu_items)
menu_items.autosuspend = {
sorting_hint = "device",
text = _("Autosuspend timeout"),
callback = function()
local InfoMessage = require("ui/widget/infomessage")
local Screen = Device.screen
local SpinWidget = require("ui/widget/spinwidget")
local autosuspend_spin = SpinWidget:new {
width = math.floor(Screen:getWidth() * 0.6),
value = self.auto_suspend_timeout_seconds / 60,
value_min = 5,
value_max = 240,
value_hold_step = 15,
ok_text = _("Set timeout"),
title_text = _("Timeout in minutes"),
callback = function(autosuspend_spin)
self.auto_suspend_timeout_seconds = autosuspend_spin.value * 60
G_reader_settings:saveSetting("auto_suspend_timeout_seconds", self.auto_suspend_timeout_seconds)
UIManager:show(InfoMessage:new{
text = T(_("The system will automatically suspend after %1 minutes of inactivity."),
string.format("%.2f", self.auto_suspend_timeout_seconds / 60)),
timeout = 3,
})
self:_unschedule()
self:_start()
end
}
UIManager:show(autosuspend_spin)
end,
}
if not (Device:canPowerOff() or Device:isEmulator()) then return end
menu_items.autoshutdown = {
sorting_hint = "device",
text = _("Autoshutdown timeout"),
callback = function()
local InfoMessage = require("ui/widget/infomessage")
local Screen = Device.screen
local SpinWidget = require("ui/widget/spinwidget")
local autosuspend_spin = SpinWidget:new {
width = math.floor(Screen:getWidth() * 0.6),
value = self.autoshutdown_timeout_seconds / 60 / 60,
-- About a minute, good for testing and battery life fanatics.
-- Just high enough to avoid an instant shutdown death scenario.
value_min = 0.017,
-- More than three weeks seems a bit excessive if you want to enable authoshutdown,
-- even if the battery can last up to three months.
value_max = 28*24,
value_hold_step = 24,
precision = "%.2f",
ok_text = _("Set timeout"),
title_text = _("Timeout in hours"),
callback = function(autosuspend_spin)
self.autoshutdown_timeout_seconds = math.floor(autosuspend_spin.value * 60 * 60)
G_reader_settings:saveSetting("autoshutdown_timeout_seconds", self.autoshutdown_timeout_seconds)
UIManager:show(InfoMessage:new{
text = T(_("The system will automatically shut down after %1 hours of inactivity."),
string.format("%.2f", self.autoshutdown_timeout_seconds / 60 / 60)),
timeout = 3,
})
self:_unschedule()
self:_start()
end
}
UIManager:show(autosuspend_spin)
end,
}
end
return AutoSuspend