PM: Minor refactor to suspend/resume code flow (#10426)

Make sure we only send Suspend/Resume events when we *actually* suspend/resume. This is done via the Device `_beforeSuspend`/`_afterResume` methods, and those were called by the *input handlers*, not the PM logic; which means they would fire, while the PM logic could actually take a smarter decision and *not* do what the event just sent implied ;).

(i.e., sleep with a cover -> suspend + actual suspend, OK; but if you then resume with a button -> input assumes resume, but PM will actually suspend again!).

Existing design issue made more apparent by #9448 ;).

Also fixes/generalizes a few corner-cases related to screen_saver_lock handling (e.g., don't allow USBMS during a lock).

And deal with the fallout of the main change to the Kobo frontlight ramp behavior ;).
reviewable/pr10462/r1
NiLuJe 11 months ago committed by GitHub
parent 7ab832081b
commit 7e98b9de4b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -95,7 +95,7 @@ else
cp -f $(KOR_BASE)/ev_replay.py $(INSTALL_DIR)/koreader/
@echo "[*] create symlink instead of copying files in development mode"
cd $(INSTALL_DIR)/koreader && \
bash -O extglob -c "ln -sf ../../$(KOR_BASE)/$(OUTPUT_DIR)/!(cache) ."
bash -O extglob -c "ln -sf ../../$(KOR_BASE)/$(OUTPUT_DIR)/!(cache|history) ."
@echo "[*] install front spec only for the emulator"
cd $(INSTALL_DIR)/koreader/spec && test -e front || \
ln -sf ../../../../spec ./front

