~aleteoryx/muditaos

fb279f17b12d4a91c69095a6fef7fb66bc7a3edd — Mateusz Piesta 4 years ago cb4aac9
[BH-843] Reset procedure

Added reset procedure.
Refactored linux KeyInput.
Added support for multipress.
39 files changed, 659 insertions(+), 256 deletions(-)

M image/assets/lang/English.json
M module-apps/apps-common/popups/Popups.cpp
M module-apps/apps-common/popups/Popups.hpp
M module-apps/apps-common/popups/presenter/PowerOffPresenter.cpp
M module-apps/apps-common/popups/presenter/PowerOffPresenter.hpp
M module-bsp/board/linux/hal/battery_charger/BatteryCharger.cpp
M module-bsp/board/linux/hal/key_input/KeyInput.cpp
M module-bsp/board/linux/hal/key_input/KeyInput.hpp
M module-services/service-evtmgr/service-evtmgr/WorkerEventCommon.hpp
M products/BellHybrid/apps/Application.cpp
M products/BellHybrid/apps/application-bell-alarm/ApplicationBellAlarm.cpp
M products/BellHybrid/apps/application-bell-background-sounds/ApplicationBellBackgroundSounds.cpp
M products/BellHybrid/apps/application-bell-main/ApplicationBellMain.cpp
M products/BellHybrid/apps/application-bell-powernap/ApplicationBellPowerNap.cpp
M products/BellHybrid/apps/application-bell-settings/ApplicationBellSettings.cpp
M products/BellHybrid/apps/common/CMakeLists.txt
M products/BellHybrid/apps/common/include/common/BellPowerOffPresenter.hpp
A products/BellHybrid/apps/common/include/common/popups/BellRebootWindow.hpp
M products/BellHybrid/apps/common/src/BellPowerOffPresenter.cpp
A products/BellHybrid/apps/common/src/popups/BellRebootWindow.cpp
M products/BellHybrid/apps/common/src/windows/BellTurnOffWindow.cpp
M products/BellHybrid/apps/include/Application.hpp
M products/BellHybrid/keymap/include/keymap/KeyMap.hpp
M products/BellHybrid/services/appmgr/CMakeLists.txt
A products/BellHybrid/services/appmgr/include/appmgr/messages/RebootPopupRequestParams.hpp
M products/BellHybrid/services/evtmgr/CMakeLists.txt
M products/BellHybrid/services/evtmgr/EventManager.cpp
M products/BellHybrid/services/evtmgr/WorkerEvent.cpp
M products/BellHybrid/services/evtmgr/WorkerEvent.hpp
A products/BellHybrid/services/evtmgr/internal/key_sequences/GenericLongPressSequence.hpp
M products/BellHybrid/services/evtmgr/internal/key_sequences/KeySequenceMgr.cpp
M products/BellHybrid/services/evtmgr/internal/key_sequences/KeySequenceMgr.hpp
D products/BellHybrid/services/evtmgr/internal/key_sequences/LongPressSequence.cpp
D products/BellHybrid/services/evtmgr/internal/key_sequences/LongPressSequence.hpp
M products/BellHybrid/services/evtmgr/internal/key_sequences/PowerOffSequence.hpp
A products/BellHybrid/services/evtmgr/internal/key_sequences/ResetSequence.hpp
M products/BellHybrid/services/evtmgr/tests/CMakeLists.txt
A products/BellHybrid/services/evtmgr/tests/unittest_GenericLongPressSequence.cpp
M products/BellHybrid/services/evtmgr/tests/unittest_KeySequenceMgr.cpp
M image/assets/lang/English.json => image/assets/lang/English.json +1 -0
@@ 631,6 631,7 @@
  "app_bell_background_sounds_timer_title": "Timer",
  "app_bell_turn_off_question": "Turn off the device?",
  "app_bell_goodbye": "Goodbye",
  "app_bell_reset_message": "<text>Resetting Mudita<br />Harmony</text>",
  "app_bell_greeting_msg": [
    "<text>Good Morning!<br />It's a Beautiful Day!</text>",
    "<text>Happy Day!<br />It's a brand new morning!</text>",

M module-apps/apps-common/popups/Popups.cpp => module-apps/apps-common/popups/Popups.cpp +2 -0
@@ 40,6 40,8 @@ namespace gui::popup
            return gui::popup::window::alarm_window;
        case ID::PowerOff:
            return gui::popup::window::power_off_window;
        case ID::Reboot:
            return gui::popup::window::reboot_window;
        }

        return {};

M module-apps/apps-common/popups/Popups.hpp => module-apps/apps-common/popups/Popups.hpp +2 -0
@@ 27,6 27,7 @@ namespace gui
            AlarmDeactivated,
            Alarm,
            PowerOff,
            Reboot
        };

        namespace window


@@ 48,6 49,7 @@ namespace gui
            inline constexpr auto alarm_activated_window            = "AlarmActivatedPopup";
            inline constexpr auto alarm_deactivated_window          = "AlarmDeactivatedPopup";
            inline constexpr auto alarm_window                      = "AlarmPopup";
            inline constexpr auto reboot_window                     = "RebootPopup";
        } // namespace window

        std::string resolveWindowName(ID id);

M module-apps/apps-common/popups/presenter/PowerOffPresenter.cpp => module-apps/apps-common/popups/presenter/PowerOffPresenter.cpp +2 -0
@@ 15,5 15,7 @@ namespace gui
        auto msg = std::make_shared<app::UserPowerDownRequest>();
        application->bus.sendUnicast(std::move(msg), service::name::system_manager);
    }
    void PowerOffPresenter::reboot()
    {}

} // namespace gui

M module-apps/apps-common/popups/presenter/PowerOffPresenter.hpp => module-apps/apps-common/popups/presenter/PowerOffPresenter.hpp +2 -0
@@ 15,6 15,7 @@ namespace gui
      public:
        virtual ~AbstractPowerOffPresenter() = default;
        virtual void powerOff()              = 0;
        virtual void reboot()                = 0;
    };

    class PowerOffPresenter : public AbstractPowerOffPresenter


@@ 22,6 23,7 @@ namespace gui
      public:
        PowerOffPresenter(app::ApplicationCommon *app);
        void powerOff() override;
        void reboot() override;

      private:
        app::ApplicationCommon *application;

M module-bsp/board/linux/hal/battery_charger/BatteryCharger.cpp => module-bsp/board/linux/hal/battery_charger/BatteryCharger.cpp +5 -2
@@ 45,6 45,7 @@ namespace hal::battery
        TaskHandle_t batteryWorkerHandle = nullptr;
        unsigned batteryLevel            = 100;
        bool isPlugged                   = false;
        bool shouldRun                   = true;

        void batteryWorker(void *parameters)
        {


@@ 55,7 56,7 @@ namespace hal::battery

            xQueueHandle targetQueueHandle = nullptr;

            while (true) {
            while (shouldRun) {
                std::uint8_t buff[fifoBuffSize];
                std::int32_t readBytes = read(fd, buff, fifoBuffSize);



@@ 93,6 94,8 @@ namespace hal::battery
                }
                vTaskDelay(taskDelay);
            }
            close(fd);
            vTaskDelete(nullptr);
        }
    } // namespace



@@ 111,9 114,9 @@ namespace hal::battery

    void BatteryCharger::deinit()
    {
        shouldRun      = false;
        IRQQueueHandle = nullptr;
        DCDQueueHandle = nullptr;
        vTaskDelete(batteryWorkerHandle);
    }

    void BatteryCharger::processStateChangeNotification(std::uint8_t notification)

