M enabled_unittests => enabled_unittests +12 -0
@@ 15,6 15,18 @@
declare -A TESTS_LIST
+TESTS_LIST["catch2-actions-registry-tests"]="
+ Given actions registry when initialise it incorrectly then verify;
+ Given empty actions registry when verify its state then no pending actions;
+ Given actions registry when enqueue then check pending action;
+ Given actions registry when enqueue then check if action was processed;
+ Given actions registry when finished queued action then no pending actions;
+ Given actions registry when enqueue multiple actions sequentially then count handled actions;
+ Given actions registry when enqueue multiple actions at once then count handled actions;
+ Given actions registry when enqueue multiple actions at once then wait for them to expire.;
+ Given actions registry when enqueue multiple actions at once then process the specific ones only.;
+"
+#---------
TESTS_LIST["catch2-audio-test"]="
Test audio tags;
Audio settings string creation;
M module-apps/Application.cpp => module-apps/Application.cpp +11 -2
@@ 44,6 44,15 @@ namespace gui
namespace app
{
+ auto actionHandled() -> ActionResult
+ {
+ return std::make_shared<ActionHandledResponse>();
+ }
+
+ auto actionNotHandled() -> ActionResult
+ {
+ return std::make_shared<ActionHandledResponse>(sys::ReturnCodes::Failure);
+ }
const char *Application::stateStr(Application::State st)
{
@@ 321,12 330,12 @@ namespace app
try {
const auto &actionHandler = receivers.at(action);
auto &data = msg->getData();
- actionHandler(std::move(data));
+ return actionHandler(std::move(data));
}
catch (const std::out_of_range &) {
LOG_ERROR("Application %s is not able to handle action #%d", GetName().c_str(), action);
}
- return msgNotHandled();
+ return actionNotHandled();
}
sys::MessagePointer Application::handleApplicationSwitch(sys::Message *msgl)
M module-apps/Application.hpp => module-apps/Application.hpp +6 -1
@@ 73,6 73,11 @@ namespace app
return std::make_shared<sys::ResponseMessage>(sys::ReturnCodes::Unresolved);
}
+ class ActionHandledResponse; // Forward declaration
+ using ActionResult = std::shared_ptr<ActionHandledResponse>;
+ auto actionHandled() -> ActionResult;
+ auto actionNotHandled() -> ActionResult;
+
using ApplicationName = std::string;
enum class StartupStatus
@@ 156,7 161,7 @@ namespace app
/// c_str() function for Application::State
static const char *stateStr(State st);
- using OnActionReceived = std::function<sys::MessagePointer(manager::actions::ActionParamsPtr &&)>;
+ using OnActionReceived = std::function<ActionResult(manager::actions::ActionParamsPtr &&)>;
private:
std::string default_window;
M module-apps/application-calendar/ApplicationCalendar.cpp => module-apps/application-calendar/ApplicationCalendar.cpp +1 -1
@@ 52,7 52,7 @@ namespace app
bus.channels.push_back(sys::BusChannel::ServiceDBNotifications);
addActionReceiver(manager::actions::ShowReminder, [this](auto &&data) {
switchWindow(style::window::calendar::name::event_reminder_window, std::move(data));
- return msgHandled();
+ return actionHandled();
});
}
M module-apps/application-call/ApplicationCall.cpp => module-apps/application-call/ApplicationCall.cpp +5 -5
@@ 40,15 40,15 @@ namespace app
Indicator::NetworkAccessTechnology});
addActionReceiver(manager::actions::Call, [this](auto &&data) {
switchWindow(window::name_call, std::forward<decltype(data)>(data));
- return msgHandled();
+ return actionHandled();
});
addActionReceiver(manager::actions::Dial, [this](auto &&data) {
switchWindow(window::name_enterNumber, std::forward<decltype(data)>(data));
- return msgHandled();
+ return actionHandled();
});
addActionReceiver(manager::actions::EmergencyDial, [this](auto &&data) {
switchWindow(app::window::name_emergencyCall, std::forward<decltype(data)>(data));
- return msgHandled();
+ return actionHandled();
});
addActionReceiver(manager::actions::NotAnEmergencyNotification, [this](auto &&data) {
auto buttonAction = [=]() -> bool {
@@ 59,7 59,7 @@ namespace app
auto textNoEmergency = utils::localize.get("app_call_wrong_emergency");
utils::findAndReplaceAll(textNoEmergency, "$NUMBER", data->getDescription());
showNotification(buttonAction, iconNoEmergency, textNoEmergency);
- return msgHandled();
+ return actionHandled();
});
addActionReceiver(manager::actions::NoSimNotification, [this](auto &&data) {
auto buttonAction = [=]() -> bool {
@@ 69,7 69,7 @@ namespace app
constexpr auto iconNoSim = "info_big_circle_W_G";
const auto textNoSim = utils::localize.get("app_call_no_sim");
showNotification(buttonAction, iconNoSim, textNoSim);
- return msgHandled();
+ return actionHandled();
});
}
M module-apps/application-calllog/ApplicationCallLog.cpp => module-apps/application-calllog/ApplicationCallLog.cpp +1 -1
@@ 28,7 28,7 @@ namespace app
{
addActionReceiver(manager::actions::ShowCallLog, [this](auto &&data) {
switchWindow(gui::name::window::main_window, std::move(data));
- return msgHandled();
+ return actionHandled();
});
}
M module-apps/application-desktop/ApplicationDesktop.cpp => module-apps/application-desktop/ApplicationDesktop.cpp +11 -11
@@ 48,57 48,57 @@ namespace app
addActionReceiver(app::manager::actions::RequestPin, [this](auto &&data) {
lockHandler.handlePasscodeRequest(gui::PinLock::LockType::SimPin, std::move(data));
- return msgHandled();
+ return actionHandled();
});
addActionReceiver(app::manager::actions::RequestPuk, [this](auto &&data) {
lockHandler.handlePasscodeRequest(gui::PinLock::LockType::SimPuk, std::move(data));
- return msgHandled();
+ return actionHandled();
});
addActionReceiver(app::manager::actions::RequestPinChange, [this](auto &&data) {
lockHandler.handlePinChangeRequest(std::move(data));
- return msgHandled();
+ return actionHandled();
});
addActionReceiver(app::manager::actions::BlockSim, [this](auto &&data) {
lockHandler.handleSimBlocked(std::move(data));
- return msgHandled();
+ return actionHandled();
});
addActionReceiver(app::manager::actions::UnlockSim, [this](auto &&data) {
lockHandler.handleUnlockSim(std::move(data));
- return msgHandled();
+ return actionHandled();
});
addActionReceiver(app::manager::actions::DisplayCMEError, [this](auto &&data) {
lockHandler.handleCMEError(std::move(data));
- return msgHandled();
+ return actionHandled();
});
addActionReceiver(app::manager::actions::ShowMMIResponse, [this](auto &&data) {
switchWindow(app::window::name::desktop_mmi_pull, std::move(data));
- return msgHandled();
+ return actionHandled();
});
addActionReceiver(app::manager::actions::ShowMMIPush, [this](auto &&data) {
switchWindow(app::window::name::desktop_mmi_push, std::move(data));
- return msgHandled();
+ return actionHandled();
});
addActionReceiver(app::manager::actions::ShowMMIResult, [this](auto &&data) {
switchWindow(app::window::name::desktop_mmi_internal, std::move(data));
- return msgHandled();
+ return actionHandled();
});
addActionReceiver(app::manager::actions::DisplayLowBatteryNotification, [this](auto &&data) {
handleLowBatteryNotification(std::move(data));
- return msgHandled();
+ return actionHandled();
});
addActionReceiver(app::manager::actions::SystemBrownout, [this](auto &&data) {
switchWindow(app::window::name::dead_battery, std::move(data));
- return msgHandled();
+ return actionHandled();
});
}
M module-apps/application-messages/ApplicationMessages.cpp => module-apps/application-messages/ApplicationMessages.cpp +3 -3
@@ 45,11 45,11 @@ namespace app
bus.channels.push_back(sys::BusChannel::ServiceDBNotifications);
addActionReceiver(manager::actions::CreateSms, [this](auto &&data) {
switchWindow(gui::name::window::new_sms, std::move(data));
- return msgHandled();
+ return actionHandled();
});
addActionReceiver(manager::actions::ShowSmsTemplates, [this](auto &&data) {
switchWindow(gui::name::window::sms_templates, std::move(data));
- return msgHandled();
+ return actionHandled();
});
addActionReceiver(manager::actions::SmsRejectNoSim, [this](auto &&data) {
auto action = [this]() {
@@ 57,7 57,7 @@ namespace app
return true;
};
showNotification(action);
- return msgHandled();
+ return actionHandled();
});
}
M module-apps/application-phonebook/ApplicationPhonebook.cpp => module-apps/application-phonebook/ApplicationPhonebook.cpp +5 -5
@@ 27,23 27,23 @@ namespace app
bus.channels.push_back(sys::BusChannel::ServiceDBNotifications);
addActionReceiver(manager::actions::ShowContacts, [this](auto &&data) {
switchWindow(gui::name::window::main_window, std::move(data));
- return msgHandled();
+ return actionHandled();
});
addActionReceiver(manager::actions::AddContact, [this](auto &&data) {
switchWindow(gui::window::name::new_contact, std::move(data));
- return msgHandled();
+ return actionHandled();
});
addActionReceiver(manager::actions::EditContact, [this](auto &&data) {
switchWindow(gui::window::name::new_contact, std::move(data));
- return msgHandled();
+ return actionHandled();
});
addActionReceiver(manager::actions::ShowContactDetails, [this](auto &&data) {
switchWindow(gui::window::name::contact, std::move(data));
- return msgHandled();
+ return actionHandled();
});
addActionReceiver(manager::actions::ShowEmergencyContacts, [this](auto &&data) {
switchWindow(gui::window::name::ice_contacts, std::move(data));
- return msgHandled();
+ return actionHandled();
});
}
M module-apps/application-settings/ApplicationSettings.cpp => module-apps/application-settings/ApplicationSettings.cpp +1 -1
@@ 46,7 46,7 @@ namespace app
bus.channels.push_back(sys::BusChannel::AntennaNotifications);
addActionReceiver(manager::actions::SelectSimCard, [this](auto &&data) {
switchWindow(app::sim_select);
- return msgHandled();
+ return actionHandled();
});
}
M module-apps/application-special-input/ApplicationSpecialInput.cpp => module-apps/application-special-input/ApplicationSpecialInput.cpp +1 -1
@@ 14,7 14,7 @@ ApplicationSpecialInput::ApplicationSpecialInput(std::string name,
{
addActionReceiver(manager::actions::ShowSpecialInput, [this](auto &&data) {
switchWindow(app::char_select, std::move(data));
- return msgHandled();
+ return actionHandled();
});
windowsFactory.attach(app::char_select, [](Application *app, const std::string &name) {
M module-apps/messages/AppMessage.hpp => module-apps/messages/AppMessage.hpp +8 -0
@@ 35,6 35,14 @@ namespace app
manager::actions::ActionParamsPtr data;
};
+ class ActionHandledResponse : public sys::ResponseMessage
+ {
+ public:
+ explicit ActionHandledResponse(sys::ReturnCodes retCode = sys::ReturnCodes::Success)
+ : sys::ResponseMessage{retCode, MessageType::AppAction}
+ {}
+ };
+
// this message is used to notify application about switching event. Application will gain or lose focus upon
// receiving this message. Application gains focus when it was in init or active background state. Application loose
// focus when it was in active foreground state. if no window is specified it is assumed that MainWindow is the
M module-services/service-appmgr/CMakeLists.txt => module-services/service-appmgr/CMakeLists.txt +4 -0
@@ 9,6 9,7 @@ set(SOURCES
model/ApplicationManager.cpp
model/ApplicationHandle.cpp
model/ApplicationsRegistry.cpp
+ model/ActionsRegistry.cpp
messages/ActionRequest.cpp
messages/ApplicationCloseRequest.cpp
messages/ApplicationInitialised.cpp
@@ 41,3 42,6 @@ target_include_directories(${PROJECT_NAME}
${CMAKE_CURRENT_LIST_DIR}
)
+if (${ENABLE_TESTS})
+ add_subdirectory(tests)
+endif()
A module-services/service-appmgr/model/ActionsRegistry.cpp => module-services/service-appmgr/model/ActionsRegistry.cpp +122 -0
@@ 0,0 1,122 @@
+// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
+// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
+
+#include <service-appmgr/model/ActionsRegistry.hpp>
+
+#include <module-utils/log/log.hpp>
+#include <module-utils/magic_enum/include/magic_enum.hpp>
+
+#include <cassert>
+#include <stdexcept>
+
+namespace app::manager
+{
+ namespace
+ {
+ constexpr auto ExpirationTimeout = std::chrono::seconds{30};
+ } // namespace
+
+ ActionEntry::ActionEntry(actions::ActionId action, actions::ActionParamsPtr &¶ms, Timestamp creationTime)
+ : actionId{action}, params{std::move(params)}, createdAt{creationTime}
+ {}
+
+ void ActionEntry::setTargetApplication(std::string targetApplication) noexcept
+ {
+ target = std::move(targetApplication);
+ }
+
+ auto ActionEntry::isExpired() const noexcept -> bool
+ {
+ const auto diff = Clock::now() - createdAt;
+ return diff > ExpirationTimeout;
+ }
+
+ bool operator==(const ActionEntry &lhs, const ActionEntry &rhs) noexcept
+ {
+ return lhs.actionId == rhs.actionId && lhs.createdAt == rhs.createdAt && lhs.params == rhs.params;
+ }
+
+ bool operator!=(const ActionEntry &lhs, const ActionEntry &rhs) noexcept
+ {
+ return !operator==(lhs, rhs);
+ }
+
+ ActionsRegistry::ActionsRegistry(OnNextActionReadyCallback &&callback) : nextActionReady{std::move(callback)}
+ {
+ if (!nextActionReady) {
+ throw std::invalid_argument{"No OnNextActionReadyCallback passed."};
+ }
+ }
+
+ void ActionsRegistry::enqueue(ActionEntry &&action)
+ {
+ LOG_INFO("Enqueue action %s", magic_enum::enum_name(action.actionId).data());
+
+ addAction(std::move(action));
+ if (!isCurrentlyProcessing()) {
+ notifyAboutNextAction();
+ }
+ }
+
+ void ActionsRegistry::addAction(ActionEntry &&action)
+ {
+ actions.push_back(std::move(action));
+ }
+
+ auto ActionsRegistry::isCurrentlyProcessing() const noexcept -> bool
+ {
+ return hasPendingAction();
+ }
+
+ void ActionsRegistry::notifyAboutNextAction()
+ {
+ for (auto &action : actions) {
+ if (const auto handled = nextActionReady(action); handled) {
+ LOG_INFO(
+ "Pending action %s to %s", magic_enum::enum_name(action.actionId).data(), action.target.c_str());
+ actionInProgress = &action;
+ return;
+ }
+ }
+ }
+
+ void ActionsRegistry::finished()
+ {
+ assert(isCurrentlyProcessing());
+
+ LOG_INFO("Finished action %s on %s",
+ magic_enum::enum_name(actionInProgress->actionId).data(),
+ actionInProgress->target.c_str());
+ if (const auto it = std::find(actions.begin(), actions.end(), *actionInProgress); it != actions.end()) {
+ actions.erase(it);
+ }
+
+ actionInProgress = nullptr;
+ onFinished();
+ }
+
+ void ActionsRegistry::onFinished()
+ {
+ removeExpiredActions();
+ if (!actions.empty()) {
+ notifyAboutNextAction();
+ }
+ }
+
+ void ActionsRegistry::removeExpiredActions()
+ {
+ actions.erase(
+ std::remove_if(actions.begin(), actions.end(), [](const auto &action) { return action.isExpired(); }),
+ actions.end());
+ }
+
+ auto ActionsRegistry::hasPendingAction() const noexcept -> bool
+ {
+ return actionInProgress != nullptr;
+ }
+
+ auto ActionsRegistry::getPendingAction() noexcept -> ActionEntry *
+ {
+ return actionInProgress;
+ }
+} // namespace app::manager
M module-services/service-appmgr/model/ApplicationManager.cpp => module-services/service-appmgr/model/ApplicationManager.cpp +57 -28
@@ 106,6 106,7 @@ namespace app::manager
std::vector<std::unique_ptr<app::ApplicationLauncher>> &&launchers,
const ApplicationName &_rootApplicationName)
: Service{serviceName}, ApplicationManagerBase(std::move(launchers)), rootApplicationName{_rootApplicationName},
+ actionsRegistry{[this](ActionEntry &action) { return handleAction(action); }},
blockingTimer{std::make_unique<sys::Timer>(
timerBlock, this, std::numeric_limits<sys::ms>::max(), sys::Timer::Type::SingleShot)},
shutdownDelay{std::make_unique<sys::Timer>(timerShutdownDelay, this, shutdown_delay_ms)},
@@ 285,9 286,13 @@ namespace app::manager
});
connect(typeid(ActionRequest), [this](sys::Message *request) {
auto actionMsg = static_cast<ActionRequest *>(request);
- handleAction(actionMsg);
+ handleActionRequest(actionMsg);
return std::make_shared<sys::ResponseMessage>();
});
+ connect(typeid(ActionHandledResponse), [this](sys::Message *response) {
+ actionsRegistry.finished();
+ return nullptr;
+ });
connect(typeid(GetCurrentDisplayLanguageRequest), [&](sys::Message *request) {
return std::make_shared<GetCurrentDisplayLanguageResponse>(utils::localize.getDisplayLanguage());
});
@@ 463,37 468,42 @@ namespace app::manager
}
}
- auto ApplicationManager::handleAction(ActionRequest *actionMsg) -> bool
+ void ApplicationManager::handleActionRequest(ActionRequest *actionMsg)
{
- switch (const auto action = actionMsg->getAction(); action) {
+ ActionEntry entry{actionMsg->getAction(), std::move(actionMsg->getData())};
+ actionsRegistry.enqueue(std::move(entry));
+ }
+
+ bool ApplicationManager::handleAction(ActionEntry &action)
+ {
+ switch (action.actionId) {
case actions::Home:
- return handleHomeAction();
- case actions::Launch: {
- auto params = static_cast<ApplicationLaunchData *>(actionMsg->getData().get());
- return handleLaunchAction(params);
- }
+ return handleHomeAction(action);
+ case actions::Launch:
+ return handleLaunchAction(action);
case actions::CloseSystem:
return handleCloseSystem();
- default: {
- auto &actionParams = actionMsg->getData();
- return handleCustomAction(action, std::move(actionParams));
- }
+ default:
+ return handleCustomAction(action);
}
}
- auto ApplicationManager::handleHomeAction() -> bool
+ auto ApplicationManager::handleHomeAction(ActionEntry &action) -> bool
{
+ action.setTargetApplication(rootApplicationName);
SwitchRequest switchRequest(ServiceName, rootApplicationName, gui::name::window::main_window, nullptr);
return handleSwitchApplication(&switchRequest);
}
- auto ApplicationManager::handleLaunchAction(ApplicationLaunchData *launchParams) -> bool
+ auto ApplicationManager::handleLaunchAction(ActionEntry &action) -> bool
{
+ auto launchParams = static_cast<ApplicationLaunchData *>(action.params.get());
auto targetApp = getApplication(launchParams->getTargetApplicationName());
if (targetApp == nullptr || !targetApp->handles(actions::Launch)) {
return false;
}
+ action.setTargetApplication(targetApp->name());
SwitchRequest switchRequest(ServiceName, targetApp->name(), gui::name::window::main_window, nullptr);
return handleSwitchApplication(&switchRequest);
}
@@ 506,12 516,11 @@ namespace app::manager
return true;
}
- auto ApplicationManager::handleCustomAction(actions::ActionId action, actions::ActionParamsPtr &&actionParams)
- -> bool
+ auto ApplicationManager::handleCustomAction(ActionEntry &action) -> bool
{
- const auto actionHandlers = applications.findByAction(action);
+ const auto actionHandlers = applications.findByAction(action.actionId);
if (actionHandlers.empty()) {
- LOG_ERROR("No applications handling action #%d.", action);
+ LOG_ERROR("No applications handling action #%d.", action.actionId);
return false;
}
if (actionHandlers.size() > 1) {
@@ 520,16 529,16 @@ namespace app::manager
}
const auto targetApp = actionHandlers.front();
+ action.setTargetApplication(targetApp->name());
+ auto &actionParams = action.params;
if (targetApp->state() != ApplicationHandle::State::ACTIVE_FORGROUND) {
const auto focusedAppClose = !(actionParams && actionParams->disableAppClose);
- pendingAction = std::make_tuple(targetApp->name(), action, std::move(actionParams));
-
SwitchRequest switchRequest(
ServiceName, targetApp->name(), targetApp->switchWindow, std::move(targetApp->switchData));
return handleSwitchApplication(&switchRequest, focusedAppClose);
}
- app::Application::requestAction(this, targetApp->name(), action, std::move(actionParams));
+ app::Application::requestAction(this, targetApp->name(), action.actionId, std::move(actionParams));
return true;
}
@@ 734,12 743,7 @@ namespace app::manager
app.setState(ApplicationHandle::State::ACTIVE_FORGROUND);
setState(State::Running);
EventManager::messageSetApplication(this, app.name());
-
- auto &[appName, action, data] = pendingAction;
- if (appName == app.name()) {
- app::Application::requestAction(this, appName, action, std::move(data));
- pendingAction = std::make_tuple(ApplicationName{}, 0, nullptr);
- }
+ onLaunchFinished(app);
return true;
}
if (getState() == State::AwaitingLostFocusConfirmation) {
@@ 756,6 760,31 @@ namespace app::manager
return false;
}
+ void ApplicationManager::onLaunchFinished(ApplicationHandle &app)
+ {
+ if (!actionsRegistry.hasPendingAction()) {
+ return;
+ }
+
+ auto action = actionsRegistry.getPendingAction();
+ if (app.name() != action->target) {
+ return;
+ }
+
+ switch (action->actionId) {
+ case actions::Home:
+ [[fallthrough]];
+ case actions::Launch:
+ actionsRegistry.finished();
+ break;
+ default: {
+ auto ¶ms = action->params;
+ app::Application::requestAction(this, app.name(), action->actionId, std::move(params));
+ break;
+ }
+ }
+ }
+
auto ApplicationManager::handleCloseConfirmation(CloseConfirmation *msg) -> bool
{
auto senderApp = getApplication(msg->getSenderName());
@@ 791,7 820,7 @@ namespace app::manager
return std::make_shared<sys::ResponseMessage>(sys::ReturnCodes::Failure);
}
auto action = actionMsg->toAction();
- handleAction(action.get());
+ handleActionRequest(action.get());
return std::make_shared<sys::ResponseMessage>();
}
A module-services/service-appmgr/service-appmgr/model/ActionsRegistry.hpp => module-services/service-appmgr/service-appmgr/model/ActionsRegistry.hpp +65 -0
@@ 0,0 1,65 @@
+// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
+// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
+
+#pragma once
+
+#include <service-appmgr/Actions.hpp>
+
+#include <chrono>
+#include <deque>
+#include <functional>
+
+namespace app::manager
+{
+ struct ActionEntry
+ {
+ public:
+ using Clock = std::chrono::steady_clock;
+ using Timestamp = std::chrono::time_point<Clock>;
+
+ explicit ActionEntry(actions::ActionId action,
+ actions::ActionParamsPtr &¶ms = {},
+ Timestamp creationTime = Clock::now());
+
+ void setTargetApplication(std::string targetApplication) noexcept;
+ [[nodiscard]] auto isExpired() const noexcept -> bool;
+
+ actions::Action actionId;
+ actions::ActionParamsPtr params;
+ std::string target;
+
+ private:
+ friend bool operator==(const ActionEntry &lhs, const ActionEntry &rhs) noexcept;
+ friend bool operator!=(const ActionEntry &lhs, const ActionEntry &rhs) noexcept;
+
+ Timestamp createdAt = Clock::now();
+ };
+
+ bool operator==(const ActionEntry &lhs, const ActionEntry &rhs) noexcept;
+ bool operator!=(const ActionEntry &lhs, const ActionEntry &rhs) noexcept;
+
+ class ActionsRegistry
+ {
+ public:
+ using OnNextActionReadyCallback = std::function<bool(ActionEntry &action)>;
+
+ explicit ActionsRegistry(OnNextActionReadyCallback &&callback);
+
+ void enqueue(ActionEntry &&action);
+ void finished();
+
+ [[nodiscard]] auto hasPendingAction() const noexcept -> bool;
+ [[nodiscard]] auto getPendingAction() noexcept -> ActionEntry *;
+
+ private:
+ [[nodiscard]] auto isCurrentlyProcessing() const noexcept -> bool;
+ void addAction(ActionEntry &&action);
+ void notifyAboutNextAction();
+ void removeExpiredActions();
+ void onFinished();
+
+ std::deque<ActionEntry> actions;
+ OnNextActionReadyCallback nextActionReady;
+ ActionEntry *actionInProgress = nullptr;
+ };
+} // namespace app::manager
M module-services/service-appmgr/service-appmgr/model/ApplicationManager.hpp => module-services/service-appmgr/service-appmgr/model/ApplicationManager.hpp +8 -7
@@ 5,6 5,7 @@
#include "ApplicationHandle.hpp"
#include "ApplicationsRegistry.hpp"
+#include "ActionsRegistry.hpp"
#include <module-apps/Application.hpp>
#include <module-apps/ApplicationLauncher.hpp>
@@ 115,11 116,12 @@ namespace app::manager
// Message handlers
void registerMessageHandlers();
- auto handleAction(ActionRequest *actionMsg) -> bool;
- auto handleHomeAction() -> bool;
- auto handleLaunchAction(ApplicationLaunchData *launchParams) -> bool;
+ bool handleAction(ActionEntry &action);
+ void handleActionRequest(ActionRequest *actionMsg);
+ auto handleHomeAction(ActionEntry &action) -> bool;
+ auto handleLaunchAction(ActionEntry &action) -> bool;
auto handleCloseSystem() -> bool;
- auto handleCustomAction(actions::ActionId action, actions::ActionParamsPtr &&actionParams) -> bool;
+ auto handleCustomAction(ActionEntry &action) -> bool;
auto handleSwitchApplication(SwitchRequest *msg, bool closeCurrentlyFocusedApp = true) -> bool;
auto handleCloseConfirmation(CloseConfirmation *msg) -> bool;
auto handleSwitchConfirmation(SwitchConfirmation *msg) -> bool;
@@ 144,18 146,17 @@ namespace app::manager
void onApplicationInitialised(ApplicationHandle &app, StartInBackground startInBackground);
void onApplicationInitFailure(ApplicationHandle &app);
auto onSwitchConfirmed(ApplicationHandle &app) -> bool;
+ void onLaunchFinished(ApplicationHandle &app);
auto onCloseConfirmed(ApplicationHandle &app) -> bool;
void onPhoneLocked();
ApplicationName rootApplicationName;
+ ActionsRegistry actionsRegistry;
std::unique_ptr<sys::Timer> blockingTimer; //< timer to count time from last user's activity. If it reaches time
// defined in settings database application
// manager is sending signal to power manager and changing window to
// the desktop window in the blocked state.
std::unique_ptr<sys::Timer> shutdownDelay;
- // Temporary solution - to be replaced with ActionsMiddleware.
- std::tuple<ApplicationName, actions::ActionId, actions::ActionParamsPtr> pendingAction;
-
std::unique_ptr<settings::Settings> settings;
std::unique_ptr<sys::phone_modes::Observer> phoneModeObserver;
void displayLanguageChanged(std::string value);
A module-services/service-appmgr/tests/CMakeLists.txt => module-services/service-appmgr/tests/CMakeLists.txt +10 -0
@@ 0,0 1,10 @@
+add_catch2_executable(
+ NAME
+ actions-registry-tests
+ SRCS
+ tests-main.cpp
+ test-ActionsRegistry.cpp
+ LIBS
+ service-appmgr
+ module-utils
+)
A module-services/service-appmgr/tests/test-ActionsRegistry.cpp => module-services/service-appmgr/tests/test-ActionsRegistry.cpp +145 -0
@@ 0,0 1,145 @@
+// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
+// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
+
+#include <catch2/catch.hpp>
+
+#include <module-services/service-appmgr/service-appmgr/model/ActionsRegistry.hpp>
+
+using namespace app::manager;
+
+TEST_CASE("Given actions registry when initialise it incorrectly then verify")
+{
+ REQUIRE_THROWS(ActionsRegistry{nullptr});
+}
+
+TEST_CASE("Given empty actions registry when verify its state then no pending actions")
+{
+ auto nextActionCallback = [](ActionEntry & /*entry*/) { return true; };
+ ActionsRegistry registry{std::move(nextActionCallback)};
+
+ REQUIRE(!registry.hasPendingAction());
+}
+
+TEST_CASE("Given actions registry when enqueue then check pending action")
+{
+ auto nextActionCallback = [](ActionEntry & /*entry*/) { return true; };
+ ActionsRegistry registry{std::move(nextActionCallback)};
+
+ registry.enqueue(ActionEntry{actions::Launch});
+ REQUIRE(registry.hasPendingAction());
+ REQUIRE(registry.getPendingAction()->actionId == actions::Launch);
+}
+
+TEST_CASE("Given actions registry when enqueue then check if action was processed")
+{
+ bool handled = false;
+ auto nextActionCallback = [&handled](ActionEntry & /*entry*/) {
+ handled = true;
+ return true;
+ };
+ ActionsRegistry registry{std::move(nextActionCallback)};
+
+ registry.enqueue(ActionEntry{actions::Launch});
+ REQUIRE(handled);
+}
+
+TEST_CASE("Given actions registry when finished queued action then no pending actions")
+{
+ auto nextActionCallback = [](ActionEntry & /*entry*/) { return true; };
+ ActionsRegistry registry{std::move(nextActionCallback)};
+
+ registry.enqueue(ActionEntry{actions::Launch});
+ registry.finished();
+ REQUIRE(!registry.hasPendingAction());
+}
+
+TEST_CASE("Given actions registry when enqueue multiple actions sequentially then count handled actions")
+{
+ int counter = 0;
+ auto nextActionCallback = [&counter](ActionEntry & /*entry*/) {
+ ++counter;
+ return true;
+ };
+ ActionsRegistry registry{std::move(nextActionCallback)};
+
+ registry.enqueue(ActionEntry{actions::Launch});
+ registry.finished();
+
+ registry.enqueue(ActionEntry{actions::Launch});
+ registry.finished();
+
+ registry.enqueue(ActionEntry{actions::Launch});
+ registry.finished();
+
+ REQUIRE(counter == 3);
+}
+
+TEST_CASE("Given actions registry when enqueue multiple actions at once then count handled actions")
+{
+ int counter = 0;
+ auto nextActionCallback = [&counter](ActionEntry & /*entry*/) {
+ ++counter;
+ return true;
+ };
+ ActionsRegistry registry{std::move(nextActionCallback)};
+
+ registry.enqueue(ActionEntry{actions::Launch});
+ REQUIRE(counter == 1);
+ REQUIRE(registry.hasPendingAction());
+
+ registry.enqueue(ActionEntry{actions::Launch});
+ registry.enqueue(ActionEntry{actions::Launch});
+
+ registry.finished(); // Finished processing the 1st action
+ REQUIRE(counter == 2);
+ REQUIRE(registry.hasPendingAction());
+
+ registry.finished(); // Finished processing the 2nd action
+ REQUIRE(counter == 3);
+ REQUIRE(registry.hasPendingAction());
+
+ registry.finished(); // Finished processing the 3rd action
+ REQUIRE(!registry.hasPendingAction());
+}
+
+TEST_CASE("Given actions registry when enqueue multiple actions at once then wait for them to expire.")
+{
+ auto nextActionCallback = [](ActionEntry & /*entry*/) { return true; };
+ ActionsRegistry registry{std::move(nextActionCallback)};
+
+ registry.enqueue(ActionEntry{actions::Launch});
+ REQUIRE(registry.hasPendingAction());
+
+ registry.enqueue(ActionEntry{actions::Launch, {}, std::chrono::steady_clock::now() - std::chrono::seconds{29}});
+ registry.enqueue(ActionEntry{actions::Launch, {}, std::chrono::steady_clock::now() - std::chrono::seconds{30}});
+
+ registry.finished(); // Finished processing the 1st action
+ REQUIRE(registry.hasPendingAction());
+
+ registry.finished(); // Finished processing the 2nd action
+ REQUIRE(!registry.hasPendingAction()); // 3rd action has expired.
+}
+
+TEST_CASE("Given actions registry when enqueue multiple actions at once then process the specific ones only.")
+{
+ int counter = 0;
+ auto nextActionCallback = [&counter](ActionEntry &entry) {
+ if (entry.actionId == actions::UserAction) {
+ ++counter;
+ return true;
+ }
+ return false;
+ };
+ ActionsRegistry registry{std::move(nextActionCallback)};
+
+ registry.enqueue(ActionEntry{actions::Launch});
+ REQUIRE(!registry.hasPendingAction());
+
+ registry.enqueue(ActionEntry{actions::UserAction});
+ registry.enqueue(ActionEntry{actions::Launch});
+ REQUIRE(counter == 1);
+
+ registry.finished();
+ REQUIRE(counter == 1);
+ REQUIRE(!registry.hasPendingAction());
+}
A module-services/service-appmgr/tests/tests-main.cpp => module-services/service-appmgr/tests/tests-main.cpp +5 -0
@@ 0,0 1,5 @@
+// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
+// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
+
+#define CATCH_CONFIG_MAIN // This tells Catch to provide a main() - only do this in one cpp file
+#include <catch2/catch.hpp>