@ -1,9 +1,11 @@
local FFIUtil = require("ffi/util")
local Generic = require("device/generic/device")
local A, android = pcall(require, "android") -- luacheck: ignore
local Event = require("ui/event")
local Geom = require("ui/geometry")
local Generic = require("device/generic/device")
local UIManager
local ffi = require("ffi")
local C = ffi.C
local FFIUtil = require("ffi/util")
local lfs = require("libs/libkoreader-lfs")
local logger = require("logger")
local util = require("util")
@ -129,8 +131,6 @@ function Device:init()
device = self,
event_map = event_map,
handleMiscEv = function(this, ev)
local Event = require("ui/event")
local UIManager = require("ui/uimanager")
logger.dbg("Android application event", ev.code)
if ev.code == C.APP_CMD_SAVE_STATE then
UIManager:broadcastEvent(Event:new("FlushSettings"))
@ -280,6 +280,10 @@ function Device:init()
Generic.init(self)
end
function Device:UIManagerReady(uimgr)
UIManager = uimgr
end
function Device:initNetworkManager(NetworkMgr)
function NetworkMgr:turnOnWifi(complete_callback)
android.openWifiSettings()
@ -483,7 +487,6 @@ function Device:showLightDialog()
-- Delay it until next tick so that the event loop gets a chance to drain the input queue,
-- and consume the APP_CMD_LOST_FOCUS event.
-- This helps prevent ANRs on Tolino (c.f., #6583 & #7552).
local UIManager = require("ui/uimanager")
UIManager:nextTick(function() self:_showLightDialog() end)
end
@ -499,8 +502,6 @@ function Device:_showLightDialog()
self.powerd.fl_warmth = self.powerd:frontlightWarmthHW()
logger.dbg("Dialog OK, warmth: " .. self.powerd.fl_warmth)
end
local Event = require("ui/event")
local UIManager = require("ui/uimanager")
UIManager:broadcastEvent(Event:new("FrontlightStateChanged"))
elseif action == C.ALIGHTS_DIALOG_CANCEL then
logger.dbg("Dialog Cancel, brightness: " .. self.powerd.fl_intensity)
@ -519,7 +520,6 @@ end
function Device:download(link, name, ok_text)
local ConfirmBox = require("ui/widget/confirmbox")
local InfoMessage = require("ui/widget/infomessage")
local UIManager = require("ui/uimanager")
local ok = android.download(link, name)
if ok == C.ADOWNLOAD_EXISTS then
self:install()
@ -540,8 +540,6 @@ end
function Device:install()
local ConfirmBox = require("ui/widget/confirmbox")
local Event = require("ui/event")
local UIManager = require("ui/uimanager")
UIManager:show(ConfirmBox:new{
text = _("Update is ready. Install it now?"),
ok_text = _("Install"),

@ -1,4 +1,6 @@
local BasePowerD = require("device/generic/powerd")
local Event = require("ui/event")
local UIManager
local _, android = pcall(require, "android")
local AndroidPowerD = BasePowerD:new{
@ -8,9 +10,7 @@ local AndroidPowerD = BasePowerD:new{
-- Let the footer know of the change
local function broadcastLightChanges()
if package.loaded["ui/uimanager"] ~= nil then
local Event = require("ui/event")
local UIManager = require("ui/uimanager")
if UIManager then
UIManager:broadcastEvent(Event:new("FrontlightStateChanged"))
end
end
@ -83,4 +83,8 @@ function AndroidPowerD:turnOnFrontlightHW()
broadcastLightChanges()
end
function AndroidPowerD:UIManagerReadyHW(uimgr)
UIManager = uimgr
end
return AndroidPowerD

@ -200,16 +200,10 @@ function Cervantes:setEventHandlers(UIManager)
-- suspend. So let's unschedule it when suspending, and restart it after
-- resume. Done via the plugin's onSuspend/onResume handlers.
UIManager.event_handlers.Suspend = function()
self:_beforeSuspend()
self:onPowerEvent("Suspend")
end
UIManager.event_handlers.Resume = function()
-- MONOTONIC doesn't tick during suspend,
-- invalidate the last battery capacity pull time so that we get up to date data immediately.
self:getPowerDevice():invalidateCapacityCache()
self:onPowerEvent("Resume")
self:_afterResume()
end
UIManager.event_handlers.PowerPress = function()
-- Always schedule power off.
@ -222,10 +216,7 @@ function Cervantes:setEventHandlers(UIManager)
-- resume if we were suspended
if self.screen_saver_mode then
if self.screen_saver_lock then
logger.dbg("Pressed power while awake in screen saver mode, going back to suspend...")
self:_beforeSuspend()
self.powerd:beforeSuspend() -- this won't be run by onPowerEvent because we're in screen_saver_mode
self:onPowerEvent("Suspend")
UIManager.event_handlers.Suspend()
else
UIManager.event_handlers.Resume()
end
@ -241,7 +232,7 @@ function Cervantes:setEventHandlers(UIManager)
UIManager.event_handlers.Charging = function()
self:_beforeCharging()
-- NOTE: Plug/unplug events will wake the device up, which is why we put it back to sleep.
if self.screen_saver_mode then
if self.screen_saver_mode and not self.screen_saver_lock then
UIManager.event_handlers.Suspend()
end
end
@ -249,7 +240,7 @@ function Cervantes:setEventHandlers(UIManager)
-- We need to put the device into suspension, other things need to be done before it.
self:usbPlugOut()
self:_afterNotCharging()
if self.screen_saver_mode then
if self.screen_saver_mode and not self.screen_saver_lock then
UIManager.event_handlers.Suspend()
end
end
@ -257,9 +248,9 @@ function Cervantes:setEventHandlers(UIManager)
UIManager.event_handlers.UsbPlugIn = function()
self:_beforeCharging()
-- NOTE: Plug/unplug events will wake the device up, which is why we put it back to sleep.
if self.screen_saver_mode then
if self.screen_saver_mode and not self.screen_saver_lock then
UIManager.event_handlers.Suspend()
else
elseif not self.screen_saver_lock then
-- Potentially start an USBMS session
local MassStorage = require("ui/elements/mass_storage")
MassStorage:start()
@ -269,9 +260,9 @@ function Cervantes:setEventHandlers(UIManager)
-- We need to put the device into suspension, other things need to be done before it.
self:usbPlugOut()
self:_afterNotCharging()
if self.screen_saver_mode then
if self.screen_saver_mode and not self.screen_saver_lock then
UIManager.event_handlers.Suspend()
else
elseif not self.screen_saver_lock then
-- Potentially dismiss the USBMS ConfirmBox
local MassStorage = require("ui/elements/mass_storage")
MassStorage:dismiss()

@ -132,19 +132,29 @@ function CervantesPowerD:isChargingHW()
end
function CervantesPowerD:beforeSuspend()
if self.fl == nil then return end
-- just turn off frontlight without remembering its state
self.fl:setBrightness(0)
-- Inhibit user input and emit the Suspend event.
self.device:_beforeSuspend()
if self.fl then
-- just turn off frontlight without remembering its state
self.fl:setBrightness(0)
end
end
function CervantesPowerD:afterResume()
if self.fl == nil then return end
-- just re-set it to self.hw_intensity that we haven't change on Suspend
if not self.device:hasNaturalLight() then
self.fl:setBrightness(self.hw_intensity)
else
self.fl:setNaturalBrightness(self.hw_intensity, self.fl_warmth)
if self.fl then
-- just re-set it to self.hw_intensity that we haven't changed on Suspend
if not self.device:hasNaturalLight() then
self.fl:setBrightness(self.hw_intensity)
else
self.fl:setNaturalBrightness(self.hw_intensity, self.fl_warmth)
end
end
self:invalidateCapacityCache()
-- Restore user input and emit the Resume event.
self.device:_afterResume()
end
return CervantesPowerD

@ -5,7 +5,9 @@ This module defines stubs for common methods.
--]]
local DataStorage = require("datastorage")
local Event = require("ui/event")
local Geom = require("ui/geometry")
local UIManager -- Updated on UIManager init
local logger = require("logger")
local ffi = require("ffi")
local time = require("ui/time")
@ -259,7 +261,6 @@ function Device:getPowerDevice()
end
function Device:rescheduleSuspend()
local UIManager = require("ui/uimanager")
UIManager:unschedule(self.suspend)
UIManager:scheduleIn(self.suspend_wait_timeout, self.suspend, self)
end
@ -270,11 +271,11 @@ function Device:onPowerEvent(ev)
if self.screen_saver_mode then
if ev == "Power" or ev == "Resume" then
if self.is_cover_closed then
-- don't let power key press wake up device when the cover is in closed state.
-- Don't let power key press wake up device when the cover is in closed state.
logger.dbg("Pressed power while asleep in screen saver mode with a closed sleepcover, going back to suspend...")
self:rescheduleSuspend()
else
logger.dbg("Resuming...")
local UIManager = require("ui/uimanager")
UIManager:unschedule(self.suspend)
if self:hasWifiManager() then
local network_manager = require("ui/network/manager")
@ -291,10 +292,21 @@ function Device:onPowerEvent(ev)
self.powerd:afterResume()
end
elseif ev == "Suspend" then
-- Already in screen saver mode, no need to update UI/state before
-- suspending the hardware. This usually happens when sleep cover
-- is closed after the device was sent to suspend state.
logger.dbg("Already in screen saver mode, going back to suspend...")
-- Already in screen saver mode, no need to update the UI (and state, usually) before suspending again.
-- This usually happens when the sleep cover is closed on an already sleeping device,
-- (e.g., it was previously suspended via the Power button).
if self.screen_saver_lock then
-- This can only happen when some sort of screensaver_delay is set,
-- and the user presses the Power button *after* already having woken up the device.
-- In this case, we want to go back to suspend *without* affecting the screensaver,
-- so we simply mimic our own behavior when *not* in screen_saver_mode ;).
logger.dbg("Pressed power while awake in screen saver mode, going back to suspend...")
-- Basically, this is the only difference.
-- We need it because we're actually in a sane post-Resume event state right now.
self.powerd:beforeSuspend()
else
logger.dbg("Already in screen saver mode, going back to suspend...")
end
-- Much like the real suspend codepath below, in case we got here via screen_saver_lock,
-- make sure we murder WiFi again (because restore WiFi on resume could have kicked in).
if self:hasWifiToggle() then
@ -308,7 +320,6 @@ function Device:onPowerEvent(ev)
end
-- else we were not in screensaver mode
elseif ev == "Power" or ev == "Suspend" then
local UIManager = require("ui/uimanager")
logger.dbg("Suspending...")
-- Add the current state of the SleepCover flag...
logger.dbg("Sleep cover is", self.is_cover_closed and "closed" or "open")
@ -348,7 +359,6 @@ end
function Device:showLightDialog()
local FrontLightWidget = require("ui/widget/frontlightwidget")
local UIManager = require("ui/uimanager")
UIManager:show(FrontLightWidget:new{})
end
@ -357,8 +367,6 @@ function Device:info()
end
function Device:install()
local Event = require("ui/event")
local UIManager = require("ui/uimanager")
local ConfirmBox = require("ui/widget/confirmbox")
UIManager:show(ConfirmBox:new{
text = _("Update is ready. Install it now?"),
@ -925,8 +933,28 @@ function Device:untar(archive, extract_to, with_stripped_root)
return os.execute(cmd:format(archive, extract_to))
end
-- Update our UIManager reference once it's ready
function Device:_UIManagerReady(uimgr)
-- Our own ref
UIManager = uimgr
-- Let implementations do the same thing
self:UIManagerReady(uimgr)
-- Forward that to PowerD
self.powerd:UIManagerReady(uimgr)
-- And to Input
self.input:UIManagerReady(uimgr)
-- Setup PM event handlers
-- NOTE: We keep forwarding the uimgr reference because some implementations don't actually have a module-local UIManager ref to update
self:_setEventHandlers(uimgr)
end
-- In case implementations *also* need a reference to UIManager, *this* is the one to implement!
function Device:UIManagerReady(uimgr) end
-- Set device event handlers common to all devices
function Device:_setEventHandlers(UIManager)
function Device:_setEventHandlers(uimgr)
if self:canReboot() then
UIManager.event_handlers.Reboot = function(message_text)
local ConfirmBox = require("ui/widget/confirmbox")
@ -934,7 +962,6 @@ function Device:_setEventHandlers(UIManager)
text = message_text or _("Are you sure you want to reboot the device?"),
ok_text = _("Reboot"),
ok_callback = function()
local Event = require("ui/event")
UIManager:broadcastEvent(Event:new("Reboot"))
UIManager:nextTick(UIManager.reboot_action)
end,
@ -951,7 +978,6 @@ function Device:_setEventHandlers(UIManager)
text = message_text or _("Are you sure you want to power off the device?"),
ok_text = _("Power off"),
ok_callback = function()
local Event = require("ui/event")
UIManager:broadcastEvent(Event:new("PowerOff"))
UIManager:nextTick(UIManager.poweroff_action)
end,
@ -968,7 +994,6 @@ function Device:_setEventHandlers(UIManager)
text = message_text or _("This will take effect on next restart."),
ok_text = _("Restart now"),
ok_callback = function()
local Event = require("ui/event")
UIManager:broadcastEvent(Event:new("Restart"))
end,
cancel_text = _("Restart later"),
@ -983,24 +1008,23 @@ function Device:_setEventHandlers(UIManager)
end
end
self:setEventHandlers(UIManager)
-- Let implementations expand on that
self:setEventHandlers(uimgr)
end
-- Devices can add additional event handlers by overwriting this method.
function Device:setEventHandlers(UIManager)
-- These will be most probably overwritten in the device specific `setEventHandlers`
-- Devices can add additional event handlers by implementing this method.
function Device:setEventHandlers(uimgr)
-- These will most probably be overwritten by device-specific `setEventHandlers` implementations
UIManager.event_handlers.Suspend = function()
self:_beforeSuspend(false)
self.powerd:beforeSuspend()
end
UIManager.event_handlers.Resume = function()
self:_afterResume(false)
self.powerd:afterResume()
end
end
-- The common operations that should be performed before suspending the device.
function Device:_beforeSuspend(inhibit)
local Event = require("ui/event")
local UIManager = require("ui/uimanager")
UIManager:flushSettings()
UIManager:broadcastEvent(Event:new("Suspend"))
@ -1023,16 +1047,12 @@ function Device:_afterResume(inhibit)
self.input:inhibitInput(false)
end
local Event = require("ui/event")
local UIManager = require("ui/uimanager")
UIManager:broadcastEvent(Event:new("Resume"))
end
-- The common operations that should be performed when the device is plugged to a power source.
function Device:_beforeCharging()
-- Leave the kernel some time to figure it out ;o).
local Event = require("ui/event")
local UIManager = require("ui/uimanager")
UIManager:scheduleIn(1, function() self:setupChargingLED() end)
UIManager:broadcastEvent(Event:new("Charging"))
end
@ -1040,8 +1060,6 @@ end
-- The common operations that should be performed when the device is unplugged from a power source.
function Device:_afterNotCharging()
-- Leave the kernel some time to figure it out ;o).
local Event = require("ui/event")
local UIManager = require("ui/uimanager")
UIManager:scheduleIn(1, function() self:setupChargingLED() end)
UIManager:broadcastEvent(Event:new("NotCharging"))
end

@ -1,5 +1,6 @@
local UIManager = nil -- will be updated when available
local Event = require("ui/event")
local Math = require("optmath")
local UIManager
local logger = require("logger")
local time = require("ui/time")
local BasePowerD = {
@ -41,11 +42,6 @@ function BasePowerD:new(o)
return o
end
function BasePowerD:readyUI()
UIManager = require("ui/uimanager")
self:readyUIHW(UIManager)
end
function BasePowerD:init() end
function BasePowerD:setIntensityHW(intensity) end
--- @note: Unlike the "public" setWarmth, this one takes a value in the *native* scale!
@ -66,13 +62,30 @@ function BasePowerD:isFrontlightOnHW() return self.fl_intensity > self.fl_min en
function BasePowerD:turnOffFrontlightHW(done_callback) self:setIntensityHW(self.fl_min) end
function BasePowerD:turnOnFrontlightHW(done_callback) self:setIntensityHW(self.fl_intensity) end --- @fixme: what if fl_intensity == fl_min (c.f., kindle)?
function BasePowerD:frontlightWarmthHW() return 0 end
function BasePowerD:readyUIHW(uimgr) end
-- Anything that needs to be done before doing a real hardware suspend.
-- (Such as turning the front light off).
function BasePowerD:beforeSuspend() end
-- Do *not* omit calling Device's _beforeSuspend method! This default implementation passes `false` so as *not* to disable input events during PM.
function BasePowerD:beforeSuspend() self.device:_beforeSuspend(false) end
-- Anything that needs to be done after doing a real hardware resume.
-- (Such as restoring front light state).
function BasePowerD:afterResume() end
-- Do *not* omit calling Device's _afterResume method!
function BasePowerD:afterResume()
-- MONOTONIC doesn't tick during suspend,
-- invalidate the last battery capacity pull time so that we get up to date data immediately.
self:invalidateCapacityCache()
self.device:_afterResume(false)
end
-- Update our UIManager reference once it's ready
function BasePowerD:UIManagerReady(uimgr)
-- Our own ref
UIManager = uimgr
-- Let implementations do the same thing, too
self:UIManagerReadyHW(uimgr)
end
-- Ditto, but for implementations
function BasePowerD:UIManagerReadyHW(uimgr) end
function BasePowerD:isFrontlightOn()
return self.is_fl_on
@ -275,7 +288,6 @@ end
function BasePowerD:stateChanged()
-- BasePowerD is loaded before UIManager. So we cannot broadcast events before UIManager has been loaded.
if UIManager then
local Event = require("ui/event")
UIManager:broadcastEvent(Event:new("FrontlightStateChanged"))
end
end

@ -7,6 +7,7 @@ local DEBUG = require("dbg")
local Event = require("ui/event")
local GestureDetector = require("device/gesturedetector")
local Key = require("device/key")
local UIManager
local framebuffer = require("ffi/framebuffer")
local input = require("ffi/input")
local logger = require("logger")
@ -275,6 +276,10 @@ function Input:init()
self._inhibitInputUntil_func = function() self:inhibitInputUntil() end
end
function Input:UIManagerReady(uimgr)
UIManager = uimgr
end
--[[--
Setup a rotation_map that does nothing (for platforms where the events we get are already translated).
--]]
@ -606,14 +611,12 @@ function Input:handleKeyBoardEv(ev)
-- toggle fullscreen on F11
if self:isEvKeyPress(ev) and keycode == "F11" and not self.device:isAlwaysFullscreen() then
local UIManager = require("ui/uimanager")
UIManager:broadcastEvent(Event:new("ToggleFullscreen"))
end
-- quit on Alt + F4
-- this is also emitted by the close event in SDL
if self:isEvKeyPress(ev) and self.modifiers["Alt"] and keycode == "F4" then
local UIManager = require("ui/uimanager")
UIManager:broadcastEvent(Event:new("Close")) -- Tell all widgets to close.
UIManager:nextTick(function() UIManager:quit() end) -- Ensure the program closes in case of some lingering dialog.
end
@ -986,7 +989,6 @@ function Input:handleMiscGyroEv(ev)
if rotation_mode and rotation_mode ~= old_rotation_mode and screen_mode == old_screen_mode then
-- Cheaper than a full SetRotationMode event, as we don't need to re-layout anything.
self.device.screen:setRotationMode(rotation_mode)
local UIManager = require("ui/uimanager")
UIManager:onRotation()
end
else
@ -1284,7 +1286,6 @@ function Input:waitEvent(now, deadline)
elseif ok == nil then
-- Something went horribly wrong, abort.
logger.err("Polling for input events failed catastrophically")
local UIManager = require("ui/uimanager")
UIManager:abort()
break
end
@ -1453,7 +1454,6 @@ Request all input events to be ignored for some duration.
@param set_or_seconds either `true`, in which case a platform-specific delay is chosen, or a duration in seconds (***int***).
]]
function Input:inhibitInputUntil(set_or_seconds)
local UIManager = require("ui/uimanager")
UIManager:unschedule(self._inhibitInputUntil_func)
if not set_or_seconds then -- remove any previously set
self:inhibitInput(false)

@ -1,4 +1,5 @@
local Generic = require("device/generic/device")
local UIManager
local time = require("ui/time")
local lfs = require("libs/libkoreader-lfs")
local logger = require("logger")
@ -177,7 +178,6 @@ function Kindle:initNetworkManager(NetworkMgr)
kindleEnableWifi(0)
-- NOTE: Same here, except disconnect is simpler, so a dumb delay will do...
if complete_callback then
local UIManager = require("ui/uimanager")
UIManager:scheduleIn(2, complete_callback)
end
end
@ -291,8 +291,9 @@ function Kindle:intoScreenSaver()
-- so that we do the right thing on resume ;).
self.screen_saver_mode = true
end
self.powerd:beforeSuspend()
end
self.powerd:beforeSuspend()
end
function Kindle:outofScreenSaver()
@ -300,7 +301,6 @@ function Kindle:outofScreenSaver()
if self:supportsScreensaver() then
local Screensaver = require("ui/screensaver")
local widget_was_closed = Screensaver:close()
local UIManager = require("ui/uimanager")
if widget_was_closed then
-- And redraw everything in case the framework managed to screw us over...
UIManager:nextTick(function() UIManager:setDirty("all", "full") end)
@ -338,15 +338,15 @@ function Kindle:outofScreenSaver()
elseif os.getenv("CVM_STOPPED") == "yes" then
os.execute("killall -STOP cvm")
end
local UIManager = require("ui/uimanager")
-- NOTE: We redraw after a slightly longer delay to take care of the potentially dynamic ad screen...
-- This is obviously brittle as all hell. Tested on a slow-ass PW1.
UIManager:scheduleIn(3, function() UIManager:setDirty("all", "full") end)
-- Flip the switch again
self.screen_saver_mode = false
end
self.powerd:afterResume()
end
self.powerd:afterResume()
end
function Kindle:usbPlugOut()
@ -369,17 +369,19 @@ function Kindle:untar(archive, extract_to)
return os.execute(("./tar --no-same-permissions --no-same-owner -xf %q -C %q"):format(archive, extract_to))
end
function Kindle:setEventHandlers(UIManager)
function Kindle:UIManagerReady(uimgr)
UIManager = uimgr
end
function Kindle:setEventHandlers(uimgr)
UIManager.event_handlers.Suspend = function()
self.powerd:toggleSuspend()
end
UIManager.event_handlers.IntoSS = function()
self:_beforeSuspend()
self:intoScreenSaver()
end
UIManager.event_handlers.OutOfSS = function()
self:outofScreenSaver()
self:_afterResume()
end
UIManager.event_handlers.Charging = function()
self:_beforeCharging()