M module-bsp/board/linux/hal/key_input/KeyInput.cpp => module-bsp/board/linux/hal/key_input/KeyInput.cpp +83 -62
@@ 6,90 6,111 @@
#include <hal/GenericFactory.hpp>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/epoll.h>

namespace hal::key_input
{
    namespace
    std::shared_ptr<AbstractKeyInput> AbstractKeyInput::Factory::create()
    {
        TaskHandle_t linux_keyboard_worker_handle = nullptr;

        std::uint8_t keyCode      = 0;
        std::uint8_t keyEventType = 0;
        int fd;

        bool handleSliderKey()
        {
            bool breakIfSliderReleased = false;
            if (keyCode == static_cast<uint8_t>(bsp::KeyCodes::SSwitchUp) ||
                keyCode == static_cast<uint8_t>(bsp::KeyCodes::SSwitchMid) ||
                keyCode == static_cast<uint8_t>(bsp::KeyCodes::SSwitchDown)) {
                if (keyEventType == static_cast<uint8_t>(bsp::KeyEvents::Pressed)) {
                    keyEventType = static_cast<uint8_t>(bsp::KeyEvents::Moved);
                }
                else {
                    breakIfSliderReleased = true;
                }
            }
            return breakIfSliderReleased;
        }
        return hal::impl::factory<LinuxKeyInput, AbstractKeyInput>();
    }

        void linux_keyboard_worker(void *pvp)
        {
    void LinuxKeyInput::init(xQueueHandle qHandle)
    {
        queueHandle = qHandle;
        xTaskCreate([](void *o) { static_cast<LinuxKeyInput *>(o)->worker(); }, "keyboard", 512, this, 0, &taskHandle);
    }

            const char *myfifo = "/tmp/myfifo3";
    void LinuxKeyInput::deinit()
    {
        shouldRun = false;
    }

            // Creating the named file(FIFO)
            // mkfifo(<pathname>, <permission>)
            mkfifo(myfifo, 0666);
    std::vector<bsp::KeyEvent> LinuxKeyInput::getKeyEvents(KeyNotificationSource)
    {
        return keyEvents;
    }
    LinuxKeyInput::LinuxKeyInput()
    {
        keyEvents.reserve(4);
    }

            // Open FIFO for write only
            fd = open(myfifo, O_RDONLY | O_NONBLOCK);
    void LinuxKeyInput::worker()
    {
        constexpr static auto maxEvents   = 1U;
        constexpr auto *keyFifo           = "/tmp/myfifo3";
        constexpr auto keyFifoPermissions = 0666;

        struct epoll_event event
        {};
        struct epoll_event events[maxEvents];

        const auto epollFd = epoll_create1(0);
        if (epollFd == -1) {
            fprintf(stderr, "Failed to create epoll file descriptor\n");
            assert(0);
        }

            while (1) {
                std::uint8_t buff[10];
                std::int32_t readedBytes = read(fd, buff, 10);
        mkfifo(keyFifo, keyFifoPermissions);
        const auto fd = open(keyFifo, O_RDONLY | O_NONBLOCK);
        event.events  = EPOLLIN;
        event.data.fd = fd;

                if (readedBytes > 1) {
                    keyEventType = buff[0];
                    keyCode      = buff[1];
        if (epoll_ctl(epollFd, EPOLL_CTL_ADD, fd, &event)) {
            fprintf(stderr, "Failed to add file descriptor to epoll\n");
            close(epollFd);
            assert(0);
        }

                    xQueueHandle qhandle = reinterpret_cast<xQueueHandle>(pvp);
        while (shouldRun) {
            const auto eventCount = epoll_wait(epollFd, events, maxEvents, 1000);
            for (auto i = 0; i < eventCount; ++i) {
                std::uint8_t buff[16]{};
                const auto bytesRead = read(events[i].data.fd, buff, sizeof buff);
                if (bytesRead > 1) {
                    keyEvents.clear();
                    consumeBytes(buff, bytesRead);

                    if (handleSliderKey()) {
                        break;
                    }
                    std::uint8_t notification = 0x01;
                    xQueueSend(qhandle, &notification, 100);
                    xQueueSend(queueHandle, &notification, 100);
                }
                vTaskDelay(50);
            }

            close(fd);
        }
    } // namespace

    std::shared_ptr<AbstractKeyInput> AbstractKeyInput::Factory::create()
    {
        return hal::impl::factory<LinuxKeyInput, AbstractKeyInput>();
        close(fd);
        close(epollFd);
        vTaskDelete(nullptr);
    }

    void LinuxKeyInput::init(xQueueHandle qHandle)
    void LinuxKeyInput::addKeyEvent(LinuxKeyInput::KeyData data)
    {
        xTaskCreate(linux_keyboard_worker, "keyboard", 512, qHandle, 0, &linux_keyboard_worker_handle);
        if (const auto event = translateSliderEvent(data); event) {
            keyEvents.emplace_back(*event);
        }
    }

    void LinuxKeyInput::deinit()
    void LinuxKeyInput::consumeBytes(uint8_t *buff, std::size_t size)
    {
        vTaskDelete(linux_keyboard_worker_handle);
        close(fd);
        auto bytesLeft = size;
        while (bytesLeft > 0) {
            if (bytesLeft % keyDataSize == 0) {
                addKeyEvent(KeyData{buff[size - bytesLeft], buff[size - bytesLeft + 1]});
                bytesLeft -= keyDataSize;
            }
        }
    }

    std::vector<bsp::KeyEvent> LinuxKeyInput::getKeyEvents(KeyNotificationSource)
    std::optional<bsp::KeyEvent> LinuxKeyInput::translateSliderEvent(LinuxKeyInput::KeyData data)
    {
        using namespace bsp;
        KeyEvent keyEvent;
        keyEvent.code  = static_cast<KeyCodes>(keyCode);
        keyEvent.event = static_cast<KeyEvents>(keyEventType);
        return std::vector<KeyEvent>{keyEvent};
        const auto code  = static_cast<bsp::KeyCodes>(data[1]);
        const auto event = static_cast<bsp::KeyEvents>(data[0]);

        if (code == bsp::KeyCodes::SSwitchUp || code == bsp::KeyCodes::SSwitchMid ||
            code == bsp::KeyCodes::SSwitchDown) {
            if (event == bsp::KeyEvents::Pressed) {
                return bsp::KeyEvent{code, bsp::KeyEvents::Moved};
            }
            else {
                return {};
            }
        }
        return bsp::KeyEvent{code, event};
    }
} // namespace hal::key_input

M module-bsp/board/linux/hal/key_input/KeyInput.hpp => module-bsp/board/linux/hal/key_input/KeyInput.hpp +17 -0
@@ 9,9 9,26 @@ namespace hal::key_input
{
    class LinuxKeyInput : public AbstractKeyInput
    {
        using KeyEvents                   = std::vector<bsp::KeyEvent>;
        static constexpr auto keyDataSize = 2U;
        using KeyData                     = std::array<int, keyDataSize>;

      public:
        LinuxKeyInput();
        void init(xQueueHandle qHandle) final;
        void deinit() final;
        std::vector<bsp::KeyEvent> getKeyEvents(KeyNotificationSource) final;

      private:
        void worker();

        void addKeyEvent(KeyData data);
        void consumeBytes(std::uint8_t *buff, std::size_t size);
        std::optional<bsp::KeyEvent> translateSliderEvent(KeyData key);

        bool shouldRun{true};
        KeyEvents keyEvents;
        xQueueHandle queueHandle{};
        TaskHandle_t taskHandle{};
    };
} // namespace hal::key_input

M module-services/service-evtmgr/service-evtmgr/WorkerEventCommon.hpp => module-services/service-evtmgr/service-evtmgr/WorkerEventCommon.hpp +1 -1
@@ 46,7 46,7 @@ class WorkerEventCommon : public sys::Worker
    virtual void initProductHardware();
    virtual void deinitProductHardware();

    void processKeyEvent(bsp::KeyEvents event, bsp::KeyCodes code);
    virtual void processKeyEvent(bsp::KeyEvents event, bsp::KeyCodes code);

    sys::Service *service = nullptr;


M products/BellHybrid/apps/Application.cpp => products/BellHybrid/apps/Application.cpp +13 -0
@@ 10,6 10,7 @@
#include <common/popups/AlarmActivatedWindow.hpp>
#include <common/popups/AlarmDeactivatedWindow.hpp>
#include <common/popups/BellTurnOffOptionWindow.hpp>
#include <common/popups/BellRebootWindow.hpp>
#include <common/windows/BellTurnOffWindow.hpp>
#include <common/windows/BellWelcomeWindow.hpp>



@@ 50,9 51,21 @@ namespace app
                                          return std::make_unique<gui::BellWelcomeWindow>(app);
                                      });
                break;
            case ID::Reboot:
                windowsFactory.attach(window::reboot_window, [](ApplicationCommon *app, const std::string &name) {
                    return std::make_unique<gui::BellRebootWindow>(app,
                                                                   std::make_unique<gui::BellPowerOffPresenter>(app));
                });
            default:
                break;
            }
        }
    }

    bool Application::isPopupPermitted(gui::popup::ID) const
    {
        return not((isCurrentWindow(gui::popup::resolveWindowName(gui::popup::ID::Reboot))) ||
                   (isCurrentWindow(gui::popup::resolveWindowName(gui::popup::ID::PowerOff))) ||
                   (isCurrentWindow(gui::BellTurnOffWindow::name)));
    }
} // namespace app

