local BD = require("ui/bidi") local Device = require("device") local Event = require("ui/event") local EventListener = require("ui/widget/eventlistener") local InfoMessage = require("ui/widget/infomessage") local NetworkMgr = require("ui/network/manager") local UIManager = require("ui/uimanager") local logger = require("logger") local _ = require("gettext") local T = require("ffi/util").template local NetworkListener = EventListener:extend{} function NetworkListener:onToggleWifi() if not NetworkMgr:isWifiOn() then local toggle_im = InfoMessage:new{ text = _("Turning on Wi-Fi…"), } UIManager:show(toggle_im) UIManager:forceRePaint() -- NB Normal widgets should use NetworkMgr:promptWifiOn() -- (or, better yet, the NetworkMgr:beforeWifiAction wrappers: NetworkMgr:runWhenOnline() & co.) -- This is specifically the toggle Wi-Fi action, so consent is implied. local complete_callback = function() UIManager:broadcastEvent(Event:new("NetworkConnected")) end NetworkMgr:turnOnWifi(complete_callback) UIManager:close(toggle_im) else local complete_callback = function() UIManager:broadcastEvent(Event:new("NetworkDisconnected")) end local toggle_im = InfoMessage:new{ text = _("Turning off Wi-Fi…"), } UIManager:show(toggle_im) UIManager:forceRePaint() NetworkMgr:turnOffWifi(complete_callback) UIManager:close(toggle_im) UIManager:show(InfoMessage:new{ text = _("Wi-Fi off."), timeout = 1, }) end end function NetworkListener:onInfoWifiOff() -- That's the end goal local complete_callback = function() UIManager:broadcastEvent(Event:new("NetworkDisconnected")) end local toggle_im = InfoMessage:new{ text = _("Turning off Wi-Fi…"), } UIManager:show(toggle_im) UIManager:forceRePaint() NetworkMgr:turnOffWifi(complete_callback) UIManager:close(toggle_im) UIManager:show(InfoMessage:new{ text = _("Wi-Fi off."), timeout = 1, }) end function NetworkListener:onInfoWifiOn() if not NetworkMgr:isOnline() then local toggle_im = InfoMessage:new{ text = _("Enabling Wi-Fi…"), } UIManager:show(toggle_im) UIManager:forceRePaint() -- NB Normal widgets should use NetworkMgr:promptWifiOn() -- (or, better yet, the NetworkMgr:beforeWifiAction wrappers: NetworkMgr:runWhenOnline() & co.) -- This is specifically the toggle Wi-Fi action, so consent is implied. local complete_callback = function() UIManager:broadcastEvent(Event:new("NetworkConnected")) end NetworkMgr:turnOnWifi(complete_callback) UIManager:close(toggle_im) else local info_text local current_network = NetworkMgr:getCurrentNetwork() -- this method is only available for some implementations if current_network and current_network.ssid then info_text = T(_("Already connected to network %1."), BD.wrap(current_network.ssid)) else info_text = _("Already connected.") end UIManager:show(InfoMessage:new{ text = info_text, timeout = 1, }) end end -- Everything below is to handle auto_disable_wifi ;). local default_network_timeout_seconds = 5*60 local max_network_timeout_seconds = 30*60 -- If autostandby is enabled, shorten the timeouts local auto_standby = G_reader_settings:readSetting("auto_standby_timeout_seconds", -1) if auto_standby > 0 then default_network_timeout_seconds = default_network_timeout_seconds / 2 max_network_timeout_seconds = max_network_timeout_seconds / 2 end -- This should be more than enough to catch actual activity vs. noise spread over 5 minutes. local network_activity_noise_margin = 12 -- unscaled_size_check: ignore -- Read the statistics/tx_packets sysfs entry for the current network interface. -- It *should* be the least noisy entry on an idle network... -- The fact that auto_disable_wifi is only available on (Device:hasWifiManager() and not Device:isEmulator()) -- allows us to get away with a Linux-only solution. function NetworkListener:_getTxPackets() -- read tx_packets stats from sysfs (for the right network if) local file = io.open("/sys/class/net/" .. NetworkMgr:getNetworkInterfaceName() .. "/statistics/tx_packets", "rb") -- file exists only when Wi-Fi module is loaded. if not file then return nil end local tx_packets = file:read("*number") file:close() -- Will be nil if NaN, just like we want it return tx_packets end function NetworkListener:_unscheduleActivityCheck() logger.dbg("NetworkListener: unschedule network activity check") if self._activity_check_scheduled then UIManager:unschedule(self._scheduleActivityCheck) self._activity_check_scheduled = nil logger.dbg("NetworkListener: network activity check unscheduled") end -- We also need to reset the stats, otherwise we'll be comparing apples vs. oranges... (i.e., two different network sessions) if self._last_tx_packets then self._last_tx_packets = nil end if self._activity_check_delay_seconds then self._activity_check_delay_seconds = nil end end function NetworkListener:_scheduleActivityCheck() logger.dbg("NetworkListener: network activity check") local keep_checking = true local tx_packets = NetworkListener:_getTxPackets() if self._last_tx_packets and tx_packets then -- Compute noise threshold based on the current delay local delay_seconds = self._activity_check_delay_seconds or default_network_timeout_seconds local noise_threshold = delay_seconds / default_network_timeout_seconds * network_activity_noise_margin local delta = tx_packets - self._last_tx_packets -- If there was no meaningful activity (+/- a couple packets), kill the Wi-Fi if delta <= noise_threshold then logger.dbg("NetworkListener: No meaningful network activity (delta:", delta, "<= threshold:", noise_threshold, "[ then:", self._last_tx_packets, "vs. now:", tx_packets, "]) -> disabling Wi-Fi") keep_checking = false local complete_callback = function() UIManager:broadcastEvent(Event:new("NetworkDisconnected")) end NetworkMgr:turnOffWifi(complete_callback) -- NOTE: We leave wifi_was_on as-is on purpose, we wouldn't want to break auto_restore_wifi workflows on the next start... else logger.dbg("NetworkListener: Significant network activity (delta:", delta, "> threshold:", noise_threshold, "[ then:", self._last_tx_packets, "vs. now:", tx_packets, "]) -> keeping Wi-Fi enabled") end end -- If we've just killed Wi-Fi, onNetworkDisconnected will take care of unscheduling us, so we're done if not keep_checking then return end -- Update tracker for next iter self._last_tx_packets = tx_packets -- If it's already been scheduled, increase the delay until we hit the ceiling if self._activity_check_delay_seconds then self._activity_check_delay_seconds = self._activity_check_delay_seconds + default_network_timeout_seconds if self._activity_check_delay_seconds > max_network_timeout_seconds then self._activity_check_delay_seconds = max_network_timeout_seconds end else self._activity_check_delay_seconds = default_network_timeout_seconds end UIManager:scheduleIn(self._activity_check_delay_seconds, self._scheduleActivityCheck, self) self._activity_check_scheduled = true logger.dbg("NetworkListener: network activity check scheduled in", self._activity_check_delay_seconds, "seconds") end function NetworkListener:onNetworkConnected() logger.dbg("NetworkListener: onNetworkConnected") if not (Device:hasWifiManager() and not Device:isEmulator()) then return end -- This is for the sake of events that don't emanate from NetworkMgr itself... NetworkMgr:setWifiState(true) NetworkMgr:setConnectionState(true) if not G_reader_settings:isTrue("auto_disable_wifi") then return end -- If the activity check has already been scheduled for some reason, unschedule it first. NetworkListener:_unscheduleActivityCheck() NetworkListener:_scheduleActivityCheck() end function NetworkListener:onNetworkDisconnected() logger.dbg("NetworkListener: onNetworkDisconnected") if not (Device:hasWifiManager() and not Device:isEmulator()) then return end NetworkMgr:setWifiState(false) NetworkMgr:setConnectionState(false) if not G_reader_settings:isTrue("auto_disable_wifi") then return end NetworkListener:_unscheduleActivityCheck() -- Reset NetworkMgr's beforeWifiAction marker NetworkMgr:clearBeforeActionFlag() end -- Also unschedule on suspend (and we happen to also kill Wi-Fi to do so, so resetting the stats is also relevant here) function NetworkListener:onSuspend() self:onNetworkDisconnected() end function NetworkListener:onShowNetworkInfo() if Device.retrieveNetworkInfo then UIManager:show(InfoMessage:new{ text = Device:retrieveNetworkInfo(), }) else UIManager:show(InfoMessage:new{ text = _("Could not retrieve network info."), timeout = 3, }) end end return NetworkListener