@ -1,4 +1,5 @@
local BasePowerD = require("device/generic/powerd")
local UIManager
local WakeupMgr = require("device/wakeupmgr")
local logger = require("logger")
local util = require("util")
@ -185,7 +186,6 @@ function KindlePowerD:afterResume()
if not self.device:hasFrontlight() then
return
end
local UIManager = require("ui/uimanager")
if self:isFrontlightOn() then
-- The Kindle framework should turn the front light back on automatically.
-- The following statement ensures consistency of intensity, but should basically always be redundant,
@ -249,7 +249,6 @@ function KindlePowerD:initWakeupMgr()
-- This filters out user input resumes -> device will resume to active
-- Also the Kindle stays in Ready to suspend for 10 seconds
-- so the alarm may fire 10 seconds early
local UIManager = require("ui/uimanager")
UIManager:scheduleIn(15, self.checkUnexpectedWakeup, self)
end
@ -284,6 +283,22 @@ function KindlePowerD:resetT1Timeout()
end
end
function KindlePowerD:beforeSuspend()
-- Inhibit user input and emit the Suspend event.
self.device:_beforeSuspend()
end
function KindlePowerD:afterResume()
self:invalidateCapacityCache()
-- Restore user input and emit the Resume event.
self.device:_afterResume()
end
function KindlePowerD:UIManagerReadyHW(uimgr)
UIManager = uimgr
end
--- @fixme: This won't ever fire on its own, as KindlePowerD is already a metatable on a plain table.
function KindlePowerD:__gc()
if self.lipc_handle then