M products/BellHybrid/apps/application-bell-alarm/ApplicationBellAlarm.cpp => products/BellHybrid/apps/application-bell-alarm/ApplicationBellAlarm.cpp +4 -1
@@ 62,7 62,10 @@ namespace app
            return std::make_unique<gui::BellAlarmSetWindow>(app, priv->alarmSetPresenter);
        });

        attachPopups({gui::popup::ID::AlarmActivated, gui::popup::ID::AlarmDeactivated, gui::popup::ID::PowerOff});
        attachPopups({gui::popup::ID::AlarmActivated,
                      gui::popup::ID::AlarmDeactivated,
                      gui::popup::ID::PowerOff,
                      gui::popup::ID::Reboot});
    }

    sys::MessagePointer ApplicationBellAlarm::DataReceivedHandler(sys::DataMessage *msgl, sys::ResponseMessage *resp)

M products/BellHybrid/apps/application-bell-background-sounds/ApplicationBellBackgroundSounds.cpp => products/BellHybrid/apps/application-bell-background-sounds/ApplicationBellBackgroundSounds.cpp +4 -1
@@ 56,7 56,10 @@ namespace app
            return std::make_unique<gui::BGSoundsVolumeWindow>(app, std::move(presenter));
        });

        attachPopups({gui::popup::ID::AlarmActivated, gui::popup::ID::AlarmDeactivated, gui::popup::ID::PowerOff});
        attachPopups({gui::popup::ID::AlarmActivated,
                      gui::popup::ID::AlarmDeactivated,
                      gui::popup::ID::PowerOff,
                      gui::popup::ID::Reboot});
    }

    sys::MessagePointer ApplicationBellBackgroundSounds::DataReceivedHandler(sys::DataMessage *msgl,

M products/BellHybrid/apps/application-bell-main/ApplicationBellMain.cpp => products/BellHybrid/apps/application-bell-main/ApplicationBellMain.cpp +4 -1
@@ 63,7 63,10 @@ namespace app
            gui::window::name::bell_main_menu_dialog,
            [](ApplicationCommon *app, const std::string &name) { return std::make_unique<gui::Dialog>(app, name); });

        attachPopups({gui::popup::ID::AlarmActivated, gui::popup::ID::AlarmDeactivated, gui::popup::ID::PowerOff});
        attachPopups({gui::popup::ID::AlarmActivated,
                      gui::popup::ID::AlarmDeactivated,
                      gui::popup::ID::PowerOff,
                      gui::popup::ID::Reboot});
    }

    sys::MessagePointer ApplicationBellMain::DataReceivedHandler(sys::DataMessage *msgl, sys::ResponseMessage *resp)

M products/BellHybrid/apps/application-bell-powernap/ApplicationBellPowerNap.cpp => products/BellHybrid/apps/application-bell-powernap/ApplicationBellPowerNap.cpp +4 -1
@@ 50,7 50,10 @@ namespace app
                                  return std::make_unique<gui::PowerNapSessionEndedWindow>(app, std::move(presenter));
                              });

        attachPopups({gui::popup::ID::AlarmActivated, gui::popup::ID::AlarmDeactivated, gui::popup::ID::PowerOff});
        attachPopups({gui::popup::ID::AlarmActivated,
                      gui::popup::ID::AlarmDeactivated,
                      gui::popup::ID::PowerOff,
                      gui::popup::ID::Reboot});
    }

    sys::MessagePointer ApplicationBellPowerNap::DataReceivedHandler(sys::DataMessage *msgl, sys::ResponseMessage *resp)

M products/BellHybrid/apps/application-bell-settings/ApplicationBellSettings.cpp => products/BellHybrid/apps/application-bell-settings/ApplicationBellSettings.cpp +4 -1
@@ 191,7 191,10 @@ namespace app
                return std::make_unique<gui::AboutYourBellWindow>(app, std::move(aboutYourBellPresenter));
            });

        attachPopups({gui::popup::ID::AlarmActivated, gui::popup::ID::AlarmDeactivated, gui::popup::ID::PowerOff});
        attachPopups({gui::popup::ID::AlarmActivated,
                      gui::popup::ID::AlarmDeactivated,
                      gui::popup::ID::PowerOff,
                      gui::popup::ID::Reboot});
    }

    sys::MessagePointer ApplicationBellSettings::DataReceivedHandler(sys::DataMessage *msgl, sys::ResponseMessage *resp)

