~aleteoryx/muditaos

106440c78daac588394d01910fe7a5514497a5d1 — Kuba 3 years ago 35e587b
[MOS-326] Change call logic removed from call window

Call logic is now removed from call window and call app.
There was spaghetti logic mixed in window and app, now
call logic is based on notificatins from service cellular.
M module-apps/application-call/ApplicationCall.cpp => module-apps/application-call/ApplicationCall.cpp +52 -83
@@ 12,7 12,7 @@
#include <apps-common/windows/DialogMetadata.hpp>
#include <log/log.hpp>
#include <module-apps/application-phonebook/data/PhonebookItemData.hpp>
#include <Timers/TimerFactory.hpp>

#include <PhoneNumber.hpp>
#include <service-appmgr/Controller.hpp>
#include <service-appmgr/data/MmiActionsParams.hpp>


@@ 37,7 37,7 @@ namespace app
            if (popupParams.getPopupId() == gui::popup::ID::Volume) {
                return true;
            }
            return this->getCallState() == call::State::IDLE;
            return true;
        });
        statusBarManager->enableIndicators(
            {Indicator::Signal, Indicator::Time, Indicator::Battery, Indicator::SimCard});


@@ 82,34 82,45 @@ namespace app
            return actionHandled();
        });
        addActionReceiver(manager::actions::AbortCall, [this](auto &&data) {
            callerIdTimer.reset();
            if (const auto state = getState(); state == Application::State::ACTIVE_FORGROUND) {
                switchWindow(window::name_call, std::make_unique<app::CallAbortData>());
                switchWindow(window::name_call);
            }
            else if (state == Application::State::ACTIVE_BACKGROUND) {
                setCallState(call::State::IDLE);
                callModel->setState(app::call::CallState::None);
                manager::Controller::finish(this);
            }
            return actionHandled();
        });
        addActionReceiver(manager::actions::ActivateCall, [this](auto &&data) {
            switchWindow(window::name_call, std::make_unique<app::CallActiveData>());
            switchWindow(window::name_call);
            return actionHandled();
        });
        addActionReceiver(manager::actions::HandleOutgoingCall, [this](auto &&data) {
            auto callParams = static_cast<app::manager::actions::CallParams *>(data.get());
            switchWindow(window::name_call, std::make_unique<app::ExecuteCallData>(callParams->getNumber()));
            switchWindow(window::name_call);
            return actionHandled();
        });
        addActionReceiver(manager::actions::HandleIncomingCall, [this](auto &&data) {
            handleIncomingCall();
            callModel->setState(call::CallState::Incoming);
            auto window = getCurrentWindow();
            if (window->getName() != app::window::name_call) {
                LOG_INFO("Switch to call window");
                switchWindow(app::window::name_call);
            }
            return actionHandled();
        });
        addActionReceiver(manager::actions::HandleCallerId, [this](auto &&data) {
            auto callParams = static_cast<app::manager::actions::CallParams *>(data.get());
            handleCallerId(callParams);
            callModel->setPhoneNumber(callParams->getNumber());
            callModel->setState(call::CallState::Incoming);
            auto window = getCurrentWindow();
            if (window->getName() != app::window::name_call) {
                LOG_INFO("Switch to call window");
                switchWindow(app::window::name_call);
            }
            return actionHandled();
        });

        callModel = std::make_shared<app::call::CallModel>(this);
    }

    bool ApplicationCall::conditionalReturnToPreviousView()


@@ 145,6 156,29 @@ namespace app
            return ret;
        }

        connect(typeid(cellular::CallDurationNotification), [&](sys::Message *request) {
            auto message = static_cast<cellular::CallDurationNotification *>(request);
            callModel->setTime(message->callDuration);
            return sys::MessageNone{};
        });

        connect(typeid(cellular::CallActiveNotification), [&](sys::Message *request) {
            callModel->setState(app::call::CallState::Active);
            return sys::MessageNone{};
        });

        connect(typeid(cellular::CallEndedNotification), [&](sys::Message *request) {
            callModel->setState(app::call::CallState::Ended);
            return sys::MessageNone{};
        });

        connect(typeid(cellular::CallStartedNotification), [&](sys::Message *request) {
            auto message = static_cast<cellular::CallStartedNotification *>(request);
            callModel->setPhoneNumber(message->getNumber());
            callModel->setState(app::call::CallState::Outgoing);
            return sys::MessageNone{};
        });

        createUserInterface();

        return ret;


