~aleteoryx/muditaos

9fe5815a65416ed8d1ee16b28b6646aa860ddd7b — Kuba Kleczkowski 5 years ago 1f9f429
[EGD-6301] Add Connecting to the network in intervals

It implements mode Offline Messages only - connecting to
the GSM network in defined by user intervals and rejecting
incomig calls during connected phase. It also implements
handling of connetion interval setiing and small fix in modes
popup.
23 files changed, 769 insertions(+), 171 deletions(-)

M module-apps/Application.cpp
M module-apps/application-settings-new/ApplicationSettings.cpp
M module-apps/popups/HomeModesWindow.cpp
M module-apps/popups/data/PopupData.hpp
M module-apps/widgets/ModesBox.cpp
M module-apps/widgets/ModesBox.hpp
M module-services/service-antenna/ServiceAntenna.cpp
M module-services/service-cellular/CMakeLists.txt
M module-services/service-cellular/CellularServiceAPI.cpp
M module-services/service-cellular/ServiceCellular.cpp
A module-services/service-cellular/connection-manager/ConnectionManager.cpp
A module-services/service-cellular/connection-manager/ConnectionManagerCellularCommands.cpp
M module-services/service-cellular/doc/README.md
A module-services/service-cellular/doc/connection_manager.puml
A module-services/service-cellular/doc/connection_manager.svg
M module-services/service-cellular/service-cellular/CellularMessage.hpp
M module-services/service-cellular/service-cellular/CellularServiceAPI.hpp
M module-services/service-cellular/service-cellular/ServiceCellular.hpp
A module-services/service-cellular/service-cellular/connection-manager/ConnectionManager.hpp
A module-services/service-cellular/service-cellular/connection-manager/ConnectionManagerCellularCommands.hpp
A module-services/service-cellular/service-cellular/connection-manager/ConnectionManagerCellularCommandsInterface.hpp
M module-services/service-cellular/tests/CMakeLists.txt
A module-services/service-cellular/tests/unittest_connection-manager.cpp
M module-apps/Application.cpp => module-apps/Application.cpp +5 -2
@@ 708,13 708,16 @@ namespace app

    void Application::handlePhoneModeChanged(sys::phone_modes::PhoneMode mode)
    {
        auto flightModeSetting = settings->getValue(settings::Cellular::offlineMode, settings::SettingsScope::Global);
        bool flightMode        = flightModeSetting == "1" ? true : false;

        using namespace gui::popup;
        const auto &popupName = resolveWindowName(gui::popup::ID::PhoneModes);
        if (const auto currentWindowName = getCurrentWindow()->getName(); currentWindowName == popupName) {
            updateWindow(popupName, std::make_unique<gui::ModesPopupData>(mode));
            updateWindow(popupName, std::make_unique<gui::ModesPopupData>(mode, flightMode));
        }
        else {
            switchWindow(popupName, std::make_unique<gui::ModesPopupData>(mode));
            switchWindow(popupName, std::make_unique<gui::ModesPopupData>(mode, flightMode));
        }
    }


M module-apps/application-settings-new/ApplicationSettings.cpp => module-apps/application-settings-new/ApplicationSettings.cpp +1 -2
@@ 740,7 740,6 @@ namespace app
    void ApplicationSettingsNew::setConnectionFrequency(uint8_t val) noexcept
    {
        connectionFrequency = val;
        settings->setValue(
            ::settings::Offline::connectionFrequency, std::to_string(val), ::settings::SettingsScope::Global);
        CellularServiceAPI::SetConnectionFrequency(this, val);
    }
} /* namespace app */

M module-apps/popups/HomeModesWindow.cpp => module-apps/popups/HomeModesWindow.cpp +2 -1
@@ 35,7 35,8 @@ namespace gui
        const auto popupData = dynamic_cast<ModesPopupData *>(data);
        if (popupData != nullptr) {
            const auto currentMode = popupData->getPhoneMode();
            modesBox->update(currentMode);
            const auto currentFlightMode = popupData->getFlightMode();
            modesBox->update(currentMode, currentFlightMode);
        }
    }


M module-apps/popups/data/PopupData.hpp => module-apps/popups/data/PopupData.hpp +8 -1
@@ 35,7 35,8 @@ namespace gui
    class ModesPopupData : public SwitchData
    {
      public:
        explicit ModesPopupData(const sys::phone_modes::PhoneMode phoneMode) : SwitchData(), phoneMode{phoneMode}
        explicit ModesPopupData(sys::phone_modes::PhoneMode phoneMode, bool flightMode)
            : SwitchData(), phoneMode{phoneMode}, isFlightModeEnabled(flightMode)
        {}

        [[nodiscard]] auto getPhoneMode() const noexcept -> sys::phone_modes::PhoneMode


@@ 43,7 44,13 @@ namespace gui
            return phoneMode;
        }

        [[nodiscard]] auto getFlightMode() const noexcept -> bool
        {
            return isFlightModeEnabled;
        }

      private:
        const sys::phone_modes::PhoneMode phoneMode;
        const bool isFlightModeEnabled;
    };
} // namespace gui