M products/BellHybrid/apps/common/CMakeLists.txt => products/BellHybrid/apps/common/CMakeLists.txt +2 -0
@@ 22,6 22,7 @@ target_sources(application-bell-common
        src/popups/AlarmActivatedWindow.cpp
        src/popups/AlarmDeactivatedWindow.cpp
        src/popups/BellTurnOffOptionWindow.cpp
        src/popups/BellRebootWindow.cpp
        src/widgets/ListItems.cpp

        src/options/BellOptionWindow.cpp


@@ 41,6 42,7 @@ target_sources(application-bell-common
        include/common/popups/AlarmActivatedWindow.hpp
        include/common/popups/AlarmDeactivatedWindow.hpp
        include/common/popups/BellTurnOffOptionWindow.hpp
        include/common/popups/BellRebootWindow.hpp
        include/common/widgets/BellSideListItemWithCallbacks.hpp
        include/common/widgets/ListItems.hpp
        include/common/options/BellOptionWindow.hpp

M products/BellHybrid/apps/common/include/common/BellPowerOffPresenter.hpp => products/BellHybrid/apps/common/include/common/BellPowerOffPresenter.hpp +1 -0
@@ 12,6 12,7 @@ namespace gui
      public:
        explicit BellPowerOffPresenter(app::ApplicationCommon *app);
        void powerOff() override;
        void reboot() override;

      private:
        app::ApplicationCommon *application;

A products/BellHybrid/apps/common/include/common/popups/BellRebootWindow.hpp => products/BellHybrid/apps/common/include/common/popups/BellRebootWindow.hpp +30 -0
@@ 0,0 1,30 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once

#include <apps-common/popups/presenter/PowerOffPresenter.hpp>
#include <apps-common/popups/WindowWithTimer.hpp>

namespace gui
{
    class Icon;

    class BellRebootWindow : public WindowWithTimer
    {
      public:
        static constexpr auto defaultName = "BellRebootWindow";

        BellRebootWindow(app::ApplicationCommon *app,
                         std::unique_ptr<AbstractPowerOffPresenter> presenter,
                         const char *name = defaultName);

      private:
        void buildInterface() override;
        bool onInput(const InputEvent &inputEvent) override;

        Icon *icon{};
        std::unique_ptr<AbstractPowerOffPresenter> presenter;
    };

} // namespace gui

M products/BellHybrid/apps/common/src/BellPowerOffPresenter.cpp => products/BellHybrid/apps/common/src/BellPowerOffPresenter.cpp +5 -0
@@ 5,6 5,7 @@

#include <common/windows/BellWelcomeWindow.hpp>
#include <service-appmgr/messages/UserPowerDownRequest.hpp>
#include <SystemManager/SystemManagerCommon.hpp>
#include <system/Constants.hpp>

namespace gui


@@ 18,5 19,9 @@ namespace gui
        auto msg = std::make_shared<app::UserPowerDownRequest>();
        application->bus.sendUnicast(std::move(msg), service::name::system_manager);
    }
    void BellPowerOffPresenter::reboot()
    {
        sys::SystemManagerCommon::Reboot(application);
    }

} // namespace gui

A products/BellHybrid/apps/common/src/popups/BellRebootWindow.cpp => products/BellHybrid/apps/common/src/popups/BellRebootWindow.cpp +46 -0
@@ 0,0 1,46 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "popups/BellRebootWindow.hpp"
#include <gui/input/InputEvent.hpp>
#include <gui/widgets/Icon.hpp>
#include <i18n/i18n.hpp>
#include <Application.hpp>

namespace gui
{

    BellRebootWindow::BellRebootWindow(app::ApplicationCommon *app,
                                       std::unique_ptr<AbstractPowerOffPresenter> presenter,
                                       const char *name)
        : WindowWithTimer(app, name), presenter(std::move(presenter))
    {
        buildInterface();

        timerCallback = [this](Item &, sys::Timer &) {
            this->presenter->reboot();
            return true;
        };
    }
    void BellRebootWindow::buildInterface()
    {
        WindowWithTimer::buildInterface();

        statusBar->setVisible(false);
        header->setTitleVisibility(false);
        bottomBar->setVisible(false);

        icon = new Icon(this,
                        0,
                        0,
                        style::window_width,
                        style::window_height,
                        "bell_mudita_logo_W_G",
                        utils::translate("app_bell_reset_message"));
        icon->text->setFont(style::window::font::verybiglight);
    }
    bool BellRebootWindow::onInput(const InputEvent &)
    {
        return false;
    }
} // namespace gui

M products/BellHybrid/apps/common/src/windows/BellTurnOffWindow.cpp => products/BellHybrid/apps/common/src/windows/BellTurnOffWindow.cpp +1 -1
@@ 40,7 40,7 @@ namespace gui
    }
    bool BellTurnOffWindow::onInput(const InputEvent &)
    {
        return true;
        return false;
    }

} // namespace gui

M products/BellHybrid/apps/include/Application.hpp => products/BellHybrid/apps/include/Application.hpp +1 -0
@@ 14,5 14,6 @@ namespace app

      protected:
        void attachPopups(const std::vector<gui::popup::ID> &popupsList) override;
        bool isPopupPermitted(gui::popup::ID popupId) const override;
    };
} // namespace app

M products/BellHybrid/keymap/include/keymap/KeyMap.hpp => products/BellHybrid/keymap/include/keymap/KeyMap.hpp +25 -1
@@ 5,6 5,7 @@

#include <gui/input/InputEvent.hpp>
#include <bitset>
#include <numeric>

/// Key mapping structure to ease translation between PureOS key definitions and nomenclature used throughout the
/// GUI design


@@ 19,7 20,7 @@ enum class KeyMap : std::uint8_t
    DeepPressDown = 6
};

inline static KeyMap mapKey(gui::KeyCode key)
inline static KeyMap mapKey(const gui::KeyCode key)
{
    switch (key) {
    case gui::KeyCode::KEY_LF:


@@ 42,6 43,16 @@ inline static KeyMap mapKey(gui::KeyCode key)
    }
}

inline static KeyMap mapKey(const RawKey &key)
{
    return mapKey(static_cast<gui::KeyCode>(key.keyCode));
}

inline static KeyMap mapKey(const bsp::KeyCodes key)
{
    return mapKey(static_cast<gui::KeyCode>(key));
}

struct KeyStates
{
    using KeySet = std::bitset<32U>;


@@ 55,12 66,25 @@ struct KeyStates
        return states.test(magic_enum::enum_integer(key));
    }

    std::size_t count()
    {
        return states.count();
    }

    template <typename... Args> bool ifOnlySet(Args... args)
    {
        const auto mask = (... | [](auto x) { return KeySet{1UL << magic_enum::enum_integer(x)}; }(args));
        return (states | mask) == mask;
    }

    template <std::size_t N> bool ifOnlySet(std::array<KeyMap, N> keys)
    {
        const auto mask = std::accumulate(keys.begin(), keys.end(), KeySet{}, [](auto m, auto val) {
            return m | KeySet{1UL << magic_enum::enum_integer(val)};
        });
        return (states | mask) == mask;
    }

  private:
    KeySet states;
};

M products/BellHybrid/services/appmgr/CMakeLists.txt => products/BellHybrid/services/appmgr/CMakeLists.txt +1 -0
@@ 8,6 8,7 @@ target_sources(appmgr
        include/appmgr/messages/AlarmMessage.hpp
        include/appmgr/ApplicationManager.hpp
        include/appmgr/messages/PowerOffPopupRequestParams.hpp
        include/appmgr/messages/RebootPopupRequestParams.hpp
)