@ -1,6 +1,6 @@
local Generic = require("device/generic/device")
local Geom = require("ui/geometry")
local UIManager -- Updated on UIManager init
local UIManager
local WakeupMgr = require("device/wakeupmgr")
local time = require("ui/time")
local ffiUtil = require("ffi/util")
@ -1369,24 +1369,19 @@ function Kobo:isStartupScriptUpToDate()
return md5.sumFile(current_script) == md5.sumFile(new_script)
end
function Kobo:setEventHandlers(uimgr)
-- Update our module-local
function Kobo:UIManagerReady(uimgr)
UIManager = uimgr
end
function Kobo:setEventHandlers(uimgr)
-- We do not want auto suspend procedure to waste battery during
-- suspend. So let's unschedule it when suspending, and restart it after
-- resume. Done via the plugin's onSuspend/onResume handlers.
UIManager.event_handlers.Suspend = function()
self:_beforeSuspend()
self:onPowerEvent("Suspend")
end
UIManager.event_handlers.Resume = function()
-- MONOTONIC doesn't tick during suspend,
-- invalidate the last battery capacity pull time so that we get up to date data immediately.
self:getPowerDevice():invalidateCapacityCache()
self:onPowerEvent("Resume")
self:_afterResume()
end
UIManager.event_handlers.PowerPress = function()
-- Always schedule power off.
@ -1399,14 +1394,7 @@ function Kobo:setEventHandlers(uimgr)
-- resume if we were suspended
if self.screen_saver_mode then
if self.screen_saver_lock then
-- This can only happen when some sort of screensaver_delay is set,
-- and the user presses the Power button *after* already having woken up the device.
-- In this case, we want to go back to suspend *without* affecting the screensaver,
-- so we mimic UIManager.event_handlers.Suspend's behavior when *not* in screen_saver_mode ;).
logger.dbg("Pressed power while awake in screen saver mode, going back to suspend...")
self:_beforeSuspend()
self.powerd:beforeSuspend() -- this won't be run by onPowerEvent because we're in screen_saver_mode
self:onPowerEvent("Suspend")
UIManager.event_handlers.Suspend()
else
UIManager.event_handlers.Resume()
end
@ -1422,7 +1410,7 @@ function Kobo:setEventHandlers(uimgr)
UIManager.event_handlers.Charging = function()
self:_beforeCharging()
-- NOTE: Plug/unplug events will wake the device up, which is why we put it back to sleep.
if self.screen_saver_mode then
if self.screen_saver_mode and not self.screen_saver_lock then
UIManager.event_handlers.Suspend()
end
end
@ -1430,7 +1418,7 @@ function Kobo:setEventHandlers(uimgr)
-- We need to put the device into suspension, other things need to be done before it.
self:usbPlugOut()
self:_afterNotCharging()
if self.screen_saver_mode then
if self.screen_saver_mode and not self.screen_saver_lock then
UIManager.event_handlers.Suspend()
end
end
@ -1438,9 +1426,9 @@ function Kobo:setEventHandlers(uimgr)
UIManager.event_handlers.UsbPlugIn = function()
self:_beforeCharging()
-- NOTE: Plug/unplug events will wake the device up, which is why we put it back to sleep.
if self.screen_saver_mode then
if self.screen_saver_mode and not self.screen_saver_lock then
UIManager.event_handlers.Suspend()
else
elseif not self.screen_saver_lock then
-- Potentially start an USBMS session
local MassStorage = require("ui/elements/mass_storage")
MassStorage:start()
@ -1450,9 +1438,9 @@ function Kobo:setEventHandlers(uimgr)
-- We need to put the device into suspension, other things need to be done before it.
self:usbPlugOut()
self:_afterNotCharging()
if self.screen_saver_mode then
if self.screen_saver_mode and not self.screen_saver_lock then
UIManager.event_handlers.Suspend()
else
elseif not self.screen_saver_lock then
-- Potentially dismiss the USBMS ConfirmBox
local MassStorage = require("ui/elements/mass_storage")
MassStorage:dismiss()

