From 4c4cfa2db0f827f7ebb1c232e4bc37f726a20f3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Vesel=C3=BD?= Date: Tue, 25 Jun 2024 21:17:36 +0200 Subject: [PATCH] Kindle: add wifi selector (#12056) * Kindle: Implement a NetworkMgr backend loosely based on WpaClient in order to allow feature-parity with hasWifiManager platforms. This involves dealing with the native wifid over lipc (the native IPC system, based on DBus), through custom Lua bindings (https://github.com/notmarek/openlipclua), since the stock ones lack support for the needed hasharray data type. * NetworkMgr: Clear up leftover hallucinations from #10669, making `enableWifi` much simpler (and much more similar to `turnOnWifiAndWaitForConnection`). * NetworkMgr: Made it clearer that `turnOnWifi` implementations *must* deal with `complete_callback`, as part of the aforementioned changes mean that it's *always* wrapped in a connectivity check, and we need that for proper event signaling. * Android, Emu: Run `complete_callback` properly in `turnOnWifi`. * Kindle: Support `powerd:isCharged()` on the PW2 (yes, this is random, it just happened to be my test device :D). * NetworkMgr:disableWifi: Properly tear down any potential ongoing connection attempt (e.g., connectivity check). * NetworkMgr:promptWifi: Make the "wifi enabled but not connected" popup clearer if there's an ongoing connection attempt, and gray out the "Connect" button in this case (as it would only lead to another "connection already in progress" popup anyway). * NetworkMgr:reconnectOrShowNetworkMenu: Make *total* scanning failures fatal (they will lead to an immediate wifi teardown). * NetworkMgr:reconnectOrShowNetworkMenu: Clear up the long-press behavior (which *always* shows the network list popup) so that it doesn't weirdly break all the things (technical term!). * NetworkMgr:reconnectOrShowNetworkMenu: When we manage to connect to a preferred network on our own *before* showing the network list, make sure it is flagged as "connected" in said list. * NetworkMgr:reconnectOrShowNetworkMenu: Make connection failures fatal in non-interactive workflows (they'll lead to a wifi teardown). * NetworkSetting (the aforementioned network list widget): Clear NetworkMgr's "connection pending" flag on dismiss when nothing else will (i.e., when there's no connectivity check ticking). --- base | 2 +- frontend/device/android/device.lua | 6 + frontend/device/kindle/device.lua | 272 ++++++++++++++++++++++-- frontend/device/kindle/powerd.lua | 2 +- frontend/device/sdl/device.lua | 9 +- frontend/ui/network/manager.lua | 241 ++++++++++++--------- frontend/ui/network/networklistener.lua | 2 +- frontend/ui/widget/networksetting.lua | 19 +- 8 files changed, 424 insertions(+), 129 deletions(-) diff --git a/base b/base index 171222f22..a9e32ffc7 160000 --- a/base +++ b/base @@ -1 +1 @@ -Subproject commit 171222f22686fb11eb611fa25ed91cb132a79b76 +Subproject commit a9e32ffc7c6ba1644a788f59fc228ba4d056571f diff --git a/frontend/device/android/device.lua b/frontend/device/android/device.lua index 4b4ed49ee..a0c403172 100644 --- a/frontend/device/android/device.lua +++ b/frontend/device/android/device.lua @@ -288,9 +288,15 @@ end function Device:initNetworkManager(NetworkMgr) function NetworkMgr:turnOnWifi(complete_callback) android.openWifiSettings() + if complete_callback then + complete_callback() + end end function NetworkMgr:turnOffWifi(complete_callback) android.openWifiSettings() + if complete_callback then + complete_callback() + end end function NetworkMgr:openSettings() diff --git a/frontend/device/kindle/device.lua b/frontend/device/kindle/device.lua index 78509a5ee..76fd37363 100644 --- a/frontend/device/kindle/device.lua +++ b/frontend/device/kindle/device.lua @@ -15,10 +15,181 @@ require("ffi/fbink_input_h") local function yes() return true end local function no() return false end -- luacheck: ignore +local function kindleGetSavedNetworks() + local haslipc, lipc = pcall(require, "libopenlipclua") -- use our lua lipc library with access to hasharray properties + local lipc_handle + if haslipc then + lipc_handle = lipc.open_no_name() + end + if lipc_handle then + local ha_input = lipc_handle:new_hasharray() -- an empty hash array since we only want to read + local ha_result = lipc_handle:access_hash_property("com.lab126.wifid", "profileData", ha_input) + local profiles = ha_result:to_table() + ha_result:destroy() + ha_input:destroy() + lipc_handle:close() + return profiles + end +end + +local function kindleGetCurrentProfile() + local haslipc, lipc = pcall(require, "libopenlipclua") -- use our lua lipc library with access to hasharray properties + local lipc_handle + if haslipc then + lipc_handle = lipc.open_no_name() + end + if lipc_handle then + local ha_input = lipc_handle:new_hasharray() -- an empty hash array since we only want to read + local ha_result = lipc_handle:access_hash_property("com.lab126.wifid", "currentEssid", ha_input) + local profile = ha_result:to_table()[1] -- theres only a single element + ha_input:destroy() + ha_result:destroy() + lipc_handle:close() + return profile + else + return nil + end +end + +local function kindleAuthenticateNetwork(essid) + local haslipc, lipc = pcall(require, "liblipclua") + local lipc_handle + if haslipc then + lipc_handle = lipc.init("com.github.koreader.networkmgr") + end + if lipc_handle then + lipc_handle:set_string_property("com.lab126.cmd", "ensureConnection", "wifi:" .. essid) + lipc_handle:close() + end +end + +local function kindleSaveNetwork(data) + local haslipc, lipc = pcall(require, "libopenlipclua") -- use our lua lipc library with access to hasharray properties + local lipc_handle + if haslipc then + lipc_handle = lipc.open_no_name() + end + if lipc_handle then + local profile = lipc_handle:new_hasharray() + profile:add_hash() + profile:put_string(0, "essid", data.ssid) + if string.find(data.flags, "WPA") then + profile:put_string(0, "secured", "yes") + profile:put_string(0, "psk", data.password) + profile:put_int(0, "store_nw_user_pref", 0) -- tells amazon we don't want them to have our password + else + profile:put_string(0, "secured", "no") + end + lipc_handle:access_hash_property("com.lab126.wifid", "createProfile", profile):destroy() -- destroy the returned empty ha + profile:destroy() + lipc_handle:close() + end +end + +local function kindleGetScanList() + local _ = require("gettext") + local haslipc, lipc = pcall(require, "libopenlipclua") -- use our lua lipc library with access to hasharray properties + local lipc_handle + if haslipc then + lipc_handle = lipc.open_no_name() + end + if lipc_handle then + if lipc_handle:get_string_property("com.lab126.wifid", "cmState") ~= "CONNECTED" then + local ha_input = lipc_handle:new_hasharray() + local ha_results = lipc_handle:access_hash_property("com.lab126.wifid", "scanList", ha_input) + if ha_results == nil then + -- Shouldn't really happen, access_hash_property will throw if LipcAccessHasharrayProperty failed + ha_input:destroy() + lipc_handle:close() + -- NetworkMgr will ask for a re-scan on seeing an empty table, the second attempt *should* work ;). + return {}, nil + end + local scan_result = ha_results:to_table() + ha_results:destroy() + ha_input:destroy() + lipc_handle:close() + if not scan_result then + -- e.g., to_table hit lha->ha == NULL + return {}, nil + else + return scan_result, nil + end + end + lipc_handle:close() + -- return a fake scan list containing only the currently connected profile :) + local profile = kindleGetCurrentProfile() + return { profile }, nil + else + logger.dbg("kindleGetScanList: Failed to acquire an anonymous lipc handle") + return nil, _("Unable to communicate with the Wi-Fi backend") + end +end + +local function kindleScanThenGetResults() + local _ = require("gettext") + local haslipc, lipc = pcall(require, "liblipclua") + local lipc_handle + if haslipc then + lipc_handle = lipc.init("com.github.koreader.networkmgr") + end + if not lipc_handle then + logger.dbg("kindleScanThenGetResults: Failed to acquire a lipc handle for NetworkMgr") + return nil, _("Unable to communicate with the Wi-Fi backend") + end + + lipc_handle:set_string_property("com.lab126.wifid", "scan", "") -- trigger a scan + + -- Mimic WpaClient:scanThenGetResults: block while waiting for the scan to finish. + -- Ideally, we'd do this via a poll/event workflow, but, eh', this is going to be good enough for now ;p. + -- For future reference, see `lipc-wait-event -m -s 0 -t com.lab126.wifid '*'` + --[[ + -- For a connection: + [00:00:04.675699] cmStateChange "PENDING" + [00:00:04.677402] scanning + [00:00:05.488043] scanComplete + [00:00:05.973188] cmConnected + [00:00:05.977862] cmStateChange "CONNECTED" + [00:00:05.980698] signalStrength "1/5" + [00:00:06.417549] cmConnected + + -- And a disconnection: + [00:01:34.094652] cmDisconnected + [00:01:34.096088] cmStateChange "NA" + [00:01:34.219802] signalStrength "0/5" + [00:01:34.221802] cmStateChange "READY" + [00:01:35.656375] cmIntfNotAvailable + [00:01:35.658710] cmStateChange "NA" + --]] + local done_scanning = false + local wait_cnt = 80 -- 20s in chunks on 250ms + while wait_cnt > 0 do + local scan_state = lipc_handle:get_string_property("com.lab126.wifid", "scanState") + + if scan_state == "idle" then + done_scanning = true + logger.dbg("kindleScanThenGetResults: Wi-Fi scan took", (80 - wait_cnt) * 0.25, "seconds") + break + end + + -- Whether it's still "scanning" or in whatever other state we don't know about, + -- try again until it says it's done. + wait_cnt = wait_cnt - 1 + C.usleep(250 * 1000) + end + lipc_handle:close() + + if done_scanning then + return kindleGetScanList() + else + logger.warn("kindleScanThenGetResults: Timed-out scanning for Wi-Fi networks") + return nil, _("Scanning for Wi-Fi networks timed out") + end +end + local function kindleEnableWifi(toggle) local haslipc, lipc = pcall(require, "liblipclua") - local lipc_handle = nil - if haslipc and lipc then + local lipc_handle + if haslipc then lipc_handle = lipc.init("com.github.koreader.networkmgr") end if lipc_handle then @@ -44,8 +215,8 @@ end --[[ local function isWifiUp() local haslipc, lipc = pcall(require, "liblipclua") - local lipc_handle = nil - if haslipc and lipc then + local lipc_handle + if haslipc then lipc_handle = lipc.init("com.github.koreader.networkmgr") end if lipc_handle then @@ -77,7 +248,7 @@ Test if a kindle device is flagged as a Special Offers device (i.e., ad supporte local function isSpecialOffers() -- Look at the current blanket modules to see if the SO screensavers are enabled... local haslipc, lipc = pcall(require, "liblipclua") - if not (haslipc and lipc) then + if not haslipc then logger.warn("could not load liblipclua:", lipc) return true end @@ -115,7 +286,7 @@ end local function frameworkStopped() if os.getenv("STOP_FRAMEWORK") == "yes" then local haslipc, lipc = pcall(require, "liblipclua") - if not (haslipc and lipc) then + if not haslipc then logger.warn("could not load liblibclua") return end @@ -168,13 +339,19 @@ local Kindle = Generic:extend{ } function Kindle:initNetworkManager(NetworkMgr) - function NetworkMgr:turnOnWifi(complete_callback) - kindleEnableWifi(1) - -- NOTE: As we defer the actual work to lipc, - -- we have no guarantee the Wi-Fi state will have changed by the time kindleEnableWifi returns, - -- so, delay the callback until we at least can ensure isConnect is true. - if complete_callback then - NetworkMgr:scheduleConnectivityCheck(complete_callback) + local haslipc, _ = pcall(require, "liblipclua") + if haslipc then + function NetworkMgr:turnOnWifi(complete_callback, interactive) + kindleEnableWifi(1) + return self:reconnectOrShowNetworkMenu(complete_callback, interactive) + end + else + -- If we can't use the lipc Lua bindings, we can't support any kind of interactive Wi-Fi UI... + function NetworkMgr:turnOnWifi(complete_callback, interactive) + kindleEnableWifi(1) + if complete_callback then + complete_callback() + end end end @@ -194,6 +371,66 @@ function Kindle:initNetworkManager(NetworkMgr) kindleEnableWifi(1) end + function NetworkMgr:authenticateNetwork(network) + kindleAuthenticateNetwork(network.ssid) + return true, nil + end + + -- NOTE: We don't have a disconnectNetwork & releaseIP implementation, + -- which means the "disconnect" button in NetworkSetting kind of does nothing ;p. + + function NetworkMgr:saveNetwork(setting) + kindleSaveNetwork(setting) + end + + function NetworkMgr:getNetworkList() + local scan_list, err = kindleScanThenGetResults() + if not scan_list then + return nil, err + end + + -- trick ui/widget/networksetting into displaying the correct signal strength icon + local qualities = { + [1] = 0, + [2] = 6, + [3] = 31, + [4] = 56, + [5] = 81 + } + + local network_list = {} + local saved_profiles = kindleGetSavedNetworks() + local current_profile = kindleGetCurrentProfile() + for _, network in ipairs(scan_list) do + local password = nil + if network.known == "yes" then + for _, p in ipairs(saved_profiles) do + -- Earlier FW do not have a netid field at all, fall back to essid as that's the best we'll get (we don't get bssid either)... + if (p.netid and p.netid == network.netid) or (p.netid == nil and p.essid == network.essid) then + password = p.psk + break + end + end + end + table.insert(network_list, { + -- signal_level is purely for fun, the widget doesn't do anything with it. The WpaClient backend stores the raw dBa attenuation in it. + signal_level = string.format("%d/%d", network.signal, network.signal_max), + signal_quality = qualities[network.signal], + -- See comment above about netid being unfortunately optional... + connected = (current_profile.netid and current_profile.netid ~= -1 and current_profile.netid == network.netid) + or (current_profile.netid == nil and current_profile.essid ~= "" and current_profile.essid == network.essid), + flags = network.key_mgmt, + ssid = network.essid ~= "" and network.essid, + password = password, + }) + end + return network_list, nil + end + + function NetworkMgr:getCurrentNetwork() + return { ssid = kindleGetCurrentProfile().essid } + end + NetworkMgr.isWifiOn = NetworkMgr.sysfsWifiOn NetworkMgr.isConnected = NetworkMgr.ifHasAnAddress end @@ -835,6 +1072,7 @@ function KindlePaperWhite2:init() fl_intensity_file = "/sys/class/backlight/max77696-bl/brightness", batt_capacity_file = "/sys/devices/system/wario_battery/wario_battery0/battery_capacity", is_charging_file = "/sys/devices/system/wario_charger/wario_charger0/charging", + batt_status_file = "/sys/class/power_supply/max77696-battery/status", hall_file = "/sys/devices/system/wario_hall/wario_hall0/hall_enable", } @@ -991,7 +1229,7 @@ function KindleOasis:init() --- @note See comments in KindleOasis2:init() for details. local haslipc, lipc = pcall(require, "liblipclua") - if haslipc and lipc then + if haslipc then local lipc_handle = lipc.init("com.github.koreader.screen") if lipc_handle then local orientation_code = lipc_handle:get_string_property( @@ -1102,7 +1340,7 @@ function KindleOasis2:init() -- NOTE: It'd take some effort to actually start KOReader while in a LANDSCAPE orientation, -- since they're only exposed inside the stock reader, and not the Home/KUAL Booklets. local haslipc, lipc = pcall(require, "liblipclua") - if haslipc and lipc then + if haslipc then local lipc_handle = lipc.init("com.github.koreader.screen") if lipc_handle then local orientation_code = lipc_handle:get_string_property( @@ -1170,7 +1408,7 @@ function KindleOasis3:init() --- @note The same quirks as on the Oasis 2 apply ;). local haslipc, lipc = pcall(require, "liblipclua") - if haslipc and lipc then + if haslipc then local lipc_handle = lipc.init("com.github.koreader.screen") if lipc_handle then local orientation_code = lipc_handle:get_string_property( @@ -1313,7 +1551,7 @@ function KindleScribe:init() --- @note The same quirks as on the Oasis 2 and 3 apply ;). local haslipc, lipc = pcall(require, "liblipclua") - if haslipc and lipc then + if haslipc then local lipc_handle = lipc.init("com.github.koreader.screen") if lipc_handle then local orientation_code = lipc_handle:get_string_property( diff --git a/frontend/device/kindle/powerd.lua b/frontend/device/kindle/powerd.lua index 24fd0e30c..8f2f5886c 100644 --- a/frontend/device/kindle/powerd.lua +++ b/frontend/device/kindle/powerd.lua @@ -14,7 +14,7 @@ local KindlePowerD = BasePowerD:new{ function KindlePowerD:init() local haslipc, lipc = pcall(require, "liblipclua") - if haslipc and lipc then + if haslipc then self.lipc_handle = lipc.init("com.github.koreader.kindlepowerd") end diff --git a/frontend/device/sdl/device.lua b/frontend/device/sdl/device.lua index 12d8f2d36..6930de770 100644 --- a/frontend/device/sdl/device.lua +++ b/frontend/device/sdl/device.lua @@ -417,20 +417,23 @@ end -- fake network manager for the emulator function Emulator:initNetworkManager(NetworkMgr) - local connectionChangedEvent = function() + local connectionChangedEvent = function(complete_callback) if G_reader_settings:nilOrTrue("emulator_fake_wifi_connected") then UIManager:broadcastEvent(Event:new("NetworkConnected")) else UIManager:broadcastEvent(Event:new("NetworkDisconnected")) end + if complete_callback then + complete_callback() + end end function NetworkMgr:turnOffWifi(complete_callback) G_reader_settings:flipNilOrTrue("emulator_fake_wifi_connected") - UIManager:scheduleIn(2, connectionChangedEvent) + UIManager:scheduleIn(2, connectionChangedEvent, complete_callback) end function NetworkMgr:turnOnWifi(complete_callback) G_reader_settings:flipNilOrTrue("emulator_fake_wifi_connected") - UIManager:scheduleIn(2, connectionChangedEvent) + UIManager:scheduleIn(2, connectionChangedEvent, complete_callback) end function NetworkMgr:isWifiOn() return G_reader_settings:nilOrTrue("emulator_fake_wifi_connected") diff --git a/frontend/ui/network/manager.lua b/frontend/ui/network/manager.lua index 826f25851..8559215fb 100644 --- a/frontend/ui/network/manager.lua +++ b/frontend/ui/network/manager.lua @@ -156,6 +156,9 @@ end -- as opposed to an indirect one (like the beforeWifiAction framework). -- It allows the backend to skip UI prompts for non-interactive use-cases. -- NOTE: May optionally return a boolean, e.g., return false if the backend can guarantee the connection failed. +-- NOTE: These *must* run or appropriately forward complete_callback (e.g., to reconnectOrShowNetworkMenu), +-- as said callback is responsible for schedulig the connectivity check, +-- which, in turn, is responsible for the Event signaling! function NetworkMgr:turnOnWifi(complete_callback, interactive) end function NetworkMgr:turnOffWifi(complete_callback) end -- This function returns the current status of the WiFi radio @@ -175,8 +178,8 @@ end function NetworkMgr:getNetworkInterfaceName() end function NetworkMgr:getNetworkList() end function NetworkMgr:getCurrentNetwork() end -function NetworkMgr:authenticateNetwork() end -function NetworkMgr:disconnectNetwork() end +function NetworkMgr:authenticateNetwork(network) end +function NetworkMgr:disconnectNetwork(network) end -- NOTE: This is currently only called on hasWifiManager platforms! function NetworkMgr:obtainIP() end function NetworkMgr:releaseIP() end @@ -317,55 +320,47 @@ function NetworkMgr:canResolveHostnames() end -- Wrappers around turnOnWifi & turnOffWifi with proper Event signaling -function NetworkMgr:enableWifi(wifi_cb, connectivity_cb, connectivity_widget, interactive) - local status = self:requestToTurnOnWifi(wifi_cb, interactive) +function NetworkMgr:enableWifi(wifi_cb, interactive) + -- NOTE: Let the backend run the wifi_cb via a connectivity check once it's *actually* attempted a connection, + -- as it knows best when that actually happens (especially reconnectOrShowNetworkMenu), unlike us. + local connectivity_cb = function() + -- NOTE: We *could* arguably have multiple connectivity checks running concurrently, + -- but only having a single one running makes things somewhat easier to follow... + if self.pending_connectivity_check then + self:unscheduleConnectivityCheck() + end + + -- This will handle sending the proper Event, manage wifi_was_on, as well as tearing down Wi-Fi in case of failures. + self:scheduleConnectivityCheck(wifi_cb) + end + + -- Some implementations (usually, hasWifiManager) can report whether they were successful + local status = self:requestToTurnOnWifi(connectivity_cb, interactive) -- If turnOnWifi failed, abort early if status == false then logger.warn("NetworkMgr:enableWifi: Connection failed!") self:_abortWifiConnection() return false elseif status == EBUSY then + -- NOTE: This means turnOnWifi was *not* called (this time). logger.warn("NetworkMgr:enableWifi: A previous connection attempt is still ongoing!") - -- We warn, but do keep going on with scheduling the callback iff it was interactive. - -- If it wasn't, it might have been from a beforeWifiAction, and, much like in turnOnWifiAndWaitForConnection, - -- we don't want to risk rescheduling the same thing over and over again. + -- We don't really have a great way of dealing with the wifi_cb in this case, + -- so, much like in turnOnWifiAndWaitForConnection, we'll just drop it... + -- We don't want to run multiple concurrent connectivity checks, + -- which means we'd need to unschedule the pending one, which would effectively rewind the timer, + -- which we don't want, especially if we're non-interactive, + -- as that would risk rescheduling the same thing over and over again... + if wifi_cb then + logger.warn("NetworkMgr:enableWifi: We've had to drop wifi_cb:", wifi_cb) + end + -- Make it more obvious to the user when interactive... if interactive then - -- Unlike the next branch, turnOnWifi was *not* called, so we don't need the extra checks. - self:scheduleConnectivityCheck(connectivity_cb, connectivity_widget) - else - -- No connectivity check to handle that for us, so close the widget *now* - if connectivity_widget then - UIManager:close(connectivity_widget) - end + UIManager:show(InfoMessage:new{ + text = _("A previous connection attempt is still ongoing, this one will be ignored!"), + timeout = 3, + }) end return - else - -- Some turnOnWifi implementations may fire a connectivity check, - -- but we *need* our own, because of the callback & widget passing, - -- as we might have been called by the "prompt" beforeWifiAction... - -- NOTE: We *could* arguably have multiple connectivity checks running concurrently, - -- but only having a single one running makes things somewhat easier to follow... - -- NOTE: Also, most of the platforms that use a connectivity check in turnOnWifi do it to handle wifi_cb, - -- so we'll want to preserve it by wrapping both possible callbacks in a single function... - local wrapped_cb - if self.pending_connectivity_check then - self:unscheduleConnectivityCheck() - - wrapped_cb = function() - if wifi_cb then - wifi_cb() - end - if connectivity_cb then - connectivity_cb() - end - end - else - -- If the turnOnWifi implementation didn't rely on a connectivity check, assume wifi_cb was already run. - wrapped_cb = connectivity_cb - end - - -- This will handle sending the proper Event, manage wifi_was_on, as well as tearing down Wi-Fi in case of failures. - self:scheduleConnectivityCheck(wrapped_cb, connectivity_widget) end return true @@ -379,6 +374,17 @@ function NetworkMgr:disableWifi(cb, interactive) end end UIManager:broadcastEvent(Event:new("NetworkDisconnecting")) + + -- NOTE: This is a subset of _abortWifiConnection, in case we disable wifi during a connection attempt. + -- Cancel any pending connectivity check, because it wouldn't achieve anything + self:unscheduleConnectivityCheck() + -- Make sure we don't have an async script running... + if Device:hasWifiRestore() and not Device:isKindle() then + os.execute("pkill -TERM restore-wifi-async.sh 2>/dev/null") + end + -- Can't be connecting since we're killing Wi-Fi ;) + self.pending_connection = false + self:turnOffWifi(complete_callback) if interactive then @@ -396,7 +402,7 @@ function NetworkMgr:toggleWifiOn(complete_callback, long_press, interactive) self.wifi_toggle_long_press = long_press - self:enableWifi(complete_callback, nil, nil, interactive) + self:enableWifi(complete_callback, interactive) UIManager:close(toggle_im) end @@ -445,13 +451,26 @@ end -- NOTE: Currently only has a single caller, the Menu entry, so it's always flagged as interactive function NetworkMgr:promptWifi(complete_callback, long_press, interactive) + local text = _("Wi-Fi is enabled, but you're currently not connected to a network.") + -- Detail whether there's an attempt and/or a connectivity check in progress. + if self.pending_connection then + -- NOTE: Incidentally, this means that tapping Connect would yield EBUSY, so we gray it out... + text = text .. "\n" .. _("Please note that a connection attempt is currently in progress!") + end + if self.pending_connectivity_check then + text = text .. "\n" .. _("KOReader is currently waiting for connectivity. This may take up to 45s, so you may just want to try again later.") + end + text = text .. "\n" .. _("How would you like to proceed?") UIManager:show(MultiConfirmBox:new{ - text = _("Wi-Fi is enabled, but you're currently not connected to a network.\nHow would you like to proceed?"), + text = text, + -- "Cancel" could be construed as "cancel the current attempt", which is not what this does ;p. + cancel_text = _("Dismiss"), choice1_text = _("Turn Wi-Fi off"), choice1_callback = function() self:toggleWifiOff(complete_callback, interactive) end, choice2_text = _("Connect"), + choice2_enabled = not self.pending_connection, choice2_callback = function() self:toggleWifiOn(complete_callback, long_press, interactive) end, @@ -474,10 +493,15 @@ function NetworkMgr:turnOnWifiAndWaitForConnection(callback) UIManager:show(info) UIManager:forceRePaint() - -- NOTE: This is a slightly tweaked variant of enableWifi, because of our peculiar connectivityCheck usage... - -- Some implementations (usually, hasWifiManager) can report whether they were successfull - local status = self:requestToTurnOnWifi() - -- If turnOnWifi failed, abort early + -- NOTE: This is a slightly tweaked variant of enableWifi, in order to handle our info widget... + local connectivity_cb = function() + if self.pending_connectivity_check then + self:unscheduleConnectivityCheck() + end + + self:scheduleConnectivityCheck(callback, info) + end + local status = self:requestToTurnOnWifi(connectivity_cb) if status == false then logger.warn("NetworkMgr:turnOnWifiAndWaitForConnection: Connection failed!") self:_abortWifiConnection() @@ -489,16 +513,8 @@ function NetworkMgr:turnOnWifiAndWaitForConnection(callback) -- but it's just plain saner to just abort here, as we'd risk calling the same thing over and over... UIManager:close(info) return - else - -- Some turnOnWifi implementations may fire a connectivity check, - -- but we *need* our own, because of the callback & widget passing. - if self.pending_connectivity_check then - self:unscheduleConnectivityCheck() - end end - self:scheduleConnectivityCheck(callback, info) - return info end @@ -1002,7 +1018,7 @@ function NetworkMgr:getDismissScanMenuTable() return { text = _("Dismiss Wi-Fi scan popup after connection"), checked_func = function() return G_reader_settings:nilOrTrue("auto_dismiss_wifi_scan") end, - enabled_func = function() return Device:hasWifiManager() and not Device:isEmulator() end, + enabled_func = function() return Device:hasWifiManager() or Device:isEmulator() end, callback = function() G_reader_settings:flipNilOrTrue("auto_dismiss_wifi_scan") end, } end @@ -1041,7 +1057,7 @@ function NetworkMgr:reconnectOrShowNetworkMenu(complete_callback, interactive) UIManager:close(info) if network_list == nil then UIManager:show(InfoMessage:new{text = err}) - return + return false end -- NOTE: Fairly hackish workaround for #4387, -- rescan if the first scan appeared to yield an empty list. @@ -1051,66 +1067,75 @@ function NetworkMgr:reconnectOrShowNetworkMenu(complete_callback, interactive) network_list, err = self:getNetworkList() if network_list == nil then UIManager:show(InfoMessage:new{text = err}) - return + return false end end table.sort(network_list, function(l, r) return l.signal_quality > r.signal_quality end) - local success = false - if self.wifi_toggle_long_press then - self.wifi_toggle_long_press = nil - else - local ssid - -- We need to do two passes, as we may have *both* an already connected network (from the global wpa config), - -- *and* preferred networks, and if the prferred networks have a better signal quality, - -- they'll be sorted *earlier*, which would cause us to try to associate to a different AP than - -- what wpa_supplicant is already trying to do... - for dummy, network in ipairs(network_list) do - if network.connected then - -- On platforms where we use wpa_supplicant (if we're calling this, we are), - -- the invocation will check its global config, and if an AP configured there is reachable, - -- it'll already have connected to it on its own. - success = true - ssid = network.ssid - break - end + -- true: we're connected; false: things went kablooey; nil: we don't know yet (e.g., interactive) + -- NOTE: false *will* lead enableWifi to kill Wi-Fi via _abortWifiConnection! + local success + local ssid + -- We need to do two passes, as we may have *both* an already connected network (from the global wpa config), + -- *and* preferred networks, and if the preferred networks have a better signal quality, + -- they'll be sorted *earlier*, which would cause us to try to associate to a different AP than + -- what wpa_supplicant is already trying to do... + -- NOTE: We can't really skip this, even when we force showing the scan list, + -- as the backend *will* connect in the background regardless of what we do, + -- and we *need* our complete_callback to run, + -- which would not be the case if we were to just dismiss the scan list, + -- especially since it wouldn't show as "connected" in this case... + for dummy, network in ipairs(network_list) do + if network.connected then + -- On platforms where we use wpa_supplicant (if we're calling this, we probably are), + -- the invocation will check its global config, and if an AP configured there is reachable, + -- it'll already have connected to it on its own. + success = true + ssid = network.ssid + break end + end - -- Next, look for our own prferred networks... - local err_msg = _("Connection failed") - if not success then - for dummy, network in ipairs(network_list) do - if network.password then - -- If we hit a preferred network and we're not already connected, - -- attempt to connect to said preferred network.... - success, err_msg = self:authenticateNetwork(network) - if success then - ssid = network.ssid - break - end + -- Next, look for our own preferred networks... + local err_msg = _("Connection failed") + if not success then + for dummy, network in ipairs(network_list) do + if network.password then + -- If we hit a preferred network and we're not already connected, + -- attempt to connect to said preferred network.... + success, err_msg = self:authenticateNetwork(network) + if success then + ssid = network.ssid + network.connected = true + break end end end + end - if success then - self:obtainIP() - if complete_callback then - complete_callback() - end - UIManager:show(InfoMessage:new{ - tag = "NetworkMgr", -- for crazy KOSync purposes - text = T(_("Connected to network %1"), BD.wrap(util.fixUtf8(ssid, "�"))), - timeout = 3, - }) - else - UIManager:show(InfoMessage:new{ - text = err_msg, - timeout = 3, - }) + if success then + self:obtainIP() + if complete_callback then + complete_callback() end + -- NOTE: On Kindle, we don't have an explicit obtainIP implementation, + -- and authenticateNetwork is async, + -- so we don't *actually* have a full connection yet, + -- we've just *started* connecting to the requested network... + UIManager:show(InfoMessage:new{ + tag = "NetworkMgr", -- for crazy KOSync purposes + text = T(_(Device:isKindle() and "Connecting to network %1…" or "Connected to network %1"), BD.wrap(util.fixUtf8(ssid, "�"))), + timeout = 3, + }) + else + UIManager:show(InfoMessage:new{ + text = err_msg, + timeout = 3, + }) end + if not success then -- NOTE: Also supports a disconnect_callback, should we use it for something? -- Tearing down Wi-Fi completely when tapping "disconnect" would feel a bit harsh, though... @@ -1120,9 +1145,19 @@ function NetworkMgr:reconnectOrShowNetworkMenu(complete_callback, interactive) network_list = network_list, connect_callback = complete_callback, }) + else + -- Let enableWifi tear it all down when we're non-interactive + success = false end + elseif self.wifi_toggle_long_press then + -- Success, but we asked for the list, show it w/o any callbacks. + -- (We *could* potentially setup a pair of callbacks that just send Network* events, but it's probably not worth it). + UIManager:show(require("ui/widget/networksetting"):new{ + network_list = network_list, + }) end + self.wifi_toggle_long_press = nil return success end diff --git a/frontend/ui/network/networklistener.lua b/frontend/ui/network/networklistener.lua index 2e783eaef..3a0d0cf1f 100644 --- a/frontend/ui/network/networklistener.lua +++ b/frontend/ui/network/networklistener.lua @@ -30,7 +30,7 @@ local function enableWifi() -- 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(nil, nil, nil, true) -- flag it as interactive + NetworkMgr:enableWifi(nil, true) -- flag it as interactive UIManager:close(toggle_im) end diff --git a/frontend/ui/widget/networksetting.lua b/frontend/ui/widget/networksetting.lua index eb16cccd7..2ed25ddb9 100644 --- a/frontend/ui/widget/networksetting.lua +++ b/frontend/ui/widget/networksetting.lua @@ -483,6 +483,14 @@ function NetworkSetting:init() } end + -- If the backend is already authenticated, + -- and NetworkMgr:reconnectOrShowNetworkMenu somehow missed it, + -- expedite the process. + -- Yes, this is a very old codepath that's hardly ever exercised anymore... + if not self.connect_callback then + return + end + UIManager:nextTick(function() local connected_item = self:getConnectedItem() if connected_item ~= nil then @@ -494,9 +502,7 @@ function NetworkSetting:init() text = T(_("Connected to network %1"), BD.wrap(connected_item.display_ssid)), timeout = 3, }) - if self.connect_callback then - self.connect_callback() - end + self.connect_callback() end end) end @@ -516,4 +522,11 @@ function NetworkSetting:onTapClose(arg, ges_ev) end end +function NetworkSetting:onCloseWidget() + -- If we don't have a connectivity check ticking, assume we're done with this connection attempt *now* + if not NetworkMgr.pending_connectivity_check then + NetworkMgr.pending_connection = false + end +end + return NetworkSetting