@@ 155,8 189,9 @@ namespace app
        windowsFactory.attach(app::window::name_enterNumber, [](ApplicationCommon *app, const std::string &name) {
            return std::make_unique<gui::EnterNumberWindow>(app, static_cast<ApplicationCall *>(app));
        });
        windowsFactory.attach(app::window::name_call, [](ApplicationCommon *app, const std::string &name) {
            return std::make_unique<gui::CallWindow>(app, static_cast<ApplicationCall *>(app));
        windowsFactory.attach(app::window::name_call, [this](ApplicationCommon *app, const std::string &name) {
            return std::make_unique<gui::CallWindow>(
                app, std::make_unique<app::call::CallWindowContract::Presenter>(this->callModel));
        });
        windowsFactory.attach(app::window::name_emergencyCall, [](ApplicationCommon *app, const std::string &name) {
            return std::make_unique<gui::EmergencyCallWindow>(app, static_cast<ApplicationCall *>(app));


@@ 181,47 216,17 @@ namespace app
    {
        auto buttonAction = [=]() -> bool { return conditionalReturnToPreviousView(); };
        auto icon         = type == NotificationType::Info ? "info_128px_W_G" : "fail_128px_W_G";
        setCallState(call::State::IDLE);
        callModel->clear();
        return showNotification(buttonAction, icon, text);
    }

    void ApplicationCall::destroyUserInterface()
    {}

    void ApplicationCall::handleIncomingCall()
    {
        if (getCallState() != call::State::IDLE) {
            return;
        }

        constexpr auto callerIdTimeout = std::chrono::seconds{1};
        callerIdTimer                  = sys::TimerFactory::createSingleShotTimer(
            this, "CallerIdTimer", callerIdTimeout, [this]([[maybe_unused]] sys::Timer &timer) {
                switchWindow(window::name_call,
                             std::make_unique<app::IncomingCallData>(utils::PhoneNumber().getView()));
            });
        callerIdTimer.start();
    }

    void ApplicationCall::handleCallerId(const app::manager::actions::CallParams *params)
    {
        /// there is race condition in this code, we require `INCOMMING_CALL` state check due to it.
        /// alternatively we would require rewritting this application state handling
        if (getCallState() != call::State::IDLE && getCallState() != app::call::State::INCOMING_CALL) {
            LOG_ERROR("call id not handled");
            return;
        }
        if (callerIdTimer.isValid()) {
            callerIdTimer.stop();
            callerIdTimer.reset();
        }
        switchWindow(window::name_call, std::make_unique<app::IncomingCallData>(params->getNumber()));
    }

    void ApplicationCall::handleEmergencyCallEvent(const std::string &number)
    {
        auto state = getCallState();
        if (state != call::State::IDLE) {
        auto state = callModel->getState();
        if (state != call::CallState::None) {
            LOG_WARN("Cannot call in %s state", c_str(state));
            return;
        }


@@ 230,13 235,12 @@ namespace app

    void ApplicationCall::handleCallEvent(const std::string &number, ExternalRequest isExternalRequest)
    {
        auto state = getCallState();
        if (state != call::State::IDLE) {
        auto state = callModel->getState();
        if (state != call::CallState::None) {
            LOG_WARN("Cannot call in %s state", c_str(state));
            return;
        }
        CellularServiceAPI::DialNumber(this, utils::PhoneNumber(number));
        setCallState(call::State::OUTGOING_CALL);
        externalRequest = isExternalRequest;
    }



@@ 262,39 266,4 @@ namespace app
                this, manager::actions::AddContact, std::move(data), manager::OnSwitchBehaviour::RunInBackground);
        }
    }

    void ApplicationCall::sendAudioEvent(AudioEvent audioEvent)
    {
        switch (audioEvent) {
        case AudioEvent::Mute:
            CellularServiceAPI::CallAudioMuteEvent(this);
            break;
        case AudioEvent::Unmute:
            CellularServiceAPI::CallAudioUnmuteEvent(this);
            break;
        case AudioEvent::LoudspeakerOn:
            CellularServiceAPI::CallAudioLoudspeakerOnEvent(this);
            break;
        case AudioEvent::LoudspeakerOff:
            CellularServiceAPI::CallAudioLoudspeakerOffEvent(this);
            break;
        }
    }

    void ApplicationCall::hangupCall()
    {
        CellularServiceAPI::HangupCall(this);
    }

    void ApplicationCall::answerIncomingCall()
    {
        CellularServiceAPI::AnswerIncomingCall(this);
    }

    void ApplicationCall::transmitDtmfTone(uint32_t digit)
    {
        if (!CellularServiceAPI::TransmitDtmfTones(this, digit)) {
            LOG_ERROR("transmitDtmfTone failed");
        }
    }
} // namespace app

M module-apps/application-call/CMakeLists.txt => module-apps/application-call/CMakeLists.txt +6 -1
@@ 20,6 20,8 @@ target_sources(application-call
		ApplicationCall.cpp
		data/CallAppStyle.hpp
		data/CallSwitchData.hpp
		model/CallModel.cpp
		presenter/CallPresenter.cpp
		widgets/StateIcon.hpp
		widgets/StateIcons.cpp
		widgets/StateIcons.hpp


@@ 33,7 35,6 @@ target_sources(application-call
		windows/NumberWindow.hpp
	PUBLIC
		include/application-call/ApplicationCall.hpp
		include/application-call/CallState.hpp
)

target_link_libraries(application-call


@@ 55,3 56,7 @@ target_link_libraries(application-call
		service-cellular
		service-evtmgr
)

if (${ENABLE_TESTS})
	add_subdirectory( test )
endif()

M module-apps/application-call/data/CallSwitchData.hpp => module-apps/application-call/data/CallSwitchData.hpp +0 -19
@@ 57,29 57,10 @@ namespace app
        }
    };

    class IncomingCallData : public CallSwitchData
    {
      public:
        IncomingCallData(const utils::PhoneNumber::View &phoneNumber)
            : CallSwitchData(phoneNumber, CallSwitchData::Type::INCOMING_CALL){};
    };

    class ExecuteCallData : public CallSwitchData
    {
      public:
        ExecuteCallData(const utils::PhoneNumber::View &phoneNumber)
            : CallSwitchData(phoneNumber, app::CallSwitchData::Type::EXECUTE_CALL){};
    };

    class CallAbortData : public gui::SwitchData
    {
      public:
        CallAbortData() = default;
    };

    class CallActiveData : public gui::SwitchData
    {
      public:
        CallActiveData() = default;
    };
} /* namespace app */

M module-apps/application-call/include/application-call/ApplicationCall.hpp => module-apps/application-call/include/application-call/ApplicationCall.hpp +4 -55
@@ 3,8 3,7 @@

#pragma once

#include "Application.hpp"
#include "CallState.hpp"
#include <application-call/model/CallModel.hpp>

#include <Timers/TimerHandle.hpp>
#include <service-cellular/CellularMessage.hpp>


@@ 13,6 12,7 @@
#include <Service/Message.hpp>
#include <SystemManager/SystemManagerCommon.hpp>
#include <AppWindowConstants.hpp>
#include <products/PurePhone/apps/include/Application.hpp>

namespace app
{


@@ 28,29 28,6 @@ namespace app
        inline constexpr auto name_dialogConfirm     = "DialogConfirm";
        inline constexpr auto name_number            = "NumberWindow";
    } // namespace window
    class CallWindowInterface
    {
      public:
        using NumberChangeCallback              = std::function<void(utils::PhoneNumber::View)>;
        virtual ~CallWindowInterface() noexcept = default;

        virtual void setCallState(app::call::State state) noexcept              = 0;
        [[nodiscard]] virtual auto getCallState() const noexcept -> call::State = 0;

        enum class AudioEvent
        {
            Mute,
            Unmute,
            LoudspeakerOn,
            LoudspeakerOff
        };

        virtual void sendAudioEvent(AudioEvent audioEvent) = 0;

        virtual void transmitDtmfTone(uint32_t digit) = 0;
        virtual void hangupCall()                     = 0;
        virtual void answerIncomingCall()             = 0;
    };

    class EnterNumberWindowInterface
    {


@@ 67,7 44,7 @@ namespace app
        virtual void handleAddContactEvent(const std::string &number)                            = 0;
    };

    class ApplicationCall : public Application, public CallWindowInterface, public EnterNumberWindowInterface
    class ApplicationCall : public Application, public EnterNumberWindowInterface
    {
      public:
        explicit ApplicationCall(std::string name                    = name_call,


@@ 85,8 62,6 @@ namespace app
        void createUserInterface() override;
        void destroyUserInterface() override;

        void handleIncomingCall();
        void handleCallerId(const app::manager::actions::CallParams *params);
        void handleEmergencyCallEvent(const std::string &number) override;
        void handleCallEvent(const std::string &number, ExternalRequest isExternalRequest) override;
        void handleAddContactEvent(const std::string &number) override;


@@ 99,36 74,10 @@ namespace app
        };
        auto showNotificationAndRestartCallFlow(NotificationType type, const std::string &text) -> bool;

        [[nodiscard]] auto getCallState() const noexcept -> call::State override
        {
            return callState;
        }
        void setCallState(call::State state) noexcept override
        {
            if (state == call::State::CALL_IN_PROGRESS) {
                bus.sendUnicast(
                    std::make_shared<sevm::KeypadBacklightMessage>(bsp::keypad_backlight::Action::turnOnCallMode),
                    service::name::evt_manager);
            }
            else {
                bus.sendUnicast(
                    std::make_shared<sevm::KeypadBacklightMessage>(bsp::keypad_backlight::Action::turnOffCallMode),
                    service::name::evt_manager);
            }
            this->callState = state;
        }

        void sendAudioEvent(AudioEvent audioEvent) override;

        void transmitDtmfTone(uint32_t digit) override;
        void hangupCall() override;
        void answerIncomingCall() override;

      private:
        sys::TimerHandle callerIdTimer;
        std::shared_ptr<app::call::AbstractCallModel> callModel;

      protected:
        call::State callState           = call::State::IDLE;
        ExternalRequest externalRequest = ExternalRequest::False;

        bool conditionalReturnToPreviousView();

A module-apps/application-call/model/CallModel.cpp => module-apps/application-call/model/CallModel.cpp +169 -0
@@ 0,0 1,169 @@
// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "CallModel.hpp"

#include <service-cellular/service-cellular/CellularServiceAPI.hpp>
#include <service-evtmgr/service-evtmgr/EVMessages.hpp>
#include <service-evtmgr/Constants.hpp>

namespace app::call
{
    time_t CallModel::getTime()
    {
        return callTime;
    }

    void CallModel::setTime(time_t newTime)
    {
        callTime = newTime;

        if (notifyPresenterOnDurationChange != nullptr) {
            notifyPresenterOnDurationChange();
        }
    }

    CallState CallModel::getState()
    {
        return callState;
    }

    void CallModel::setState(CallState newState)
    {
        if (callState == newState) {
            LOG_INFO("Dropping call state change");
        }

        if (callState == CallState::Incoming && newState == CallState::Ended && callWasRejected) {
            callWasRejected = false;
            callState       = CallState::Rejected;
        }
        else {
            callState = newState;
        }

        if (callState == CallState::Active) {
            turnOnKeypadBacklight();
        }
        else {
            turnOffKeypadBacklight();
        }

        if (notifyPresenterOnCallStateChange != nullptr) {
            notifyPresenterOnCallStateChange();
        }
    }

    void CallModel::hangUpCall()
    {
        CellularServiceAPI::HangupCall(application);
        if (callState == CallState::Incoming) {
            callWasRejected = true;
        }
    }

    void CallModel::answerCall()
    {
        CellularServiceAPI::AnswerIncomingCall(application);
    }

    void CallModel::transmitDtmfTone(const uint32_t &digit)
    {
        CellularServiceAPI::TransmitDtmfTones(application, digit);
    }

    utils::PhoneNumber CallModel::getPhoneNumber()
    {
        return phoneNumber;
    }

    void CallModel::muteCall()
    {
        CellularServiceAPI::CallAudioMuteEvent(application);
    }

    void CallModel::unmuteCall()
    {
        CellularServiceAPI::CallAudioUnmuteEvent(application);
    }

    void CallModel::turnLoudspeakerOn()
    {
        CellularServiceAPI::CallAudioLoudspeakerOnEvent(application);
    }

    void CallModel::turnLoudspeakerOff()
    {
        CellularServiceAPI::CallAudioLoudspeakerOffEvent(application);
    }

    void CallModel::turnOnKeypadBacklight()
    {
        application->bus.sendUnicast(
            std::make_shared<sevm::KeypadBacklightMessage>(bsp::keypad_backlight::Action::turnOnCallMode),
            service::name::evt_manager);
    }

    void CallModel::turnOffKeypadBacklight()
    {
        application->bus.sendUnicast(
            std::make_shared<sevm::KeypadBacklightMessage>(bsp::keypad_backlight::Action::turnOffCallMode),
            service::name::evt_manager);
    }

    void CallModel::clear()
    {
        callState = CallState::None;
        callTime  = 0;
        callerId.clear();
        callWasRejected = false;
        phoneNumber     = utils::PhoneNumber();

        notifyPresenterOnDurationChange  = nullptr;
        notifyPresenterOnCallStateChange = nullptr;
    }
    void CallModel::attachDurationChangeCallback(std::function<void()> callback)
    {
        notifyPresenterOnDurationChange = callback;
    }

    void CallModel::attachCallStateChangeCallback(std::function<void()> callback)
    {
        notifyPresenterOnCallStateChange = callback;
    }

    void CallModel::setPhoneNumber(const utils::PhoneNumber number)
    {
        if (phoneNumber.getFormatted() == number.getFormatted()) {
            LOG_INFO("Dropping number duplication.");
            return;
        }

        phoneNumber = number;

        if (notifyPresenterOnCallStateChange != nullptr) {
            notifyPresenterOnCallStateChange();
        }
    }

    UTF8 CallModel::getCallerId()
    {
        if (phoneNumber.getFormatted().empty()) {
            LOG_INFO("No number provided");
            callerId = UTF8();
            return callerId;
        }

        auto contact = DBServiceAPI::MatchContactByPhoneNumber(application, phoneNumber.getView());
        if (contact && !contact->isTemporary()) {
            LOG_INFO("number recognized as contact id = %" PRIu32, contact->ID);
            callerId = contact->getFormattedName();
        }
        else {
            LOG_INFO("number was not recognized as any valid contact");
            callerId = UTF8(phoneNumber.getFormatted());
        }

        return callerId;
    }
} // namespace app::call

A module-apps/application-call/model/CallModel.hpp => module-apps/application-call/model/CallModel.hpp +113 -0
@@ 0,0 1,113 @@
// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once

#include <module-utils/time/time/time_conversion.hpp>
#include <module-utils/phonenumber/PhoneNumber.hpp>

#include <products/PurePhone/apps/include/Application.hpp>

#include <cstdint>

namespace app::call
{
    enum class CallState
    {
        None,
        Incoming,
        Outgoing,
        Active,
        Ended,
        Rejected
    };

    inline auto c_str(app::call::CallState state) -> const char *
    {
        switch (state) {
        case CallState::None:
            return "None";
        case CallState::Incoming:
            return "Incoming";
        case CallState::Outgoing:
            return "Outgoing";
        case CallState::Active:
            return "Active";
        case CallState::Ended:
            return "Ended";
        case CallState::Rejected:
            return "Ended";
        }
        return "";
    }

    class AbstractCallModel
    {
      public:
        virtual ~AbstractCallModel() noexcept                        = default;
        virtual time_t getTime()                                     = 0;
        virtual void setTime(time_t newTime)                         = 0;
        virtual CallState getState()                                 = 0;
        virtual void setState(CallState newState)                    = 0;
        virtual void setPhoneNumber(const utils::PhoneNumber number) = 0;
        virtual utils::PhoneNumber getPhoneNumber()                  = 0;
        virtual UTF8 getCallerId()                                   = 0;

        virtual void hangUpCall()                            = 0;
        virtual void answerCall()                            = 0;
        virtual void transmitDtmfTone(const uint32_t &digit) = 0;
        virtual void muteCall()                              = 0;
        virtual void unmuteCall()                            = 0;
        virtual void turnLoudspeakerOn()                     = 0;
        virtual void turnLoudspeakerOff()                    = 0;

        virtual void turnOnKeypadBacklight()  = 0;
        virtual void turnOffKeypadBacklight() = 0;

        virtual void clear()                                                       = 0;
        virtual void attachDurationChangeCallback(std::function<void()> callback)  = 0;
        virtual void attachCallStateChangeCallback(std::function<void()> callback) = 0;
    };

    class CallModel : public AbstractCallModel
    {
      public:
        CallModel() = delete;
        explicit CallModel(app::Application *app) : application(app){};
        time_t getTime() final;
        void setTime(time_t newTime) final;
        CallState getState() final;
        void setState(CallState newState) final;
        void setPhoneNumber(const utils::PhoneNumber number) final;
        utils::PhoneNumber getPhoneNumber() final;
        UTF8 getCallerId() final;

        void hangUpCall() final;
        void answerCall() final;
        void transmitDtmfTone(const uint32_t &digit) final;
        void muteCall();
        void unmuteCall();
        void turnLoudspeakerOn();
        void turnLoudspeakerOff();

        void turnOnKeypadBacklight() final;
        void turnOffKeypadBacklight() final;

        void clear() final;
        void attachDurationChangeCallback(std::function<void()> callback) final;
        void attachCallStateChangeCallback(std::function<void()> callback) final;

      private:
        Application *application;
        CallState callState = CallState::None;
        time_t callTime     = 0;

        std::function<void()> notifyPresenterOnDurationChange;
        std::function<void()> notifyPresenterOnCallStateChange;

        utils::PhoneNumber phoneNumber;
        UTF8 callerId;

        bool callWasRejected = false;
    };
} // namespace app::call

A module-apps/application-call/presenter/CallPresenter.cpp => module-apps/application-call/presenter/CallPresenter.cpp +162 -0
@@ 0,0 1,162 @@
// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "CallPresenter.hpp"
#include <application-call/data/CallAppStyle.hpp>

namespace app::call
{

    CallWindowContract::Presenter::Presenter(std::shared_ptr<app::call::AbstractCallModel> callModel) : model(callModel)
    {
        model->attachCallStateChangeCallback([this]() { this->handleCallStateChange(); });
        model->attachDurationChangeCallback([this]() { this->handleCallDurationChange(); });
    }

    CallWindowContract::Presenter::~Presenter() noexcept
    {
        model->clear();
    }

    void CallWindowContract::Presenter::handleCallDurationChange()
    {
        if (not isCallInProgress()) {
            return;
        }
        auto view = getView();
        view->updateDuration(utils::time::Duration(model->getTime()).str());
        view->refreshWindow();
    }

    void CallWindowContract::Presenter::handleCallStateChange()
    {
        buildLayout();
        auto view = getView();
        view->refreshWindow();
    }

    void CallWindowContract::Presenter::buildLayout()
    {
        auto view      = getView();
        auto callState = model->getState();

        switch (callState) {
        case app::call::CallState::Incoming:
            view->updateDuration(utils::translate(callAppStyle::strings::iscalling));
            view->setIncomingCallLayout(model->getCallerId().empty() ? true : false);
            view->updateNumber(getCallerId());
            break;
        case app::call::CallState::Outgoing:
            view->updateDuration(utils::translate(callAppStyle::strings::calling));
            view->updateNumber(getCallerId());
            view->setActiveCallLayout();
            break;
        case app::call::CallState::Active:
            view->updateDuration(utils::time::Duration(model->getTime()).str());
            view->setActiveCallLayout();
            break;
        case app::call::CallState::Rejected:
            view->updateDuration(utils::translate(callAppStyle::strings::callrejected));
            view->setCallEndedLayout();
            break;
        case app::call::CallState::Ended:
            view->updateDuration(utils::translate(callAppStyle::strings::callended));
            view->setCallEndedLayout();
            break;
        case app::call::CallState::None:
            view->clearNavBar();
            break;
        }
    }

    UTF8 CallWindowContract::Presenter::getCallerId()
    {
        auto callerId = model->getCallerId();
        if (not callerId.empty()) {
            return callerId;
        }
        return UTF8(callAppStyle::strings::privateNumber);
    }

    bool CallWindowContract::Presenter::handleLeftButton()
    {
        if (model->getState() == app::call::CallState::Incoming) {
            model->answerCall();
            return true;
        }
        return false;
    }

    bool CallWindowContract::Presenter::handleRightButton()
    {
        if (isIncomingCall() || isCallInProgress()) {
            model->hangUpCall();
            return true;
        }
        return false;
    }

    bool CallWindowContract::Presenter::handleHeadsetOk()
    {
        if (isIncomingCall()) {
            model->answerCall();
            return true;
        }

        if (isCallInProgress()) {
            model->hangUpCall();
            return true;
        }

        return false;
    }

    void CallWindowContract::Presenter::hangUpCall()
    {
        model->hangUpCall();
    }

    bool CallWindowContract::Presenter::handleDigitButton(const uint32_t &digit)
    {
        model->transmitDtmfTone(digit);
        return true;
    }

    void CallWindowContract::Presenter::muteCall()
    {
        model->muteCall();
    }

    void CallWindowContract::Presenter::unmuteCall()
    {
        model->unmuteCall();
    }

    void CallWindowContract::Presenter::turnLoudspeakerOn()
    {
        model->turnLoudspeakerOn();
    }

    void CallWindowContract::Presenter::turnLoudspeakerOff()
    {
        model->turnLoudspeakerOff();
    }

    bool CallWindowContract::Presenter::isIncomingCall()
    {
        return model->getState() == CallState::Incoming;
    }

    bool CallWindowContract::Presenter::isCallInProgress()
    {
        if (auto state = model->getState(); state == CallState::Outgoing || state == CallState::Active) {
            return true;
        }
        return false;
    }

    utils::PhoneNumber CallWindowContract::Presenter::getPhoneNumber()
    {
        return model->getPhoneNumber();
    }
} // namespace app::call

A module-apps/application-call/presenter/CallPresenter.hpp => module-apps/application-call/presenter/CallPresenter.hpp +60 -0
@@ 0,0 1,60 @@
// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once

#include <module-apps/application-call/model/CallModel.hpp>
#include <module-apps/application-call/widgets/StateIcons.hpp>
#include "BasePresenter.hpp"

namespace app::call
{
    class CallWindowContract
    {
      public:
        class View
        {
          public:
            virtual void updateDuration(const UTF8 &text, bool isVisible = true) = 0;
            virtual void refreshWindow()                                         = 0;
            virtual void setNavBarForActiveCall()                                = 0;
            virtual void setNavBarForIncomingCall()                              = 0;
            virtual void clearNavBar()                                           = 0;
            virtual void setIncomingCallLayout(bool isValidCallerId)             = 0;
            virtual void setActiveCallLayout()                                   = 0;
            virtual void setCallEndedLayout()                                    = 0;
            virtual void updateNumber(const UTF8 &text)                          = 0;

            virtual ~View() noexcept = default;
        };

        class Presenter : public BasePresenter<CallWindowContract::View>
        {
          public:
            Presenter(std::shared_ptr<app::call::AbstractCallModel> callModel);
            ~Presenter() noexcept override;

            void handleCallDurationChange();
            void handleCallStateChange();
            void buildLayout();

            bool handleLeftButton();
            bool handleRightButton();
            bool handleHeadsetOk();
            bool handleDigitButton(const uint32_t &digit);
            void muteCall();
            void unmuteCall();
            void turnLoudspeakerOn();
            void turnLoudspeakerOff();

            void hangUpCall();
            utils::PhoneNumber getPhoneNumber();

          private:
            std::shared_ptr<app::call::AbstractCallModel> model;
            UTF8 getCallerId();
            bool isIncomingCall();
            bool isCallInProgress();
        };
    };
} // namespace app::call

A module-apps/application-call/test/CMakeLists.txt => module-apps/application-call/test/CMakeLists.txt +14 -0
@@ 0,0 1,14 @@
add_catch2_executable(
        NAME
        call-presenter
        SRCS
        unittest_call_presenter.cpp
        LIBS
        application-call
        i18n
        log
        utils-time
        module-utils
        app
        apps-common
)

A module-apps/application-call/test/mock/CallPresenterMocks.hpp => module-apps/application-call/test/mock/CallPresenterMocks.hpp +126 -0
@@ 0,0 1,126 @@
// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once

#include <module-apps/application-call/model/CallModel.hpp>
#include <module-apps/application-call/presenter/CallPresenter.hpp>

namespace app::call
{

    class ViewMock : public CallWindowContract::View
    {
      public:
        void updateDuration(const UTF8 &text, bool isVisible = true) override
        {
            duration = text;
        };
        void refreshWindow() override
        {
            windowRefreshed = true;
        };
        void setNavBarForActiveCall() override{};
        void setNavBarForIncomingCall() override{};
        void clearNavBar() override{};
        void setIncomingCallLayout(bool isValidCallerId) override
        {
            layoutShowed = LayoutShowed::Incoming;
        };
        void setActiveCallLayout() override
        {
            layoutShowed = LayoutShowed::Active;
        };
        ;
        void setCallEndedLayout() override
        {
            layoutShowed = LayoutShowed::Ended;
        };
        ;
        void updateNumber(const UTF8 &text) override
        {
            number = text;
        };

        bool windowRefreshed = false;

        enum class LayoutShowed
        {
            Incoming,
            Active,
            Ended,
            None
        };

        LayoutShowed layoutShowed = LayoutShowed::None;
        UTF8 number;
        UTF8 duration;
    };

    class ModelMock : public AbstractCallModel
    {
      public:
        time_t getTime() override
        {
            return callTime;
        };
        void setTime(time_t newTime) override
        {
            callTime = newTime;
        };
        CallState getState() override
        {
            return callState;
        };
        void setState(CallState newState) override
        {
            callState = newState;
        };
        void setPhoneNumber(const utils::PhoneNumber number) override{};
        utils::PhoneNumber getPhoneNumber() override
        {
            return phoneNumber;
        };
        UTF8 getCallerId() override
        {
            return callerId;
        };

        void hangUpCall() override
        {
            hangupCallCalled = true;
        };
        void answerCall() override
        {
            answerCallCalled = true;
        };
        void transmitDtmfTone(const uint32_t &digit) override{};
        void muteCall() override{};
        void unmuteCall() override{};
        void turnLoudspeakerOn() override{};
        void turnLoudspeakerOff() override{};

        void turnOnKeypadBacklight() override{};
        void turnOffKeypadBacklight() override{};

        void clear() override{};
        void attachDurationChangeCallback(std::function<void()> callback) override{};
        void attachCallStateChangeCallback(std::function<void()> callback) override{};

        bool hangupCallCalled = false;
        bool answerCallCalled = false;

      private:
        Application *application;
        CallState callState = CallState::None;
        time_t callTime     = 0;

        std::function<void()> notifyPresenterOnDurationChange;
        std::function<void()> notifyPresenterOnCallStateChange;

        utils::PhoneNumber phoneNumber;
        UTF8 callerId;

        bool callWasRejected = false;
    };
} // namespace app::call

A module-apps/application-call/test/unittest_call_presenter.cpp => module-apps/application-call/test/unittest_call_presenter.cpp +245 -0
@@ 0,0 1,245 @@
// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include <catch2/catch.hpp>
#include <module-apps/application-call/presenter/CallPresenter.hpp>
#include <module-apps/application-call/test/mock/CallPresenterMocks.hpp>
#include <module-apps/application-call/data/CallAppStyle.hpp>

using app::call::ViewMock;

TEST_CASE("Call presenter")
{
    SECTION("Show incoming call layout")
    {
        auto model     = std::make_shared<app::call::ModelMock>();
        auto presenter = app::call::CallWindowContract::Presenter(model);
        auto view      = app::call::ViewMock();
        presenter.attach(&view);

        model->setState(app::call::CallState::Incoming);
        presenter.handleCallStateChange();

        REQUIRE(view.windowRefreshed == true);
        REQUIRE(view.layoutShowed == ViewMock::LayoutShowed::Incoming);
        REQUIRE(view.duration == callAppStyle::strings::iscalling);
    }

    SECTION("Show call active layout")
    {
        auto model     = std::make_shared<app::call::ModelMock>();
        auto presenter = app::call::CallWindowContract::Presenter(model);
        auto view      = app::call::ViewMock();
        presenter.attach(&view);

        model->setState(app::call::CallState::Active);
        presenter.handleCallStateChange();

        REQUIRE(view.windowRefreshed == true);
        REQUIRE(view.layoutShowed == ViewMock::LayoutShowed::Active);
    }

    SECTION("Show call outgoing layout")
    {
        auto model     = std::make_shared<app::call::ModelMock>();
        auto presenter = app::call::CallWindowContract::Presenter(model);
        auto view      = app::call::ViewMock();
        presenter.attach(&view);

        model->setState(app::call::CallState::Outgoing);
        presenter.handleCallStateChange();

        REQUIRE(view.windowRefreshed == true);
        REQUIRE(view.layoutShowed == ViewMock::LayoutShowed::Active);
        REQUIRE(view.duration == callAppStyle::strings::calling);
    }

    SECTION("Show call ended layout")
    {
        auto model     = std::make_shared<app::call::ModelMock>();
        auto presenter = app::call::CallWindowContract::Presenter(model);
        auto view      = app::call::ViewMock();
        presenter.attach(&view);

        model->setState(app::call::CallState::Ended);
        presenter.handleCallStateChange();

        REQUIRE(view.windowRefreshed == true);
        REQUIRE(view.duration == callAppStyle::strings::callended);
    }

    SECTION("Show call rejected layout")
    {
        auto model     = std::make_shared<app::call::ModelMock>();
        auto presenter = app::call::CallWindowContract::Presenter(model);
        auto view      = app::call::ViewMock();
        presenter.attach(&view);

        model->setState(app::call::CallState::Rejected);
        presenter.handleCallStateChange();

        REQUIRE(view.windowRefreshed == true);
        REQUIRE(view.layoutShowed == ViewMock::LayoutShowed::Ended);
        REQUIRE(view.duration == callAppStyle::strings::callrejected);
    }

    SECTION("Handle left button for incoming call")
    {
        auto model     = std::make_shared<app::call::ModelMock>();
        auto presenter = app::call::CallWindowContract::Presenter(model);

        model->setState(app::call::CallState::Incoming);
        auto result = presenter.handleLeftButton();

        REQUIRE(result == true);
        REQUIRE(model->answerCallCalled == true);
    }

    SECTION("Handle left button for not incoming call")
    {
        auto model     = std::make_shared<app::call::ModelMock>();
        auto presenter = app::call::CallWindowContract::Presenter(model);

        model->setState(app::call::CallState::None);
        auto result = presenter.handleLeftButton();

        REQUIRE(result == false);
        REQUIRE(model->answerCallCalled == false);

        model->answerCallCalled = false;
        model->setState(app::call::CallState::Outgoing);
        result = presenter.handleLeftButton();

        REQUIRE(result == false);

        model->answerCallCalled = false;
        model->setState(app::call::CallState::Active);
        result = presenter.handleLeftButton();

        REQUIRE(result == false);
        REQUIRE(model->answerCallCalled == false);

        model->answerCallCalled = false;
        model->setState(app::call::CallState::Ended);
        result = presenter.handleLeftButton();

        REQUIRE(result == false);
        REQUIRE(model->answerCallCalled == false);

        model->answerCallCalled = false;
        model->setState(app::call::CallState::Rejected);
        result = presenter.handleLeftButton();

        REQUIRE(result == false);
        REQUIRE(model->answerCallCalled == false);
    }

    SECTION("Handle right button for incoming call")
    {
        auto model     = std::make_shared<app::call::ModelMock>();
        auto presenter = app::call::CallWindowContract::Presenter(model);

        model->setState(app::call::CallState::Incoming);
        auto result = presenter.handleRightButton();

        REQUIRE(result == true);
        REQUIRE(model->hangupCallCalled == true);
    }

    SECTION("Handle right button for active call")
    {
        auto model     = std::make_shared<app::call::ModelMock>();
        auto presenter = app::call::CallWindowContract::Presenter(model);

        model->setState(app::call::CallState::Active);
        auto result = presenter.handleRightButton();

        REQUIRE(result == true);
        REQUIRE(model->hangupCallCalled == true);

        model->hangupCallCalled = false;
        model->setState(app::call::CallState::Outgoing);
        result = presenter.handleRightButton();

        REQUIRE(result == true);
        REQUIRE(model->hangupCallCalled == true);
    }

    SECTION("Handle right button for not active call")
    {
        auto model     = std::make_shared<app::call::ModelMock>();
        auto presenter = app::call::CallWindowContract::Presenter(model);

        model->setState(app::call::CallState::None);
        auto result = presenter.handleRightButton();

        REQUIRE(result == false);
        REQUIRE(model->hangupCallCalled == false);

        model->hangupCallCalled = false;
        model->setState(app::call::CallState::Ended);
        result = presenter.handleRightButton();

        REQUIRE(result == false);
        REQUIRE(model->hangupCallCalled == false);

        model->hangupCallCalled = false;
        model->setState(app::call::CallState::Rejected);
        result = presenter.handleRightButton();

        REQUIRE(result == false);
        REQUIRE(model->hangupCallCalled == false);
    }

    SECTION("Handle headset ok for incoming call")
    {
        auto model     = std::make_shared<app::call::ModelMock>();
        auto presenter = app::call::CallWindowContract::Presenter(model);

        model->setState(app::call::CallState::Incoming);
        auto result = presenter.handleHeadsetOk();

        REQUIRE(result == true);
        REQUIRE(model->answerCallCalled == true);
    }

    SECTION("Handle headset ok active call")
    {
        auto model     = std::make_shared<app::call::ModelMock>();
        auto presenter = app::call::CallWindowContract::Presenter(model);

        model->setState(app::call::CallState::Outgoing);
        auto result = presenter.handleHeadsetOk();

        REQUIRE(result == true);
        REQUIRE(model->hangupCallCalled == true);

        model->hangupCallCalled = false;
        model->setState(app::call::CallState::Active);
        result = presenter.handleHeadsetOk();

        REQUIRE(result == true);
        REQUIRE(model->hangupCallCalled == true);
    }

    SECTION("Handle headset ok for not active call")
    {
        auto model     = std::make_shared<app::call::ModelMock>();
        auto presenter = app::call::CallWindowContract::Presenter(model);

        model->setState(app::call::CallState::None);
        auto result = presenter.handleHeadsetOk();

        REQUIRE(result == false);
        REQUIRE(model->hangupCallCalled == false);
        REQUIRE(model->answerCallCalled == false);

        model->hangupCallCalled = false;
        model->setState(app::call::CallState::Ended);
        result = presenter.handleHeadsetOk();

        REQUIRE(result == false);
        REQUIRE(model->hangupCallCalled == false);
        REQUIRE(model->answerCallCalled == false);
    }
}

M module-apps/application-call/windows/CallWindow.cpp => module-apps/application-call/windows/CallWindow.cpp +93 -242
@@ 3,7 3,6 @@

#include "ApplicationCall.hpp"
#include "CallAppStyle.hpp"
#include "CallState.hpp"
#include "CallSwitchData.hpp"
#include "CallWindow.hpp"
#include "StateIcons.hpp"


@@ 32,13 31,12 @@ namespace gui
    using namespace callAppStyle;
    using namespace callAppStyle::callWindow;
    using namespace app::call;
    using AudioEvent = app::CallWindowInterface::AudioEvent;

    CallWindow::CallWindow(app::ApplicationCommon *app, app::CallWindowInterface *interface, std::string windowName)
        : AppWindow(app, windowName), interface(interface)
    CallWindow::CallWindow(app::ApplicationCommon *app,
                           std::unique_ptr<app::call::CallWindowContract::Presenter> &&windowPresenter)
        : gui::AppWindow{app, app::window::name_call}, presenter{std::move(windowPresenter)}
    {
        assert(interface != nullptr);
        assert(app != nullptr);
        presenter->attach(this);
        buildInterface();
    }



@@ 86,8 84,7 @@ namespace gui
            microphoneIcon->setNext();
            LOG_INFO("Mic activated %d", static_cast<int>(microphoneIcon->get()));

            microphoneIcon->get() == MicrophoneIconState::MUTED ? interface->sendAudioEvent(AudioEvent::Mute)
                                                                : interface->sendAudioEvent(AudioEvent::Unmute);
            microphoneIcon->get() == MicrophoneIconState::MUTED ? presenter->muteCall() : presenter->unmuteCall();

            return true;
        };


@@ 99,10 96,10 @@ namespace gui

            switch (speakerIcon->get()) {
            case SpeakerIconState::SPEAKER: {
                interface->sendAudioEvent(AudioEvent::LoudspeakerOff);
                presenter->turnLoudspeakerOff();
            } break;
            case SpeakerIconState::SPEAKERON: {
                interface->sendAudioEvent(AudioEvent::LoudspeakerOn);
                presenter->turnLoudspeakerOn();
            } break;
            // case SpeakerIconState::BLUETOOTH: {
            //     // TODO: need implementation


@@ 118,7 115,7 @@ namespace gui
        sendSmsIcon->activatedCallback = [=](gui::Item &item) {
            LOG_INFO("Send message template and reject the call");
            constexpr auto preventAutoLock  = true;
            auto msg                        = std::make_unique<SMSSendTemplateRequest>(phoneNumber, preventAutoLock);
            auto msg = std::make_unique<SMSSendTemplateRequest>(presenter->getPhoneNumber().getView(), preventAutoLock);
            msg->ignoreCurrentWindowOnStack = true;
            return app::manager::Controller::sendAction(application,
                                                        app::manager::actions::ShowSmsTemplates,


@@ 132,8 129,6 @@ namespace gui

        speakerIcon->setNavigationItem(NavigationDirection::LEFT, microphoneIcon);
        speakerIcon->setNavigationItem(NavigationDirection::RIGHT, microphoneIcon);

        setState(State::IDLE);
    }

    void CallWindow::destroyInterface()


@@ 147,217 142,24 @@ namespace gui
        return appConfiguration;
    }

    void CallWindow::setState(State state)
    {
        auto prevState = getState();
        LOG_INFO("==> Call state change: %s -> %s", c_str(prevState), c_str(state));
        interface->setCallState(state);

        switch (state) {
        case State::INCOMING_CALL: {
            navBar->setText(gui::nav_bar::Side::Left, utils::translate(strings::answer), true);
            navBar->setText(gui::nav_bar::Side::Right, utils::translate(strings::reject), true);
            durationLabel->setText(utils::translate(strings::iscalling));
            durationLabel->setVisible(true);
            speakerIcon->setVisible(false);
            microphoneIcon->setVisible(false);
            if (phoneNumber.getFormatted().empty()) {
                navBar->setActive(gui::nav_bar::Side::Center, false);
                sendSmsIcon->setVisible(false);
                setFocusItem(nullptr);
            }
            else {
                navBar->setActive(gui::nav_bar::Side::Center, true);
                navBar->setText(gui::nav_bar::Side::Center, utils::translate(style::strings::common::send), true);
                sendSmsIcon->setVisible(true);
                setFocusItem(sendSmsIcon);
            }
        } break;
        case State::CALL_ENDED: {
            stopCallTimer();
            navBar->setActive(gui::nav_bar::Side::Left, false);
            navBar->setActive(gui::nav_bar::Side::Center, false);
            navBar->setActive(gui::nav_bar::Side::Right, false);
            durationLabel->setVisible(true);
            setCallEndMessage();
            sendSmsIcon->setVisible(false);
            speakerIcon->setVisible(false);
            microphoneIcon->setVisible(false);
            speakerIcon->set(SpeakerIconState::SPEAKER);
            microphoneIcon->set(MicrophoneIconState::MUTE);
            setFocusItem(nullptr);
            connectTimerOnExit();
        } break;
        case State::CALL_IN_PROGRESS: {
            runCallTimer();
            navBar->setActive(gui::nav_bar::Side::Left, false);
            navBar->setActive(gui::nav_bar::Side::Center, false);
            navBar->setText(gui::nav_bar::Side::Right, utils::translate(strings::endcall), true);
            navBar->setText(gui::nav_bar::Side::Center, utils::translate(style::strings::common::Switch), true);
            durationLabel->setVisible(true);
            sendSmsIcon->setVisible(false);
            speakerIcon->setVisible(true);
            microphoneIcon->setVisible(true);
            setFocusItem(microphoneIcon);
        } break;
        case State::OUTGOING_CALL: {
            navBar->setActive(gui::nav_bar::Side::Left, false);
            navBar->setActive(gui::nav_bar::Side::Center, false);
            navBar->setText(gui::nav_bar::Side::Right, utils::translate(strings::endcall), true);
            navBar->setText(gui::nav_bar::Side::Center, utils::translate(style::strings::common::Switch), true);
            durationLabel->setText(utils::translate(strings::calling));
            durationLabel->setVisible(true);
            sendSmsIcon->setVisible(false);
            speakerIcon->setVisible(true);
            microphoneIcon->setVisible(true);
            setFocusItem(microphoneIcon);
        } break;
        case State::IDLE:
            stopCallTimer();
            [[fallthrough]];
        default:
            numberLabel->clear();
            navBar->setActive(gui::nav_bar::Side::Left, false);
            navBar->setActive(gui::nav_bar::Side::Center, false);
            navBar->setActive(gui::nav_bar::Side::Right, false);
            durationLabel->setVisible(false);
            sendSmsIcon->setVisible(false);
            speakerIcon->setVisible(false);
            microphoneIcon->setVisible(false);
            setFocusItem(nullptr);
            break;
        };
        iconsBox->resizeItems();
    }

    auto CallWindow::getState() const noexcept -> State
    {
        return interface->getCallState();
    }

    void CallWindow::updateDuration(const utils::time::Duration duration)
    void CallWindow::updateDuration(const UTF8 &text, bool isVisible)
    {
        if (durationLabel != nullptr) {
            durationLabel->setText(duration.str());
            durationLabel->setText(text);
            durationLabel->setVisible(isVisible);
        }
    }

    void CallWindow::onBeforeShow(ShowMode mode, SwitchData *data)
    {
        if (auto callData = dynamic_cast<app::CallSwitchData *>(data); callData != nullptr) {
            phoneNumber = callData->getPhoneNumber();
            if (!callData->getPhoneNumber().getFormatted().empty()) {
                auto contact     = DBServiceAPI::MatchContactByPhoneNumber(this->application, phoneNumber);
                auto displayName = phoneNumber.getFormatted();
                if (contact && !contact->isTemporary()) {
                    LOG_INFO("number recognized as contact id = %" PRIu32, contact->ID);
                    displayName = contact->getFormattedName();
                }
                else {
                    LOG_INFO("number was not recognized as any valid contact");
                }

                numberLabel->setText(displayName);
            }
            else {
                numberLabel->setText(utils::translate(strings::privateNumber));
            }

            if (dynamic_cast<app::IncomingCallData *>(data) != nullptr) {
                if (getState() == State::INCOMING_CALL) {
                    LOG_DEBUG("ignoring IncomingCallData message");
                    return;
                }
                callEndType = CallEndType::None;
                setState(State::INCOMING_CALL);
                return;
            }
            if (dynamic_cast<app::ExecuteCallData *>(data) != nullptr) {
                callEndType = CallEndType::None;
                setState(State::OUTGOING_CALL);
                return;
            }
        }

        if (dynamic_cast<app::CallAbortData *>(data) != nullptr) {
            if (callEndType == CallEndType::None) {
                callEndType = CallEndType::Ended;
            }
            setState(State::CALL_ENDED);
            return;
        }

        if (dynamic_cast<app::CallActiveData *>(data) != nullptr) {
            if (getState() != State::CALL_ENDED) {
                setState(State::CALL_IN_PROGRESS);
                return;
            }
            LOG_DEBUG("Ignoring CallActiveData message");
            return;
        }
        presenter->buildLayout();

        if (dynamic_cast<SMSTemplateSent *>(data) != nullptr) {
            callEndType = CallEndType::Rejected;
            interface->hangupCall();
            presenter->hangUpCall();
            return;
        }
    }

    bool CallWindow::handleLeftButton()
    {
        if (getState() == State::INCOMING_CALL) {
            interface->answerIncomingCall();
            return true;
        }

        return false;
    }

    bool CallWindow::handleRightButton()
    {
        switch (getState()) {
        case State::INCOMING_CALL:
            callEndType = CallEndType::Rejected;
            interface->hangupCall();
            return true;
        case State::OUTGOING_CALL:
        case State::CALL_IN_PROGRESS:
            callEndType = CallEndType::Ended;
            interface->hangupCall();
            return true;
        case State::IDLE:
        case State::CALL_ENDED:
            break;
        }

        return false;
    }

    bool CallWindow::handleHeadsetOkButton()
    {
        switch (getState()) {
        case State::INCOMING_CALL:
            interface->answerIncomingCall();
            return true;
        case State::OUTGOING_CALL:
            [[fallthrough]];
        case State::CALL_IN_PROGRESS:
            interface->hangupCall();
            return true;
        case State::IDLE:
        case State::CALL_ENDED:
            break;
        }

        return false;
    }

    bool CallWindow::handleDigit(const uint32_t digit)
    {
        interface->transmitDtmfTone(digit);
        return true;
    }

    bool CallWindow::onInput(const InputEvent &inputEvent)
    {
        bool handled = false;


@@ 369,19 171,19 @@ namespace gui
            auto code = translator.handle(inputEvent.getRawKey(), InputMode({InputMode::phone}).get());
            switch (inputEvent.getKeyCode()) {
            case KeyCode::KEY_LF:
                handled = handleLeftButton();
                handled = presenter->handleLeftButton();
                break;
            case KeyCode::KEY_RF:
                handled = handleRightButton();
                handled = presenter->handleRightButton();
                break;
            case KeyCode::HEADSET_OK:
                handled = handleHeadsetOkButton();
                handled = presenter->handleHeadsetOk();
                break;
            default:
                break;
            }
            if (!handled && code != 0) {
                handled = handleDigit(code);
                handled = presenter->handleDigitButton(code);
            }
        }



@@ 398,7 200,6 @@ namespace gui
    {
        timerCallback = [this](Item &, sys::Timer &timer) {
            LOG_DEBUG("Delayed exit timer callback");
            setState(State::IDLE);
            app::manager::Controller::switchBack(application);
            application->popCurrentWindow();
            return true;


@@ 408,39 209,89 @@ namespace gui
        delayedExitTimer.start();
    }

    void CallWindow::runCallTimer()
    void CallWindow::refreshWindow()
    {
        constexpr auto callTimeTimeout = std::chrono::seconds{1};
        callTimer = app::GuiTimerFactory::createPeriodicTimer(application, durationLabel, "CallTime", callTimeTimeout);
        durationLabel->timerCallback = [&](Item &item, sys::Timer &timer) {
            std::chrono::time_point<std::chrono::system_clock> systemUnitDuration(callDuration);
            updateDuration(std::chrono::system_clock::to_time_t(systemUnitDuration));
            callDuration++;
            LOG_DEBUG("Update duration timer callback - %" PRIu32, static_cast<uint32_t>(callDuration.count()));
            application->refreshWindow(gui::RefreshModes::GUI_REFRESH_FAST);
            return true;
        };
        callTimer.start();
        application->refreshWindow(gui::RefreshModes::GUI_REFRESH_FAST);
    }

    void CallWindow::stopCallTimer()
    void CallWindow::setNavBarForActiveCall()
    {
        callDuration = std::chrono::seconds().zero();
        if (callTimer.isActive()) {
            callTimer.stop();
        }
        navBar->setActive(gui::nav_bar::Side::Left, false);
        navBar->setActive(gui::nav_bar::Side::Center, false);
        navBar->setText(gui::nav_bar::Side::Right, utils::translate(strings::endcall), true);
        navBar->setText(gui::nav_bar::Side::Center, utils::translate(style::strings::common::Switch), true);
    }

    void CallWindow::setNavBarForIncomingCall()
    {
        navBar->setText(gui::nav_bar::Side::Left, utils::translate(strings::answer), true);
        navBar->setText(gui::nav_bar::Side::Right, utils::translate(strings::reject), true);
    }

    void CallWindow::setCallEndMessage()
    void CallWindow::clearNavBar()
    {
        switch (callEndType) {
        case CallEndType::None:
        case CallEndType::Ended:
            durationLabel->setText(utils::translate(strings::callended));
            break;
        case CallEndType::Rejected:
            durationLabel->setText(utils::translate(strings::callrejected));
            break;
        navBar->setActive(gui::nav_bar::Side::Left, false);
        navBar->setActive(gui::nav_bar::Side::Center, false);
        navBar->setActive(gui::nav_bar::Side::Right, false);
    }
    void CallWindow::setIncomingCallLayout(bool isValidCallerId)
    {
        navBar->setText(gui::nav_bar::Side::Left, utils::translate(strings::answer), true);
        navBar->setText(gui::nav_bar::Side::Right, utils::translate(strings::reject), true);

        microphoneIcon->setVisible(false);
        speakerIcon->setVisible(false);

        if (isValidCallerId) {
            navBar->setActive(gui::nav_bar::Side::Center, false);
            sendSmsIcon->setVisible(false);
            setFocusItem(nullptr);
        }
        else {
            navBar->setActive(gui::nav_bar::Side::Center, true);
            navBar->setText(gui::nav_bar::Side::Center, utils::translate(style::strings::common::send), true);
            sendSmsIcon->setVisible(true);
            setFocusItem(sendSmsIcon);
        }

        iconsBox->resizeItems();
    }

    void CallWindow::setActiveCallLayout()
    {
        navBar->setActive(gui::nav_bar::Side::Left, false);
        navBar->setActive(gui::nav_bar::Side::Center, false);
        navBar->setText(gui::nav_bar::Side::Right, utils::translate(strings::endcall), true);
        navBar->setText(gui::nav_bar::Side::Center, utils::translate(style::strings::common::Switch), true);

        sendSmsIcon->setVisible(false);
        microphoneIcon->setVisible(true);
        speakerIcon->setVisible(true);

        iconsBox->resizeItems();

        setFocusItem(microphoneIcon);
    }

    void CallWindow::setCallEndedLayout()
    {
        navBar->setActive(gui::nav_bar::Side::Left, false);
        navBar->setActive(gui::nav_bar::Side::Center, false);
        navBar->setActive(gui::nav_bar::Side::Right, false);

        sendSmsIcon->setVisible(false);
        microphoneIcon->setVisible(false);
        speakerIcon->setVisible(false);

        iconsBox->resizeItems();

        setFocusItem(nullptr);
        connectTimerOnExit();
    }

    void CallWindow::updateNumber(const UTF8 &text)
    {
        numberLabel->setText(text);
    }

} /* namespace gui */

M module-apps/application-call/windows/CallWindow.hpp => module-apps/application-call/windows/CallWindow.hpp +15 -25
@@ 4,8 4,8 @@
#pragma once

#include "ApplicationCall.hpp"
#include "CallState.hpp"
#include "StateIcons.hpp"
#include <application-call/presenter/CallPresenter.hpp>

#include <AppWindow.hpp>
#include <gui/input/Translator.hpp>


@@ 15,30 15,22 @@

namespace gui
{
    class CallWindow : public AppWindow
    class CallWindow : public AppWindow, public app::call::CallWindowContract::View
    {
      private:
        enum class CallEndType
        {
            None,
            Ended,
            Rejected,
        } callEndType = CallEndType::None;

        gui::KeyInputMappedTranslation translator;
        sys::TimerHandle callTimer;
        sys::TimerHandle delayedExitTimer;
        std::chrono::seconds callDuration                = std::chrono::seconds().zero();
        static constexpr inline auto callDelayedStopTime = std::chrono::milliseconds{3000};

        [[nodiscard]] auto getDelayedStopTime() const
        {
            return callDelayedStopTime;
        }
        void setCallEndMessage();

        std::unique_ptr<app::call::CallWindowContract::Presenter> presenter;

      protected:
        app::CallWindowInterface *interface = nullptr;
        // used to display both number and name of contact
        gui::Label *numberLabel = nullptr;
        // used to inform user about call state of call and display duration of call


@@ 53,18 45,9 @@ namespace gui

        utils::PhoneNumber::View phoneNumber;

        bool handleLeftButton();
        bool handleRightButton();
        bool handleHeadsetOkButton();
        void setState(app::call::State state);
        [[nodiscard]] auto getState() const noexcept -> app::call::State;

      public:
        CallWindow(app::ApplicationCommon *app,
                   app::CallWindowInterface *interface,
                   std::string windowName = app::window::name_call);

        void updateDuration(const utils::time::Duration duration);
                   std::unique_ptr<app::call::CallWindowContract::Presenter> &&windowPresenter);

        bool onInput(const InputEvent &inputEvent) override;
        void onBeforeShow(ShowMode mode, SwitchData *data) override;


@@ 74,10 57,17 @@ namespace gui
        void destroyInterface() override;
        status_bar::Configuration configureStatusBar(status_bar::Configuration appConfiguration) override;

        bool handleDigit(const uint32_t digit);
        void connectTimerOnExit();
        void runCallTimer();
        void stopCallTimer();
        void refreshWindow() override;

        void updateDuration(const UTF8 &text, bool isVisible = true) override;
        void setNavBarForActiveCall() override;
        void setNavBarForIncomingCall() override;
        void clearNavBar() override;
        void setIncomingCallLayout(bool isValidCallerId) override;
        void setActiveCallLayout() override;
        void setCallEndedLayout() override;
        void updateNumber(const UTF8 &text) override;
    };

} /* namespace gui */

M module-services/service-cellular/call/CallGUI.cpp => module-services/service-cellular/call/CallGUI.cpp +3 -2
@@ 21,9 21,9 @@ void CallGUI::notifyCLIP(const utils::PhoneNumber::View &number)
        &owner, app::manager::actions::HandleCallerId, std::make_unique<app::manager::actions::CallParams>(number));
}

void CallGUI::notifyCallStarted()
void CallGUI::notifyCallStarted(utils::PhoneNumber phoneNumber)
{
    owner.bus.sendMulticast(std::make_shared<cellular::CallStartedNotification>(),
    owner.bus.sendMulticast(std::make_shared<cellular::CallStartedNotification>(phoneNumber),
                            sys::BusChannel::ServiceCellularNotifications);
}



@@ 38,6 38,7 @@ void CallGUI::notifyCallActive()
    owner.bus.sendMulticast(std::make_shared<cellular::CallActiveNotification>(),
                            sys::BusChannel::ServiceCellularNotifications);
}

void CallGUI::notifyCallDurationUpdate(const time_t &duration)
{
    owner.bus.sendMulticast(std::make_shared<cellular::CallDurationNotification>(duration),

M module-services/service-cellular/call/CallGUI.hpp => module-services/service-cellular/call/CallGUI.hpp +1 -1
@@ 20,7 20,7 @@ class CallGUI

    void notifyRING();
    void notifyCLIP(const utils::PhoneNumber::View &number);
    void notifyCallStarted();
    void notifyCallStarted(utils::PhoneNumber phoneNumber);
    void notifyCallEnded();
    void notifyCallActive();
    void notifyCallDurationUpdate(const time_t &duration);

M module-services/service-cellular/call/CellularCall.cpp => module-services/service-cellular/call/CellularCall.cpp +4 -1
@@ 109,7 109,7 @@ namespace CellularCall
            return false;
        }

        gui.notifyCallStarted();
        gui.notifyCallStarted(number);
        LOG_INFO("call started");
        return true;
    }


@@ 209,6 209,9 @@ namespace CellularCall
    }
    void Call::handleCallDurationTimer()
    {
        if (not isActiveCall) {
            return;
        }
        auto now         = utils::time::getCurrentTimestamp();
        callDurationTime = (now - startActiveTime).getSeconds();
        gui.notifyCallDurationUpdate(callDurationTime);

M module-services/service-cellular/service-cellular/CellularMessage.hpp => module-services/service-cellular/service-cellular/CellularMessage.hpp +11 -8
@@ 602,17 602,11 @@ class CellularAnswerIncomingCallMessage : public CellularMessage
    {}
};