@ -1,8 +1,8 @@
local UIManager = nil -- will be updated when available
local BasePowerD = require("device/generic/powerd")
local Math = require("optmath")
local NickelConf = require("device/kobo/nickel_conf")
local SysfsLight = require ("device/sysfs_light")
local UIManager
local RTC = require("ffi/rtc")
-- Here, we only deal with the real hw intensity.
@ -415,26 +415,44 @@ end
-- Turn off front light before suspend.
function KoboPowerD:beforeSuspend()
if self.fl == nil then return end
-- Remember the current frontlight state
self.fl_was_on = self.is_fl_on
-- Turn off the frontlight
self:turnOffFrontlight()
-- Inhibit user input and emit the Suspend event.
self.device:_beforeSuspend()
-- Handle the frontlight last,
-- to prevent as many things as we can from interfering with the smoothness of the ramp
if self.fl then
-- Remember the current frontlight state
self.fl_was_on = self.is_fl_on
-- Turn off the frontlight
-- NOTE: Funky delay mainly to yield to the EPDC's refresh on UP systems.
-- (Neither yieldToEPDC nor nextTick & friends quite cut it here)...
UIManager:scheduleIn(0.001, self.turnOffFrontlight, self)
end
end
-- Restore front light state after resume.
function KoboPowerD:afterResume()
if self.fl == nil then return end
-- Don't bother if the light was already off on suspend
if not self.fl_was_on then return end
-- Turn the frontlight back on
self:turnOnFrontlight()
-- Set the system clock to the hardware clock's time.
RTC:HCToSys()
self:invalidateCapacityCache()
-- Restore user input and emit the Resume event.
self.device:_afterResume()
-- There's a whole bunch of stuff happening before us in Generic:onPowerEvent,
-- so we'll delay this ever so slightly so as to appear as smooth as possible...
if self.fl then
-- Don't bother if the light was already off on suspend
if self.fl_was_on then
-- Turn the frontlight back on
-- NOTE: There's quite likely *more* resource contention than on suspend here :/.
UIManager:scheduleIn(0.001, self.turnOnFrontlight, self)
end
end
end
function KoboPowerD:readyUIHW(uimgr)
function KoboPowerD:UIManagerReadyHW(uimgr)
UIManager = uimgr
end

