From d57325aaf6c102a0f57e0706b60b96579827728c Mon Sep 17 00:00:00 2001 From: NiLuJe Date: Wed, 12 Jul 2023 02:42:16 +0200 Subject: [PATCH] NetworkManager: Enable "before wifi" action support on every hasWifiToggle platform (#10669) * Enable before_wifi_action & after_wifi_action on hasWifiToggle platforms (which is basically all of 'em except naked SDL). * Decouple restoreWifiAsync from hasWifiManger, because we can do that on other platforms (namely, Kindle. Probably PB, too, but WiFi is already a mess there, and I can't test it). * Implement restoreWifiAsync on Kindle. * Properly flag rM as hasWifiManager & hasFastWifiStatusQuery, because it is actually both of those (it uses our wpa_supplicant backend). * Update the KOSync checks to take these changes into account, to properly disable auto_sync if necessary. * Really made the Network* event signaling consistent. For realz this time. * In an effort to make the whole beforeWifiAction framework somewhat usable there, we now assume connectivity is always available on !hasWifiToggle platforms... --- frontend/device/cervantes/device.lua | 1 + frontend/device/generic/device.lua | 14 +- frontend/device/kindle/device.lua | 5 + frontend/device/kobo/device.lua | 1 + frontend/device/remarkable/device.lua | 2 + frontend/device/sony-prstux/device.lua | 4 - frontend/ui/data/onetime_migration.lua | 64 ++++----- frontend/ui/network/manager.lua | 157 +++++++++++---------- frontend/ui/network/networklistener.lua | 174 +++++++++++------------- plugins/autosuspend.koplugin/main.lua | 2 +- plugins/kosync.koplugin/main.lua | 10 +- spec/unit/network_manager_spec.lua | 4 + 12 files changed, 221 insertions(+), 217 deletions(-) diff --git a/frontend/device/cervantes/device.lua b/frontend/device/cervantes/device.lua index 3e7abade4..c879e66f3 100644 --- a/frontend/device/cervantes/device.lua +++ b/frontend/device/cervantes/device.lua @@ -32,6 +32,7 @@ local Cervantes = Generic:extend{ hasFastWifiStatusQuery = yes, hasKeys = yes, hasWifiManager = yes, + hasWifiRestore = yes, canReboot = yes, canPowerOff = yes, canSuspend = yes, diff --git a/frontend/device/generic/device.lua b/frontend/device/generic/device.lua index 560383a88..5f240fea9 100644 --- a/frontend/device/generic/device.lua +++ b/frontend/device/generic/device.lua @@ -46,6 +46,7 @@ local Device = { hasFewKeys = no, hasWifiToggle = yes, hasWifiManager = no, + hasWifiRestore = no, isDefaultFullscreen = yes, isHapticFeedbackEnabled = no, isDeprecated = no, -- device no longer receive OTA updates @@ -278,13 +279,6 @@ function Device:onPowerEvent(ev) else logger.dbg("Resuming...") UIManager:unschedule(self.suspend) - if self:hasWifiManager() then - local network_manager = require("ui/network/manager") - if network_manager.wifi_was_on and G_reader_settings:isTrue("auto_restore_wifi") then - network_manager:restoreWifiAsync() - network_manager:scheduleConnectivityCheck() - end - end self:resume() local widget_was_closed = Screensaver:close() if widget_was_closed and self:needsScreenRefreshAfterResume() then @@ -313,8 +307,7 @@ function Device:onPowerEvent(ev) if self:hasWifiToggle() then local network_manager = require("ui/network/manager") if network_manager:isWifiOn() then - UIManager:broadcastEvent(Event:new("NetworkDisconnecting")) - network_manager:turnOffWifi() + network_manager:disableWifi() end end self:rescheduleSuspend() @@ -347,8 +340,7 @@ function Device:onPowerEvent(ev) -- because suspend will at best fail, and at worst deadlock the system if Wi-Fi is on, -- regardless of who enabled it! if network_manager:isWifiOn() then - UIManager:broadcastEvent(Event:new("NetworkDisconnecting")) - network_manager:turnOffWifi() + network_manager:disableWifi() end end -- Only turn off the frontlight *after* we've displayed the screensaver and dealt with Wi-Fi, diff --git a/frontend/device/kindle/device.lua b/frontend/device/kindle/device.lua index cf4458e00..28beaee2b 100644 --- a/frontend/device/kindle/device.lua +++ b/frontend/device/kindle/device.lua @@ -138,6 +138,7 @@ local Kindle = Generic:extend{ isSpecialOffers = isSpecialOffers(), hasOTAUpdates = yes, hasFastWifiStatusQuery = yes, + hasWifiRestore = yes, -- NOTE: HW inversion is generally safe on mxcfb Kindles canHWInvert = yes, -- NOTE: And the fb driver is generally sane on those, too @@ -186,6 +187,10 @@ function Kindle:initNetworkManager(NetworkMgr) return "wlan0" -- so far, all Kindles appear to use wlan0 end + function NetworkMgr:restoreWifiAsync() + kindleEnableWifi(1) + end + NetworkMgr.isWifiOn = NetworkMgr.sysfsWifiOn NetworkMgr.isConnected = NetworkMgr.ifHasAnAddress end diff --git a/frontend/device/kobo/device.lua b/frontend/device/kobo/device.lua index 4cefbd4cc..fc8ebe7cd 100644 --- a/frontend/device/kobo/device.lua +++ b/frontend/device/kobo/device.lua @@ -95,6 +95,7 @@ local Kobo = Generic:extend{ hasOTAUpdates = yes, hasFastWifiStatusQuery = yes, hasWifiManager = yes, + hasWifiRestore = yes, canStandby = no, -- will get updated by checkStandby() canReboot = yes, canPowerOff = yes, diff --git a/frontend/device/remarkable/device.lua b/frontend/device/remarkable/device.lua index bc084a4b5..d58ceed59 100644 --- a/frontend/device/remarkable/device.lua +++ b/frontend/device/remarkable/device.lua @@ -35,6 +35,8 @@ local Remarkable = Generic:extend{ hasKeys = yes, needsScreenRefreshAfterResume = no, hasOTAUpdates = yes, + hasFastWifiStatusQuery = yes, + hasWifiManager = yes, canReboot = yes, canPowerOff = yes, canSuspend = yes, diff --git a/frontend/device/sony-prstux/device.lua b/frontend/device/sony-prstux/device.lua index dcaf9b4c7..aa3224f9b 100644 --- a/frontend/device/sony-prstux/device.lua +++ b/frontend/device/sony-prstux/device.lua @@ -168,10 +168,6 @@ function SonyPRSTUX:initNetworkManager(NetworkMgr) os.execute("dhclient -x wlan0") end - function NetworkMgr:restoreWifiAsync() - -- os.execute("./restore-wifi-async.sh") - end - --[[ function NetworkMgr:isWifiOn() return 0 == os.execute("wmiconfig -i wlan0 --wlan query | grep -q enabled") diff --git a/frontend/ui/data/onetime_migration.lua b/frontend/ui/data/onetime_migration.lua index 68c0d66d3..dc7dc3702 100644 --- a/frontend/ui/data/onetime_migration.lua +++ b/frontend/ui/data/onetime_migration.lua @@ -8,7 +8,7 @@ local logger = require("logger") local _ = require("gettext") -- Date at which the last migration snippet was added -local CURRENT_MIGRATION_DATE = 20230707 +local CURRENT_MIGRATION_DATE = 20230710 -- Retrieve the date of the previous migration, if any local last_migration_date = G_reader_settings:readSetting("last_migration_date", 0) @@ -517,9 +517,36 @@ if last_migration_date < 20230531 then end end --- 20230627, Migrate to a full settings table, and disable KOSync's auto sync mode if wifi_enable_action is not turn_on -if last_migration_date < 20230627 then - logger.info("Performing one-time migration for 20230627") +-- 20230703, FileChooser Sort by: "date modified" only +if last_migration_date < 20230703 then + logger.info("Performing one-time migration for 20230703") + local collate = G_reader_settings:readSetting("collate") + if collate == "modification" or collate == "access" or collate == "change" then + G_reader_settings:saveSetting("collate", "date") + end +end + +-- 20230707, OPDS, no more special calibre catalog +if last_migration_date < 20230707 then + logger.info("Performing one-time migration for 20230707") + + local calibre_opds = G_reader_settings:readSetting("calibre_opds") + if calibre_opds and calibre_opds.host and calibre_opds.port then + local opds_servers = G_reader_settings:readSetting("opds_servers") or {} + table.insert(opds_servers, 1, { + title = _("Local calibre library"), + url = string.format("http://%s:%d/opds", calibre_opds.host, calibre_opds.port), + username = calibre_opds.username, + password = calibre_opds.password, + }) + G_reader_settings:saveSetting("opds_servers", opds_servers) + G_reader_settings:delSetting("calibre_opds") + end +end + +-- 20230710, Migrate to a full settings table, and disable KOSync's auto sync mode if wifi_enable_action is not turn_on +if last_migration_date < 20230710 then + logger.info("Performing one-time migration for 20230710") -- c.f., PluginLoader local package_path = package.path @@ -549,7 +576,7 @@ if last_migration_date < 20230627 then end local Device = require("device") - if Device:hasWifiManager() and G_reader_settings:readSetting("wifi_enable_action") ~= "turn_on" then + if Device:hasWifiToggle() and G_reader_settings:readSetting("wifi_enable_action") ~= "turn_on" then local kosync = G_reader_settings:readSetting("kosync") if kosync and kosync.auto_sync then kosync.auto_sync = false @@ -558,32 +585,5 @@ if last_migration_date < 20230627 then end end --- 20230703, FileChooser Sort by: "date modified" only -if last_migration_date < 20230703 then - logger.info("Performing one-time migration for 20230703") - local collate = G_reader_settings:readSetting("collate") - if collate == "modification" or collate == "access" or collate == "change" then - G_reader_settings:saveSetting("collate", "date") - end -end - --- 20230707, OPDS, no more special calibre catalog -if last_migration_date < 20230707 then - logger.info("Performing one-time migration for 20230707") - - local calibre_opds = G_reader_settings:readSetting("calibre_opds") - if calibre_opds and calibre_opds.host and calibre_opds.port then - local opds_servers = G_reader_settings:readSetting("opds_servers") or {} - table.insert(opds_servers, 1, { - title = _("Local calibre library"), - url = string.format("http://%s:%d/opds", calibre_opds.host, calibre_opds.port), - username = calibre_opds.username, - password = calibre_opds.password, - }) - G_reader_settings:saveSetting("opds_servers", opds_servers) - G_reader_settings:delSetting("calibre_opds") - end -end - -- We're done, store the current migration date G_reader_settings:saveSetting("last_migration_date", CURRENT_MIGRATION_DATE) diff --git a/frontend/ui/network/manager.lua b/frontend/ui/network/manager.lua index 4369c9057..cc4f2654f 100644 --- a/frontend/ui/network/manager.lua +++ b/frontend/ui/network/manager.lua @@ -21,6 +21,7 @@ require("ffi/posix_h") local NetworkMgr = { is_wifi_on = false, is_connected = false, + pending_connectivity_check = false, interface = nil, } @@ -32,12 +33,12 @@ end -- as quite a few things rely on it (KOSync, c.f. #5109; the network activity check, c.f., #6424). function NetworkMgr:connectivityCheck(iter, callback, widget) -- Give up after a while (restoreWifiAsync can take over 45s, so, try to cover that)... - if iter > 180 then + if iter >= 180 then logger.info("Failed to restore Wi-Fi (after", iter * 0.25, "seconds)!") self.wifi_was_on = false G_reader_settings:makeFalse("wifi_was_on") - -- If we abort, murder Wi-Fi and the async script first... - if Device:hasWifiManager() then + -- If we abort, murder Wi-Fi and the async script (if any) first... + if Device:hasWifiRestore() and not Device:isKindle() then os.execute("pkill -TERM restore-wifi-async.sh 2>/dev/null") end -- We were never connected to begin with, so, no disconnecting broadcast required @@ -48,6 +49,7 @@ function NetworkMgr:connectivityCheck(iter, callback, widget) UIManager:close(widget) UIManager:show(InfoMessage:new{ text = _("Error connecting to the network") }) end + self.pending_connectivity_check = false return end @@ -74,13 +76,15 @@ function NetworkMgr:connectivityCheck(iter, callback, widget) }) end end + self.pending_connectivity_check = false else UIManager:scheduleIn(0.25, self.connectivityCheck, self, iter + 1, callback, widget) end end function NetworkMgr:scheduleConnectivityCheck(callback, widget) - UIManager:scheduleIn(0.5, self.connectivityCheck, self, 1, callback, widget) + self.pending_connectivity_check = true + UIManager:scheduleIn(0.25, self.connectivityCheck, self, 1, callback, widget) end function NetworkMgr:init() @@ -89,17 +93,16 @@ function NetworkMgr:init() self:queryNetworkState() self.wifi_was_on = G_reader_settings:isTrue("wifi_was_on") - if self.wifi_was_on and G_reader_settings:isTrue("auto_restore_wifi") then - -- Don't bother if WiFi is already up... - if not self.is_connected then - self:restoreWifiAsync() - end - self:scheduleConnectivityCheck() + -- Trigger an initial NetworkConnected event if WiFi was already up when we were launched + if self.is_connected then + -- NOTE: This needs to be delayed because we run on require, while NetworkListener gets spun up sliiightly later on FM/ReaderUI init... + UIManager:nextTick(UIManager.broadcastEvent, UIManager, Event:new("NetworkConnected")) else - -- Trigger an initial NetworkConnected event if WiFi was already up when we were launched - if self.is_connected then - -- NOTE: This needs to be delayed because NetworkListener is initialized slightly later by the FM/Reader app... - UIManager:scheduleIn(2, UIManager.broadcastEvent, UIManager, Event:new("NetworkConnected")) + -- Attempt to restore wifi in the background if necessary + if Device:hasWifiRestore() and self.wifi_was_on and G_reader_settings:isTrue("auto_restore_wifi") then + logger.dbg("NetworkMgr: init will restore Wi-Fi in the background") + self:restoreWifiAsync() + self:scheduleConnectivityCheck() end end @@ -109,16 +112,28 @@ end -- Following methods are Device specific which need to be initialized in -- Device:initNetworkManager. Some of them can be set by calling -- NetworkMgr:setWirelessBackend -function NetworkMgr:turnOnWifi() end -function NetworkMgr:turnOffWifi() end --- This function returns status of the WiFi radio -function NetworkMgr:isWifiOn() end -function NetworkMgr:isConnected() end +function NetworkMgr:turnOnWifi(complete_callback) end +function NetworkMgr:turnOffWifi(complete_callback) end +-- This function returns the current status of the WiFi radio +-- NOTE: On !hasWifiToggle platforms, we assume networking is always available, +-- so as not to confuse the whole beforeWifiAction framework +-- (and let it fail with network errors when offline, instead of looping on unimplemented stuff...). +function NetworkMgr:isWifiOn() + if not Device:hasWifiToggle() then + return true + end +end +function NetworkMgr:isConnected() + if not Device:hasWifiToggle() then + return true + end +end function NetworkMgr:getNetworkInterfaceName() end function NetworkMgr:getNetworkList() end function NetworkMgr:getCurrentNetwork() end function NetworkMgr:authenticateNetwork() end function NetworkMgr:disconnectNetwork() end +-- NOTE: This is currently only called on hasWifiManager platforms! function NetworkMgr:obtainIP() end function NetworkMgr:releaseIP() end -- This function should call both turnOnWifi() and obtainIP() in a non-blocking manner. @@ -215,6 +230,30 @@ function NetworkMgr:ifHasAnAddress() return ok end +-- Wrappers around turnOnWifi & turnOffWifi with proper Event signaling +function NetworkMgr:enableWifi(wifi_cb, connectivity_cb, connectivity_widget) + -- Connecting will take a few seconds, broadcast that information so affected modules/plugins can react. + UIManager:broadcastEvent(Event:new("NetworkConnecting")) + self:turnOnWifi(wifi_cb) + + -- Some turnOnWifi implementations may already have fired a connectivity check... + if not self.pending_connectivity_check then + -- This will handle sending the proper Event, manage wifi_was_on, as well as tearing down Wi-Fi in case of failures. + self:scheduleConnectivityCheck(connectivity_cb, connectivity_widget) + end +end + +function NetworkMgr:disableWifi(cb) + local complete_callback = function() + UIManager:broadcastEvent(Event:new("NetworkDisconnected")) + if cb then + cb() + end + end + UIManager:broadcastEvent(Event:new("NetworkDisconnecting")) + self:turnOffWifi(complete_callback) +end + function NetworkMgr:toggleWifiOn(complete_callback, long_press) local toggle_im = InfoMessage:new{ text = _("Turning on Wi-Fi…"), @@ -226,10 +265,7 @@ function NetworkMgr:toggleWifiOn(complete_callback, long_press) G_reader_settings:makeTrue("wifi_was_on") self.wifi_toggle_long_press = long_press - -- Connecting might take a few seconds (hello standby). - -- Broadcast the information, that network is changing, so affected modules/plugins can react. - UIManager:broadcastEvent(Event:new("NetworkConnecting")) - self:turnOnWifi(complete_callback) + self:enableWifi(complete_callback) UIManager:close(toggle_im) end @@ -244,10 +280,7 @@ function NetworkMgr:toggleWifiOff(complete_callback) self.wifi_was_on = false G_reader_settings:makeFalse("wifi_was_on") - -- Disconnecting might take some time, but less than connecting (hello standby). - -- Broadcast the information, that network is changing, so affected modules/plugins can react. - UIManager:broadcastEvent(Event:new("NetworkDisconnecting")) - self:turnOffWifi(complete_callback) + self:disableWifi(complete_callback) UIManager:close(toggle_im) end @@ -287,17 +320,26 @@ function NetworkMgr:promptWifi(complete_callback, long_press) end function NetworkMgr:turnOnWifiAndWaitForConnection(callback) + -- Just run the callback if WiFi is already up... + if self:isWifiOn() and self:isConnected() then + -- Given the guards in beforeWifiAction callers, this shouldn't really ever happen... + callback() + return + end + local info = InfoMessage:new{ text = _("Connecting to Wi-Fi…") } UIManager:show(info) UIManager:forceRePaint() - -- Don't bother if WiFi is already up... - if not (self:isWifiOn() and self:isConnected()) then - self:turnOnWifi() + -- This is a slightly tweaked variant of enableWifi, because of our peculiar connectivityCheck usage... + UIManager:broadcastEvent(Event:new("NetworkConnecting")) + self:turnOnWifi() + -- Some implementations may fire a connectivity check, + -- but we *need* our own, because of the callback & widget passing. + if self.pending_connectivity_check then + UIManager:unschedule(self.connectivityCheck) + self.pending_connectivity_check = false end - - -- This will handle sending the proper Event, manage wifi_was_on, as well as tearing down Wi-Fi in case of failures, - -- (i.e., much like getWifiToggleMenuTable). self:scheduleConnectivityCheck(callback, info) return info @@ -347,8 +389,7 @@ function NetworkMgr:afterWifiAction(callback) callback() end elseif wifi_disable_action == "turn_off" then - UIManager:broadcastEvent(Event:new("NetworkDisconnecting")) - self:turnOffWifi(callback) + self:disableWifi(callback) else self:promptWifiOff(callback) end @@ -477,12 +518,13 @@ function NetworkMgr:goOnlineToRun(callback) local info = self:beforeWifiAction() -- We'll basically do the same but in a blocking manner... UIManager:unschedule(self.connectivityCheck) + self.pending_connectivity_check = false local iter = 0 while not self.is_connected do iter = iter + 1 if iter >= 120 then - logger.info("Failed to connect to Wi-Fi after 30s, giving up!") + logger.info("Failed to connect to Wi-Fi after", iter * 0.25, "seconds, giving up!") self.wifi_was_on = false G_reader_settings:makeFalse("wifi_was_on") if info then @@ -503,6 +545,7 @@ function NetworkMgr:goOnlineToRun(callback) -- We're finally connected! self.wifi_was_on = true G_reader_settings:makeTrue("wifi_was_on") + logger.info("Successfully connected to Wi-Fi (after", iter * 0.25, "seconds)!") callback() -- Delay this so it won't fire for dead/dying instances in case we're called by a finalizer... UIManager:scheduleIn(2, function() @@ -529,39 +572,8 @@ function NetworkMgr:getWifiToggleMenuTable() self:queryNetworkState() local fully_connected = self.is_wifi_on and self.is_connected local complete_callback = function() - -- Check the connection status again - self:queryNetworkState() -- Notify TouchMenu to update item check state touchmenu_instance:updateItems() - -- If Wi-Fi was on when the menu was shown, this means the tap meant to turn the Wi-Fi *off*, - -- as such, this callback will only be executed *after* the network has been disconnected. - if fully_connected then - UIManager:broadcastEvent(Event:new("NetworkDisconnected")) - else - -- On hasWifiManager devices that play with kernel modules directly, - -- double-check that the connection attempt was actually successful... - if Device:isKobo() or Device:isCervantes() then - if self.is_wifi_on and self.is_connected then - UIManager:broadcastEvent(Event:new("NetworkConnected")) - elseif self.is_wifi_on and not self.is_connected then - -- If we can't ping the gateway, despite a successful authentication w/ the AP, display a warning, - -- because this means that Wi-Fi is technically still enabled (e.g., modules are loaded). - -- We can't really enforce a turnOffWifi right now, because the user might want to try another AP or something. - -- (c.f., #5912, #4616). - -- NOTE: Keep in mind that NetworkSetting only runs this callback on *successful* connections! - -- (It's called connect_callback there). - -- This makes this branch somewhat hard to reach, which is why it gets a dedicated prompt below... - UIManager:show(InfoMessage:new{ - icon = "notice-warning", - text = _("Gateway is unreachable, but Wi-Fi is still on!"), - timeout = 3, - }) - end - else - -- Assume success on other platforms - UIManager:broadcastEvent(Event:new("NetworkConnected")) - end - end end -- complete_callback() if fully_connected then self:toggleWifiOff(complete_callback) @@ -624,7 +636,8 @@ end function NetworkMgr:getPowersaveMenuTable() return { text = _("Disable Wi-Fi connection when inactive"), - help_text = _([[This will automatically turn Wi-Fi off after a generous period of network inactivity, without disrupting workflows that require a network connection, so you can just keep reading without worrying about battery drain.]]), + help_text = Device:isKindle() and _([[This is unlikely to function properly on a stock Kindle, given how chatty the framework is.]]) or + _([[This will automatically turn Wi-Fi off after a generous period of network inactivity, without disrupting workflows that require a network connection, so you can just keep reading without worrying about battery drain.]]), checked_func = function() return G_reader_settings:isTrue("auto_disable_wifi") end, callback = function() G_reader_settings:flipNilOrFalse("auto_disable_wifi") @@ -639,7 +652,7 @@ function NetworkMgr:getRestoreMenuTable() text = _("Restore Wi-Fi connection on resume"), help_text = _([[This will attempt to automatically and silently re-connect to Wi-Fi on startup or on resume if Wi-Fi used to be enabled the last time you used KOReader.]]), checked_func = function() return G_reader_settings:isTrue("auto_restore_wifi") end, - enabled_func = function() return Device:hasWifiManager() end, + enabled_func = function() return Device:hasWifiRestore() end, callback = function() G_reader_settings:flipNilOrFalse("auto_restore_wifi") end, } end @@ -741,9 +754,13 @@ function NetworkMgr:getMenuTable(common_settings) common_settings.network_powersave = self:getPowersaveMenuTable() end - if Device:hasWifiManager() or Device:isEmulator() then + if Device:hasWifiRestore() or Device:isEmulator() then common_settings.network_restore = self:getRestoreMenuTable() + end + if Device:hasWifiManager() or Device:isEmulator() then common_settings.network_dismiss_scan = self:getDismissScanMenuTable() + end + if Device:hasWifiToggle() then common_settings.network_before_wifi_action = self:getBeforeWifiActionMenuTable() common_settings.network_after_wifi_action = self:getAfterWifiActionMenuTable() end diff --git a/frontend/ui/network/networklistener.lua b/frontend/ui/network/networklistener.lua index 5376d8ac2..473885366 100644 --- a/frontend/ui/network/networklistener.lua +++ b/frontend/ui/network/networklistener.lua @@ -1,6 +1,5 @@ local BD = require("ui/bidi") local Device = require("device") -local Event = require("ui/event") local EventListener = require("ui/widget/eventlistener") local Font = require("ui/font") local InfoMessage = require("ui/widget/infomessage") @@ -10,59 +9,36 @@ local logger = require("logger") local _ = require("gettext") local T = require("ffi/util").template -local NetworkListener = EventListener:extend{} +local NetworkListener = EventListener:extend{ + -- Class members, because we want the activity check to be cross-instance... + _activity_check_scheduled = nil, + _last_tx_packets = nil, + _activity_check_delay_seconds = nil, +} -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() +local function enableWifi() + local toggle_im = InfoMessage:new{ + text = _("Turning on Wi-Fi…"), + } + UIManager:show(toggle_im) + UIManager:forceRePaint() - UIManager:broadcastEvent(Event:new("NetworkDisconnecting")) - NetworkMgr:turnOffWifi(complete_callback) + -- 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. + NetworkMgr:enableWifi() - UIManager:close(toggle_im) - UIManager:show(InfoMessage:new{ - text = _("Wi-Fi off."), - timeout = 1, - }) - end + UIManager:close(toggle_im) end -function NetworkListener:onInfoWifiOff() - -- That's the end goal - local complete_callback = function() - UIManager:broadcastEvent(Event:new("NetworkDisconnected")) - end +local function disableWifi() local toggle_im = InfoMessage:new{ text = _("Turning off Wi-Fi…"), } UIManager:show(toggle_im) UIManager:forceRePaint() - UIManager:broadcastEvent(Event:new("NetworkDisconnecting")) - NetworkMgr:turnOffWifi(complete_callback) + NetworkMgr:disableWifi() UIManager:close(toggle_im) UIManager:show(InfoMessage:new{ @@ -71,23 +47,21 @@ function NetworkListener:onInfoWifiOff() }) end +function NetworkListener:onToggleWifi() + if not NetworkMgr:isWifiOn() then + enableWifi() + else + disableWifi() + end +end + +function NetworkListener:onInfoWifiOff() + disableWifi() +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) + enableWifi() else local info_text local current_network = NetworkMgr:getCurrentNetwork() @@ -136,43 +110,40 @@ 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 + if NetworkListener._activity_check_scheduled then + UIManager:unschedule(NetworkListener._scheduleActivityCheck) + NetworkListener._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 + if NetworkListener._last_tx_packets then + NetworkListener._last_tx_packets = nil end - if self._activity_check_delay_seconds then - self._activity_check_delay_seconds = nil + if NetworkListener._activity_check_delay_seconds then + NetworkListener._activity_check_delay_seconds = nil end end +-- NOTE: This must *never* access instance-specific members! 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 + if NetworkListener._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 delay_seconds = NetworkListener._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 + local delta = tx_packets - NetworkListener._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") + logger.dbg("NetworkListener: No meaningful network activity (delta:", delta, "<= threshold:", noise_threshold, "[ then:", NetworkListener._last_tx_packets, "vs. now:", tx_packets, "]) -> disabling Wi-Fi") keep_checking = false - local complete_callback = function() - UIManager:broadcastEvent(Event:new("NetworkDisconnected")) - end - UIManager:broadcastEvent(Event:new("NetworkDisconnecting")) - NetworkMgr:turnOffWifi(complete_callback) + NetworkMgr:disableWifi() -- 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") + logger.dbg("NetworkListener: Significant network activity (delta:", delta, "> threshold:", noise_threshold, "[ then:", NetworkListener._last_tx_packets, "vs. now:", tx_packets, "]) -> keeping Wi-Fi enabled") end end @@ -182,28 +153,28 @@ function NetworkListener:_scheduleActivityCheck() end -- Update tracker for next iter - self._last_tx_packets = tx_packets + NetworkListener._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 NetworkListener._activity_check_delay_seconds then + NetworkListener._activity_check_delay_seconds = NetworkListener._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 + if NetworkListener._activity_check_delay_seconds > max_network_timeout_seconds then + NetworkListener._activity_check_delay_seconds = max_network_timeout_seconds end else - self._activity_check_delay_seconds = default_network_timeout_seconds + NetworkListener._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") + UIManager:scheduleIn(NetworkListener._activity_check_delay_seconds, NetworkListener._scheduleActivityCheck) + NetworkListener._activity_check_scheduled = true + logger.dbg("NetworkListener: network activity check scheduled in", NetworkListener._activity_check_delay_seconds, "seconds") end function NetworkListener:onNetworkConnected() logger.dbg("NetworkListener: onNetworkConnected") - if Device:hasWifiManager() then - -- This is for the sake of events that don't emanate from NetworkMgr itself... + if Device:hasWifiToggle() then + -- This is for the sake of events that don't emanate from NetworkMgr itself (e.g., the Emu)... NetworkMgr:setWifiState(true) NetworkMgr:setConnectionState(true) end @@ -214,30 +185,45 @@ function NetworkListener:onNetworkConnected() -- 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 Device:hasWifiManager() then + if Device:hasWifiToggle() then NetworkMgr:setWifiState(false) NetworkMgr:setConnectionState(false) end - 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() + logger.dbg("NetworkListener: onSuspend") + + -- If we haven't already (e.g., via Generic's onPowerEvent), kill Wi-Fi. + -- Except on Android, where turnOnWifi/turnOffWifi are *interactive*... :/ + if Device:hasWifiToggle() and NetworkMgr:isWifiOn() and not Device:isAndroid() then + NetworkMgr:disableWifi() + end + + -- Wi-Fi will be down, unschedule unconditionally + NetworkListener:_unscheduleActivityCheck() + NetworkMgr:clearBeforeActionFlag() +end + +-- If the platform implements NetworkMgr:restoreWifiAsync, run it as needed +if Device:hasWifiRestore() then + function NetworkListener:onResume() + if NetworkMgr.wifi_was_on and G_reader_settings:isTrue("auto_restore_wifi") then + logger.dbg("NetworkListener: onResume will restore Wi-Fi in the background") + NetworkMgr:restoreWifiAsync() + NetworkMgr:scheduleConnectivityCheck() + end + end end function NetworkListener:onShowNetworkInfo() diff --git a/plugins/autosuspend.koplugin/main.lua b/plugins/autosuspend.koplugin/main.lua index 97588ebef..b9fb16a44 100644 --- a/plugins/autosuspend.koplugin/main.lua +++ b/plugins/autosuspend.koplugin/main.lua @@ -673,7 +673,7 @@ function AutoSuspend:onNetworkConnecting() end function AutoSuspend:onNetworkDisconnected() - logger.dbg("AutoSuspend: onNetworkDisonnected") + logger.dbg("AutoSuspend: onNetworkDisconnected") self:_unschedule_standby() -- Schedule the next check as usual. self:_start_standby() diff --git a/plugins/kosync.koplugin/main.lua b/plugins/kosync.koplugin/main.lua index 5c554be18..b3e053344 100644 --- a/plugins/kosync.koplugin/main.lua +++ b/plugins/kosync.koplugin/main.lua @@ -56,8 +56,8 @@ KOSync.default_settings = { custom_server = nil, username = nil, userkey = nil, - -- Do *not* default to auto-sync on devices w/ NetworkManager support, as wifi is unlikely to be on at all times there, and the nagging enabling this may cause requires careful consideration. - auto_sync = not Device:hasWifiManager(), + -- Do *not* default to auto-sync, as wifi may not be on at all times, and the nagging enabling this may cause requires careful consideration. + auto_sync = false, pages_before_update = nil, sync_forward = SYNC_STRATEGY.PROMPT, sync_backward = SYNC_STRATEGY.DISABLE, @@ -84,7 +84,7 @@ function KOSync:init() self.device_id = G_reader_settings:readSetting("device_id") -- Disable auto-sync if beforeWifiAction was reset to "prompt" behind our back... - if self.settings.auto_sync and Device:hasWifiManager() and G_reader_settings:readSetting("wifi_enable_action") ~= "turn_on" then + if self.settings.auto_sync and Device:hasWifiToggle() and G_reader_settings:readSetting("wifi_enable_action") ~= "turn_on" then self.settings.auto_sync = false logger.warn("KOSync: Automatic sync has been disabled because wifi_enable_action is *not* turn_on") end @@ -229,7 +229,7 @@ function KOSync:addToMainMenu(menu_items) help_text = _([[This may lead to nagging about toggling WiFi on document close and suspend/resume, depending on the device's connectivity.]]), callback = function() -- Actively recommend switching the before wifi action to "turn_on" instead of prompt, as prompt will just not be practical (or even plain usable) here. - if Device:hasWifiManager() and G_reader_settings:readSetting("wifi_enable_action") ~= "turn_on" then + if Device:hasWifiToggle() and G_reader_settings:readSetting("wifi_enable_action") ~= "turn_on" then UIManager:show(InfoMessage:new{ text = _("You will have to switch the 'Action when Wi-Fi is off' Network setting to 'turn on' to be able to enable this feature!") }) return end @@ -868,7 +868,7 @@ function KOSync:_onResume() logger.dbg("KOSync: onResume") -- If we have auto_restore_wifi enabled, skip this to prevent both the "Connecting..." UI to pop-up, -- *and* a duplicate NetworkConnected event from firing... - if Device:hasWifiManager() and NetworkMgr.wifi_was_on and G_reader_settings:isTrue("auto_restore_wifi") then + if Device:hasWifiRestore() and NetworkMgr.wifi_was_on and G_reader_settings:isTrue("auto_restore_wifi") then return end diff --git a/spec/unit/network_manager_spec.lua b/spec/unit/network_manager_spec.lua index 0c1550673..e0e795dd7 100644 --- a/spec/unit/network_manager_spec.lua +++ b/spec/unit/network_manager_spec.lua @@ -46,6 +46,9 @@ describe("network_manager module", function() self:obtainIP() end end + function Device:hasWifiRestore() + return true + end end) it("should restore wifi in init if wifi was on", function() @@ -72,6 +75,7 @@ describe("network_manager module", function() teardown(function() function Device:initNetworkManager() end + function Device:hasWifiRestore() return false end package.loaded["ui/network/manager"] = nil end) end)