From 53eb4dee507a6b3b0b268aab1a9e4d6555b50b86 Mon Sep 17 00:00:00 2001 From: Hzj_jie Date: Wed, 14 Jun 2017 10:32:16 -0700 Subject: [PATCH] AutoFrontlight plugin (#2941) * Add AutoFrontlight plugin * Add configuration to control autofrontlight feature --- frontend/apps/reader/modules/readerfooter.lua | 6 +- .../apps/reader/modules/readerfrontlight.lua | 64 +++--- frontend/device/generic/device.lua | 11 ++ frontend/device/generic/powerd.lua | 76 ++++++- frontend/device/kindle/device.lua | 14 ++ frontend/device/kindle/powerd.lua | 54 ++--- frontend/device/kobo/nickel_conf.lua | 4 +- frontend/device/kobo/powerd.lua | 67 ++----- frontend/luasettings.lua | 9 + frontend/ui/uimanager.lua | 2 +- frontend/ui/widget/frontlightwidget.lua | 5 +- plugins/autofrontlight.koplugin/main.lua | 64 ++++++ spec/autofrontlight_spec.lua | 48 +++++ spec/unit/autofrontlight_spec.lua | 73 +++++++ spec/unit/commonrequire.lua | 47 +++++ spec/unit/frontlight_spec.lua | 185 ++++++++++++++++++ spec/unit/mock_time.lua | 45 +++++ spec/unit/nickel_conf_spec.lua | 2 +- 18 files changed, 639 insertions(+), 137 deletions(-) create mode 100644 plugins/autofrontlight.koplugin/main.lua create mode 100644 spec/autofrontlight_spec.lua create mode 100644 spec/unit/autofrontlight_spec.lua create mode 100644 spec/unit/frontlight_spec.lua create mode 100644 spec/unit/mock_time.lua diff --git a/frontend/apps/reader/modules/readerfooter.lua b/frontend/apps/reader/modules/readerfooter.lua index cefc47b08..03d8005e0 100644 --- a/frontend/apps/reader/modules/readerfooter.lua +++ b/frontend/apps/reader/modules/readerfooter.lua @@ -43,10 +43,8 @@ local footerTextGeneratorMap = { frontlight = function() if not Device:hasFrontlight() then return "L: NA" end local powerd = Device:getPowerDevice() - if powerd.is_fl_on ~= nil and powerd.is_fl_on == true then - if powerd.fl_intensity ~= nil then - return ("L: %d%%"):format(powerd.fl_intensity) - end + if powerd:isFrontlightOn() then + return ("L: %d%%"):format(powerd:frontlightIntensity()) else return "L: Off" end diff --git a/frontend/apps/reader/modules/readerfrontlight.lua b/frontend/apps/reader/modules/readerfrontlight.lua index 18351182d..dd142262d 100644 --- a/frontend/apps/reader/modules/readerfrontlight.lua +++ b/frontend/apps/reader/modules/readerfrontlight.lua @@ -38,47 +38,45 @@ function ReaderFrontLight:init() end function ReaderFrontLight:onAdjust(arg, ges) + if not Device.hasFrontlight() then return true end local powerd = Device:getPowerDevice() - if powerd.fl_intensity ~= nil then - logger.dbg("frontlight intensity", powerd.fl_intensity) - local step = math.ceil(#self.steps * ges.distance / self.gestureScale) - logger.dbg("step = ", step) - local delta_int = self.steps[step] or self.steps[#self.steps] - logger.dbg("delta_int = ", delta_int) - local new_intensity - if ges.direction == "north" then - new_intensity = powerd.fl_intensity + delta_int - elseif ges.direction == "south" then - new_intensity = powerd.fl_intensity - delta_int - end - if new_intensity ~= nil then - -- when new_intensity <=0, toggle light off - if new_intensity <= 0 then - powerd:toggleFrontlight() - end - powerd:setIntensity(new_intensity) - if self.view.footer_visible and self.view.footer.settings.frontlight then - self.view.footer:updateFooter() - end - end + logger.dbg("frontlight intensity", powerd:frontlightIntensity()) + local step = math.ceil(#self.steps * ges.distance / self.gestureScale) + logger.dbg("step = ", step) + local delta_int = self.steps[step] or self.steps[#self.steps] + logger.dbg("delta_int = ", delta_int) + local new_intensity + if ges.direction == "north" then + new_intensity = powerd:frontlightIntensity() + delta_int + elseif ges.direction == "south" then + new_intensity = powerd:frontlightIntensity() - delta_int + end + if new_intensity == nil then return true end + -- when new_intensity <=0, toggle light off + if new_intensity <= 0 then + powerd:turnOffFrontlight() + else + powerd:setIntensity(new_intensity) + end + if self.view.footer_visible and self.view.footer.settings.frontlight then + self.view.footer:updateFooter() end return true end function ReaderFrontLight:onShowIntensity() + if not Device.hasFrontlight() then return true end local powerd = Device:getPowerDevice() - if powerd.fl_intensity ~= nil then - local new_text - if powerd.fl_intensity == 0 then - new_text = _("Frontlight is off.") - else - new_text = T(_("Frontlight intensity is set to %1."), powerd.fl_intensity) - end - UIManager:show(Notification:new{ - text = new_text, - timeout = 1.0, - }) + local new_text + if powerd:isFrontlightOff() then + new_text = _("Frontlight is off.") + else + new_text = T(_("Frontlight intensity is set to %1."), powerd:frontlightIntensity()) end + UIManager:show(Notification:new{ + text = new_text, + timeout = 1.0, + }) return true end diff --git a/frontend/device/generic/device.lua b/frontend/device/generic/device.lua index 52c5a4ca2..3cf47dfdd 100644 --- a/frontend/device/generic/device.lua +++ b/frontend/device/generic/device.lua @@ -52,6 +52,7 @@ function Device:new(o) end function Device:init() + assert(self ~= nil) if not self.screen then error("screen/framebuffer must be implemented") end @@ -204,4 +205,14 @@ function Device:retrieveNetworkInfo() end end +-- Return an integer value to indicate the brightness of the environment. The value should be in +-- range [0, 3]. +-- 0: dark. +-- 1: dim, frontlight is needed. +-- 2: bright, frontlight is not needed. +-- 3: dazzling. +function Device:ambientBrightnessLevel() + return 0 +end + return Device diff --git a/frontend/device/generic/powerd.lua b/frontend/device/generic/powerd.lua index 1221a3288..82d734fbe 100644 --- a/frontend/device/generic/powerd.lua +++ b/frontend/device/generic/powerd.lua @@ -8,21 +8,35 @@ local BasePowerD = { device = nil, -- device object last_capacity_pull_time = 0, -- timestamp of last pull + + is_fl_on = false, -- whether the frontlight is on } function BasePowerD:new(o) o = o or {} setmetatable(o, self) self.__index = self + assert(o.fl_min < o.fl_max) if o.init then o:init() end + if o.device and o.device.hasFrontlight() then + o.fl_intensity = o:frontlightIntensityHW() + o:_decideFrontlightState() + if o:isFrontlightOn() then + o:turnOnFrontlightHW() + else + o:turnOffFrontlightHW() + end + end return o end function BasePowerD:init() end -function BasePowerD:toggleFrontlight() end -function BasePowerD:setIntensityHW() end +function BasePowerD:setIntensityHW(intensity) end function BasePowerD:getCapacityHW() return 0 end function BasePowerD:isChargingHW() return false end +function BasePowerD:frontlightIntensityHW() return 0 end +function BasePowerD:turnOffFrontlightHW() self:setIntensityHW(self.fl_min) end +function BasePowerD:turnOnFrontlightHW() self:setIntensityHW(self.fl_intensity) end -- Anything needs to be done before do a real hardware suspend. Such as turn off -- front light. function BasePowerD:beforeSuspend() end @@ -30,6 +44,57 @@ function BasePowerD:beforeSuspend() end -- front light state. function BasePowerD:afterResume() end +function BasePowerD:isFrontlightOn() + assert(self ~= nil) + return self.is_fl_on +end + +function BasePowerD:_decideFrontlightState() + assert(self ~= nil) + assert(self.device.hasFrontlight()) + self.is_fl_on = (self.fl_intensity > self.fl_min) +end + +function BasePowerD:isFrontlightOff() + return not self:isFrontlightOn() +end + +function BasePowerD:frontlightIntensity() + assert(self ~= nil) + if not self.device.hasFrontlight() then return 0 end + if self:isFrontlightOff() then return 0 end + return self.fl_intensity +end + +function BasePowerD:toggleFrontlight() + assert(self ~= nil) + if not self.device.hasFrontlight() then return false end + if self:isFrontlightOn() then + return self:turnOffFrontlight() + else + return self:turnOnFrontlight() + end +end + +function BasePowerD:turnOffFrontlight() + assert(self ~= nil) + if not self.device.hasFrontlight() then return end + if self:isFrontlightOff() then return false end + self:turnOffFrontlightHW() + self.is_fl_on = false + return true +end + +function BasePowerD:turnOnFrontlight() + assert(self ~= nil) + if not self.device.hasFrontlight() then return end + if self:isFrontlightOn() then return false end + if self.fl_intensity == self.fl_min then return false end + self:turnOnFrontlightHW() + self.is_fl_on = true + return true +end + function BasePowerD:read_int_file(file) local fd = io.open(file, "r") if fd then @@ -58,10 +123,13 @@ function BasePowerD:normalizeIntensity(intensity) end function BasePowerD:setIntensity(intensity) - if intensity == self.fl_intensity then return end + if not self.device.hasFrontlight() then return false end + if intensity == self.fl_intensity then return false end self.fl_intensity = self:normalizeIntensity(intensity) + self:_decideFrontlightState() logger.dbg("set light intensity", self.fl_intensity) - self:setIntensityHW() + self:setIntensityHW(self.fl_intensity) + return true end function BasePowerD:getCapacity() diff --git a/frontend/device/kindle/device.lua b/frontend/device/kindle/device.lua index 07b15c05a..bb481f5f9 100644 --- a/frontend/device/kindle/device.lua +++ b/frontend/device/kindle/device.lua @@ -133,6 +133,20 @@ function Kindle:usbPlugOut() self.charging_mode = false end +function Kindle:ambientBrightnessLevel() + local haslipc, lipc = pcall(require, "liblipclua") + if not haslipc or lipc == nil then return 0 end + local lipc_handle = lipc.init("com.github.koreader.ambientbrightness") + if not lipc_handle then return 0 end + local value = lipc_handle:get_int_property("com.lab126.powerd", "alsLux") + lipc_handle:close() + if type(value) ~= "number" then return 0 end + if value < 10 then return 0 end + if value < 256 then return 1 end + if value < 32768 then return 2 end + return 3 +end + local Kindle2 = Kindle:new{ model = "Kindle2", diff --git a/frontend/device/kindle/powerd.lua b/frontend/device/kindle/powerd.lua index b0e1a026e..d54b3499a 100644 --- a/frontend/device/kindle/powerd.lua +++ b/frontend/device/kindle/powerd.lua @@ -4,10 +4,7 @@ local BasePowerD = require("device/generic/powerd") local KindlePowerD = BasePowerD:new{ fl_min = 0, fl_max = 24, - fl_intensity = nil, lipc_handle = nil, - - is_fl_on = false, } function KindlePowerD:init() @@ -15,40 +12,29 @@ function KindlePowerD:init() if haslipc and lipc then self.lipc_handle = lipc.init("com.github.koreader.kindlepowerd") end - if self.device.hasFrontlight() then - -- Kindle stock software does not use intensity file directly, so we need to read from its - -- lipc property first. - if self.lipc_handle ~= nil then - self.fl_intensity = self.lipc_handle:get_int_property("com.lab126.powerd", "flIntensity") - else - self.fl_intensity = self:_readFLIntensity() - end - self:_set_fl_on() - end end -function KindlePowerD:toggleFrontlight() - if not self.device.hasFrontlight() then - return - end - - if self:_readFLIntensity() == 0 then - self:setIntensityHW() +function KindlePowerD:frontlightIntensityHW() + if not self.device.hasFrontlight() then return 0 end + -- Kindle stock software does not use intensity file directly, so we need to read from its + -- lipc property first. + if self.lipc_handle ~= nil then + return self.lipc_handle:get_int_property("com.lab126.powerd", "flIntensity") else - self:_turnOffFL() + return self:_readFLIntensity() end end -function KindlePowerD:setIntensityHW() - if self.lipc_handle ~= nil and self.fl_intensity > 0 then +function KindlePowerD:setIntensityHW(intensity) + if self.lipc_handle ~= nil and intensity > 0 then -- NOTE: We want to bypass setIntensity's shenanigans and simply restore the light as-is - self.lipc_handle:set_int_property("com.lab126.powerd", "flIntensity", self.fl_intensity) + self.lipc_handle:set_int_property( + "com.lab126.powerd", "flIntensity", self.fronglightIntensity()) else - -- NOTE: when fl_intensity is 0, We want to really kill the light, so do it manually + -- NOTE: when intensity is 0, We want to really kill the light, so do it manually -- (asking lipc to set it to 0 would in fact set it to 1)... - os.execute("echo -n ".. self.fl_intensity .." > " .. self.fl_intensity_file) + os.execute("echo -n ".. intensity .." > " .. self.fl_intensity_file) end - self:_set_fl_on() end function KindlePowerD:getCapacityHW() @@ -76,30 +62,20 @@ function KindlePowerD:__gc() end end -function KindlePowerD:_turnOffFL() - -- NOTE: We want to really kill the light, so do it manually (asking lipc to set it to 0 would in fact set it to 1)... - os.execute("echo -n 0 > " .. self.fl_intensity_file) - self.is_fl_on = false -end - function KindlePowerD:_readFLIntensity() return self:read_int_file(self.fl_intensity_file) end -function KindlePowerD:_set_fl_on() - self.is_fl_on = (self.fl_intensity > 0) -end - function KindlePowerD:afterResume() if not self.device.hasFrontlight() then return end - if self.is_fl_on then + if self:isFrontlightOn() then -- Kindle stock software should turn on the front light automatically. The follow statement -- ensure the consistency of intensity. self:setIntensityHW() else - self:_turnOffFL() + self:turnOffFrontlightHW() end end diff --git a/frontend/device/kobo/nickel_conf.lua b/frontend/device/kobo/nickel_conf.lua index c200617a4..ee29db178 100644 --- a/frontend/device/kobo/nickel_conf.lua +++ b/frontend/device/kobo/nickel_conf.lua @@ -53,9 +53,7 @@ function NickelConf.frontLightLevel.get() -- FrontLightState config, so don't normalize the value here yet. return tonumber(new_intensity) else - -- In NickelConfSpec, require("device") won't return KoboDevice - local powerd = require("device/kobo/powerd") - local fallback_fl_level = powerd.fl_intensity or 1 + local fallback_fl_level = 1 assert(NickelConf.frontLightLevel.set(fallback_fl_level)) return fallback_fl_level end diff --git a/frontend/device/kobo/powerd.lua b/frontend/device/kobo/powerd.lua index 98bcb4fae..86f82cf09 100644 --- a/frontend/device/kobo/powerd.lua +++ b/frontend/device/kobo/powerd.lua @@ -8,15 +8,8 @@ local KoboPowerD = BasePowerD:new{ -- Do not actively set front light to 0, it may confuse users -- pressing -- hardware button won't take any effect. fl_min = 1, fl_max = 100, - fl_intensity = 20, - restore_settings = true, fl = nil, - -- We will set this value for all kobo models. but it will only be synced - -- with nickel's FrontLightState config if the current nickel firmware - -- supports this config. - is_fl_on = false, - batt_capacity_file = batt_state_folder .. "capacity", is_charging_file = batt_state_folder .. "status", } @@ -36,42 +29,24 @@ function KoboPowerD:init() end end -function KoboPowerD:toggleFrontlight() - if self.fl ~= nil then - if self.is_fl_on then - self.fl:setBrightness(0) - else - self.fl:setBrightness(self.fl_intensity) - end - self.is_fl_on = not self.is_fl_on - if self.has_fl_state_cfg and KOBO_SYNC_BRIGHTNESS_WITH_NICKEL then - NickelConf.frontLightState.set(self.is_fl_on) - end +function KoboPowerD:_syncNickelConf() + if self.has_fl_state_cfg and KOBO_SYNC_BRIGHTNESS_WITH_NICKEL then + NickelConf.frontLightState.set(self:isFrontlightOn()) + NickelConf.frontLightLevel.set(self:frontlightIntensity()) end end -function KoboPowerD:setIntensityHW() - if self.fl ~= nil then - self.fl:setBrightness(self.fl_intensity) - -- Make sure we persist intensity config in reader setting - G_reader_settings:saveSetting("frontlight_intensity", self.fl_intensity) - if KOBO_SYNC_BRIGHTNESS_WITH_NICKEL then - NickelConf.frontLightLevel.set(self.fl_intensity) - end - -- also keep self.is_fl_on in sync with intensity if needed - local is_fl_on - if self.fl_intensity > 0 then - is_fl_on = true - else - is_fl_on = false - end - if self.is_fl_on ~= is_fl_on then - self.is_fl_on = is_fl_on - if self.has_fl_state_cfg and KOBO_SYNC_BRIGHTNESS_WITH_NICKEL then - NickelConf.frontLightState.set(self.is_fl_on) - end - end +function KoboPowerD:frontlightIntensityHW() + if self.has_fl_state_cfg then + return NickelConf.frontLightLevel.get() end + return 20 +end + +function KoboPowerD:setIntensityHW(intensity) + if self.fl == nil then return end + self.fl:setBrightness(self.frontlightIntensity()) + self:_syncNickelConf() end function KoboPowerD:getCapacityHW() @@ -84,19 +59,15 @@ end -- Turn off front light before suspend. function KoboPowerD:beforeSuspend() - if self.fl ~= nil then - self.fl:setBrightness(0) - end + self:turnOffFrontlightHW() end -- Restore front light state after resume. function KoboPowerD:afterResume() - if self.fl ~= nil then - if KOBO_LIGHT_ON_START and tonumber(KOBO_LIGHT_ON_START) > -1 then - self:setIntensity(math.min(KOBO_LIGHT_ON_START, 100)) - elseif self.is_fl_on then - self.fl:setBrightness(self.fl_intensity) - end + if KOBO_LIGHT_ON_START and tonumber(KOBO_LIGHT_ON_START) > -1 then + self:setIntensity(math.min(KOBO_LIGHT_ON_START, 100)) + elseif self:isFrontlightOn() then + self:turnOnFrontlightHW() end end diff --git a/frontend/luasettings.lua b/frontend/luasettings.lua index 1f5d39b0d..4ed6ee99d 100644 --- a/frontend/luasettings.lua +++ b/frontend/luasettings.lua @@ -54,11 +54,13 @@ end --- Saves a setting. function LuaSettings:saveSetting(key, value) self.data[key] = value + return self end --- Deletes a setting. function LuaSettings:delSetting(key) self.data[key] = nil + return self end --- Checks if setting exists. @@ -98,6 +100,7 @@ function LuaSettings:flipNilOrTrue(key) else self:delSetting(key) end + return self end --- Flips `nil` or `false` to `true`. @@ -107,6 +110,7 @@ function LuaSettings:flipNilOrFalse(key) else self:delSetting(key) end + return self end --- Flips setting to `true`. @@ -116,6 +120,7 @@ function LuaSettings:flipTrue(key) else self:saveSetting(key, true) end + return self end --- Flips setting to `false`. @@ -125,11 +130,13 @@ function LuaSettings:flipFalse(key) else self:saveSetting(key, true) end + return self end --- Replaces existing settings with table. function LuaSettings:reset(table) self.data = table + return self end --- Writes settings to disk. @@ -143,6 +150,7 @@ function LuaSettings:flush() f_out:write("\n") f_out:close() end + return self end --- Closes settings file. @@ -155,6 +163,7 @@ function LuaSettings:purge() if self.file then os.remove(self.file) end + return self end return LuaSettings diff --git a/frontend/ui/uimanager.lua b/frontend/ui/uimanager.lua index 0da45eda9..e487eb734 100644 --- a/frontend/ui/uimanager.lua +++ b/frontend/ui/uimanager.lua @@ -299,7 +299,7 @@ function UIManager:scheduleIn(seconds, action) local usecs = (seconds - s) * MILLION when[1] = when[1] + s when[2] = when[2] + usecs - if when[2] > MILLION then + if when[2] >= MILLION then when[1] = when[1] + 1 when[2] = when[2] - MILLION end diff --git a/frontend/ui/widget/frontlightwidget.lua b/frontend/ui/widget/frontlightwidget.lua index e87421460..d6107c5f0 100644 --- a/frontend/ui/widget/frontlightwidget.lua +++ b/frontend/ui/widget/frontlightwidget.lua @@ -38,10 +38,7 @@ function FrontLightWidget:init() local powerd = Device:getPowerDevice() self.fl_min = powerd.fl_min self.fl_max = powerd.fl_max - self.fl_cur = powerd.fl_intensity - if self.fl_cur == nil then - self.fl_cur = self.fl_min - end + self.fl_cur = powerd:frontlightIntensity() local steps_fl = self.fl_max - self.fl_min + 1 self.one_step = math.ceil(steps_fl / 25) self.steps = math.ceil(steps_fl / self.one_step) diff --git a/plugins/autofrontlight.koplugin/main.lua b/plugins/autofrontlight.koplugin/main.lua new file mode 100644 index 000000000..73fa31f94 --- /dev/null +++ b/plugins/autofrontlight.koplugin/main.lua @@ -0,0 +1,64 @@ +local Device = require("device") + +if not Device:isKindle() or + (Device.model ~= "KindleVoyage" and Device.model ~= "KindleOasis") then + return { disabled = true, } +end + +local DataStorage = require("datastorage") +local LuaSettings = require("luasettings") +local UIManager = require("ui/uimanager") +local WidgetContainer = require("ui/widget/container/widgetcontainer") +local logger = require("logger") + +local AutoFrontlight = { + settings = LuaSettings:open(DataStorage:getSettingsDir() .. "/autofrontlight.lua"), + settings_id = 0, + enabled = false, +} + +function AutoFrontlight:_schedule() + if not self.enabled then + logger.dbg("AutoFrontlight:_schedule() is disabled") + return + end + + local settings_id = self.settings_id + logger.dbg("AutoFrontlight:_schedule() @ ", os.time(), ", it should be executed at ", os.time() + 1) + UIManager:scheduleIn(1, function() + self:_action(settings_id) + self:_schedule(self.settings_id) + end) +end + +function AutoFrontlight:_action(settings_id) + if settings_id ~= self.settings_id then + logger.dbg("AutoFrontlight:_action(): registered settings_id ", + settings_id, + " does not equal to current one ", + self.settings_id) + return + end + logger.dbg("AutoFrontlight:_action() @ ", os.time()) + if Device:ambientBrightnessLevel() <= 1 then + logger.dbg("AutoFrontlight: going to turn on frontlight") + Device:getPowerDevice():turnOnFrontlight() + else + logger.dbg("AutoFrontlight: going to turn off frontlight") + Device:getPowerDevice():turnOffFrontlight() + end +end + +function AutoFrontlight:init() + self.enabled = not self.settings:nilOrFalse("enable") + logger.dbg("AutoFrontlight:init() self.enabled: ", self.enabled) + self:_schedule() +end + +AutoFrontlight:init() + +local AutoFrontlightWidget = WidgetContainer:new{ + name = "AutoFrontlight", +} + +return AutoFrontlightWidget diff --git a/spec/autofrontlight_spec.lua b/spec/autofrontlight_spec.lua new file mode 100644 index 000000000..89b71def1 --- /dev/null +++ b/spec/autofrontlight_spec.lua @@ -0,0 +1,48 @@ +describe("AutoFrontlight widget tests", function() + local Device, MockTime + + setup(function() + require("commonrequire") + package.unloadAll() + + Device = require("device/generic/device"):new() + Device.brightness = 0 + Device.hasFrontlight = function() return true end + Device.powerd = require("device/generic/powerd"):new{ + frontlight = 0, + } + Device.powerd.frontlightIntensityHW = function() + return 2 + end + Device.powerd.setIntensityHW = function(self, intensity) + self.frontlight = intensity + end + Device.ambientBrightnessLevel = function(self) + return self.brightness + end + + MockTime = require("mock_time") + MockTime:install() + end) + + teardown(function() + MockTime:uninstall() + package.unloadAll() + end) + + it("should automatically turn on or off frontlight", function() + local UIManager = require("ui/uimanager") + Device.brightness = 0 + MockTime:increase(2) + assert.are.equal(Device:getPowerDevice().frontlight, 2) + Device.brightness = 1 + MockTime:increase(2) + assert.are.equal(Device:getPowerDevice().frontlight, 2) + Device.brightness = 2 + MockTime:increase(2) + assert.are.equal(Device:getPowerDevice().frontlight, 0) + Device.brightness = 3 + MockTime:increase(2) + assert.are.equal(Device:getPowerDevice().frontlight, 0) + end) +end) diff --git a/spec/unit/autofrontlight_spec.lua b/spec/unit/autofrontlight_spec.lua new file mode 100644 index 000000000..bbbe5a0ee --- /dev/null +++ b/spec/unit/autofrontlight_spec.lua @@ -0,0 +1,73 @@ +describe("AutoFrontlight widget tests", function() + local Device, PowerD, MockTime + + setup(function() + require("commonrequire") + package.unloadAll() + + MockTime = require("mock_time") + MockTime:install() + + PowerD = require("device/generic/powerd"):new{ + frontlight = 0, + } + PowerD.frontlightIntensityHW = function() + return 2 + end + PowerD.setIntensityHW = function(self, intensity) + self.frontlight = intensity + end + end) + + teardown(function() + MockTime:uninstall() + package.unloadAll() + end) + + before_each(function() + Device = require("device") + Device.isKindle = function() return true end + Device.model = "KindleVoyage" + Device.brightness = 0 + Device.hasFrontlight = function() return true end + Device.powerd = PowerD:new{ + device = Device, + } + Device.ambientBrightnessLevel = function(self) + return self.brightness + end + Device.input.waitEvent = function() end + end) + + it("should automatically turn on or off frontlight", function() + local UIManager = require("ui/uimanager") + UIManager._run_forever = true + require("luasettings"): + open(require("datastorage"):getSettingsDir() .. "/autofrontlight.lua"): + saveSetting("enable", "true"): + close() + local class = dofile("plugins/autofrontlight.koplugin/main.lua") + local AutoFrontlight = class:new() + AutoFrontlight:init() + Device.brightness = 3 + MockTime:increase(1) + UIManager:handleInput() + assert.are.equal(0, Device:getPowerDevice().frontlight) + Device.brightness = 0 + MockTime:increase(1) + UIManager:handleInput() + assert.are.equal(2, Device:getPowerDevice().frontlight) + Device.brightness = 1 + MockTime:increase(1) + UIManager:handleInput() + assert.are.equal(2, Device:getPowerDevice().frontlight) + Device.brightness = 2 + MockTime:increase(1) + UIManager:handleInput() + assert.are.equal(0, Device:getPowerDevice().frontlight) + Device.brightness = 3 + MockTime:increase(1) + UIManager:handleInput() + assert.are.equal(0, Device:getPowerDevice().frontlight) + end) +end) diff --git a/spec/unit/commonrequire.lua b/spec/unit/commonrequire.lua index f04f1b8a5..899409538 100644 --- a/spec/unit/commonrequire.lua +++ b/spec/unit/commonrequire.lua @@ -50,3 +50,50 @@ function assertNotAlmostEquals(expected, actual, margin) .. ', received: ' .. actual ) end + +package.unload = function(module) + if type(module) ~= "string" then return false end + package.loaded[module] = nil + _G[module] = nil + return true +end + +package.replace = function(name, module) + if type(name) ~= "string" then return false end + assert(package.unload(name)) + package.loaded[name] = module + return true +end + +package.reload = function(name) + if type(name) ~= "string" then return false end + assert(package.unload(name)) + return require(name) +end + +package.unloadAll = function() + local candidates = { + "spec/", + "frontend/", + "plugins/", + "datastorage.lua", + "defaults.lua", + } + local pending = {} + for name, _ in pairs(package.loaded) do + local path = package.searchpath(name, package.path) + if path ~= nil then + for _, candidate in ipairs(candidates) do + if path:find(candidate) == 1 then + table.insert(pending, name) + end + end + end + end + for _, name in ipairs(pending) do + if name ~= "commonrequire" then + assert(package.unload(name)) + end + end + return #pending +end diff --git a/spec/unit/frontlight_spec.lua b/spec/unit/frontlight_spec.lua new file mode 100644 index 000000000..3f0fe5283 --- /dev/null +++ b/spec/unit/frontlight_spec.lua @@ -0,0 +1,185 @@ +describe("Frontlight function in PowerD", function() + local PowerD, device + setup(function() + require("commonrequire") + + PowerD = require("device/generic/powerd"):new{ + frontlight = 0, + } + + param = { + fl_min = 1, + fl_max = 5, + device = { + hasFrontlight = function() return true end + }, + } + end) + + before_each(function() + frontlight = param.fl_min + stub(PowerD, "init") + stub(PowerD, "frontlightIntensityHW") + stub(PowerD, "setIntensityHW") + PowerD.setIntensityHW = function(self, intensity) + self.frontlight = intensity + end + spy.on(PowerD, "setIntensityHW") + spy.on(PowerD, "turnOnFrontlightHW") + spy.on(PowerD, "turnOffFrontlightHW") + end) + + it("should read frontlight intensity during initialization", function() + PowerD.frontlightIntensityHW.returns(2) + local p = PowerD:new(param) + assert.are.equal(2, p:frontlightIntensity()) + assert.is.truthy(p:isFrontlightOn()) + assert.stub(p.init).is_called(1) + assert.stub(p.frontlightIntensityHW).is_called(1) + end) + + test_when_off = function(fl_min) + param.fl_min = fl_min + PowerD.frontlightIntensityHW.returns(fl_min) + local p = PowerD:new(param) + assert.are.equal(0, p:frontlightIntensity()) + assert.is.truthy(p:isFrontlightOff()) + assert.stub(p.init).is_called(1) + assert.stub(p.setIntensityHW).is_called(1) + assert.are.equal(param.fl_min, p.frontlight) + assert.stub(p.frontlightIntensityHW).is_called(1) + assert.spy(p.turnOnFrontlightHW).is_called(0) + assert.spy(p.turnOffFrontlightHW).is_called(1) + + -- The intensity is param.fl_min, turnOnFrontlight() should take no effect. + assert.is.falsy(p:turnOnFrontlight()) + assert.are.equal(0, p:frontlightIntensity()) + assert.is.truthy(p:isFrontlightOff()) + assert.stub(p.setIntensityHW).is_called(1) + assert.spy(p.turnOnFrontlightHW).is_called(0) + assert.spy(p.turnOffFrontlightHW).is_called(1) + + -- Same as the above one, toggleFrontlight() should also take no effect. + assert.is.falsy(p:toggleFrontlight()) + assert.are.equal(0, p:frontlightIntensity()) + assert.is.truthy(p:isFrontlightOff()) + assert.stub(p.setIntensityHW).is_called(1) + assert.spy(p.turnOnFrontlightHW).is_called(0) + assert.spy(p.turnOffFrontlightHW).is_called(1) + + assert.is.truthy(p:setIntensity(2)) + assert.are.equal(2, p:frontlightIntensity()) + assert.is.truthy(p:isFrontlightOn()) + assert.stub(p.setIntensityHW).is_called(2) + assert.are.equal(2, p.frontlight) + assert.spy(p.turnOnFrontlightHW).is_called(0) + assert.spy(p.turnOffFrontlightHW).is_called(1) + + assert.is.falsy(p:turnOnFrontlight()) + assert.are.equal(2, p:frontlightIntensity()) + assert.is.truthy(p:isFrontlightOn()) + assert.stub(p.setIntensityHW).is_called(2) + assert.spy(p.turnOnFrontlightHW).is_called(0) + assert.spy(p.turnOffFrontlightHW).is_called(1) + + assert.is.truthy(p:turnOffFrontlight()) + assert.are.equal(0, p:frontlightIntensity()) + assert.is.truthy(p:isFrontlightOff()) + assert.stub(p.setIntensityHW).is_called(3) + assert.are.equal(param.fl_min, p.frontlight) + assert.spy(p.turnOnFrontlightHW).is_called(0) + assert.spy(p.turnOffFrontlightHW).is_called(2) + + assert.is.truthy(p:turnOnFrontlight()) + assert.are.equal(2, p:frontlightIntensity()) + assert.is.truthy(p:isFrontlightOn()) + assert.stub(p.setIntensityHW).is_called(4) + assert.are.equal(2, p.frontlight) + assert.spy(p.turnOnFrontlightHW).is_called(1) + assert.spy(p.turnOffFrontlightHW).is_called(2) + + assert.is.truthy(p:toggleFrontlight()) + assert.are.equal(0, p:frontlightIntensity()) + assert.is.truthy(p:isFrontlightOff()) + assert.stub(p.setIntensityHW).is_called(5) + assert.are.equal(param.fl_min, p.frontlight) + assert.spy(p.turnOnFrontlightHW).is_called(1) + assert.spy(p.turnOffFrontlightHW).is_called(3) + + assert.is.truthy(p:toggleFrontlight()) + assert.are.equal(2, p:frontlightIntensity()) + assert.is.truthy(p:isFrontlightOn()) + assert.stub(p.setIntensityHW).is_called(6) + assert.are.equal(2, p.frontlight) + assert.spy(p.turnOnFrontlightHW).is_called(2) + assert.spy(p.turnOffFrontlightHW).is_called(3) + end + + test_when_on = function(fl_min) + assert(fl_min < 2) + param.fl_min = fl_min + PowerD.frontlightIntensityHW.returns(2) + local p = PowerD:new(param) + assert.are.equal(2, p:frontlightIntensity()) + assert.is.truthy(p:isFrontlightOn()) + assert.stub(p.init).is_called(1) + assert.stub(p.setIntensityHW).is_called(1) + assert.are.equal(2, p.frontlight) + assert.spy(p.turnOnFrontlightHW).is_called(1) + assert.spy(p.turnOffFrontlightHW).is_called(0) + + assert.is.falsy(p:setIntensity(2)) + assert.are.equal(2, p:frontlightIntensity()) + assert.is.truthy(p:isFrontlightOn()) + assert.stub(p.setIntensityHW).is_called(1) + assert.are.equal(2, p.frontlight) + assert.spy(p.turnOnFrontlightHW).is_called(1) + assert.spy(p.turnOffFrontlightHW).is_called(0) + + assert.is.falsy(p:turnOnFrontlight()) + assert.are.equal(2, p:frontlightIntensity()) + assert.is.truthy(p:isFrontlightOn()) + assert.stub(p.setIntensityHW).is_called(1) + assert.are.equal(2, p.frontlight) + assert.spy(p.turnOnFrontlightHW).is_called(1) + assert.spy(p.turnOffFrontlightHW).is_called(0) + + assert.is.truthy(p:turnOffFrontlight()) + assert.are.equal(0, p:frontlightIntensity()) + assert.is.truthy(p:isFrontlightOff()) + assert.stub(p.setIntensityHW).is_called(2) + assert.are.equal(param.fl_min, p.frontlight) + assert.spy(p.turnOnFrontlightHW).is_called(1) + assert.spy(p.turnOffFrontlightHW).is_called(1) + + assert.is.truthy(p:toggleFrontlight()) + assert.are.equal(2, p:frontlightIntensity()) + assert.is.truthy(p:isFrontlightOn()) + assert.stub(p.setIntensityHW).is_called(3) + assert.are.equal(2, p.frontlight) + assert.spy(p.turnOnFrontlightHW).is_called(2) + assert.spy(p.turnOffFrontlightHW).is_called(1) + + assert.is.truthy(p:toggleFrontlight()) + assert.are.equal(0, p:frontlightIntensity()) + assert.is.truthy(p:isFrontlightOff()) + assert.stub(p.setIntensityHW).is_called(4) + assert.are.equal(param.fl_min, p.frontlight) + assert.spy(p.turnOnFrontlightHW).is_called(2) + assert.spy(p.turnOffFrontlightHW).is_called(2) + end + + it("should turn on and off frontlight when the frontlight was off", function() + test_when_off(0) + end) + + it("should turn on and off frontlight when the minimum level is 1 and frontlight was off", + function() test_when_off(1) end) + + it("should turn on and off frontlight when the frontlight was on", function() + test_when_on(0) + end) + + it("should turn on and off frontlight when the minimum level is 1 and frontlight was on", + function() test_when_on(1) end) +end) diff --git a/spec/unit/mock_time.lua b/spec/unit/mock_time.lua new file mode 100644 index 000000000..6596ed314 --- /dev/null +++ b/spec/unit/mock_time.lua @@ -0,0 +1,45 @@ +local MockTime = { + original_os_time = os.time, + original_util_time = nil, + value = os.time(), +} + +function MockTime:install() + assert(self ~= nil) + local util = require("ffi/util") + if self.original_util_time == nil then + self.original_util_time = util.gettime + assert(self.original_util_time ~= nil) + end + os.time = function() return self.value end + util.gettime = function() return self.value, 0 end +end + +function MockTime:uninstall() + assert(self ~= nil) + local util = require("ffi/util") + os.time = self.original_os_time + if self.original_util_time ~= nil then + util.gettime = self.original_util_time + end +end + +function MockTime:set(value) + assert(self ~= nil) + if type(value) ~= "number" then + return false + end + self.value = math.floor(value) + return true +end + +function MockTime:increase(value) + assert(self ~= nil) + if type(value) ~= "number" then + return false + end + self.value = math.floor(self.value + value) + return true +end + +return MockTime diff --git a/spec/unit/nickel_conf_spec.lua b/spec/unit/nickel_conf_spec.lua index 4d1d738b9..c30df125e 100644 --- a/spec/unit/nickel_conf_spec.lua +++ b/spec/unit/nickel_conf_spec.lua @@ -61,7 +61,7 @@ bar=baz fd:close() NickelConf._set_kobo_conf_path(fn) - assert.Equals(NickelConf.frontLightLevel.get(), 20) + assert.Equals(NickelConf.frontLightLevel.get(), 1) assert.Equals(NickelConf.frontLightState.get(), nil) os.remove(fn)