@ -1,4 +1,5 @@
local Generic = require("device/generic/device") -- <= look at this file!
local UIManager
local logger = require("logger")
local ffi = require("ffi")
local C = ffi.C
@ -333,8 +334,6 @@ function PocketBook:reboot()
end
function PocketBook:initNetworkManager(NetworkMgr)
local UIManager = require("ui/uimanager")
local function keepWifiAlive()
-- Make sure only one wifiKeepAlive is scheduled
UIManager:unschedule(keepWifiAlive)
@ -386,13 +385,17 @@ function PocketBook:getDefaultCoverPath()
return "/mnt/ext1/system/logo/offlogo/cover.bmp"
end
function PocketBook:setEventHandlers(UIManager)
function PocketBook:UIManagerReady(uimgr)
UIManager = uimgr
end
function PocketBook:setEventHandlers(uimgr)
-- Only fg/bg state plugin notifiers, not real power event.
UIManager.event_handlers.Suspend = function()
self:_beforeSuspend()
self.powerd:beforeSuspend()
end
UIManager.event_handlers.Resume = function()
self:_afterResume()
self.powerd:afterResume()
end
UIManager.event_handlers.Exit = function()
local Event = require("ui/event")

@ -73,4 +73,16 @@ function PocketBookPowerD:isChargingHW()
end
end
function PocketBookPowerD:beforeSuspend()
-- Inhibit user input and emit the Suspend event.
self.device:_beforeSuspend()
end
function PocketBookPowerD:afterResume()
self:invalidateCapacityCache()
-- Restore user input and emit the Resume event.
self.device:_afterResume()
end
return PocketBookPowerD

@ -268,12 +268,10 @@ end
function Remarkable:setEventHandlers(UIManager)
UIManager.event_handlers.Suspend = function()
self:_beforeSuspend()
self:onPowerEvent("Suspend")
end
UIManager.event_handlers.Resume = function()
self:onPowerEvent("Resume")
self:_afterResume()
end
UIManager.event_handlers.PowerPress = function()
UIManager:scheduleIn(2, UIManager.poweroff_action)
@ -284,10 +282,7 @@ function Remarkable:setEventHandlers(UIManager)
-- resume if we were suspended
if self.screen_saver_mode then
if self.screen_saver_lock then
logger.dbg("Pressed power while awake in screen saver mode, going back to suspend...")
self:_beforeSuspend()
self.powerd:beforeSuspend() -- this won't be run by onPowerEvent because we're in screen_saver_mode
self:onPowerEvent("Suspend")
UIManager.event_handlers.Suspend()
else
UIManager.event_handlers.Resume()
end

@ -22,4 +22,16 @@ function Remarkable_PowerD:isChargingHW()
return self:read_str_file(self.status_file) == "Charging"
end
function Remarkable_PowerD:beforeSuspend()
-- Inhibit user input and emit the Suspend event.
self.device:_beforeSuspend()
end
function Remarkable_PowerD:afterResume()
self:invalidateCapacityCache()
-- Restore user input and emit the Resume event.
self.device:_afterResume()
end
return Remarkable_PowerD

@ -1,5 +1,7 @@
local Event = require("ui/event")
local Geom = require("ui/geometry")
local Generic = require("device/generic/device")
local UIManager
local SDL = require("ffi/SDL2_0")
local ffi = require("ffi")
local logger = require("logger")
@ -180,8 +182,7 @@ function Device:init()
device = self,
event_map = require("device/sdl/event_map_sdl2"),
handleSdlEv = function(device_input, ev)
local Geom = require("ui/geometry")
local UIManager = require("ui/uimanager")
-- SDL events can remain cdata but are almost completely transparent
local SDL_TEXTINPUT = 771
@ -342,7 +343,11 @@ function Device:toggleFullscreen()
end
end
function Device:setEventHandlers(UIManager)
function Device:UIManagerReady(uimgr)
UIManager = uimgr
end
function Device:setEventHandlers(uimgr)
if not self:canSuspend() then
-- If we can't suspend, we have no business even trying to, as we may not have overloaded `Device:simulateResume`.
-- Instead, rely on the Generic Suspend/Resume handlers.
@ -350,17 +355,19 @@ function Device:setEventHandlers(UIManager)
end
UIManager.event_handlers.Suspend = function()
self:_beforeSuspend()
self:simulateSuspend()
end
UIManager.event_handlers.Resume = function()
self:simulateResume()
self:_afterResume()
end
UIManager.event_handlers.PowerRelease = function()
-- Resume if we were suspended
if self.screen_saver_mode then
UIManager.event_handlers.Resume()
if self.screen_saver_lock then
UIManager.event_handlers.Suspend()
else
UIManager.event_handlers.Resume()
end
else
UIManager.event_handlers.Suspend()
end
@ -385,16 +392,19 @@ function Emulator:simulateSuspend()
local Screensaver = require("ui/screensaver")
Screensaver:setup()
Screensaver:show()
self.powerd:beforeSuspend()
end
function Emulator:simulateResume()
local Screensaver = require("ui/screensaver")
Screensaver:close()
self.powerd:afterResume()
end
-- fake network manager for the emulator
function Emulator:initNetworkManager(NetworkMgr)
local UIManager = require("ui/uimanager")
local connectionChangedEvent = function()
if G_reader_settings:nilOrTrue("emulator_fake_wifi_connected") then
UIManager:broadcastEvent(Event:new("NetworkConnected"))