target_include_directories(appmgr

A products/BellHybrid/services/appmgr/include/appmgr/messages/RebootPopupRequestParams.hpp => products/BellHybrid/services/appmgr/include/appmgr/messages/RebootPopupRequestParams.hpp +16 -0
@@ 0,0 1,16 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once

#include <Service/Message.hpp>
#include <apps-common/popups/data/PopupRequestParams.hpp>
#include <service-appmgr/Actions.hpp>
#include <service-appmgr/messages/ActionRequest.hpp>

class RebootPopupRequestParams : public gui::PopupRequestParams
{
  public:
    RebootPopupRequestParams() : PopupRequestParams{gui::popup::ID::Reboot}
    {}
};

M products/BellHybrid/services/evtmgr/CMakeLists.txt => products/BellHybrid/services/evtmgr/CMakeLists.txt +2 -2
@@ 8,7 8,6 @@ target_sources(evtmgr
        WorkerEvent.hpp
        internal/StaticData.cpp
        internal/TemperatureApi.cpp
        internal/key_sequences/LongPressSequence.cpp
        internal/key_sequences/KeySequenceMgr.cpp
        internal/StaticData.hpp
        screen-light-control/ScreenLightControl.cpp


@@ 20,7 19,8 @@ target_sources(evtmgr
        internal/key_sequences/AlarmActivateSequence.hpp
        internal/key_sequences/AlarmDeactivateSequence.hpp
        internal/key_sequences/ReleaseSequence.hpp
        internal/key_sequences/LongPressSequence.hpp
        internal/key_sequences/ResetSequence.hpp
        internal/key_sequences/GenericLongPressSequence.hpp
    PUBLIC
        include/evtmgr/EventManager.hpp
        include/evtmgr/api/TemperatureApi.hpp

M products/BellHybrid/services/evtmgr/EventManager.cpp => products/BellHybrid/services/evtmgr/EventManager.cpp +9 -0
@@ 7,8 7,10 @@
#include "internal/key_sequences/PowerOffSequence.hpp"
#include "internal/key_sequences/AlarmActivateSequence.hpp"
#include "internal/key_sequences/AlarmDeactivateSequence.hpp"
#include "internal/key_sequences/ResetSequence.hpp"

#include <appmgr/messages/PowerOffPopupRequestParams.hpp>
#include <appmgr/messages/RebootPopupRequestParams.hpp>
#include <evtmgr/EventManager.hpp>
#include <service-appmgr/Controller.hpp>
#include <hal/temperature_source/TemperatureSource.hpp>


@@ 119,6 121,13 @@ void EventManager::buildKeySequences()
    };
    collection.emplace_back(std::move(powerOffSeq));

    auto resetSeq      = std::make_unique<ResetSequence>(*this);
    resetSeq->onAction = [this]() {
        app::manager::Controller::sendAction(
            this, app::manager::actions::ShowPopup, std::make_unique<RebootPopupRequestParams>());
    };
    collection.emplace_back(std::move(resetSeq));

    auto alarmActivateSeq      = std::make_unique<AlarmActivateSequence>();
    alarmActivateSeq->onAction = [this]() {
        bus.sendUnicast(

M products/BellHybrid/services/evtmgr/WorkerEvent.cpp => products/BellHybrid/services/evtmgr/WorkerEvent.cpp +21 -0
@@ 65,4 65,25 @@ namespace bell
            }
        }
    }
    void WorkerEvent::processKeyEvent(bsp::KeyEvents event, bsp::KeyCodes code)
    {
        auto message = std::make_shared<sevm::KbdMessage>();

        message->key.keyCode = code;

        switch (event) {
        case bsp::KeyEvents::Pressed:
            message->key.state     = RawKey::State::Pressed;
            message->key.timePress = xTaskGetTickCount();
            break;
        case bsp::KeyEvents::Released:
            message->key.state       = RawKey::State::Released;
            message->key.timeRelease = xTaskGetTickCount();
            break;
        case bsp::KeyEvents::Moved:
            message->key.state = RawKey::State::Moved;
            break;
        }
        service->bus.sendUnicast(message, service::name::evt_manager);
    }
} // namespace bell

M products/BellHybrid/services/evtmgr/WorkerEvent.hpp => products/BellHybrid/services/evtmgr/WorkerEvent.hpp +1 -0
@@ 16,6 16,7 @@ namespace bell
        void addProductQueues(std::list<sys::WorkerQueueInfo> &queuesList) final;
        void initProductHardware() final;
        void deinitProductHardware() final;
        void processKeyEvent(bsp::KeyEvents event, bsp::KeyCodes code) final;
        bool handleMessage(std::uint32_t queueID) override;
        void processRotaryAsShortRelease(bsp::KeyCodes code);
        void handleRotaryEncoderEvent();

A products/BellHybrid/services/evtmgr/internal/key_sequences/GenericLongPressSequence.hpp => products/BellHybrid/services/evtmgr/internal/key_sequences/GenericLongPressSequence.hpp +87 -0
@@ 0,0 1,87 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once

#include "AbstractKeySequence.hpp"
#include <keymap/KeyMap.hpp>
#include <Timers/TimerHandle.hpp>

#include <chrono>
#include <memory>

template <KeyMap... keys> class GenericLongPressSequence : public AbstractKeySequence
{
    using Keys = std::array<KeyMap, sizeof...(keys)>;
    enum class State
    {
        Idle,
        InProgress,
        Ready,
        Abort
    };

  public:
    GenericLongPressSequence(sys::TimerHandle &&timer) : timer{std::move(timer)}
    {}

    void process(const RawKey &key) override
    {
        keyStates.set(mapKey(key), key.state == RawKey::State::Pressed);

        switch (state) {
        case State::Idle:
            if (keyStates.count() == keysToScanCount && keyStates.ifOnlySet(keysToScan)) {
                switch_to_in_progress();
            }
            break;
        case State::InProgress:
        case State::Ready:
            if (keyStates.count() > keysToScanCount) {
                switch_to_abort();
            }
            else if (keyStates.count() < keysToScanCount) {
                switch_to_idle();
            }
            break;
        case State::Abort:
            if (keyStates.count() == 0) {
                state = State::Idle;
            }
        }
    }

    void handleTimer()
    {
        state = State::Ready;
        ready();
    }

  private:
    void switch_to_abort()
    {
        state = State::Abort;
        abort();
        timer.stop();
    }

    void switch_to_idle()
    {
        state = State::Idle;
        idle();
    }

    void switch_to_in_progress()
    {
        state = State::InProgress;
        trigger();
        timer.stop();
        timer.start();
    }

    static constexpr Keys keysToScan      = {keys...};
    static constexpr auto keysToScanCount = keysToScan.size();
    State state                           = State::Idle;
    KeyStates keyStates;
    sys::TimerHandle timer;
};

