From 7e98b9de4b5917a2568779d7281cfc67e770c73d Mon Sep 17 00:00:00 2001 From: NiLuJe Date: Thu, 18 May 2023 23:13:43 +0200 Subject: [PATCH] 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 ;). --- Makefile | 2 +- frontend/device/android/device.lua | 18 ++-- frontend/device/android/powerd.lua | 10 +- frontend/device/cervantes/device.lua | 23 ++-- frontend/device/cervantes/powerd.lua | 28 +++-- frontend/device/generic/device.lua | 76 ++++++++----- frontend/device/generic/powerd.lua | 32 ++++-- frontend/device/input.lua | 10 +- frontend/device/kindle/device.lua | 18 ++-- frontend/device/kindle/powerd.lua | 19 +++- frontend/device/kobo/device.lua | 34 ++---- frontend/device/kobo/powerd.lua | 44 +++++--- frontend/device/pocketbook/device.lua | 13 ++- frontend/device/pocketbook/powerd.lua | 12 +++ frontend/device/remarkable/device.lua | 7 +- frontend/device/remarkable/powerd.lua | 12 +++ frontend/device/sdl/device.lua | 24 +++-- frontend/device/sdl/powerd.lua | 12 +++ frontend/device/sony-prstux/device.lua | 25 +++-- frontend/device/sony-prstux/powerd.lua | 12 +++ frontend/ui/uimanager.lua | 6 +- spec/unit/device_spec.lua | 142 ++++++++++++++----------- 22 files changed, 350 insertions(+), 229 deletions(-) diff --git a/Makefile b/Makefile index 1af1575f5..4e9e16212 100644 --- a/Makefile +++ b/Makefile @@ -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 diff --git a/frontend/device/android/device.lua b/frontend/device/android/device.lua index 11e99af19..b6f25db63 100644 --- a/frontend/device/android/device.lua +++ b/frontend/device/android/device.lua @@ -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"), diff --git a/frontend/device/android/powerd.lua b/frontend/device/android/powerd.lua index 25e93cd1a..60d7c5c8f 100644 --- a/frontend/device/android/powerd.lua +++ b/frontend/device/android/powerd.lua @@ -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 diff --git a/frontend/device/cervantes/device.lua b/frontend/device/cervantes/device.lua index 106a3ee2c..3e7abade4 100644 --- a/frontend/device/cervantes/device.lua +++ b/frontend/device/cervantes/device.lua @@ -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() diff --git a/frontend/device/cervantes/powerd.lua b/frontend/device/cervantes/powerd.lua index c8aad9d76..90c7ecb1c 100644 --- a/frontend/device/cervantes/powerd.lua +++ b/frontend/device/cervantes/powerd.lua @@ -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 diff --git a/frontend/device/generic/device.lua b/frontend/device/generic/device.lua index d305d74cf..dcef3b658 100644 --- a/frontend/device/generic/device.lua +++ b/frontend/device/generic/device.lua @@ -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 diff --git a/frontend/device/generic/powerd.lua b/frontend/device/generic/powerd.lua index 95cf31eef..e97dc8e79 100644 --- a/frontend/device/generic/powerd.lua +++ b/frontend/device/generic/powerd.lua @@ -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 diff --git a/frontend/device/input.lua b/frontend/device/input.lua index 9698917a9..b3c0d1d7b 100644 --- a/frontend/device/input.lua +++ b/frontend/device/input.lua @@ -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) diff --git a/frontend/device/kindle/device.lua b/frontend/device/kindle/device.lua index 14f0dfe99..58e2725fe 100644 --- a/frontend/device/kindle/device.lua +++ b/frontend/device/kindle/device.lua @@ -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() diff --git a/frontend/device/kindle/powerd.lua b/frontend/device/kindle/powerd.lua index 173f01dc3..f4a92940c 100644 --- a/frontend/device/kindle/powerd.lua +++ b/frontend/device/kindle/powerd.lua @@ -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 diff --git a/frontend/device/kobo/device.lua b/frontend/device/kobo/device.lua index c16491e26..f93b2b3db 100644 --- a/frontend/device/kobo/device.lua +++ b/frontend/device/kobo/device.lua @@ -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() diff --git a/frontend/device/kobo/powerd.lua b/frontend/device/kobo/powerd.lua index 9a87ddf2f..6a385efdb 100644 --- a/frontend/device/kobo/powerd.lua +++ b/frontend/device/kobo/powerd.lua @@ -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 diff --git a/frontend/device/pocketbook/device.lua b/frontend/device/pocketbook/device.lua index 0c8211b16..a0390ab70 100644 --- a/frontend/device/pocketbook/device.lua +++ b/frontend/device/pocketbook/device.lua @@ -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") diff --git a/frontend/device/pocketbook/powerd.lua b/frontend/device/pocketbook/powerd.lua index ce12e18ca..11f9dc94d 100644 --- a/frontend/device/pocketbook/powerd.lua +++ b/frontend/device/pocketbook/powerd.lua @@ -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 diff --git a/frontend/device/remarkable/device.lua b/frontend/device/remarkable/device.lua index 7edb9d367..9d3981e31 100644 --- a/frontend/device/remarkable/device.lua +++ b/frontend/device/remarkable/device.lua @@ -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 diff --git a/frontend/device/remarkable/powerd.lua b/frontend/device/remarkable/powerd.lua index e7f729fe8..60df3fe03 100644 --- a/frontend/device/remarkable/powerd.lua +++ b/frontend/device/remarkable/powerd.lua @@ -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 diff --git a/frontend/device/sdl/device.lua b/frontend/device/sdl/device.lua index 4ea741e3a..1a47a29c2 100644 --- a/frontend/device/sdl/device.lua +++ b/frontend/device/sdl/device.lua @@ -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")) diff --git a/frontend/device/sdl/powerd.lua b/frontend/device/sdl/powerd.lua index fcb734f50..6fa015b9c 100644 --- a/frontend/device/sdl/powerd.lua +++ b/frontend/device/sdl/powerd.lua @@ -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 diff --git a/frontend/device/sony-prstux/device.lua b/frontend/device/sony-prstux/device.lua index 64c40b094..dcaf9b4c7 100644 --- a/frontend/device/sony-prstux/device.lua +++ b/frontend/device/sony-prstux/device.lua @@ -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 diff --git a/frontend/device/sony-prstux/powerd.lua b/frontend/device/sony-prstux/powerd.lua index 5723fc813..6bea2a7a0 100644 --- a/frontend/device/sony-prstux/powerd.lua +++ b/frontend/device/sony-prstux/powerd.lua @@ -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 diff --git a/frontend/ui/uimanager.lua b/frontend/ui/uimanager.lua index c3d42b147..57c849438 100644 --- a/frontend/ui/uimanager.lua +++ b/frontend/ui/uimanager.lua @@ -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 diff --git a/spec/unit/device_spec.lua b/spec/unit/device_spec.lua index ab7d05397..020728a2f 100644 --- a/spec/unit/device_spec.lua +++ b/spec/unit/device_spec.lua @@ -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()