#include "ActivityManager.h" #include #include "boot_sleep/BootActivity.h" #include "boot_sleep/SleepActivity.h" #include "browser/OpdsBookBrowserActivity.h" #include "home/HomeActivity.h" #include "home/MyLibraryActivity.h" #include "home/RecentBooksActivity.h" #include "network/CrossPointWebServerActivity.h" #include "reader/ReaderActivity.h" #include "settings/SettingsActivity.h" #include "util/FullScreenMessageActivity.h" void ActivityManager::begin() { xTaskCreate(&renderTaskTrampoline, "ActivityManagerRender", 8192, // Stack size this, // Parameters 1, // Priority &renderTaskHandle // Task handle ); assert(renderTaskHandle != nullptr && "Failed to create render task"); } void ActivityManager::renderTaskTrampoline(void* param) { auto* self = static_cast(param); self->renderTaskLoop(); } void ActivityManager::renderTaskLoop() { while (true) { ulTaskNotifyTake(pdTRUE, portMAX_DELAY); // Acquire the lock before reading currentActivity to avoid a TOCTOU race // where the main task deletes the activity between the null-check and render(). RenderLock lock; if (currentActivity) { HalPowerManager::Lock powerLock; // Ensure we don't go into low-power mode while rendering currentActivity->render(std::move(lock)); } } } void ActivityManager::loop() { if (currentActivity) { // Note: do not hold a lock here, the loop() method must be responsible for acquire one if needed currentActivity->loop(); } while (pendingAction != PendingAction::None) { if (pendingAction == PendingAction::Pop) { RenderLock lock; if (!currentActivity) { // Should never happen in practice LOG_ERR("ACT", "Pop set but currentActivity is null; ignoring pop request"); pendingAction = PendingAction::None; continue; } ActivityResult pendingResult = std::move(currentActivity->result); // Destroy the current activity exitActivity(lock); pendingAction = PendingAction::None; if (stackActivities.empty()) { LOG_DBG("ACT", "No more activities on stack, going home"); lock.unlock(); // goHome may acquire its own lock goHome(); continue; // Will launch goHome immediately } else { currentActivity = std::move(stackActivities.back()); stackActivities.pop_back(); LOG_DBG("ACT", "Popped from activity stack, new size = %zu", stackActivities.size()); // Handle result if necessary if (currentActivity->resultHandler) { LOG_DBG("ACT", "Handling result for popped activity"); // Move it here to avoid the case where handler calling another startActivityForResult() auto handler = std::move(currentActivity->resultHandler); currentActivity->resultHandler = nullptr; lock.unlock(); // Handler may acquire its own lock handler(pendingResult); } // Request an update to ensure the popped activity gets re-rendered if (pendingAction == PendingAction::None) { requestUpdate(); } // Handler may request another pending action, we will handle it in the next loop iteration continue; } } else if (pendingActivity) { // Current activity has requested a new activity to be launched RenderLock lock; if (pendingAction == PendingAction::Replace) { // Destroy the current activity exitActivity(lock); // Clear the stack while (!stackActivities.empty()) { stackActivities.back()->onExit(); stackActivities.pop_back(); } } else if (pendingAction == PendingAction::Push) { // Move current activity to stack stackActivities.push_back(std::move(currentActivity)); LOG_DBG("ACT", "Pushed to activity stack, new size = %zu", stackActivities.size()); } pendingAction = PendingAction::None; currentActivity = std::move(pendingActivity); lock.unlock(); // onEnter may acquire its own lock currentActivity->onEnter(); // onEnter may request another pending action, we will handle it in the next loop iteration continue; } } if (requestedUpdate) { requestedUpdate = false; // Using direct notification to signal the render task to update // Increment counter so multiple rapid calls won't be lost if (renderTaskHandle) { xTaskNotify(renderTaskHandle, 1, eIncrement); } } } void ActivityManager::exitActivity(const RenderLock& lock) { // Note: lock must be held by the caller if (currentActivity) { currentActivity->onExit(); currentActivity.reset(); } } void ActivityManager::replaceActivity(std::unique_ptr&& newActivity) { // Note: no lock here, this is usually called by loop() and we may run into deadlock if (currentActivity) { // Defer launch if we're currently in an activity, to avoid deleting the current activity // leading to the "delete this" problem pendingActivity = std::move(newActivity); pendingAction = PendingAction::Replace; } else { // No current activity, safe to launch immediately currentActivity = std::move(newActivity); currentActivity->onEnter(); } } void ActivityManager::goToFileTransfer() { replaceActivity(std::make_unique(renderer, mappedInput)); } void ActivityManager::goToSettings() { replaceActivity(std::make_unique(renderer, mappedInput)); } void ActivityManager::goToMyLibrary(std::string path) { replaceActivity(std::make_unique(renderer, mappedInput, std::move(path))); } void ActivityManager::goToRecentBooks() { replaceActivity(std::make_unique(renderer, mappedInput)); } void ActivityManager::goToBrowser() { replaceActivity(std::make_unique(renderer, mappedInput)); } void ActivityManager::goToReader(std::string path) { replaceActivity(std::make_unique(renderer, mappedInput, std::move(path))); } void ActivityManager::goToSleep() { replaceActivity(std::make_unique(renderer, mappedInput)); loop(); // Important: sleep screen must be rendered immediately, the caller will go to sleep right after this returns } void ActivityManager::goToBoot() { replaceActivity(std::make_unique(renderer, mappedInput)); } void ActivityManager::goToFullScreenMessage(std::string message, EpdFontFamily::Style style) { replaceActivity(std::make_unique(renderer, mappedInput, std::move(message), style)); } void ActivityManager::goHome() { replaceActivity(std::make_unique(renderer, mappedInput)); } void ActivityManager::pushActivity(std::unique_ptr&& activity) { if (pendingActivity) { // Should never happen in practice LOG_ERR("ACT", "pendingActivity while pushActivity is not expected"); pendingActivity.reset(); } pendingActivity = std::move(activity); pendingAction = PendingAction::Push; } void ActivityManager::popActivity() { if (pendingActivity) { // Should never happen in practice LOG_ERR("ACT", "pendingActivity while popActivity is not expected"); pendingActivity.reset(); } pendingAction = PendingAction::Pop; } bool ActivityManager::preventAutoSleep() const { return currentActivity && currentActivity->preventAutoSleep(); } bool ActivityManager::isReaderActivity() const { return currentActivity && currentActivity->isReaderActivity(); } bool ActivityManager::skipLoopDelay() const { return currentActivity && currentActivity->skipLoopDelay(); } void ActivityManager::requestUpdate(bool immediate) { if (immediate) { if (renderTaskHandle) { xTaskNotify(renderTaskHandle, 1, eIncrement); } } else { // Deferring the update until current loop is finished // This is to avoid multiple updates being requested in the same loop requestedUpdate = true; } } // RenderLock RenderLock::RenderLock() { xSemaphoreTake(activityManager.renderingMutex, portMAX_DELAY); isLocked = true; } RenderLock::RenderLock(Activity& /* unused */) { xSemaphoreTake(activityManager.renderingMutex, portMAX_DELAY); isLocked = true; } RenderLock::~RenderLock() { if (isLocked) { xSemaphoreGive(activityManager.renderingMutex); isLocked = false; } } void RenderLock::unlock() { if (isLocked) { xSemaphoreGive(activityManager.renderingMutex); isLocked = false; } }