M products/BellHybrid/services/evtmgr/internal/key_sequences/KeySequenceMgr.cpp => products/BellHybrid/services/evtmgr/internal/key_sequences/KeySequenceMgr.cpp +3 -3
@@ 8,12 8,12 @@ KeySequenceMgr::KeySequenceMgr(std::vector<std::unique_ptr<AbstractKeySequence>>
{
    auto onIdle = [this](auto &seq) {
        removeFromInProgressList(seq);
        actionIfPossible(seq);
        actionIfPossible();
    };
    auto onTriggered = [this](auto &seq) { addToInProgressList(seq); };
    auto onReady     = [this](auto &seq) {
        moveToReadyList(seq);
        actionIfPossible(seq);
        actionIfPossible();
    };
    auto onAbort = [this](auto &seq) {
        removeFromInProgressList(seq);


@@ 38,7 38,7 @@ void KeySequenceMgr::moveToReadyList(const AbstractKeySequence &seq)
        inProgressSequences.remove(&seq);
    }
}
void KeySequenceMgr::actionIfPossible(const AbstractKeySequence &seq)
void KeySequenceMgr::actionIfPossible()
{
    if (inProgressSequences.empty()) {
        if (const auto s = readySequences.back(); s != nullptr) {

M products/BellHybrid/services/evtmgr/internal/key_sequences/KeySequenceMgr.hpp => products/BellHybrid/services/evtmgr/internal/key_sequences/KeySequenceMgr.hpp +2 -2
@@ 17,7 17,7 @@ class KeySequenceMgr
    void process(const RawKey &key);

  private:
    void actionIfPossible(const AbstractKeySequence &seq);
    void actionIfPossible();

    void moveToReadyList(const AbstractKeySequence &seq);
    bool searchInReadyList(const AbstractKeySequence &seq);


@@ 26,7 26,7 @@ class KeySequenceMgr
    void addToInProgressList(const AbstractKeySequence &seq);
    void removeFromInProgressList(const AbstractKeySequence &seq);

    SequenceCollection sequenceCollection;
    const SequenceCollection sequenceCollection;
    std::list<const AbstractKeySequence *> inProgressSequences;
    std::list<const AbstractKeySequence *> readySequences;
};

D products/BellHybrid/services/evtmgr/internal/key_sequences/LongPressSequence.cpp => products/BellHybrid/services/evtmgr/internal/key_sequences/LongPressSequence.cpp +0 -39
@@ 1,39 0,0 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "LongPressSequence.hpp"

LongPressSequence::LongPressSequence(KeyMap keyToScan, sys::Service &service, std::chrono::milliseconds timeout)
    : keyToScan{keyToScan}
{
    timerHandle = sys::TimerFactory::createSingleShotTimer(&service, "lpressseq", timeout, [this](auto &) {
        state = State::Ready;
        ready();
    });
}
void LongPressSequence::process(const RawKey &key)
{
    keyStates.set(mapKey(static_cast<gui::KeyCode>(key.keyCode)), key.state == RawKey::State::Pressed);

    if (gate = keyStates.state(keyToScan) && not keyStates.ifOnlySet(keyToScan); gate) {
        if (state != State::Idle) {
            timerHandle.stop();
            state = State::Idle;
            abort();
        }
        return;
    }

    if (keyStates.state(keyToScan) && state == State::Idle) {
        timerHandle.stop();
        timerHandle.start();
        state = State::InProgress;
        trigger();
    }

    if (not keyStates.state(keyToScan) && state != State::Idle) {
        timerHandle.stop();
        state = State::Idle;
        idle();
    }
}

D products/BellHybrid/services/evtmgr/internal/key_sequences/LongPressSequence.hpp => products/BellHybrid/services/evtmgr/internal/key_sequences/LongPressSequence.hpp +0 -30
@@ 1,30 0,0 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once

#include "AbstractKeySequence.hpp"
#include <keymap/KeyMap.hpp>
#include <Timers/TimerFactory.hpp>

class LongPressSequence : public AbstractKeySequence
{
  public:
    enum class State
    {
        Idle,
        InProgress,
        Ready
    };
    LongPressSequence(KeyMap keyToScan, sys::Service &service, std::chrono::milliseconds timeout);

    void process(const RawKey &key) override;

  private:
    bool gate{false};
    KeyStates keyStates;
    KeyMap keyToScan;
    State state = State::Idle;
    std::chrono::milliseconds timeout;
    sys::TimerHandle timerHandle;
};

M products/BellHybrid/services/evtmgr/internal/key_sequences/PowerOffSequence.hpp => products/BellHybrid/services/evtmgr/internal/key_sequences/PowerOffSequence.hpp +6 -5
@@ 3,13 3,14 @@

#pragma once

#include "LongPressSequence.hpp"
#include "GenericLongPressSequence.hpp"
#include <Timers/TimerFactory.hpp>

class PowerOffSequence : public LongPressSequence
class PowerOffSequence : public GenericLongPressSequence<KeyMap::Back>
{
  public:
    explicit PowerOffSequence(sys::Service &service,
                              std::chrono::milliseconds timeout = std::chrono::milliseconds{5000})
        : LongPressSequence(KeyMap::Back, service, timeout)
    explicit PowerOffSequence(sys::Service &service)
        : GenericLongPressSequence<KeyMap::Back>{sys::TimerFactory::createSingleShotTimer(
              &service, "poffseq", std::chrono::milliseconds{5000}, [this](auto &) { handleTimer(); })}
    {}
};

A products/BellHybrid/services/evtmgr/internal/key_sequences/ResetSequence.hpp => products/BellHybrid/services/evtmgr/internal/key_sequences/ResetSequence.hpp +17 -0
@@ 0,0 1,17 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once

#include "GenericLongPressSequence.hpp"
#include <Timers/TimerFactory.hpp>

class ResetSequence : public GenericLongPressSequence<KeyMap::Back, KeyMap::Frontlight>
{
  public:
    explicit ResetSequence(sys::Service &service)
        : GenericLongPressSequence<KeyMap::Back, KeyMap::Frontlight>{sys::TimerFactory::createSingleShotTimer(
              &service, "rseq", std::chrono::milliseconds{10000}, [this](auto &) { handleTimer(); })}

    {}
};

M products/BellHybrid/services/evtmgr/tests/CMakeLists.txt => products/BellHybrid/services/evtmgr/tests/CMakeLists.txt +12 -1
@@ 1,6 1,6 @@
add_catch2_executable(
        NAME
        bell_evtmgr
        bell_evtmgr_KeySequenceMgr
        SRCS
        unittest_KeySequenceMgr.cpp
        LIBS


@@ 8,3 8,14 @@ add_catch2_executable(
        INCLUDE
        $<TARGET_PROPERTY:bell::evtmgr,INCLUDE_DIRECTORIES>
)

add_catch2_executable(
        NAME
        bell_evtmgr_GenericLongPressSequence
        SRCS
        unittest_GenericLongPressSequence.cpp
        LIBS
        bell::evtmgr
        INCLUDE
        $<TARGET_PROPERTY:bell::evtmgr,INCLUDE_DIRECTORIES>
)

A products/BellHybrid/services/evtmgr/tests/unittest_GenericLongPressSequence.cpp => products/BellHybrid/services/evtmgr/tests/unittest_GenericLongPressSequence.cpp +179 -0
@@ 0,0 1,179 @@
// 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

#include <catch2/catch.hpp>
#include <internal/key_sequences/GenericLongPressSequence.hpp>

namespace
{
    RawKey make_press_back()
    {
        RawKey rawKey{};
        rawKey.state   = RawKey::State::Pressed;
        rawKey.keyCode = bsp::KeyCodes::FnRight;
        return rawKey;
    }

    RawKey make_press_frontlight()
    {
        RawKey rawKey{};
        rawKey.state   = RawKey::State::Pressed;
        rawKey.keyCode = bsp::KeyCodes::FnLeft;
        return rawKey;
    }

    RawKey make_press_not_scanned()
    {
        RawKey rawKey{};
        rawKey.state   = RawKey::State::Pressed;
        rawKey.keyCode = bsp::KeyCodes::JoystickEnter;
        return rawKey;
    }

    RawKey make_release_back()
    {
        RawKey rawKey{};
        rawKey.state   = RawKey::State::Released;
        rawKey.keyCode = bsp::KeyCodes::FnRight;
        return rawKey;
    }

    RawKey make_release_frontlight()
    {
        RawKey rawKey{};
        rawKey.state   = RawKey::State::Released;
        rawKey.keyCode = bsp::KeyCodes::FnLeft;
        return rawKey;
    }

    class TimerMock : public sys::Timer
    {
        void start() override
        {}
        void restart(std::chrono::milliseconds newInterval) override
        {}
        void stop() override
        {}
        bool isActive() const noexcept override
        {
            return false;
        }
    };

    sys::TimerHandle mockFactory()
    {
        auto timer = new TimerMock();
        return sys::TimerHandle{timer};
    }

    class TestSequence : public GenericLongPressSequence<KeyMap::Back, KeyMap::Frontlight>
    {
      public:
        TestSequence() : GenericLongPressSequence<KeyMap::Back, KeyMap::Frontlight>{mockFactory()}
        {}
    };
} // namespace

TEST_CASE("GenericLongPressSequence")
{
    TestSequence sequence;

    std::uint8_t idleInvoke{};
    std::uint8_t triggerInvoke{};
    std::uint8_t abortInvoke{};
    std::uint8_t readyInvoke{};

    auto onIdle      = [&idleInvoke](auto &) { idleInvoke++; };
    auto onTriggered = [&triggerInvoke](auto &) { triggerInvoke++; };
    auto onReady     = [&readyInvoke](auto &) { readyInvoke++; };
    auto onAbort     = [&abortInvoke](auto &) { abortInvoke++; };

    sequence.setCallbacks(onTriggered, onReady, onIdle, onAbort);

    SECTION("Frontlight&Back pressed ought to invoke trigger callback")
    {
        sequence.process(make_press_back());
        CHECK(not triggerInvoke);

        sequence.process(make_press_frontlight());
        CHECK(triggerInvoke == 1);
    }

    SECTION("Frontlight&Back pressed for specified time interval ought to invoke ready callback")
    {
        sequence.process(make_press_back());
        sequence.process(make_press_frontlight());
        CHECK(not readyInvoke);

        sequence.handleTimer();
        CHECK(readyInvoke == 1);
    }

    SECTION("Frontlight release ought to invoke idle callback, case 1")
    {
        sequence.process(make_press_back());
        sequence.process(make_press_frontlight());
        CHECK(not readyInvoke);

        sequence.handleTimer();
        CHECK(readyInvoke == 1);

        sequence.process(make_release_frontlight());
        CHECK(idleInvoke == 1);
        CHECK(abortInvoke == 0);
    }

    SECTION("Frontlight release ought to invoke idle callback, case 2")
    {
        sequence.process(make_press_back());
        sequence.process(make_press_frontlight());

        sequence.process(make_release_frontlight());
        CHECK(idleInvoke == 1);
        CHECK(abortInvoke == 0);
    }

    SECTION("Back release ought to invoke idle callback, case 1")
    {
        sequence.process(make_press_back());
        sequence.process(make_press_frontlight());
        CHECK(not readyInvoke);

        sequence.handleTimer();
        CHECK(readyInvoke == 1);

        sequence.process(make_release_back());
        CHECK(idleInvoke == 1);
        CHECK(abortInvoke == 0);
    }

    SECTION("Back release ought to invoke idle callback, case 2")
    {
        sequence.process(make_press_back());
        sequence.process(make_press_frontlight());

        sequence.process(make_release_back());
        CHECK(idleInvoke == 1);
        CHECK(abortInvoke == 0);
    }

    SECTION("Press of any key different than Back or Frontlight ought to invoke abort callback, case 1")
    {
        sequence.process(make_press_back());
        sequence.process(make_press_frontlight());

        sequence.process(make_press_not_scanned());
        CHECK(abortInvoke == 1);
    }

    SECTION("Press of any keys different than Back or Frontlight ought to invoke abort callback, case 2")
    {
        sequence.process(make_press_back());
        sequence.process(make_press_frontlight());

        sequence.handleTimer();
        sequence.process(make_press_not_scanned());
        CHECK(abortInvoke == 1);
    }
}

M products/BellHybrid/services/evtmgr/tests/unittest_KeySequenceMgr.cpp => products/BellHybrid/services/evtmgr/tests/unittest_KeySequenceMgr.cpp +44 -102
@@ 5,101 5,43 @@

#include <catch2/catch.hpp>
#include <internal/key_sequences/KeySequenceMgr.hpp>
#include <internal/key_sequences/GenericLongPressSequence.hpp>
#include <keymap/KeyMap.hpp>

namespace
{
    enum class State
    class TimerMock : public sys::Timer
    {
        Idle,
        InProgress,
        Ready
    };

    class TwoKeysSequence : public AbstractKeySequence
    {
      public:
        explicit TwoKeysSequence(KeyMap keyToScan1, KeyMap keyToScan2) : keyToScan1{keyToScan1}, keyToScan2{keyToScan2}
        void start() override
        {}
        void restart(std::chrono::milliseconds newInterval) override
        {}
        void stop() override
        {}
        void process(const RawKey &key) override
        bool isActive() const noexcept override
        {
            const auto mappedKey = mapKey(static_cast<gui::KeyCode>(key.keyCode));
            if (mappedKey != keyToScan1 && mappedKey != keyToScan2) {
                return;
            }

            if (mappedKey == keyToScan1) {
                key1Pressed = key.state == RawKey::State::Pressed;
            }
            if (mappedKey == keyToScan2) {
                key2Pressed = key.state == RawKey::State::Pressed;
            }

            if (key1Pressed && key2Pressed && state == State::Idle) {
                state = State::InProgress;
                trigger();
            }

            if (not key1Pressed || not key2Pressed) {
                state = State::Idle;
                idle();
            }
            return false;
        }
    };

        void make_ready()
        {
            state = State::Ready;
            ready();
        }
    sys::TimerHandle mockFactory()
    {
        auto timer = new TimerMock();
        return sys::TimerHandle{timer};
    }

      private:
        bool key1Pressed{false};
        bool key2Pressed{false};
        KeyMap keyToScan1;
        KeyMap keyToScan2;
        State state = State::Idle;
    class TwoKeysSequence : public GenericLongPressSequence<KeyMap::Back, KeyMap::Frontlight>
    {
      public:
        TwoKeysSequence() : GenericLongPressSequence<KeyMap::Back, KeyMap::Frontlight>{mockFactory()}
        {}
    };

    class LongPressSequence : public AbstractKeySequence
    class LongPressSequence : public GenericLongPressSequence<KeyMap::Back>
    {
      public:
        explicit LongPressSequence(KeyMap keyToScan) : keyToScan{keyToScan}
        LongPressSequence() : GenericLongPressSequence<KeyMap::Back>{mockFactory()}
        {}
        void process(const RawKey &key) override
        {
            keyStates.set(mapKey(static_cast<gui::KeyCode>(key.keyCode)), key.state == RawKey::State::Pressed);

            if (gate = keyStates.state(keyToScan) && not keyStates.ifOnlySet(keyToScan); gate) {
                if (state != State::Idle) {
                    state = State::Idle;
                    abort();
                }
                return;
            }

            if (keyStates.state(keyToScan) && state == State::Idle) {
                state = State::InProgress;
                trigger();
            }

            if (not keyStates.state(keyToScan) && state != State::Idle) {
                state = State::Idle;
                idle();
            }
        }

        void make_ready()
        {
            state = State::Ready;
            ready();
        }

      private:
        bool gate{false};
        KeyStates keyStates;

        KeyMap keyToScan;
        State state = State::Idle;
    };

    RawKey make_press_back()


@@ 141,7 83,7 @@ TEST_CASE("Single long press")
    std::uint8_t actionTriggered{};
    KeySequenceMgr::SequenceCollection collection;

    auto mockLongPressBack      = std::make_unique<LongPressSequence>(KeyMap::Back);
    auto mockLongPressBack      = std::make_unique<LongPressSequence>();
    mockLongPressBack->onAction = [&actionTriggered]() { actionTriggered++; };
    LongPressSequence *mockRef  = mockLongPressBack.get();



@@ 159,14 101,14 @@ TEST_CASE("Single long press")
    SECTION("Long press without release")
    {
        keySequenceMgr.process(make_press_back());
        mockRef->make_ready();
        mockRef->handleTimer();
        CHECK(actionTriggered == 1);
    }

    SECTION("Valid sequence")
    {
        keySequenceMgr.process(make_press_back());
        mockRef->make_ready();
        mockRef->handleTimer();
        keySequenceMgr.process(make_release_back());
        CHECK(actionTriggered == 1);
    }


@@ 177,19 119,19 @@ TEST_CASE("Many long press sequences")
    KeySequenceMgr::SequenceCollection collection;

    std::uint8_t actionTriggered_1{};
    auto longPressBack_1      = std::make_unique<LongPressSequence>(KeyMap::Back);
    auto longPressBack_1      = std::make_unique<LongPressSequence>();
    longPressBack_1->onAction = [&actionTriggered_1]() { actionTriggered_1++; };
    LongPressSequence *ref_1  = longPressBack_1.get();
    collection.emplace_back(std::move(longPressBack_1));

    std::uint8_t actionTriggered_2{};
    auto longPressBack_2      = std::make_unique<LongPressSequence>(KeyMap::Back);
    auto longPressBack_2      = std::make_unique<LongPressSequence>();
    longPressBack_2->onAction = [&actionTriggered_2]() { actionTriggered_2++; };
    LongPressSequence *ref_2  = longPressBack_2.get();
    collection.emplace_back(std::move(longPressBack_2));

    std::uint8_t actionTriggered_3{};
    auto longPressBack_3      = std::make_unique<LongPressSequence>(KeyMap::Back);
    auto longPressBack_3      = std::make_unique<LongPressSequence>();
    longPressBack_3->onAction = [&actionTriggered_3]() { actionTriggered_3++; };
    LongPressSequence *ref_3  = longPressBack_3.get();
    collection.emplace_back(std::move(longPressBack_3));


@@ 202,15 144,15 @@ TEST_CASE("Many long press sequences")
        CHECK(not actionTriggered_1);
        CHECK(not actionTriggered_2);

        ref_1->make_ready();
        ref_1->handleTimer();
        CHECK(not actionTriggered_1);
        CHECK(not actionTriggered_2);

        ref_2->make_ready();
        ref_2->handleTimer();
        CHECK(not actionTriggered_1);
        CHECK(not actionTriggered_2);

        ref_3->make_ready();
        ref_3->handleTimer();
        CHECK(not actionTriggered_1);
        CHECK(not actionTriggered_2);
        CHECK(actionTriggered_3 == 1);


@@ 227,11 169,11 @@ TEST_CASE("Many long press sequences")
        CHECK(not actionTriggered_1);
        CHECK(not actionTriggered_2);

        ref_1->make_ready();
        ref_1->handleTimer();
        CHECK(not actionTriggered_1);
        CHECK(not actionTriggered_2);

        ref_2->make_ready();
        ref_2->handleTimer();
        CHECK(not actionTriggered_1);
        CHECK(not actionTriggered_2);



@@ 247,7 189,7 @@ TEST_CASE("Many long press sequences")
        CHECK(not actionTriggered_1);
        CHECK(not actionTriggered_2);

        ref_1->make_ready();
        ref_1->handleTimer();
        CHECK(not actionTriggered_1);
        CHECK(not actionTriggered_2);



@@ 263,12 205,12 @@ TEST_CASE("Many long press sequences")
        CHECK(not actionTriggered_2);
        CHECK(not actionTriggered_3);

        ref_1->make_ready();
        ref_1->handleTimer();
        CHECK(not actionTriggered_1);
        CHECK(not actionTriggered_2);
        CHECK(not actionTriggered_3);

        ref_2->make_ready();
        ref_2->handleTimer();
        CHECK(not actionTriggered_1);
        CHECK(not actionTriggered_2);
        CHECK(not actionTriggered_3);


@@ 299,7 241,7 @@ TEST_CASE("Two keys sequence")
    KeySequenceMgr::SequenceCollection collection;

    std::uint8_t actionTriggered_1{};
    auto sequence          = std::make_unique<TwoKeysSequence>(KeyMap::Back, KeyMap::Frontlight);
    auto sequence          = std::make_unique<TwoKeysSequence>();
    sequence->onAction     = [&actionTriggered_1]() { actionTriggered_1++; };
    TwoKeysSequence *ref_1 = sequence.get();
    collection.emplace_back(std::move(sequence));


@@ 312,7 254,7 @@ TEST_CASE("Two keys sequence")
        keySequenceMgr.process(make_press_back());
        CHECK(not actionTriggered_1);

        ref_1->make_ready();
        ref_1->handleTimer();
        CHECK(actionTriggered_1 == 1);

        keySequenceMgr.process(make_release_frontlight());


@@ 328,7 270,7 @@ TEST_CASE("Two keys sequence")
        CHECK(not actionTriggered_1);

        keySequenceMgr.process(make_release_back());
        ref_1->make_ready();
        ref_1->handleTimer();
        CHECK(not actionTriggered_1);

        keySequenceMgr.process(make_release_frontlight());


@@ 341,19 283,19 @@ TEST_CASE("Two keys sequence type mixed with long press sequences")
    KeySequenceMgr::SequenceCollection collection;

    std::uint8_t actionTriggered_1{};
    auto sequence1      = std::make_unique<LongPressSequence>(KeyMap::Back);
    auto sequence1      = std::make_unique<LongPressSequence>();
    sequence1->onAction = [&actionTriggered_1]() { actionTriggered_1++; };
    // LongPressSequence *ref_1 = sequence1.get();
    collection.emplace_back(std::move(sequence1));

    std::uint8_t actionTriggered_2{};
    auto sequence2      = std::make_unique<LongPressSequence>(KeyMap::Back);
    auto sequence2      = std::make_unique<LongPressSequence>();
    sequence2->onAction = [&actionTriggered_2]() { actionTriggered_2++; };
    // LongPressSequence *ref_2 = sequence2.get();
    collection.emplace_back(std::move(sequence2));

    std::uint8_t actionTriggered_3{};
    auto sequence3         = std::make_unique<TwoKeysSequence>(KeyMap::Back, KeyMap::Frontlight);
    auto sequence3         = std::make_unique<TwoKeysSequence>();
    sequence3->onAction    = [&actionTriggered_3]() { actionTriggered_3++; };
    TwoKeysSequence *ref_3 = sequence3.get();
    collection.emplace_back(std::move(sequence3));


@@ 387,7 329,7 @@ TEST_CASE("Two keys sequence type mixed with long press sequences")
        CHECK(not actionTriggered_2);
        CHECK(not actionTriggered_3);

        ref_3->make_ready();
        ref_3->handleTimer();
        CHECK(not actionTriggered_1);
        CHECK(not actionTriggered_2);
        CHECK(actionTriggered_3 == 1);


@@ 411,7 353,7 @@ TEST_CASE("Two keys sequence type mixed with long press sequences")
        CHECK(not actionTriggered_2);
        CHECK(not actionTriggered_3);

        ref_3->make_ready();
        ref_3->handleTimer();
        CHECK(not actionTriggered_1);
        CHECK(not actionTriggered_2);
        CHECK(actionTriggered_3 == 1);