M module-apps/widgets/ModesBox.cpp => module-apps/widgets/ModesBox.cpp +2 -2
@@ 24,7 24,7 @@ namespace gui
        addOffline();
    }

    void ModesBox::update(const sys::phone_modes::PhoneMode &phoneMode)
    void ModesBox::update(const sys::phone_modes::PhoneMode &phoneMode, const bool flightMode)
    {
        using PhoneMode      = sys::phone_modes::PhoneMode;
        auto getUpdateValues = [&phoneMode](const PhoneMode &compare) -> std::pair<std::string, const bool> {


@@ 34,7 34,7 @@ namespace gui
        connected->update(getUpdateValues(PhoneMode::Connected));
        notDisturb->update(getUpdateValues(PhoneMode::DoNotDisturb));
        offline->update(getUpdateValues(PhoneMode::Offline));
        if (phoneMode == PhoneMode::Offline) {
        if (phoneMode == PhoneMode::Offline && !flightMode) {
            messageOnly->setVisible(true);
        }
        else {

M module-apps/widgets/ModesBox.hpp => module-apps/widgets/ModesBox.hpp +1 -1
@@ 82,6 82,6 @@ namespace gui

      public:
        ModesBox(Item *parent = nullptr, uint32_t x = 0, uint32_t y = 0);
        void update(const sys::phone_modes::PhoneMode &phoneMode);
        void update(const sys::phone_modes::PhoneMode &phoneMode, const bool flightMode);
    };
} // namespace gui

M module-services/service-antenna/ServiceAntenna.cpp => module-services/service-antenna/ServiceAntenna.cpp +5 -1
@@ 244,7 244,6 @@ bool ServiceAntenna::HandleStateChange(antenna::State state)
    switch (state) {
    case antenna::State::none:
        ret = noneStateHandler();
        ;
        break;
    case antenna::State::init:
        ret = initStateHandler();


@@ 282,6 281,11 @@ bool ServiceAntenna::initStateHandler(void)
{
    LOG_INFO("State Init");
    bsp::cellular::antenna antenna;
    if (phoneModeObserver->isInMode(sys::phone_modes::PhoneMode::Offline)) {
        AntennaServiceAPI::LockRequest(this, antenna::lockState::locked);
        return true;
    }

    if (CellularServiceAPI::GetAntenna(this, antenna)) {
        currentAntenna = antenna;
        state->enableStateTimeout(cpp_freertos::Ticks::GetTicks(),

M module-services/service-cellular/CMakeLists.txt => module-services/service-cellular/CMakeLists.txt +2 -0
@@ 14,6 14,8 @@ set(SOURCES
    QMBNManager.cpp
    RequestFactory.cpp
    CellularRequestHandler.cpp
    connection-manager/ConnectionManager.cpp
    connection-manager/ConnectionManagerCellularCommands.cpp
    requests/Request.cpp
    requests/CallRequest.cpp
    requests/SupplementaryServicesRequest.cpp

M module-services/service-cellular/CellularServiceAPI.cpp => module-services/service-cellular/CellularServiceAPI.cpp +6 -0
@@ 352,3 352,9 @@ bool CellularServiceAPI::SetFlightMode(sys::Service *serv, bool flightModeOn)
    return serv->bus.sendUnicast(std::make_shared<CellularSetFlightModeMessage>(flightModeOn),
                                 ServiceCellular::serviceName);
}

bool CellularServiceAPI::SetConnectionFrequency(sys::Service *serv, uint8_t connectionFrequency)
{
    return serv->bus.sendUnicast(std::make_shared<CellularSetConnectionFrequencyMessage>(connectionFrequency),
                                 ServiceCellular::serviceName);
}

M module-services/service-cellular/ServiceCellular.cpp => module-services/service-cellular/ServiceCellular.cpp +70 -142
@@ 12,7 12,7 @@
#include "service-cellular/State.hpp"
#include "service-cellular/USSD.hpp"
#include "service-cellular/MessageConstants.hpp"

#include <service-cellular/connection-manager/ConnectionManagerCellularCommands.hpp>
#include "SimCard.hpp"
#include "NetworkSettings.hpp"
#include "service-cellular/RequestFactory.hpp"


@@ 182,6 182,15 @@ ServiceCellular::ServiceCellular()
    bus.channels.push_back(sys::BusChannel::ServiceEvtmgrNotifications);
    bus.channels.push_back(sys::BusChannel::PhoneModeChanges);

    settings = std::make_unique<settings::Settings>(this);

    connectionManager = std::make_unique<ConnectionManager>(
        utils::getNumericValue<bool>(
            settings->getValue(settings::Cellular::offlineMode, settings::SettingsScope::Global)),
        static_cast<std::chrono::minutes>(utils::getNumericValue<int>(settings->getValue(
            settings->getValue(settings::Offline::connectionFrequency, settings::SettingsScope::Global)))),
        std::make_shared<ConnectionManagerCellularCommands>(*this));

    callStateTimer = sys::TimerFactory::createPeriodicTimer(
        this, "call_state", std::chrono::milliseconds{1000}, [this](sys::Timer &) { CallStateTimerHandler(); });
    stateTimer = sys::TimerFactory::createPeriodicTimer(


@@ 190,6 199,12 @@ ServiceCellular::ServiceCellular()
        this, "ussd", std::chrono::milliseconds{1000}, [this](sys::Timer &) { handleUSSDTimer(); });
    sleepTimer = sys::TimerFactory::createPeriodicTimer(
        this, "sleep", constants::sleepTimerInterval, [this](sys::Timer &) { SleepTimerHandler(); });
    connectionTimer =
        sys::TimerFactory::createPeriodicTimer(this, "connection", std::chrono::seconds{60}, [this](sys::Timer &) {
            utility::conditionally_invoke(
                [this]() { return phoneModeObserver->isInMode(sys::phone_modes::PhoneMode::Offline); },
                [this]() { connectionManager->onTimerTick(); });
        });

    ongoingCall.setStartCallAction([=](const CalllogRecord &rec) {
        auto call = DBServiceAPI::CalllogAdd(this, rec);


@@ 326,16 341,8 @@ void handleCellularSimNewPinDataMessage(CellularSimNewPinDataMessage *msg)
void ServiceCellular::registerMessageHandlers()
{
    phoneModeObserver->connect(this);
    phoneModeObserver->subscribe([this](sys::phone_modes::PhoneMode mode) {
        if (mode == sys::phone_modes::PhoneMode::Offline) {
            this->switchToOffline();
        }
        else {
            if (!this->isModemRadioModuleOn()) {
                this->turnOnRadioModule();
            }
        }
    });
    phoneModeObserver->subscribe(
        [this](sys::phone_modes::PhoneMode mode) { connectionManager->onPhoneModeChange(mode); });
    phoneModeObserver->subscribe([](sys::phone_modes::Tethering tethering) {
        using bsp::cellular::USB::setPassthrough;
        using bsp::cellular::USB::PassthroughState;


@@ 524,18 531,10 @@ void ServiceCellular::registerMessageHandlers()
        return handleCellularRingingMessage(msg);
    });

    connect(typeid(CellularIncominCallMessage), [&](sys::Message *request) -> sys::MessagePointer {
        if (doNotDisturbCondition()) {
            return std::make_shared<CellularResponseMessage>(hangUpCall());
        }
        auto msg = static_cast<CellularIncominCallMessage *>(request);
        return handleCellularIncominCallMessage(msg);
    });
    connect(typeid(CellularIncominCallMessage),
            [&](sys::Message *request) -> sys::MessagePointer { return handleCellularIncominCallMessage(request); });

    connect(typeid(CellularCallerIdMessage), [&](sys::Message *request) -> sys::MessagePointer {
        if (doNotDisturbCondition()) {
            return std::make_shared<CellularResponseMessage>(true);
        }
        auto msg = static_cast<CellularCallerIdMessage *>(request);
        return handleCellularCallerIdMessage(msg);
    });


@@ 639,9 638,6 @@ void ServiceCellular::registerMessageHandlers()
    connect(typeid(CellularUrcIncomingNotification),
            [&](sys::Message *request) -> sys::MessagePointer { return handleUrcIncomingNotification(request); });

    connect(typeid(CellularSetRadioOnOffMessage),
            [&](sys::Message *request) -> sys::MessagePointer { return handleCellularSetRadioOnOffMessage(request); });

    connect(typeid(CellularSendSMSMessage),
            [&](sys::Message *request) -> sys::MessagePointer { return handleCellularSendSMSMessage(request); });



@@ 651,6 647,10 @@ void ServiceCellular::registerMessageHandlers()
    connect(typeid(CellularCallerIdNotification),
            [&](sys::Message *request) -> sys::MessagePointer { return handleCellularCallerIdNotification(request); });

    connect(typeid(CellularSetConnectionFrequencyMessage), [&](sys::Message *request) -> sys::MessagePointer {
        return handleCellularSetConnectionFrequencyMessage(request);
    });

    handle_CellularGetChannelMessage();
}



@@ 950,23 950,22 @@ bool ServiceCellular::handle_audio_conf_procedure()
                LOG_DEBUG("Setting up notifications callback");
                notificationsChannel->setCallback(notificationCallback);
            }
            auto mode = phoneModeObserver->getCurrentPhoneMode();
            if (mode == sys::phone_modes::PhoneMode::Offline) {
                if (!turnOffRadioModule()) {
                    LOG_ERROR("Disabling RF modeule failed");
                    state.set(this, State::ST::Failed);
                    return false;
                }
            auto flightMode =
                settings->getValue(settings::Cellular::offlineMode, settings::SettingsScope::Global) == "1" ? true
                                                                                                            : false;
            connectionManager->setFlightMode(flightMode);
            auto interval = 0;
            if (utils::toNumeric(
                    settings->getValue(settings::Offline::connectionFrequency, settings::SettingsScope::Global),
                    interval)) {
                connectionManager->setInterval(std::chrono::minutes{interval});
            }
            else {
                if (!isModemRadioModuleOn()) {
                    if (turnOnRadioModule()) {
                        LOG_ERROR("Enabling RF modeule failed");
                        state.set(this, State::ST::Failed);
                        return false;
                    }
                }
            if (!connectionManager->onPhoneModeChange(phoneModeObserver->getCurrentPhoneMode())) {
                state.set(this, State::ST::Failed);
                LOG_ERROR("Failed to handle phone mode");
                return false;
            }

            state.set(this, State::ST::APNConfProcedure);
            return true;
        }


@@ 2376,20 2375,20 @@ auto ServiceCellular::handleCellularRingingMessage(CellularRingingMessage *msg) 
    return std::make_shared<CellularResponseMessage>(ongoingCall.startCall(msg->number, CallType::CT_OUTGOING));
}

auto ServiceCellular::handleCellularIncominCallMessage(CellularIncominCallMessage *msg)
    -> std::shared_ptr<sys::ResponseMessage>
auto ServiceCellular::handleCellularIncominCallMessage(sys::Message *msg) -> std::shared_ptr<sys::ResponseMessage>
{
    auto ret = true;
    auto ret     = true;
    auto message = static_cast<CellularIncominCallMessage *>(msg);
    if (!ongoingCall.isValid()) {
        ret = ongoingCall.startCall(msg->number, CallType::CT_INCOMING);
        ret = ongoingCall.startCall(message->number, CallType::CT_INCOMING);
    }
    return std::make_shared<CellularResponseMessage>(ret);
}

auto ServiceCellular::handleCellularCallerIdMessage(CellularCallerIdMessage *msg)
    -> std::shared_ptr<sys::ResponseMessage>
auto ServiceCellular::handleCellularCallerIdMessage(sys::Message *msg) -> std::shared_ptr<sys::ResponseMessage>
{
    ongoingCall.setNumber(msg->number);
    auto message = static_cast<CellularCallerIdMessage *>(msg);
    ongoingCall.setNumber(message->number);
    return sys::MessageNone{};
}



@@ 2643,18 2642,11 @@ auto ServiceCellular::handleCellularSetFlightModeMessage(sys::Message *msg) -> s
    auto setMsg = static_cast<CellularSetFlightModeMessage *>(msg);
    settings->setValue(
        settings::Cellular::offlineMode, std::to_string(setMsg->flightModeOn), settings::SettingsScope::Global);
    connectionManager->setFlightMode(setMsg->flightModeOn);
    connectionManager->onPhoneModeChange(phoneModeObserver->getCurrentPhoneMode());
    return std::make_shared<CellularResponseMessage>(true);
}

auto ServiceCellular::handleCellularSetRadioOnOffMessage(sys::Message *msg) -> std::shared_ptr<sys::ResponseMessage>
{
    auto message = static_cast<CellularSetRadioOnOffMessage *>(msg);
    if (message->getRadioOnOf()) {
        return std::make_shared<CellularResponseMessage>(turnOnRadioModule());
    }
    return std::make_shared<CellularResponseMessage>(turnOffRadioModule());
}

auto ServiceCellular::handleCellularSendSMSMessage(sys::Message *msg) -> std::shared_ptr<sys::ResponseMessage>
{
    auto message = static_cast<CellularSendSMSMessage *>(msg);


@@ 2674,106 2666,42 @@ auto ServiceCellular::handleCellularSendSMSMessage(sys::Message *msg) -> std::sh

auto ServiceCellular::handleCellularRingNotification(sys::Message *msg) -> std::shared_ptr<sys::ResponseMessage>
{
    auto message = static_cast<CellularRingNotification *>(msg);

    if (doNotDisturbCondition()) {
        return std::make_shared<CellularResponseMessage>(this->hangUpCall());
    if (isIncommingCallAllowed()) {
        auto message = static_cast<CellularRingNotification *>(msg);
        bus.sendMulticast(std::make_shared<CellularIncominCallMessage>(message->getNubmer()),
                          sys::BusChannel::ServiceCellularNotifications);
        return std::make_shared<CellularResponseMessage>(true);
    }
    bus.sendMulticast(std::make_shared<CellularIncominCallMessage>(message->getNubmer()),
                      sys::BusChannel::ServiceCellularNotifications);
    return std::make_shared<CellularResponseMessage>(true);
    return std::make_shared<CellularResponseMessage>(this->hangUpCall());
}

auto ServiceCellular::handleCellularCallerIdNotification(sys::Message *msg) -> std::shared_ptr<sys::ResponseMessage>
{
    auto message = static_cast<CellularCallerIdNotification *>(msg);

    if (doNotDisturbCondition()) {
        return std::make_shared<CellularResponseMessage>(this->hangUpCall());
    }

    bus.sendMulticast(std::make_shared<CellularCallerIdMessage>(message->getNubmer()),
                      sys::BusChannel::ServiceCellularNotifications);
    return std::make_shared<CellularResponseMessage>(true);
}

auto ServiceCellular::isModemRadioModuleOn() -> bool
{
    using at::cfun::Functionality;
    auto channel = cmux->get(TS0710::Channel::Commands);
    if (channel) {
        auto cmd      = at::cmd::CFUN(at::cmd::Modifier::Get);
        auto response = channel->cmd(cmd);
        if (response.code == at::Result::Code::OK) {
            auto result = cmd.parse(response);
            if (result.code == at::Result::Code::OK) {
                return result.functionality == Functionality::Full;
            }
        }
    }
    return false;
}
auto ServiceCellular::turnOnRadioModule() -> bool
{
    using at::cfun::Functionality;
    auto channel = cmux->get(TS0710::Channel::Commands);
    if (channel) {
        auto cmd = at::cmd::CFUN(at::cmd::Modifier::Set);
        cmd.set(Functionality::Full);
        auto resp = channel->cmd(cmd);
        return resp.code == at::Result::Code::OK;
    }
    return false;
}

auto ServiceCellular::turnOffRadioModule() -> bool
{
    using at::cfun::Functionality;
    auto channel = cmux->get(TS0710::Channel::Commands);
    if (channel) {
        auto cmd = at::cmd::CFUN(at::cmd::Modifier::Set);
        cmd.set(Functionality::DisableRF);
        auto resp = channel->cmd(cmd);
        return resp.code == at::Result::Code::OK;
    if (isIncommingCallAllowed()) {
        auto message = static_cast<CellularCallerIdNotification *>(msg);
        bus.sendMulticast(std::make_shared<CellularCallerIdMessage>(message->getNubmer()),
                          sys::BusChannel::ServiceCellularNotifications);
        return std::make_shared<CellularResponseMessage>(true);
    }
    return false;
    return std::make_shared<CellularResponseMessage>(this->hangUpCall());
}

auto ServiceCellular::switchToOffline() -> bool
auto ServiceCellular::handleCellularSetConnectionFrequencyMessage(sys::Message *msg)
    -> std::shared_ptr<sys::ResponseMessage>
{
    if (ongoingCall.isActive()) {
        auto channel = cmux->get(TS0710::Channel::Commands);
        if (channel) {
            if (channel->cmd(at::factory(at::AT::ATH))) {
                callStateTimer.stop();
                if (!ongoingCall.endCall(CellularCall::Forced::True)) {
                    LOG_ERROR("Failed to end ongoing call");
                    return false;
                }
                auto msg = std::make_shared<CellularCallAbortedNotification>();
                bus.sendMulticast(msg, sys::BusChannel::ServiceCellularNotifications);
            }
        }
    }

    if (!turnOffRadioModule()) {
        LOG_ERROR("Failed to disable RF module");
        return false;
    }

    // force clear signal indicator
    auto rssi = 0;
    SignalStrength signalStrength(rssi);
    Store::GSM::get()->setSignalStrength(signalStrength.data);
    auto msg = std::make_shared<CellularSignalStrengthUpdateNotification>();
    bus.sendMulticast(msg, sys::BusChannel::ServiceCellularNotifications);
    auto setMsg = static_cast<CellularSetConnectionFrequencyMessage *>(msg);
    settings->setValue(settings::Offline::connectionFrequency,
                       std::to_string(setMsg->getConnectionFrequency()),
                       settings::SettingsScope::Global);

    return true;
    connectionManager->setInterval(std::chrono::minutes{setMsg->getConnectionFrequency()});
    connectionManager->onPhoneModeChange(phoneModeObserver->getCurrentPhoneMode());
    return std::make_shared<CellularResponseMessage>(true);
}

auto ServiceCellular::doNotDisturbCondition() -> bool
auto ServiceCellular::isIncommingCallAllowed() -> bool
{
    return phoneModeObserver->isInMode(sys::phone_modes::PhoneMode::DoNotDisturb);
    return phoneModeObserver->isInMode(sys::phone_modes::PhoneMode::Connected);
}

auto ServiceCellular::hangUpCall() -> bool

A module-services/service-cellular/connection-manager/ConnectionManager.cpp => module-services/service-cellular/connection-manager/ConnectionManager.cpp +107 -0
@@ 0,0 1,107 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include <module-services/service-cellular/service-cellular/connection-manager/ConnectionManager.hpp>
#include <module-services/service-cellular/service-cellular/CellularMessage.hpp>
#include <module-cellular/at/Cmd.hpp>

auto ConnectionManager::onPhoneModeChange(sys::phone_modes::PhoneMode mode) -> bool
{
    if (mode == sys::phone_modes::PhoneMode::Offline) {
        return handleModeChangeToCommonOffline();
    }
    return handleModeChangeToConnected();
}

void ConnectionManager::onTimerTick()
{
    if (connectionInterval.count() == 0 || isFlightMode) {
        return;
    }

    minutesOfflineElapsed++;
    if (minutesOfflineElapsed.count() >= connectionInterval.count()) {
        minutesOfflineElapsed = static_cast<std::chrono::minutes>(0);
        ;
        onlinePeriod = true;
        cellular->connectToNetwork();
        return;
    }
    if (onlinePeriod) {
        minutesOnlineElapsed++;
        if (minutesOnlineElapsed.count() >= connectedPeriod.count()) {
            minutesOnlineElapsed = static_cast<std::chrono::minutes>(0);
            onlinePeriod         = false;
            cellular->disconnectFromNetwork();
        }
    }
}

void ConnectionManager::setInterval(const std::chrono::minutes interval)
{
    connectionInterval   = interval;
    minutesOnlineElapsed = static_cast<std::chrono::minutes>(0);
    ;
    minutesOfflineElapsed = static_cast<std::chrono::minutes>(0);
    ;
}

void ConnectionManager::setFlightMode(const bool mode)
{
    isFlightMode = mode;
}

auto ConnectionManager::isMessagesOnlyMode() -> bool
{
    return !isFlightMode && connectionInterval.count() != 0;
}

auto ConnectionManager::handleModeChangeToCommonOffline() -> bool
{
    if (cellular->isConnectedToNetwork()) {
        cellular->hangUpOngoingCall();
        cellular->disconnectFromNetwork();
        cellular->clearNetworkIndicator();
    }

    minutesOfflineElapsed = static_cast<std::chrono::minutes>(0);
    ;
    minutesOnlineElapsed = static_cast<std::chrono::minutes>(0);
    ;

    if (isMessagesOnlyMode()) {
        handleModeChangeToMessageOnlyMode();
        return true;
    }
    else {
        handleModeChangeToOfflineMode();
        return true;
    }
}

void ConnectionManager::handleModeChangeToOfflineMode()
{
    if (cellular->isConnectionTimerActive()) {
        cellular->stopConnectionTimer();
        onlinePeriod = false;
    }
}

void ConnectionManager::handleModeChangeToMessageOnlyMode()
{
    if (!cellular->isConnectionTimerActive()) {
        cellular->startConnectionTimer();
    }
}

auto ConnectionManager::handleModeChangeToConnected() -> bool
{
    if (cellular->isConnectionTimerActive()) {
        cellular->stopConnectionTimer();
        onlinePeriod = false;
    }
    if (!cellular->isConnectedToNetwork()) {
        cellular->connectToNetwork();
    }
    return true;
}

A module-services/service-cellular/connection-manager/ConnectionManagerCellularCommands.cpp => module-services/service-cellular/connection-manager/ConnectionManagerCellularCommands.cpp +99 -0
@@ 0,0 1,99 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include <service-cellular/service-cellular/connection-manager/ConnectionManagerCellularCommands.hpp>

#include <module-cellular/at/cmd/CFUN.hpp>
#include <module-cellular/at/Cmd.hpp>
#include <module-cellular/at/ATFactory.hpp>

auto ConnectionManagerCellularCommands::disconnectFromNetwork() -> bool
{
    using at::cfun::Functionality;
    auto channel = cellular.cmux->get(TS0710::Channel::Commands);
    if (channel) {
        auto cmd = at::cmd::CFUN(at::cmd::Modifier::Set);
        cmd.set(Functionality::DisableRF);
        auto resp = channel->cmd(cmd);
        return resp.code == at::Result::Code::OK;
    }
    return false;
}

auto ConnectionManagerCellularCommands::connectToNetwork() -> bool
{
    using at::cfun::Functionality;
    auto channel = cellular.cmux->get(TS0710::Channel::Commands);
    if (channel) {
        auto cmd = at::cmd::CFUN(at::cmd::Modifier::Set);
        cmd.set(Functionality::Full);
        auto resp = channel->cmd(cmd);
        return resp.code == at::Result::Code::OK;
    }
    return false;
}

auto ConnectionManagerCellularCommands::isConnectedToNetwork() -> bool
{

    using at::cfun::Functionality;
    auto channel = cellular.cmux->get(TS0710::Channel::Commands);
    if (channel) {
        auto cmd      = at::cmd::CFUN(at::cmd::Modifier::Get);
        auto response = channel->cmd(cmd);
        if (response.code == at::Result::Code::OK) {
            auto result = cmd.parse(response);
            if (result.code == at::Result::Code::OK) {
                return result.functionality == Functionality::Full;
            }
        }
    }
    return false;
}

auto ConnectionManagerCellularCommands::clearNetworkIndicator() -> bool
{

    // force clear signal indicator
    constexpr auto rssi = 0;
    SignalStrength signalStrength(rssi);
    Store::GSM::get()->setSignalStrength(signalStrength.data);
    auto msg = std::make_shared<CellularSignalStrengthUpdateNotification>();
    cellular.bus.sendMulticast(msg, sys::BusChannel::ServiceCellularNotifications);

    return true;
}

auto ConnectionManagerCellularCommands::hangUpOngoingCall() -> bool
{
    if (cellular.ongoingCall.isActive()) {
        auto channel = cellular.cmux->get(TS0710::Channel::Commands);
        if (channel) {
            if (channel->cmd(at::factory(at::AT::ATH))) {
                cellular.callStateTimer.stop();
                if (!cellular.ongoingCall.endCall(CellularCall::Forced::True)) {
                    LOG_ERROR("Failed to end ongoing call");
                    return false;
                }
                auto msg = std::make_shared<CellularCallAbortedNotification>();
                cellular.bus.sendMulticast(msg, sys::BusChannel::ServiceCellularNotifications);
            }
        }
    }
    return true;
}

auto ConnectionManagerCellularCommands::isConnectionTimerActive() -> bool
{
    return cellular.connectionTimer.isActive();
}

void ConnectionManagerCellularCommands::startConnectionTimer()
{
    cellular.connectionTimer.start();
}

void ConnectionManagerCellularCommands::stopConnectionTimer()
{
    cellular.connectionTimer.stop();
}

M module-services/service-cellular/doc/README.md => module-services/service-cellular/doc/README.md +14 -8
@@ 64,11 64,17 @@ Do not Disturb
Offline mode
* modem is disconnected from the GSM network, both calls and messages are rejected

| | Connected | Do not Disturb | Offline |
| ----------- | --------- | -------------- | ------- |
|Incoming calls| Allowed | Rejected | Not allowed |
|Outgoing calls| Allowed | Allowed | Not allowed |
|Incoming messages| Allowed | Allowed | Not allowed |
|Outgoing messages| Allowed | Allowed | Not allowed |

![](phone_modes.svg)
\ No newline at end of file
Messages only mode
* modem is disconnected from the GSM network, calls are rejected, sending messages is rejected
* phone is connecting to the GSM network in selected by settings period to fetch incoming messages

| | Connected | Do not Disturb | Offline | Message only |
| ----------- | --------- | -------------- | ------- | ------- |
|Incoming calls| Allowed | Rejected | Not allowed | Rejected |
|Outgoing calls| Allowed | Allowed | Not allowed | Rejected |
|Incoming messages| Allowed | Allowed | Not allowed | Rejected |
|Outgoing messages| Allowed | Allowed | Not allowed | Partially allowed |

![](phone_modes.svg)

![](connection_manager.svg)
\ No newline at end of file

A module-services/service-cellular/doc/connection_manager.puml => module-services/service-cellular/doc/connection_manager.puml +59 -0
@@ 0,0 1,59 @@
@startuml

participant "Connection manager " as manager
participant "Service cellular" as cellular
participant "Phone mode observer " as mode

== Phone is connected to the GSM network==

mode ->cellular : Phone mode change to Connected/ Do not Disturb
cellular -> manager : onPhoneMode()
manager -> cellular :isConnectedToTheNetwork() AT+CFUN?
cellular -> manager : +CFUN: 1

mode ->cellular : Phone mode change to Offline / Message only
cellular -> manager : onPhoneMode()
manager -> cellular :isConnectedToTheNetwork() AT+CFUN?
cellular -> manager : +CFUN: 1
manager -> cellular :hangUpOngoingCall()
manager -> cellular :disconnectFromNetwork() AT+CFUN=4
manager -> cellular :clearNetworkIndicator()
manager->manager : handle switch to selected oflline mode

== Phone is not connected to the GSM network==

mode ->cellular : Phone mode change to CConnected/ Do not Disturb
cellular -> manager : onPhoneMode()
manager -> cellular :isConnectedToTheNetwork() AT+CFUN?
cellular -> manager : +CFUN: 4
manager -> cellular :connectToTheNetwork() AT+CFUN=1

mode ->cellular : Phone mode change to Offline / Message only
cellular -> manager : onPhoneMode()
manager -> cellular :isConnectedToTheNetwork() AT+CFUN?
cellular -> manager : +CFUN: 4
manager->manager : handle switch to selected oflline mode

==handle switch to selected oflline mode==

==handleModeChangeToOfflineMode()==
manager -> cellular : isConnectionTimerActive()
cellular -> manager : timer is active
manager -> cellular : stopConnectionTimer()

==handleModeChangeToOfflineMode()==
manager -> cellular : isConnectionTimerActive()
cellular -> manager : timer is not active
manager -> cellular : startonnectionTimer()

==Connection manager in Messages only mode==
cellular -> manager : service timer tick (60 seconds)
manager -> manager : offline period not expired, online period not expired

manager -> manager : offline period expired onTimTick()
manager -> cellular : connectToNetwork()

manager -> manager : online period expired onTimTick()
manager -> cellular : disconnectFromNetwork()
manager -> cellular :clearNetworkIndicator()
@enduml

A module-services/service-cellular/doc/connection_manager.svg => module-services/service-cellular/doc/connection_manager.svg +69 -0
@@ 0,0 1,69 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" contentScriptType="application/ecmascript" contentStyleType="text/css" height="1447px" preserveAspectRatio="none" style="width:899px;height:1447px;" version="1.1" viewBox="0 0 899 1447" width="899px" zoomAndPan="magnify"><defs><filter height="300%" id="fatert3ea1apr" width="300%" x="-1" y="-1"><feGaussianBlur result="blurOut" stdDeviation="2.0"/><feColorMatrix in="blurOut" result="blurOut2" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 .4 0"/><feOffset dx="4.0" dy="4.0" in="blurOut2" result="blurOut3"/><feBlend in="SourceGraphic" in2="blurOut3" mode="normal"/></filter></defs><g><line style="stroke:#A80036;stroke-width:1.0;stroke-dasharray:5.0,5.0;" x1="91" x2="91" y1="40.2969" y2="1403.7422"/><line style="stroke:#A80036;stroke-width:1.0;stroke-dasharray:5.0,5.0;" x1="441" x2="441" y1="40.2969" y2="1403.7422"/><line style="stroke:#A80036;stroke-width:1.0;stroke-dasharray:5.0,5.0;" x1="798" x2="798" y1="40.2969" y2="1403.7422"/><rect fill="#FEFECE" filter="url(#fatert3ea1apr)" height="30.2969" style="stroke:#A80036;stroke-width:1.5;" width="168" x="5" y="5"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="150" x="12" y="24.9951">Connection manager</text><rect fill="#FEFECE" filter="url(#fatert3ea1apr)" height="30.2969" style="stroke:#A80036;stroke-width:1.5;" width="168" x="5" y="1402.7422"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="150" x="12" y="1422.7373">Connection manager</text><rect fill="#FEFECE" filter="url(#fatert3ea1apr)" height="30.2969" style="stroke:#A80036;stroke-width:1.5;" width="124" x="377" y="5"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="110" x="384" y="24.9951">Service cellular</text><rect fill="#FEFECE" filter="url(#fatert3ea1apr)" height="30.2969" style="stroke:#A80036;stroke-width:1.5;" width="124" x="377" y="1402.7422"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="110" x="384" y="1422.7373">Service cellular</text><rect fill="#FEFECE" filter="url(#fatert3ea1apr)" height="30.2969" style="stroke:#A80036;stroke-width:1.5;" width="174" x="709" y="5"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="156" x="716" y="24.9951">Phone mode observer</text><rect fill="#FEFECE" filter="url(#fatert3ea1apr)" height="30.2969" style="stroke:#A80036;stroke-width:1.5;" width="174" x="709" y="1402.7422"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="156" x="716" y="1422.7373">Phone mode observer</text><rect fill="#EEEEEE" filter="url(#fatert3ea1apr)" height="3" style="stroke:#EEEEEE;stroke-width:1.0;" width="892" x="0" y="70.8633"/><line style="stroke:#000000;stroke-width:1.0;" x1="0" x2="892" y1="70.8633" y2="70.8633"/><line style="stroke:#000000;stroke-width:1.0;" x1="0" x2="892" y1="73.8633" y2="73.8633"/><rect fill="#EEEEEE" filter="url(#fatert3ea1apr)" height="23.1328" style="stroke:#000000;stroke-width:2.0;" width="310" x="291" y="60.2969"/><text fill="#000000" font-family="sans-serif" font-size="13" font-weight="bold" lengthAdjust="spacing" textLength="296" x="297" y="76.3638">Phone is connected to the GSM network</text><polygon fill="#A80036" points="452,110.5625,442,114.5625,452,118.5625,448,114.5625" style="stroke:#A80036;stroke-width:1.0;"/><line style="stroke:#A80036;stroke-width:1.0;" x1="446" x2="797" y1="114.5625" y2="114.5625"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="324" x="458" y="109.4966">Phone mode change to Connected/ Do not Disturb</text><polygon fill="#A80036" points="102,139.6953,92,143.6953,102,147.6953,98,143.6953" style="stroke:#A80036;stroke-width:1.0;"/><line style="stroke:#A80036;stroke-width:1.0;" x1="96" x2="440" y1="143.6953" y2="143.6953"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="101" x="108" y="138.6294">onPhoneMode()</text><polygon fill="#A80036" points="429,168.8281,439,172.8281,429,176.8281,433,172.8281" style="stroke:#A80036;stroke-width:1.0;"/><line style="stroke:#A80036;stroke-width:1.0;" x1="91" x2="435" y1="172.8281" y2="172.8281"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="260" x="98" y="167.7622">isConnectedToTheNetwork() AT+CFUN?</text><polygon fill="#A80036" points="102,197.9609,92,201.9609,102,205.9609,98,201.9609" style="stroke:#A80036;stroke-width:1.0;"/><line style="stroke:#A80036;stroke-width:1.0;" x1="96" x2="440" y1="201.9609" y2="201.9609"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="63" x="108" y="196.895">+CFUN: 1</text><polygon fill="#A80036" points="452,227.0938,442,231.0938,452,235.0938,448,231.0938" style="stroke:#A80036;stroke-width:1.0;"/><line style="stroke:#A80036;stroke-width:1.0;" x1="446" x2="797" y1="231.0938" y2="231.0938"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="298" x="458" y="226.0278">Phone mode change to Offline / Message only</text><polygon fill="#A80036" points="102,256.2266,92,260.2266,102,264.2266,98,260.2266" style="stroke:#A80036;stroke-width:1.0;"/><line style="stroke:#A80036;stroke-width:1.0;" x1="96" x2="440" y1="260.2266" y2="260.2266"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="101" x="108" y="255.1606">onPhoneMode()</text><polygon fill="#A80036" points="429,285.3594,439,289.3594,429,293.3594,433,289.3594" style="stroke:#A80036;stroke-width:1.0;"/><line style="stroke:#A80036;stroke-width:1.0;" x1="91" x2="435" y1="289.3594" y2="289.3594"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="260" x="98" y="284.2935">isConnectedToTheNetwork() AT+CFUN?</text><polygon fill="#A80036" points="102,314.4922,92,318.4922,102,322.4922,98,318.4922" style="stroke:#A80036;stroke-width:1.0;"/><line style="stroke:#A80036;stroke-width:1.0;" x1="96" x2="440" y1="318.4922" y2="318.4922"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="63" x="108" y="313.4263">+CFUN: 1</text><polygon fill="#A80036" points="429,343.625,439,347.625,429,351.625,433,347.625" style="stroke:#A80036;stroke-width:1.0;"/><line style="stroke:#A80036;stroke-width:1.0;" x1="91" x2="435" y1="347.625" y2="347.625"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="139" x="98" y="342.5591">hangUpOngoingCall()</text><polygon fill="#A80036" points="429,372.7578,439,376.7578,429,380.7578,433,376.7578" style="stroke:#A80036;stroke-width:1.0;"/><line style="stroke:#A80036;stroke-width:1.0;" x1="91" x2="435" y1="376.7578" y2="376.7578"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="255" x="98" y="371.6919">disconnectFromNetwork() AT+CFUN=4</text><polygon fill="#A80036" points="429,401.8906,439,405.8906,429,409.8906,433,405.8906" style="stroke:#A80036;stroke-width:1.0;"/><line style="stroke:#A80036;stroke-width:1.0;" x1="91" x2="435" y1="405.8906" y2="405.8906"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="154" x="98" y="400.8247">clearNetworkIndicator()</text><line style="stroke:#A80036;stroke-width:1.0;" x1="91" x2="133" y1="435.0234" y2="435.0234"/><line style="stroke:#A80036;stroke-width:1.0;" x1="133" x2="133" y1="435.0234" y2="448.0234"/><line style="stroke:#A80036;stroke-width:1.0;" x1="92" x2="133" y1="448.0234" y2="448.0234"/><polygon fill="#A80036" points="102,444.0234,92,448.0234,102,452.0234,98,448.0234" style="stroke:#A80036;stroke-width:1.0;"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="252" x="98" y="429.9575">handle switch to selected oflline mode</text><rect fill="#EEEEEE" filter="url(#fatert3ea1apr)" height="3" style="stroke:#EEEEEE;stroke-width:1.0;" width="892" x="0" y="476.5898"/><line style="stroke:#000000;stroke-width:1.0;" x1="0" x2="892" y1="476.5898" y2="476.5898"/><line style="stroke:#000000;stroke-width:1.0;" x1="0" x2="892" y1="479.5898" y2="479.5898"/><rect fill="#EEEEEE" filter="url(#fatert3ea1apr)" height="23.1328" style="stroke:#000000;stroke-width:2.0;" width="339" x="276.5" y="466.0234"/><text fill="#000000" font-family="sans-serif" font-size="13" font-weight="bold" lengthAdjust="spacing" textLength="325" x="282.5" y="482.0903">Phone is not connected to the GSM network</text><polygon fill="#A80036" points="452,516.2891,442,520.2891,452,524.2891,448,520.2891" style="stroke:#A80036;stroke-width:1.0;"/><line style="stroke:#A80036;stroke-width:1.0;" x1="446" x2="797" y1="520.2891" y2="520.2891"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="333" x="458" y="515.2231">Phone mode change to CConnected/ Do not Disturb</text><polygon fill="#A80036" points="102,545.4219,92,549.4219,102,553.4219,98,549.4219" style="stroke:#A80036;stroke-width:1.0;"/><line style="stroke:#A80036;stroke-width:1.0;" x1="96" x2="440" y1="549.4219" y2="549.4219"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="101" x="108" y="544.356">onPhoneMode()</text><polygon fill="#A80036" points="429,574.5547,439,578.5547,429,582.5547,433,578.5547" style="stroke:#A80036;stroke-width:1.0;"/><line style="stroke:#A80036;stroke-width:1.0;" x1="91" x2="435" y1="578.5547" y2="578.5547"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="260" x="98" y="573.4888">isConnectedToTheNetwork() AT+CFUN?</text><polygon fill="#A80036" points="102,603.6875,92,607.6875,102,611.6875,98,607.6875" style="stroke:#A80036;stroke-width:1.0;"/><line style="stroke:#A80036;stroke-width:1.0;" x1="96" x2="440" y1="607.6875" y2="607.6875"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="63" x="108" y="602.6216">+CFUN: 4</text><polygon fill="#A80036" points="429,632.8203,439,636.8203,429,640.8203,433,636.8203" style="stroke:#A80036;stroke-width:1.0;"/><line style="stroke:#A80036;stroke-width:1.0;" x1="91" x2="435" y1="636.8203" y2="636.8203"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="243" x="98" y="631.7544">connectToTheNetwork() AT+CFUN=1</text><polygon fill="#A80036" points="452,661.9531,442,665.9531,452,669.9531,448,665.9531" style="stroke:#A80036;stroke-width:1.0;"/><line style="stroke:#A80036;stroke-width:1.0;" x1="446" x2="797" y1="665.9531" y2="665.9531"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="298" x="458" y="660.8872">Phone mode change to Offline / Message only</text><polygon fill="#A80036" points="102,691.0859,92,695.0859,102,699.0859,98,695.0859" style="stroke:#A80036;stroke-width:1.0;"/><line style="stroke:#A80036;stroke-width:1.0;" x1="96" x2="440" y1="695.0859" y2="695.0859"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="101" x="108" y="690.02">onPhoneMode()</text><polygon fill="#A80036" points="429,720.2188,439,724.2188,429,728.2188,433,724.2188" style="stroke:#A80036;stroke-width:1.0;"/><line style="stroke:#A80036;stroke-width:1.0;" x1="91" x2="435" y1="724.2188" y2="724.2188"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="260" x="98" y="719.1528">isConnectedToTheNetwork() AT+CFUN?</text><polygon fill="#A80036" points="102,749.3516,92,753.3516,102,757.3516,98,753.3516" style="stroke:#A80036;stroke-width:1.0;"/><line style="stroke:#A80036;stroke-width:1.0;" x1="96" x2="440" y1="753.3516" y2="753.3516"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="63" x="108" y="748.2856">+CFUN: 4</text><line style="stroke:#A80036;stroke-width:1.0;" x1="91" x2="133" y1="782.4844" y2="782.4844"/><line style="stroke:#A80036;stroke-width:1.0;" x1="133" x2="133" y1="782.4844" y2="795.4844"/><line style="stroke:#A80036;stroke-width:1.0;" x1="92" x2="133" y1="795.4844" y2="795.4844"/><polygon fill="#A80036" points="102,791.4844,92,795.4844,102,799.4844,98,795.4844" style="stroke:#A80036;stroke-width:1.0;"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="252" x="98" y="777.4185">handle switch to selected oflline mode</text><rect fill="#EEEEEE" filter="url(#fatert3ea1apr)" height="3" style="stroke:#EEEEEE;stroke-width:1.0;" width="892" x="0" y="824.0508"/><line style="stroke:#000000;stroke-width:1.0;" x1="0" x2="892" y1="824.0508" y2="824.0508"/><line style="stroke:#000000;stroke-width:1.0;" x1="0" x2="892" y1="827.0508" y2="827.0508"/><rect fill="#EEEEEE" filter="url(#fatert3ea1apr)" height="23.1328" style="stroke:#000000;stroke-width:2.0;" width="298" x="297" y="813.4844"/><text fill="#000000" font-family="sans-serif" font-size="13" font-weight="bold" lengthAdjust="spacing" textLength="284" x="303" y="829.5513">handle switch to selected oflline mode</text><rect fill="#EEEEEE" filter="url(#fatert3ea1apr)" height="3" style="stroke:#EEEEEE;stroke-width:1.0;" width="892" x="0" y="867.1836"/><line style="stroke:#000000;stroke-width:1.0;" x1="0" x2="892" y1="867.1836" y2="867.1836"/><line style="stroke:#000000;stroke-width:1.0;" x1="0" x2="892" y1="870.1836" y2="870.1836"/><rect fill="#EEEEEE" filter="url(#fatert3ea1apr)" height="23.1328" style="stroke:#000000;stroke-width:2.0;" width="277" x="307.5" y="856.6172"/><text fill="#000000" font-family="sans-serif" font-size="13" font-weight="bold" lengthAdjust="spacing" textLength="263" x="313.5" y="872.6841">handleModeChangeToOfflineMode()</text><polygon fill="#A80036" points="429,906.8828,439,910.8828,429,914.8828,433,910.8828" style="stroke:#A80036;stroke-width:1.0;"/><line style="stroke:#A80036;stroke-width:1.0;" x1="91" x2="435" y1="910.8828" y2="910.8828"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="173" x="98" y="905.8169">isConnectionTimerActive()</text><polygon fill="#A80036" points="102,936.0156,92,940.0156,102,944.0156,98,940.0156" style="stroke:#A80036;stroke-width:1.0;"/><line style="stroke:#A80036;stroke-width:1.0;" x1="96" x2="440" y1="940.0156" y2="940.0156"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="94" x="108" y="934.9497">timer is active</text><polygon fill="#A80036" points="429,965.1484,439,969.1484,429,973.1484,433,969.1484" style="stroke:#A80036;stroke-width:1.0;"/><line style="stroke:#A80036;stroke-width:1.0;" x1="91" x2="435" y1="969.1484" y2="969.1484"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="149" x="98" y="964.0825">stopConnectionTimer()</text><rect fill="#EEEEEE" filter="url(#fatert3ea1apr)" height="3" style="stroke:#EEEEEE;stroke-width:1.0;" width="892" x="0" y="997.7148"/><line style="stroke:#000000;stroke-width:1.0;" x1="0" x2="892" y1="997.7148" y2="997.7148"/><line style="stroke:#000000;stroke-width:1.0;" x1="0" x2="892" y1="1000.7148" y2="1000.7148"/><rect fill="#EEEEEE" filter="url(#fatert3ea1apr)" height="23.1328" style="stroke:#000000;stroke-width:2.0;" width="277" x="307.5" y="987.1484"/><text fill="#000000" font-family="sans-serif" font-size="13" font-weight="bold" lengthAdjust="spacing" textLength="263" x="313.5" y="1003.2153">handleModeChangeToOfflineMode()</text><polygon fill="#A80036" points="429,1037.4141,439,1041.4141,429,1045.4141,433,1041.4141" style="stroke:#A80036;stroke-width:1.0;"/><line style="stroke:#A80036;stroke-width:1.0;" x1="91" x2="435" y1="1041.4141" y2="1041.4141"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="173" x="98" y="1036.3481">isConnectionTimerActive()</text><polygon fill="#A80036" points="102,1066.5469,92,1070.5469,102,1074.5469,98,1070.5469" style="stroke:#A80036;stroke-width:1.0;"/><line style="stroke:#A80036;stroke-width:1.0;" x1="96" x2="440" y1="1070.5469" y2="1070.5469"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="119" x="108" y="1065.481">timer is not active</text><polygon fill="#A80036" points="429,1095.6797,439,1099.6797,429,1103.6797,433,1099.6797" style="stroke:#A80036;stroke-width:1.0;"/><line style="stroke:#A80036;stroke-width:1.0;" x1="91" x2="435" y1="1099.6797" y2="1099.6797"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="142" x="98" y="1094.6138">startonnectionTimer()</text><rect fill="#EEEEEE" filter="url(#fatert3ea1apr)" height="3" style="stroke:#EEEEEE;stroke-width:1.0;" width="892" x="0" y="1128.2461"/><line style="stroke:#000000;stroke-width:1.0;" x1="0" x2="892" y1="1128.2461" y2="1128.2461"/><line style="stroke:#000000;stroke-width:1.0;" x1="0" x2="892" y1="1131.2461" y2="1131.2461"/><rect fill="#EEEEEE" filter="url(#fatert3ea1apr)" height="23.1328" style="stroke:#000000;stroke-width:2.0;" width="343" x="274.5" y="1117.6797"/><text fill="#000000" font-family="sans-serif" font-size="13" font-weight="bold" lengthAdjust="spacing" textLength="329" x="280.5" y="1133.7466">Connection manager in Messages only mode</text><polygon fill="#A80036" points="102,1167.9453,92,1171.9453,102,1175.9453,98,1171.9453" style="stroke:#A80036;stroke-width:1.0;"/><line style="stroke:#A80036;stroke-width:1.0;" x1="96" x2="440" y1="1171.9453" y2="1171.9453"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="201" x="108" y="1166.8794">service timer tick (60 seconds)</text><line style="stroke:#A80036;stroke-width:1.0;" x1="91" x2="133" y1="1201.0781" y2="1201.0781"/><line style="stroke:#A80036;stroke-width:1.0;" x1="133" x2="133" y1="1201.0781" y2="1214.0781"/><line style="stroke:#A80036;stroke-width:1.0;" x1="92" x2="133" y1="1214.0781" y2="1214.0781"/><polygon fill="#A80036" points="102,1210.0781,92,1214.0781,102,1218.0781,98,1214.0781" style="stroke:#A80036;stroke-width:1.0;"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="336" x="98" y="1196.0122">offline period not expired, online period not expired</text><line style="stroke:#A80036;stroke-width:1.0;" x1="91" x2="133" y1="1243.2109" y2="1243.2109"/><line style="stroke:#A80036;stroke-width:1.0;" x1="133" x2="133" y1="1243.2109" y2="1256.2109"/><line style="stroke:#A80036;stroke-width:1.0;" x1="92" x2="133" y1="1256.2109" y2="1256.2109"/><polygon fill="#A80036" points="102,1252.2109,92,1256.2109,102,1260.2109,98,1256.2109" style="stroke:#A80036;stroke-width:1.0;"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="222" x="98" y="1238.145">offline period expired onTimTick()</text><polygon fill="#A80036" points="429,1281.3438,439,1285.3438,429,1289.3438,433,1285.3438" style="stroke:#A80036;stroke-width:1.0;"/><line style="stroke:#A80036;stroke-width:1.0;" x1="91" x2="435" y1="1285.3438" y2="1285.3438"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="132" x="98" y="1280.2778">connectToNetwork()</text><line style="stroke:#A80036;stroke-width:1.0;" x1="91" x2="133" y1="1314.4766" y2="1314.4766"/><line style="stroke:#A80036;stroke-width:1.0;" x1="133" x2="133" y1="1314.4766" y2="1327.4766"/><line style="stroke:#A80036;stroke-width:1.0;" x1="92" x2="133" y1="1327.4766" y2="1327.4766"/><polygon fill="#A80036" points="102,1323.4766,92,1327.4766,102,1331.4766,98,1327.4766" style="stroke:#A80036;stroke-width:1.0;"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="220" x="98" y="1309.4106">online period expired onTimTick()</text><polygon fill="#A80036" points="429,1352.6094,439,1356.6094,429,1360.6094,433,1356.6094" style="stroke:#A80036;stroke-width:1.0;"/><line style="stroke:#A80036;stroke-width:1.0;" x1="91" x2="435" y1="1356.6094" y2="1356.6094"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="168" x="98" y="1351.5435">disconnectFromNetwork()</text><polygon fill="#A80036" points="429,1381.7422,439,1385.7422,429,1389.7422,433,1385.7422" style="stroke:#A80036;stroke-width:1.0;"/><line style="stroke:#A80036;stroke-width:1.0;" x1="91" x2="435" y1="1385.7422" y2="1385.7422"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="154" x="98" y="1380.6763">clearNetworkIndicator()</text><!--MD5=[e46c5ad7b3b5a2a99eecdfa0cfb63c20]
@startuml

participant "Connection manager " as manager
participant "Service cellular" as cellular
participant "Phone mode observer " as mode

== Phone is connected to the GSM network==

mode ->cellular : Phone mode change to Connected/ Do not Disturb
cellular -> manager : onPhoneMode()
manager -> cellular :isConnectedToTheNetwork() AT+CFUN?
cellular -> manager : +CFUN: 1

mode ->cellular : Phone mode change to Offline / Message only
cellular -> manager : onPhoneMode()
manager -> cellular :isConnectedToTheNetwork() AT+CFUN?
cellular -> manager : +CFUN: 1
manager -> cellular :hangUpOngoingCall()
manager -> cellular :disconnectFromNetwork() AT+CFUN=4
manager -> cellular :clearNetworkIndicator()
manager->manager : handle switch to selected oflline mode

== Phone is not connected to the GSM network==

mode ->cellular : Phone mode change to CConnected/ Do not Disturb
cellular -> manager : onPhoneMode()
manager -> cellular :isConnectedToTheNetwork() AT+CFUN?
cellular -> manager : +CFUN: 4
manager -> cellular :connectToTheNetwork() AT+CFUN=1

mode ->cellular : Phone mode change to Offline / Message only
cellular -> manager : onPhoneMode()
manager -> cellular :isConnectedToTheNetwork() AT+CFUN?
cellular -> manager : +CFUN: 4
manager->manager : handle switch to selected oflline mode

==handle switch to selected oflline mode==

==handleModeChangeToOfflineMode()==
manager -> cellular : isConnectionTimerActive()
cellular -> manager : timer is active
manager -> cellular : stopConnectionTimer()

==handleModeChangeToOfflineMode()==
manager -> cellular : isConnectionTimerActive()
cellular -> manager : timer is not active
manager -> cellular : startonnectionTimer()

==Connection manager in Messages only mode==
cellular -> manager : service timer tick (60 seconds)
manager -> manager : offline period not expired, online period not expired

manager -> manager : offline period expired onTimTick()
manager -> cellular : connectToNetwork()

manager -> manager : online period expired onTimTick()
manager -> cellular : disconnectFromNetwork()
manager -> cellular :clearNetworkIndicator()
@enduml

PlantUML version 1.2021.2(Sun Mar 07 12:10:27 CET 2021)
(GPL source distribution)
Java Runtime: OpenJDK Runtime Environment
JVM: OpenJDK 64-Bit Server VM
Default Encoding: UTF-8
Language: pl
Country: PL
--></g></svg>
\ No newline at end of file

M module-services/service-cellular/service-cellular/CellularMessage.hpp => module-services/service-cellular/service-cellular/CellularMessage.hpp +17 -1
@@ 74,7 74,8 @@ class CellularMessage : public sys::DataMessage
        MMIData,
        NewIncomingSMS,
        RadioOnOff,
        SendSMS
        SendSMS,
        CellularSetConnectionFrequency
    };
    explicit CellularMessage(Type type) : sys::DataMessage(), type(type)
    {}


@@ 976,6 977,21 @@ class CellularSetFlightModeMessage : public CellularMessage
    bool flightModeOn;
};

class CellularSetConnectionFrequencyMessage : public CellularMessage
{
  public:
    explicit CellularSetConnectionFrequencyMessage(const uint8_t &connectionFrequency)
        : CellularMessage(Type::CellularSetConnectionFrequency), connectionFrequency(connectionFrequency)
    {}
    auto getConnectionFrequency() const noexcept -> uint8_t
    {
        return connectionFrequency;
    }

  private:
    uint8_t connectionFrequency;
};

class CellularCallActiveNotification : public CellularNotificationMessage
{
  public:

M module-services/service-cellular/service-cellular/CellularServiceAPI.hpp => module-services/service-cellular/service-cellular/CellularServiceAPI.hpp +2 -0
@@ 148,4 148,6 @@ namespace CellularServiceAPI

    bool SetFlightMode(sys::Service *serv, bool flightModeOn);

    bool SetConnectionFrequency(sys::Service *serv, uint8_t connectionFrequency);

}; // namespace CellularServiceAPI

M module-services/service-cellular/service-cellular/ServiceCellular.hpp => module-services/service-cellular/service-cellular/ServiceCellular.hpp +14 -10
@@ 11,6 11,7 @@
#include "USSD.hpp"
#include "PacketData.hpp"
#include "PacketDataCellularMessage.hpp"
#include <service-cellular/connection-manager/ConnectionManager.hpp>

#include <module-cellular/Modem/ATURCStream.hpp>
#include <Modem/TS0710/DLC_channel.h>


@@ 63,6 64,8 @@ namespace constants
    inline constexpr std::chrono::milliseconds enterSleepModeTime{5s};
} // namespace constants

class ConnectionManager;

class ServiceCellular : public sys::Service
{



@@ 188,13 191,18 @@ class ServiceCellular : public sys::Service
    // used to enter modem sleep mode
    sys::TimerHandle sleepTimer;

    // used to manage network connection in Messages only mode
    sys::TimerHandle connectionTimer;

    std::unique_ptr<settings::Settings> settings;

    void SleepTimerHandler();
    void CallStateTimerHandler();
    DLC_channel::Callback_t notificationCallback = nullptr;

    std::unique_ptr<packet_data::PacketData> packetData;
    std::unique_ptr<sys::phone_modes::Observer> phoneModeObserver;

    std::unique_ptr<ConnectionManager> connectionManager;
    cellular::State state;
    bsp::Board board = bsp::Board::none;



@@ 268,7 276,6 @@ class ServiceCellular : public sys::Service
    /// Process change of power state
    void handle_power_state_change();

    std::unique_ptr<settings::Settings> settings = std::make_unique<settings::Settings>(this);
    bool handle_apn_conf_procedure();

    bool handleTextMessagesInit();


@@ 334,6 341,7 @@ class ServiceCellular : public sys::Service
    friend class NetworkSettings;
    friend class packet_data::PDPContext;
    friend class packet_data::PacketData;
    friend class ConnectionManagerCellularCommands;

    void volteChanged(const std::string &value);
    void apnListChanged(const std::string &value);


@@ 346,8 354,8 @@ class ServiceCellular : public sys::Service
    auto handleCellularListCallsMessage(CellularMessage *msg) -> std::shared_ptr<sys::ResponseMessage>;
    auto handleDBNotificatioMessage(db::NotificationMessage *msg) -> std::shared_ptr<sys::ResponseMessage>;
    auto handleCellularRingingMessage(CellularRingingMessage *msg) -> std::shared_ptr<sys::ResponseMessage>;
    auto handleCellularIncominCallMessage(CellularIncominCallMessage *msg) -> std::shared_ptr<sys::ResponseMessage>;
    auto handleCellularCallerIdMessage(CellularCallerIdMessage *msg) -> std::shared_ptr<sys::ResponseMessage>;
    auto handleCellularIncominCallMessage(sys::Message *msg) -> std::shared_ptr<sys::ResponseMessage>;
    auto handleCellularCallerIdMessage(sys::Message *msg) -> std::shared_ptr<sys::ResponseMessage>;
    auto handleCellularSimProcedureMessage(sys::Message *msg) -> std::shared_ptr<sys::ResponseMessage>;
    auto handleCellularGetIMSIMessage(sys::Message *msg) -> std::shared_ptr<sys::ResponseMessage>;
    auto handleCellularGetOwnNumberMessage(sys::Message *msg) -> std::shared_ptr<sys::ResponseMessage>;


@@ 384,13 392,9 @@ class ServiceCellular : public sys::Service
    auto handleCellularSendSMSMessage(sys::Message *msg) -> std::shared_ptr<sys::ResponseMessage>;
    auto handleCellularRingNotification(sys::Message *msg) -> std::shared_ptr<sys::ResponseMessage>;
    auto handleCellularCallerIdNotification(sys::Message *msg) -> std::shared_ptr<sys::ResponseMessage>;
    auto handleCellularSetConnectionFrequencyMessage(sys::Message *msg) -> std::shared_ptr<sys::ResponseMessage>;

    auto isModemRadioModuleOn() -> bool;
    auto turnOnRadioModule() -> bool;
    auto turnOffRadioModule() -> bool;

    auto switchToOffline() -> bool;
    auto doNotDisturbCondition() -> bool;
    auto isIncommingCallAllowed() -> bool;

    auto hangUpCall() -> bool;
};

A module-services/service-cellular/service-cellular/connection-manager/ConnectionManager.hpp => module-services/service-cellular/service-cellular/connection-manager/ConnectionManager.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 "ConnectionManagerCellularCommandsInterface.hpp"
#include "module-services/service-cellular/service-cellular/ServiceCellular.hpp"
#include <module-sys/PhoneModes/Observer.hpp>
#include <functional>

// connection timer period is 1 minute, connect to network for 5 minutes
constexpr std::chrono::minutes connectedPeriod{5};

/**
 * @brief Intended for GSM network connection management depending on the selected phone mode.
 * Switching modes takes place at Phone Modes Observer event.
 */
class ConnectionManager
{
  public:
    /**
     * @brief Constructor
     * @param flightMode current setting for flight mode
     * @param interval current setting for connection interval in Message only mode
     * @param serviceCellular pointer to service cellular methods provider
     */
    explicit ConnectionManager(const bool flightMode,
                               const std::chrono::minutes interval,
                               std::shared_ptr<ConnectionManagerCellularCommandsInterface> serviceCellular)
        : isFlightMode(flightMode), connectionInterval(interval), cellular(serviceCellular)
    {}
    /**
     * @brief Handles Phone mode change. Called on event provided by Phone mode observer or settinhs change.
     * @param mode current Phone mode
     * @return true on success, false on fail
     */
    auto onPhoneModeChange(sys::phone_modes::PhoneMode mode) -> bool;
    /**
     * @brief Sets interval of connecting to the GSM network in Offline Message only mode
     * @param interval connection interval in minutes
     */
    void setInterval(const std::chrono::minutes interval);
    /**
     * @biref Sets flight mode.
     * @param mode flight mode enabled/disabled
     */
    void setFlightMode(const bool mode);
    /**
     * @brief Handles cyclic connecting/disconnecting from the GSM network in Offline Message only mode. Called in
     * service timer handler.
     */
    void onTimerTick();

  private:
    bool isFlightMode;
    std::chrono::minutes connectionInterval{0};
    std::chrono::minutes minutesOfflineElapsed{0};
    std::chrono::minutes minutesOnlineElapsed{0};
    std::shared_ptr<ConnectionManagerCellularCommandsInterface> cellular;
    bool onlinePeriod = false;

    /**
     * @brief Checks if flightMode and connection interval are set as Messages only mode
     * @return true when Messages only is configured, false when not
     */
    auto isMessagesOnlyMode() -> bool;
    /**
     * It's handling change Phone mode to common offline modes
     * @return true on success, false on fail
     */
    auto handleModeChangeToCommonOffline() -> bool;
    /**
     * It's handling change Phone mode to Offline mode
     * @return true on success, false on fail
     */
    void handleModeChangeToOfflineMode();
    /**
     * It's handling change Phone mode to Messages only mode
     * @return true on success, false on fail
     */
    void handleModeChangeToMessageOnlyMode();
    /**
     * It's handling change Phone mode to Connected / Do not disturb mode
     * @return true on success, false on fail
     */
    auto handleModeChangeToConnected() -> bool;
};

A module-services/service-cellular/service-cellular/connection-manager/ConnectionManagerCellularCommands.hpp => module-services/service-cellular/service-cellular/connection-manager/ConnectionManagerCellularCommands.hpp +26 -0
@@ 0,0 1,26 @@
// 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 "ConnectionManagerCellularCommandsInterface.hpp"

#include <module-services/service-cellular/service-cellular/ServiceCellular.hpp>

class ConnectionManagerCellularCommands : public ConnectionManagerCellularCommandsInterface
{
  public:
    explicit ConnectionManagerCellularCommands(ServiceCellular &serviceCellular) : cellular(serviceCellular)
    {}
    auto disconnectFromNetwork() -> bool final;
    auto connectToNetwork() -> bool final;
    auto isConnectedToNetwork() -> bool final;
    auto clearNetworkIndicator() -> bool final;
    auto hangUpOngoingCall() -> bool final;
    auto isConnectionTimerActive() -> bool final;
    void startConnectionTimer() final;
    void stopConnectionTimer() final;

  private:
    ServiceCellular &cellular;
};

A module-services/service-cellular/service-cellular/connection-manager/ConnectionManagerCellularCommandsInterface.hpp => module-services/service-cellular/service-cellular/connection-manager/ConnectionManagerCellularCommandsInterface.hpp +52 -0
@@ 0,0 1,52 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once

class ConnectionManagerCellularCommandsInterface
{
  public:
    /**
     * Destructor
     */
    virtual ~ConnectionManagerCellularCommandsInterface()
    {}
    /**
     * It's enabling modem radio module, so it can connect to the GSM network
     * @return true on success, false on fail
     */
    virtual auto disconnectFromNetwork() -> bool = 0;
    /**
     * It's enabling modem radio module, so it can connect to the GSM network
     * @return true on success, false on fail
     */
    virtual auto connectToNetwork() -> bool = 0;
    /**
     * * Checks current state of modem radio module
     * @return true when modem radio module is enabled, false when modem radio module is disabled
     */
    virtual auto isConnectedToNetwork() -> bool = 0;
    /**
     * * It's disabling modem radio module, so it can't connect to the GSM network
     * @return true on success, false on fail
     */
    virtual auto clearNetworkIndicator() -> bool = 0;
    /**
     * @brief Checks if there is ongoing call and terminates it
     * @return true on success, false on fail
     */
    virtual auto hangUpOngoingCall() -> bool = 0;
    /**
     * @brief Checks if connection Timer is active
     * @return true when timer is active, false when not
     */
    virtual auto isConnectionTimerActive() -> bool = 0;
    /**
     * @brief Stats connectionTimer
     */
    virtual void startConnectionTimer() = 0;
    /**
     * @brief Stops connectionTimer
     */
    virtual void stopConnectionTimer() = 0;
};

M module-services/service-cellular/tests/CMakeLists.txt => module-services/service-cellular/tests/CMakeLists.txt +8 -0
@@ 37,3 37,11 @@ add_catch2_executable(
        module-cellular
)

add_gtest_executable(
        NAME
        connection-manager
        SRCS
        unittest_connection-manager.cpp
        LIBS
        module-cellular
)

A module-services/service-cellular/tests/unittest_connection-manager.cpp => module-services/service-cellular/tests/unittest_connection-manager.cpp +113 -0
@@ 0,0 1,113 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include <gtest/gtest.h>
#include <gmock/gmock.h>

#include <service-cellular/connection-manager/ConnectionManager.hpp>
#include <service-cellular/connection-manager/ConnectionManagerCellularCommandsInterface.hpp>

using namespace testing;

class MockCellular : public ConnectionManagerCellularCommandsInterface
{
  public:
    MOCK_METHOD(bool, disconnectFromNetwork, ());
    MOCK_METHOD(bool, connectToNetwork, (), (override));
    MOCK_METHOD(bool, isConnectedToNetwork, (), (override));
    MOCK_METHOD(bool, clearNetworkIndicator, (), (override));
    MOCK_METHOD(bool, hangUpOngoingCall, (), (override));
    MOCK_METHOD(bool, isConnectionTimerActive, (), (override));
    MOCK_METHOD(void, startConnectionTimer, (), (override));
    MOCK_METHOD(void, stopConnectionTimer, (), (override));
};

TEST(ConnectionManager, onPhoneModeChange)
{
    std::shared_ptr<MockCellular> mock = std::make_shared<MockCellular>();
    ConnectionManager tested(true, std::chrono::minutes{5}, mock);

    // Connected / Do not disturb to Offline
    EXPECT_CALL(*mock, isConnectedToNetwork()).Times(1).WillOnce(Return(true));
    EXPECT_CALL(*mock, hangUpOngoingCall()).Times(1).WillOnce(Return(true));
    EXPECT_CALL(*mock, disconnectFromNetwork()).Times(1).WillOnce(Return(true));
    EXPECT_CALL(*mock, clearNetworkIndicator()).Times(1).WillOnce(Return(true));
    EXPECT_CALL(*mock, isConnectionTimerActive()).Times(1).WillOnce(Return(true));
    EXPECT_CALL(*mock, stopConnectionTimer()).Times(1);

    EXPECT_CALL(*mock, startConnectionTimer()).Times(0);
    EXPECT_CALL(*mock, connectToNetwork()).Times(0);
    tested.onPhoneModeChange(sys::phone_modes::PhoneMode::Offline);

    // Connected / Do not disturb to Messages only
    EXPECT_CALL(*mock, isConnectedToNetwork()).Times(1).WillOnce(Return(true));
    EXPECT_CALL(*mock, hangUpOngoingCall()).Times(1).WillOnce(Return(true));
    EXPECT_CALL(*mock, disconnectFromNetwork()).Times(1).WillOnce(Return(true));
    EXPECT_CALL(*mock, clearNetworkIndicator()).Times(1).WillOnce(Return(true));
    EXPECT_CALL(*mock, isConnectionTimerActive()).Times(1).WillOnce(Return(false));
    EXPECT_CALL(*mock, startConnectionTimer()).Times(1);

    EXPECT_CALL(*mock, stopConnectionTimer()).Times(0);
    EXPECT_CALL(*mock, connectToNetwork()).Times(0);
    tested.setFlightMode(false);
    tested.onPhoneModeChange(sys::phone_modes::PhoneMode::Offline);

    // Offline to Connected / Do not disturb
    EXPECT_CALL(*mock, isConnectionTimerActive()).Times(1).WillOnce(Return(false));
    EXPECT_CALL(*mock, isConnectedToNetwork()).Times(1).WillOnce(Return(false));
    EXPECT_CALL(*mock, connectToNetwork()).Times(1).WillOnce(Return(true));

    EXPECT_CALL(*mock, disconnectFromNetwork()).Times(0);
    tested.setFlightMode(true);
    tested.onPhoneModeChange(sys::phone_modes::PhoneMode::Connected);

    // Messages only to Connected / Do not disturb
    EXPECT_CALL(*mock, isConnectionTimerActive()).Times(1).WillOnce(Return(true));
    EXPECT_CALL(*mock, stopConnectionTimer()).Times(1);
    EXPECT_CALL(*mock, isConnectedToNetwork()).Times(1).WillOnce(Return(false));
    EXPECT_CALL(*mock, connectToNetwork()).Times(1).WillOnce(Return(true));

    EXPECT_CALL(*mock, disconnectFromNetwork()).Times(0);
    tested.onPhoneModeChange(sys::phone_modes::PhoneMode::Connected);
}

TEST(ConnectionManager, onTimerTick_Messages_only)
{
    std::shared_ptr<MockCellular> mock = std::make_shared<MockCellular>();
    ConnectionManager tested(false, std::chrono::minutes{15}, mock);

    // Messages only, interval 15 min
    EXPECT_CALL(*mock, isConnectedToNetwork()).Times(1).WillOnce(Return(false));
    EXPECT_CALL(*mock, isConnectionTimerActive()).Times(1).WillOnce(Return(false));
    EXPECT_CALL(*mock, startConnectionTimer()).Times(1);
    tested.onPhoneModeChange(sys::phone_modes::PhoneMode::Offline);

    EXPECT_CALL(*mock, connectToNetwork()).Times(1).WillOnce(Return(true));
    for (auto i = 0; i < 15; i++) {
        tested.onTimerTick();
    }

    EXPECT_CALL(*mock, disconnectFromNetwork()).Times(1).WillOnce(Return(true));
    for (auto i = 0; i < 5; i++) {
        tested.onTimerTick();
    }
}

TEST(ConnectionManager, onTimerTick_Offline)
{
    std::shared_ptr<MockCellular> mock = std::make_shared<MockCellular>();
    ConnectionManager tested(true, std::chrono::minutes{15}, mock);

    // Offline, timer was active
    EXPECT_CALL(*mock, isConnectedToNetwork()).Times(1).WillOnce(Return(false));
    EXPECT_CALL(*mock, isConnectionTimerActive()).Times(1).WillOnce(Return(true));
    EXPECT_CALL(*mock, stopConnectionTimer()).Times(1);

    tested.onPhoneModeChange(sys::phone_modes::PhoneMode::Offline);

    EXPECT_CALL(*mock, connectToNetwork()).Times(0);
    EXPECT_CALL(*mock, disconnectFromNetwork()).Times(0);
    for (auto i = 0; i < 20; i++) {
        tested.onTimerTick();
    }
}
\ No newline at end of file