@ -37,4 +37,16 @@ function SDLPowerD:isChargingHW()
return false
end
function SDLPowerD:beforeSuspend()
-- Inhibit user input and emit the Suspend event.
self.device:_beforeSuspend()
end
function SDLPowerD:afterResume()
self:invalidateCapacityCache()
-- Restore user input and emit the Resume event.
self.device:_afterResume()
end
return SDLPowerD

@ -1,5 +1,6 @@
local Generic = require("device/generic/device") -- <= look at this file!
local PluginShare = require("pluginshare")
local UIManager
local ffi = require("ffi")
local logger = require("logger")
@ -102,7 +103,6 @@ function SonyPRSTUX:outofScreenSaver()
if self.screen_saver_mode then
local Screensaver = require("ui/screensaver")
Screensaver:close()
local UIManager = require("ui/uimanager")
UIManager:nextTick(function() UIManager:setDirty("all", "full") end)
end
self.powerd:afterResume()
@ -190,16 +190,18 @@ function SonyPRSTUX:getDeviceModel()
return ffi.string("PRS-T2")
end
function SonyPRSTUX:setEventHandlers(UIManager)
function SonyPRSTUX:UIManagerReady(uimgr)
UIManager = uimgr
end
function SonyPRSTUX:setEventHandlers(uimgr)
UIManager.event_handlers.Suspend = function()
self:_beforeSuspend()
self:intoScreenSaver()
self:suspend()
end
UIManager.event_handlers.Resume = function()
self:resume()
self:outofScreenSaver()
self:_afterResume()
end
UIManager.event_handlers.PowerPress = function()
UIManager:scheduleIn(2, UIManager.poweroff_action)
@ -209,7 +211,11 @@ function SonyPRSTUX:setEventHandlers(UIManager)
UIManager:unschedule(UIManager.poweroff_action)
-- resume if we were suspended
if self.screen_saver_mode then
UIManager.event_handlers.Resume()
if self.screen_saver_lock then
UIManager.event_handlers.Suspend()
else
UIManager.event_handlers.Resume()
end
else
UIManager.event_handlers.Suspend()
end
@ -222,22 +228,15 @@ function SonyPRSTUX:setEventHandlers(UIManager)
self:_afterNotCharging()
end
UIManager.event_handlers.UsbPlugIn = function()
if self.screen_saver_mode then
if self.screen_saver_mode and not self.screen_saver_lock then
self:resume()
self:outofScreenSaver()
self:_afterResume()
end
self:usbPlugIn()
end
UIManager.event_handlers.UsbPlugOut = function()
self:usbPlugOut()
end
UIManager.event_handlers.__default__ = function(input_event)
-- Same as in Kobo: we want to ignore keys during suspension
if not self.screen_saver_mode then
UIManager:sendEvent(input_event)
end
end
end
-- For Sony PRS-T2

@ -28,4 +28,16 @@ function SonyPRSTUX_PowerD:isChargingHW()
return self:read_str_file(self.status_file) == "Charging"
end
function SonyPRSTUX_PowerD:beforeSuspend()
-- Inhibit user input and emit the Suspend event.
self.device:_beforeSuspend()
end
function SonyPRSTUX_PowerD:afterResume()
self:invalidateCapacityCache()
-- Restore user input and emit the Resume event.
self.device:_afterResume()
end
return SonyPRSTUX_PowerD

@ -102,7 +102,8 @@ function UIManager:init()
end)
end
Device:_setEventHandlers(self)
-- Tell Device that we're now available, so that it can setup PM event handlers
Device:_UIManagerReady(self)
-- A simple wrapper for UIManager:quit()
-- This may be overwritten by setRunForeverMode(); for testing purposes
@ -1548,9 +1549,6 @@ This is the main loop of the UI controller.
It is intended to manage input events and delegate them to dialogs.
--]]
function UIManager:run()
-- Tell PowerD that we're ready
Device:getPowerDevice():readyUI()
self:initLooper()
-- currently there is no Turbo support for Windows
-- use our own main loop

