Add keepsBluetoothAlive hook to Activity base class
Introduces a virtual method allowing activities to indicate whether they need the BLE stack to remain active. This enables selective teardown of Bluetooth to free heap memory, with the Bluetooth settings screen being able to override this for pairing and scanning functionality.
This commit is contained in:
@@ -44,6 +44,14 @@ class Activity {
|
||||
virtual bool skipLoopDelay() { return false; }
|
||||
virtual bool preventAutoSleep() { return false; }
|
||||
virtual bool isReaderActivity() const { return false; }
|
||||
// True if this activity needs the BLE stack resident (beyond the readers, which are
|
||||
// covered by isReaderActivity()). The Bluetooth settings screen overrides this so
|
||||
// pairing/scanning works there. Everywhere else BLE is torn down to free heap.
|
||||
virtual bool keepsBluetoothAlive() const { return false; }
|
||||
// Ask the activity to make its next render a full ghost-cleanup (HALF) refresh rather
|
||||
// than a fast/partial one. Used after drawing a transient popup over grayscale content
|
||||
// (e.g. the "BT Connecting..." popup over a reader page) so it clears without ghosting.
|
||||
virtual void requestGhostCleanup() {}
|
||||
virtual ScreenshotInfo getScreenshotInfo() const { return {}; }
|
||||
|
||||
// Start a new activity without destroying the current one
|
||||
|
||||
@@ -254,6 +254,17 @@ bool ActivityManager::isReaderActivity() const {
|
||||
(currentActivity && currentActivity->isReaderActivity());
|
||||
}
|
||||
|
||||
void ActivityManager::requestGhostCleanup() {
|
||||
if (currentActivity) currentActivity->requestGhostCleanup();
|
||||
}
|
||||
|
||||
bool ActivityManager::bluetoothShouldBeActive() const {
|
||||
const auto wants = [](const auto& activity) {
|
||||
return activity && (activity->isReaderActivity() || activity->keepsBluetoothAlive());
|
||||
};
|
||||
return std::any_of(stackActivities.begin(), stackActivities.end(), wants) || wants(currentActivity);
|
||||
}
|
||||
|
||||
bool ActivityManager::skipLoopDelay() const { return currentActivity && currentActivity->skipLoopDelay(); }
|
||||
|
||||
ScreenshotInfo ActivityManager::getScreenshotInfo() const {
|
||||
|
||||
@@ -101,6 +101,9 @@ class ActivityManager {
|
||||
|
||||
bool preventAutoSleep() const;
|
||||
bool isReaderActivity() const;
|
||||
// True if BLE should be resident for the current context: any reader (page-turner
|
||||
// input) or the Bluetooth settings screen (pairing) is on the stack.
|
||||
bool bluetoothShouldBeActive() const;
|
||||
bool skipLoopDelay() const;
|
||||
ScreenshotInfo getScreenshotInfo() const;
|
||||
|
||||
@@ -108,6 +111,9 @@ class ActivityManager {
|
||||
// Otherwise, it will be deferred until the end of the current loop iteration.
|
||||
void requestUpdate(bool immediate = false);
|
||||
|
||||
// Ask the current activity to make its next render a ghost-cleanup (HALF) refresh.
|
||||
void requestGhostCleanup();
|
||||
|
||||
// Trigger a render and block until it completes.
|
||||
// Must NOT be called from the render task or while holding a RenderLock.
|
||||
void requestUpdateAndWait();
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <Logging.h>
|
||||
#include <WiFi.h>
|
||||
|
||||
#include "BleInput.h"
|
||||
#include "CrossPointSettings.h"
|
||||
#include "MappedInputManager.h"
|
||||
#include "WifiCredentialStore.h"
|
||||
@@ -93,6 +94,11 @@ void WifiSelectionActivity::startWifiScan() {
|
||||
networks.clear();
|
||||
requestUpdate();
|
||||
|
||||
// Free the BLE stack before bringing WiFi up: the C3 has one radio and the two
|
||||
// stacks can't both fit in heap. Reachable from the reader via KOReader sync, where
|
||||
// BLE is still resident; a no-op when BLE is already off (launched from Settings).
|
||||
bleinput::stop();
|
||||
|
||||
// Set WiFi mode to station
|
||||
WiFi.mode(WIFI_STA);
|
||||
WiFi.disconnect();
|
||||
@@ -211,6 +217,7 @@ void WifiSelectionActivity::attemptConnection() {
|
||||
connectionError.clear();
|
||||
requestUpdate();
|
||||
|
||||
bleinput::stop(); // free the BLE stack before WiFi (shared C3 radio, tight heap)
|
||||
WiFi.persistent(false); // Credentials are managed by WifiCredentialStore; suppress SDK NVS auto-connect
|
||||
WiFi.mode(WIFI_STA);
|
||||
WiFi.disconnect(true, true); // Abort any in-progress SDK auto-connect and clear NVS-saved SSID
|
||||
|
||||
@@ -875,19 +875,11 @@ void EpubReaderActivity::render(RenderLock&& lock) {
|
||||
built = buildSection();
|
||||
const bool bleOk = bleinput::ensureStarted();
|
||||
LOG_INF("ERS", "BLE restart after build: begin=%d", bleOk);
|
||||
if (bleOk) {
|
||||
// Show a connecting popup and give the bonded remote a moment to re-link so the
|
||||
// user isn't left with an unresponsive remote and a silent pause. Bounded: a
|
||||
// remote that needs a button press to re-advertise won't stall reading — it
|
||||
// reconnects in the background after we return. The popup is drawn once (e-ink
|
||||
// holds it) and the page render overwrites it when we resume.
|
||||
GUI.drawPopup(renderer, tr(STR_BT_CONNECTING_POPUP));
|
||||
const unsigned long deadline = millis() + 4000;
|
||||
while (!BleHid.isConnected() && millis() < deadline) {
|
||||
BleHid.poll();
|
||||
delay(50);
|
||||
}
|
||||
}
|
||||
// Hold the "BT Connecting..." popup until the remote re-links, then force the
|
||||
// page render below onto the ghost-cleanup (HALF) path so the popup clears
|
||||
// without ghosting the grayscale page.
|
||||
bleinput::showConnectingUntilLinked(renderer, mappedInput);
|
||||
requestGhostCleanup();
|
||||
}
|
||||
if (!built) {
|
||||
LOG_ERR("ERS", "Failed to persist page data to SD");
|
||||
|
||||
@@ -86,6 +86,7 @@ class EpubReaderActivity final : public Activity {
|
||||
void loop() override;
|
||||
void render(RenderLock&& lock) override;
|
||||
bool isReaderActivity() const override { return true; }
|
||||
void requestGhostCleanup() override { pagesUntilFullRefresh = 1; }
|
||||
ScreenshotInfo getScreenshotInfo() const override;
|
||||
CrossPointPosition getCurrentPosition() const;
|
||||
};
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
#include <GfxRenderer.h>
|
||||
#include <I18n.h>
|
||||
|
||||
#include "BleInput.h"
|
||||
#include "CrossPointSettings.h"
|
||||
#include "MappedInputManager.h"
|
||||
#include "components/UITheme.h"
|
||||
@@ -80,13 +79,10 @@ void EpubReaderMenuActivity::loop() {
|
||||
}
|
||||
|
||||
if (selectedAction == MenuAction::TOGGLE_BLUETOOTH) {
|
||||
// Toggle in place and stay in the menu (no reader re-render needed).
|
||||
// Just flip the preference and stay in the menu. The main-loop lifecycle check
|
||||
// brings the BLE stack up/down to match (and shows the "BT Connecting..." popup),
|
||||
// so start/stop has a single owner.
|
||||
SETTINGS.bluetoothEnabled = SETTINGS.bluetoothEnabled ? 0 : 1;
|
||||
if (SETTINGS.bluetoothEnabled) {
|
||||
bleinput::ensureStarted();
|
||||
} else {
|
||||
bleinput::stop();
|
||||
}
|
||||
SETTINGS.saveToFile();
|
||||
requestUpdate();
|
||||
return;
|
||||
|
||||
@@ -49,5 +49,6 @@ class TxtReaderActivity final : public Activity {
|
||||
void loop() override;
|
||||
void render(RenderLock&&) override;
|
||||
bool isReaderActivity() const override { return true; }
|
||||
void requestGhostCleanup() override { pagesUntilFullRefresh = 1; }
|
||||
ScreenshotInfo getScreenshotInfo() const override;
|
||||
};
|
||||
|
||||
@@ -41,5 +41,6 @@ class XtcReaderActivity final : public Activity {
|
||||
void loop() override;
|
||||
void render(RenderLock&&) override;
|
||||
bool isReaderActivity() const override { return true; }
|
||||
void requestGhostCleanup() override { pagesUntilFullRefresh = 1; }
|
||||
ScreenshotInfo getScreenshotInfo() const override;
|
||||
};
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
#include <cstdio>
|
||||
|
||||
#include "BleButtonMapActivity.h"
|
||||
#include "BleInput.h"
|
||||
#include "CrossPointSettings.h"
|
||||
#include "MappedInputManager.h"
|
||||
#include "components/UITheme.h"
|
||||
@@ -62,12 +61,9 @@ void BluetoothSettingsActivity::handleMenuConfirm() {
|
||||
const Action action = menuRows[menuIndex].action;
|
||||
switch (action) {
|
||||
case Action::ToggleBt:
|
||||
// Flip the preference only; the main-loop lifecycle check starts/stops the BLE
|
||||
// stack to match (and shows the "BT Connecting..." popup). Single owner.
|
||||
SETTINGS.bluetoothEnabled = SETTINGS.bluetoothEnabled ? 0 : 1;
|
||||
if (SETTINGS.bluetoothEnabled) {
|
||||
bleinput::ensureStarted();
|
||||
} else {
|
||||
bleinput::stop();
|
||||
}
|
||||
SETTINGS.saveToFile();
|
||||
rebuildMenuRows();
|
||||
requestUpdate();
|
||||
@@ -238,14 +234,18 @@ void BluetoothSettingsActivity::render(RenderLock&&) {
|
||||
},
|
||||
true);
|
||||
} else if (view == View::Scan) {
|
||||
// Free2/3 remotes only advertise in the right slider mode — tell the user how.
|
||||
GUI.drawHelpText(renderer, Rect{0, topOffset, pageWidth, 16}, tr(STR_BT_FREE_HINT1));
|
||||
GUI.drawHelpText(renderer, Rect{0, topOffset + 16, pageWidth, 16}, tr(STR_BT_FREE_HINT2));
|
||||
const int scanTop = topOffset + 38;
|
||||
const int count = BleHid.deviceCount();
|
||||
if (count == 0) {
|
||||
GUI.drawHelpText(renderer, Rect{0, topOffset + metrics.verticalSpacing, pageWidth, 24},
|
||||
GUI.drawHelpText(renderer, Rect{0, scanTop, pageWidth, 24},
|
||||
BleHid.isScanning() ? tr(STR_SCANNING) : tr(STR_BT_NO_DEVICES));
|
||||
} else {
|
||||
GUI.drawList(
|
||||
renderer, listRect, count, scanIndex, [this](int i) { return deviceLabel(i); }, nullptr, nullptr, nullptr,
|
||||
false);
|
||||
renderer, Rect{0, scanTop, pageWidth, contentHeight - 38}, count, scanIndex,
|
||||
[this](int i) { return deviceLabel(i); }, nullptr, nullptr, nullptr, false);
|
||||
}
|
||||
} else { // Paired
|
||||
const int count = BleHid.pairedCount();
|
||||
|
||||
@@ -23,6 +23,7 @@ class BluetoothSettingsActivity final : public Activity {
|
||||
void onExit() override;
|
||||
void loop() override;
|
||||
void render(RenderLock&&) override;
|
||||
bool keepsBluetoothAlive() const override { return true; }
|
||||
|
||||
private:
|
||||
enum class View { Menu, Scan, Paired };
|
||||
|
||||
Reference in New Issue
Block a user