local ConfirmBox = require("ui/widget/confirmbox") local Device = require("device") local Event = require("ui/event") local InfoMessage = require("ui/widget/infomessage") local SpinWidget = require("ui/widget/spinwidget") local UIManager = require("ui/uimanager") local WidgetContainer = require("ui/widget/container/widgetcontainer") local powerd = Device:getPowerDevice() local _ = require("gettext") local C_ = _.pgettext local T = require("ffi/util").template local ReaderDeviceStatus = WidgetContainer:extend{ battery_confirm_box = nil, memory_confirm_box = nil, } function ReaderDeviceStatus:init() if Device:hasBattery() then self.battery_interval_m = G_reader_settings:readSetting("device_status_battery_interval_minutes", 10) self.battery_threshold = G_reader_settings:readSetting("device_status_battery_threshold", 20) self.battery_threshold_high = G_reader_settings:readSetting("device_status_battery_threshold_high", 100) -- `checkLowBatteryLevel` and `checkHighMemoryUsage` are each supposed to start one second past the top of the minute, -- as some other periodic activities do (e.g. footer). This means that the processor is woken up less often from standby. self.checkLowBatteryLevel = function(sync) local is_charging = powerd:isCharging() local battery_capacity = powerd:getCapacity() if powerd:getDismissBatteryStatus() == true then -- alerts dismissed if (is_charging and battery_capacity <= self.battery_threshold_high) or (not is_charging and battery_capacity > self.battery_threshold) then powerd:setDismissBatteryStatus(false) end else if (is_charging and battery_capacity > self.battery_threshold_high) or (not is_charging and battery_capacity <= self.battery_threshold) then if self.battery_confirm_box then UIManager:close(self.battery_confirm_box) end self.battery_confirm_box = ConfirmBox:new { text = is_charging and T(_("High battery level: %1 %\n\nDismiss battery level alert?"), battery_capacity) or T(_("Low battery level: %1 %\n\nDismiss battery level alert?"), battery_capacity), ok_text = _("Dismiss"), dismissable = false, ok_callback = function() powerd:setDismissBatteryStatus(true) end, } UIManager:show(self.battery_confirm_box) end end local offset = sync and (os.date("%S") - 1) or 0 UIManager:scheduleIn(self.battery_interval_m * 60 - offset, self.checkLowBatteryLevel) end self:startBatteryChecker() end if not Device:isAndroid() then self.memory_interval_m = G_reader_settings:readSetting("device_status_memory_interval_minutes", 5) self.memory_threshold = G_reader_settings:readSetting("device_status_memory_threshold", 100) -- `checkLowBatteryLevel` and `checkHighMemoryUsage` are each supposed to start one second past the top of the minute, -- as some other periodic activities do (e.g. footer). This means that the processor is woken up less often from standby. self.checkHighMemoryUsage = function(sync) local statm = io.open("/proc/self/statm", "r") if statm then local dummy, rss = statm:read("*number", "*number") statm:close() rss = math.floor(rss * (4096 / 1024 / 1024)) if rss >= self.memory_threshold then if self.memory_confirm_box then UIManager:close(self.memory_confirm_box) end if Device:canRestart() then if UIManager:getNthTopWidget().name == "ReaderUI" and G_reader_settings:isTrue("device_status_memory_auto_restart") then UIManager:show(InfoMessage:new{ text = _("High memory usage!\n\nKOReader is restarting…"), icon = "notice-warning", }) UIManager:nextTick(function() self.ui:handleEvent(Event:new("Restart")) end) else self.memory_confirm_box = ConfirmBox:new { text = T(_("High memory usage: %1 MB\n\nRestart KOReader?"), rss), ok_text = _("Restart"), dismissable = false, ok_callback = function() UIManager:show(InfoMessage:new{ text = _("High memory usage!\n\nKOReader is restarting…"), icon = "notice-warning", }) UIManager:nextTick(function() self.ui:handleEvent(Event:new("Restart")) end) end, } UIManager:show(self.memory_confirm_box) end else self.memory_confirm_box = ConfirmBox:new { text = T(_("High memory usage: %1 MB\n\nExit KOReader?"), rss), ok_text = _("Exit"), dismissable = false, ok_callback = function() self.ui:handleEvent(Event:new("Exit")) end, } UIManager:show(self.memory_confirm_box) end end end local offset = sync and (os.date("%S") - 1) or 0 UIManager:scheduleIn(self.memory_interval_m * 60 - offset, self.checkHighMemoryUsage) end self:startMemoryChecker() end self.ui.menu:registerToMainMenu(self) end function ReaderDeviceStatus:addToMainMenu(menu_items) menu_items.device_status_alarm = { text = _("Device status alerts"), sub_item_table = {}, } if Device:hasBattery() then table.insert(menu_items.device_status_alarm.sub_item_table, { text = _("Battery level"), checked_func = function() return G_reader_settings:isTrue("device_status_battery_alarm") end, callback = function() G_reader_settings:flipNilOrFalse("device_status_battery_alarm") if G_reader_settings:isTrue("device_status_battery_alarm") then self:startBatteryChecker(true) else self:stopBatteryChecker() powerd:setDismissBatteryStatus(false) end end, }) table.insert(menu_items.device_status_alarm.sub_item_table, { text_func = function() return T(_("Check interval: %1 min"), self.battery_interval_m) end, enabled_func = function() return G_reader_settings:isTrue("device_status_battery_alarm") end, keep_menu_open = true, callback = function(touchmenu_instance) UIManager:show(SpinWidget:new{ value = self.battery_interval_m, value_min = 1, value_max = 60, default_value = 10, unit = C_("Time", "min"), value_hold_step = 5, title_text = _("Battery check interval"), callback = function(spin) self.battery_interval_m = spin.value G_reader_settings:saveSetting("device_status_battery_interval_minutes", self.battery_interval_m) touchmenu_instance:updateItems() powerd:setDismissBatteryStatus(false) self:stopBatteryChecker() -- schedule first check on a full minute to reduce wakeups from standby) UIManager:scheduleIn(self.battery_interval_m * 60 - os.date("%S") + 1, self.checkLowBatteryLevel) end, }) end, }) table.insert(menu_items.device_status_alarm.sub_item_table, { text_func = function() return T(_("Thresholds: %1 % / %2 %"), self.battery_threshold, self.battery_threshold_high) end, enabled_func = function() return G_reader_settings:isTrue("device_status_battery_alarm") end, keep_menu_open = true, callback = function(touchmenu_instance) local DoubleSpinWidget = require("/ui/widget/doublespinwidget") local thresholds_widget thresholds_widget = DoubleSpinWidget:new{ title_text = _("Battery level alert thresholds"), info_text = _([[ Low level threshold is checked when the device is not charging. High level threshold is checked when the device is charging.]]), left_text = _("Low"), left_value = self.battery_threshold, left_min = 1, left_max = self.battery_threshold_high, left_default = 20, left_hold_step = 5, right_text = _("High"), right_value = self.battery_threshold_high, right_min = self.battery_threshold, right_max = 100, right_default = 100, right_hold_step = 5, unit = "%", callback = function(left_value, right_value) self.battery_threshold = left_value self.battery_threshold_high = right_value G_reader_settings:saveSetting("device_status_battery_threshold", self.battery_threshold) G_reader_settings:saveSetting("device_status_battery_threshold_high", self.battery_threshold_high) touchmenu_instance:updateItems() powerd:setDismissBatteryStatus(false) end, } UIManager:show(thresholds_widget) end, separator = true, }) end if not Device:isAndroid() then table.insert(menu_items.device_status_alarm.sub_item_table, { text = _("High memory usage"), checked_func = function() return G_reader_settings:isTrue("device_status_memory_alarm") end, callback = function() G_reader_settings:flipNilOrFalse("device_status_memory_alarm") if G_reader_settings:isTrue("device_status_memory_alarm") then self:startMemoryChecker(true) else self:stopMemoryChecker() end end, }) table.insert(menu_items.device_status_alarm.sub_item_table, { text_func = function() return T(_("Check interval: %1 min"), self.memory_interval_m) end, enabled_func = function() return G_reader_settings:isTrue("device_status_memory_alarm") end, keep_menu_open = true, callback = function(touchmenu_instance) UIManager:show(SpinWidget:new{ value = self.memory_interval_m, value_min = 1, value_max = 60, default_value = 5, unit = C_("Time", "min"), value_hold_step = 5, title_text = _("Memory check interval"), callback = function(spin) self.memory_interval_m = spin.value G_reader_settings:saveSetting("device_status_memory_interval_minutes", self.memory_interval_m) touchmenu_instance:updateItems() self:stopMemoryChecker() -- schedule first check on a full minute to reduce wakeups from standby) UIManager:scheduleIn(self.memory_interval_m * 60 - os.date("%S") + 1, self.checkHighMemoryUsage) end, }) end, }) table.insert(menu_items.device_status_alarm.sub_item_table, { text_func = function() return T(_("Threshold: %1 MB"), self.memory_threshold) end, enabled_func = function() return G_reader_settings:isTrue("device_status_memory_alarm") end, keep_menu_open = true, callback = function(touchmenu_instance) UIManager:show(SpinWidget:new{ value = self.memory_threshold, value_min = 20, value_max = 500, default_value = 100, unit = C_("Data storage size", "MB"), value_step = 5, value_hold_step = 10, title_text = _("Memory alert threshold"), callback = function(spin) self.memory_threshold = spin.value G_reader_settings:saveSetting("device_status_memory_threshold", self.memory_threshold) touchmenu_instance:updateItems() end, }) end, }) table.insert(menu_items.device_status_alarm.sub_item_table, { text = _("Automatic restart"), enabled_func = function() return G_reader_settings:isTrue("device_status_memory_alarm") and Device:canRestart() end, checked_func = function() return G_reader_settings:isTrue("device_status_memory_auto_restart") end, callback = function() G_reader_settings:flipNilOrFalse("device_status_memory_auto_restart") end, }) end end -- `checkLowBatteryLevel` and `checkHighMemoryUsage` are each supposed to start one second past the top of the minute, -- as some other periodic activities do (e.g. footer). This means that the processor is woken up less often from standby. function ReaderDeviceStatus:startBatteryChecker(sync) if G_reader_settings:isTrue("device_status_battery_alarm") then self.checkLowBatteryLevel(sync) end end function ReaderDeviceStatus:stopBatteryChecker() if self.checkLowBatteryLevel then UIManager:unschedule(self.checkLowBatteryLevel) end end -- `checkLowBatteryLevel` and `checkHighMemoryUsage` are each supposed to start one second past the top of the minute, -- as some other periodic activities do (e.g. footer). This means that the processor is woken up less often from standby. function ReaderDeviceStatus:startMemoryChecker(sync) if G_reader_settings:isTrue("device_status_memory_alarm") then self.checkHighMemoryUsage(sync) end end function ReaderDeviceStatus:stopMemoryChecker() if self.checkHighMemoryUsage then UIManager:unschedule(self.checkHighMemoryUsage) end end function ReaderDeviceStatus:onResume() self:startBatteryChecker(true) self:startMemoryChecker(true) end function ReaderDeviceStatus:onSuspend() self:stopBatteryChecker() self:stopMemoryChecker() end function ReaderDeviceStatus:onCloseWidget() self:stopBatteryChecker() self:stopMemoryChecker() end return ReaderDeviceStatus