@ -7,17 +7,24 @@ describe("device module", function()
local ffi, C
setup(function()
local fb = require("ffi/framebuffer")
mock_fb = {
new = function()
return {
device = package.loaded.device,
bb = require("ffi/blitbuffer").new(600, 800, 1),
getRawSize = function() return {w = 600, h = 800} end,
getWidth = function() return 600 end,
getHeight = function() return 800 end,
getDPI = function() return 72 end,
setViewport = function() end,
getRotationMode = function() return 0 end,
getScreenMode = function() return "portrait" end,
setRotationMode = function() end,
scaleByDPI = function(this, dp) return math.ceil(dp * this:getDPI() / 160) end,
scaleByDPI = fb.scaleByDPI,
scaleBySize = fb.scaleBySize,
setWindowTitle = function() end,
refreshFull = function() end,
}
end
}
@ -38,8 +45,15 @@ describe("device module", function()
end)
after_each(function()
-- Don't let UIManager hang on to a stale Device reference, and vice-versa...
package.unload("device")
package.unload("device/generic/device")
package.unload("device/generic/powerd")
package.unload("ui/uimanager")
package.unload("apps/reader/readerui")
mock_input.open:revert()
os.getenv:revert()
os.execute:revert()
os.getenv = osgetenv
io.open = iopen
@ -327,24 +341,31 @@ describe("device module", function()
os.getenv.invokes(function(key)
if key == "PRODUCT" then
return "trilogy"
elseif key == "MODEL_NUMBER" then
return "320"
else
return osgetenv(key)
end
end)
local sample_pdf = "spec/front/unit/data/tall.pdf"
local ReaderUI = require("apps/reader/readerui")
local device_to_test = require("device/kobo/device")
local Device = require("device")
Device.setEventHandlers = device_to_test.setEventHandlers
local UIManager = require("ui/uimanager")
-- Bypass frontend/device probeDevice, while making sure that it points to the right implementation
local Device = require("device/kobo/device")
-- Apparently common isn't setup properly in the testsuite, so we can't have nice things
stub(Device, "initNetworkManager")
stub(Device, "suspend")
stub(Device.powerd, "beforeSuspend")
stub(Device, "isKobo")
Device:init()
-- Don't poke the RTC
Device.wakeup_mgr = require("device/wakeupmgr"):new{rtc = require("device/kindle/mockrtc")}
-- Don't poke the fl
Device.powerd.fl = nil
package.loaded.device = Device
Device.isKobo.returns(true)
local UIManager = require("ui/uimanager")
-- Generic's onPowerEvent may request a repaint, but we can't do that
stub(UIManager, "forceRePaint")
UIManager:init()
local sample_pdf = "spec/front/unit/data/tall.pdf"
local ReaderUI = require("apps/reader/readerui")
ReaderUI:doShowReader(sample_pdf)
local readerui = ReaderUI._getRunningInstance()
stub(readerui, "onFlushSettings")
@ -352,9 +373,9 @@ describe("device module", function()
UIManager.event_handlers.PowerRelease()
assert.stub(readerui.onFlushSettings).was_called()
UIManager.forceRePaint:revert()
Device.initNetworkManager:revert()
Device.suspend:revert()
Device.powerd.beforeSuspend:revert()
Device.isKobo:revert()
readerui.onFlushSettings:revert()
Device.screen_saver_mode = false
readerui:onClose()
@ -374,52 +395,19 @@ describe("device module", function()
end
end
local sample_pdf = "spec/front/unit/data/tall.pdf"
local ReaderUI = require("apps/reader/readerui")
local Device = require("device")
local device_to_test = require("device/cervantes/device")
Device.setEventHandlers = device_to_test.setEventHandlers
local UIManager = require("ui/uimanager")
local Device = require("device/cervantes/device")
stub(Device, "initNetworkManager")
stub(Device, "suspend")
stub(Device.powerd, "beforeSuspend")
stub(Device, "isCervantes")
Device:init()
Device.powerd.fl = nil
package.loaded.device = Device
Device.isCervantes.returns(true)
local UIManager = require("ui/uimanager")
stub(UIManager, "forceRePaint")
UIManager:init()
ReaderUI:doShowReader(sample_pdf)
local readerui = ReaderUI._getRunningInstance()
stub(readerui, "onFlushSettings")
UIManager.event_handlers.PowerPress()
UIManager.event_handlers.PowerRelease()
assert.stub(readerui.onFlushSettings).was_called()
Device.suspend:revert()
Device.powerd.beforeSuspend:revert()
Device.isCervantes:revert()
readerui.onFlushSettings:revert()
Device.screen_saver_mode = false
readerui:onClose()
end)
it("SDL", function()
local sample_pdf = "spec/front/unit/data/tall.pdf"
local ReaderUI = require("apps/reader/readerui")
local Device = require("device")
local device_to_test = require("device/sdl/device")
Device.setEventHandlers = device_to_test.setEventHandlers
local UIManager = require("ui/uimanager")
stub(Device, "suspend")
stub(Device.powerd, "beforeSuspend")
stub(Device, "isSDL")
Device.isSDL.returns(true)
UIManager:init()
ReaderUI:doShowReader(sample_pdf)
local readerui = ReaderUI._getRunningInstance()
stub(readerui, "onFlushSettings")
@ -427,9 +415,9 @@ describe("device module", function()
UIManager.event_handlers.PowerRelease()
assert.stub(readerui.onFlushSettings).was_called()
UIManager.forceRePaint:revert()
Device.initNetworkManager:revert()
Device.suspend:revert()
Device.powerd.beforeSuspend:revert()
Device.isSDL:revert()
readerui.onFlushSettings:revert()
Device.screen_saver_mode = false
readerui:onClose()
@ -455,31 +443,55 @@ describe("device module", function()
return iopen(filename, mode)
end
end
local Device = require("device/remarkable/device")
stub(Device, "initNetworkManager")
stub(Device, "suspend")
Device:init()
Device.powerd.fl = nil
package.loaded.device = Device
local UIManager = require("ui/uimanager")
stub(UIManager, "forceRePaint")
UIManager:init()
local sample_pdf = "spec/front/unit/data/tall.pdf"
local ReaderUI = require("apps/reader/readerui")
local Device = require("device")
local device_to_test = require("device/remarkable/device")
Device.setEventHandlers = device_to_test.setEventHandlers
ReaderUI:doShowReader(sample_pdf)
local readerui = ReaderUI._getRunningInstance()
stub(readerui, "onFlushSettings")
UIManager.event_handlers.PowerPress()
UIManager.event_handlers.PowerRelease()
assert.stub(readerui.onFlushSettings).was_called()
local UIManager = require("ui/uimanager")
UIManager.forceRePaint:revert()
Device.initNetworkManager:revert()
Device.suspend:revert()
readerui.onFlushSettings:revert()
Device.screen_saver_mode = false
readerui:onClose()
end)
it("SDL", function()
local Device = require("device/sdl/device")
stub(Device, "initNetworkManager")
stub(Device, "suspend")
stub(Device.powerd, "beforeSuspend")
stub(Device, "isRemarkable")
Device:init()
package.loaded.device = Device
Device.isRemarkable.returns(true)
local UIManager = require("ui/uimanager")
UIManager:init()
local sample_pdf = "spec/front/unit/data/tall.pdf"
local ReaderUI = require("apps/reader/readerui")
ReaderUI:doShowReader(sample_pdf)
local readerui = ReaderUI._getRunningInstance()
stub(readerui, "onFlushSettings")
UIManager.event_handlers.PowerPress()
-- UIManager.event_handlers.PowerPress() -- We only fake a Release event on the Emu
UIManager.event_handlers.PowerRelease()
assert.stub(readerui.onFlushSettings).was_called()
Device.initNetworkManager:revert()
Device.suspend:revert()
Device.powerd.beforeSuspend:revert()
Device.isRemarkable:revert()
readerui.onFlushSettings:revert()
Device.screen_saver_mode = false
readerui:onClose()

Loading…
Cancel
Save