class CellularHangupCallMessage : public CellularMessage, public app::manager::actions::ConvertibleToAction
class CellularHangupCallMessage : public CellularMessage
{
  public:
    CellularHangupCallMessage() : CellularMessage(Type::HangupCall)
    {}

    [[nodiscard]] auto toAction() const -> std::unique_ptr<app::manager::ActionRequest>
    {
        return std::make_unique<app::manager::ActionRequest>(
            sender, app::manager::actions::AbortCall, std::make_unique<app::manager::actions::ActionParams>());
    }
};

class CellularDismissCallMessage : public CellularMessage


@@ 1048,7 1042,15 @@ namespace cellular
    class CallStartedNotification : public sys::DataMessage
    {
      public:
        explicit CallStartedNotification() : sys::DataMessage(MessageType::MessageTypeUninitialized){};
        explicit CallStartedNotification(utils::PhoneNumber phoneNumber)
            : sys::DataMessage(MessageType::MessageTypeUninitialized), phoneNumber(phoneNumber){};
        utils::PhoneNumber getNumber()
        {
            return phoneNumber;
        };

      private:
        utils::PhoneNumber phoneNumber;
    };

    class CallEndedNotification : public sys::DataMessage


@@ 1070,4 1072,5 @@ namespace cellular
            : sys::DataMessage(MessageType::MessageTypeUninitialized), callDuration(duration){};
        time_t callDuration;
    };

} // namespace cellular

M module-utils/time/time/time_conversion.hpp => module-utils/time/time/time_conversion.hpp +1 -1
@@ 6,7 6,7 @@
/// this calss uses strftime to convert timestamp to text, but for UTF8 elements (mon,day) it uses our locale
/// as stdlib builtin locale doesn't provide way to substitute C-LOCALE

#include "time/time_locale.hpp"
#include <time/time_locale.hpp>
#include <log/log.hpp>

#include <vector>