~aleteoryx/muditaos

357ae2d51cdc93679b23bb9c99bfe5ed4a54e27f — Marcin Zieliński 3 years ago 40fa31a
[MOS-825] VoLTE steering according to IMSI

Steering the GUI and the modem according to
whether VoLTE is permitted for the active
SIM card's operator. This is done based on
the SIM card's IMSI string.

Additionally, made logging more consistent.
M image/assets/lang/Deutsch.json => image/assets/lang/Deutsch.json +1 -1
@@ 469,7 469,7 @@
  "app_settings_about": "Über Mudita Pure",
  "app_settings_title_languages": "Sprachauswahl",
  "app_settings_network_sim_cards": "SIM-Karten",
  "app_settings_network_volte_not_available": "not available",
  "app_settings_network_volte_not_available": "nicht verfügbar",
  "app_settings_network_active_card": "Aktiver Slot",
  "app_settings_network_unblock_card": "Entsperren Karte",
  "app_settings_network_not_connected": "keine Karte",

M image/assets/lang/Espanol.json => image/assets/lang/Espanol.json +1 -1
@@ 469,7 469,7 @@
  "app_settings_about": "Sobre Mudita Pure",
  "app_settings_title_languages": "Selección de idioma",
  "app_settings_network_sim_cards": "Tarjetas SIM",
  "app_settings_network_volte_not_available": "not available",
  "app_settings_network_volte_not_available": "no disponible",
  "app_settings_network_active_card": "Toma activa",
  "app_settings_network_unblock_card": "Desbloquear la tarjeta",
  "app_settings_network_not_connected": "sin tarjeta",

M image/assets/lang/Francais.json => image/assets/lang/Francais.json +1 -1
@@ 437,7 437,7 @@
  "app_settings_about": "À propos de Mudita Pure",
  "app_settings_title_languages": "Sélection de langue",
  "app_settings_network_sim_cards": "Cartes SIM",
  "app_settings_network_volte_not_available": "not available",
  "app_settings_network_volte_not_available": "non disponible",
  "app_settings_network_active_card": "Fente active",
  "app_settings_network_unblock_card": "Débloquer la carte",
  "app_settings_network_not_connected": "pas de carte",

M image/assets/lang/Svenska.json => image/assets/lang/Svenska.json +1 -1
@@ 377,7 377,7 @@
  "app_settings_about": "Om Mudita Pure",
  "app_settings_title_languages": "Språkval",
  "app_settings_network_sim_cards": "SIM-kort",
  "app_settings_network_volte_not_available": "not available",
  "app_settings_network_volte_not_available": "inte tillgängligt",
  "app_settings_network_active_card": "Aktivt slot",
  "app_settings_network_unblock_card": "Avblockera kort",
  "app_settings_network_not_connected": "inget kort",

M module-apps/application-settings/windows/network/NetworkWindow.cpp => module-apps/application-settings/windows/network/NetworkWindow.cpp +1 -1
@@ 75,7 75,7 @@ namespace gui
                    settingsApp->sendVolteChangeRequest(false);
                    break;
                default:
                    LOG_INFO("Skip request due unsettled VoLTE state");
                    LOG_INFO("[VoLTE] skip request due to unsettled VoLTE state");
                    break;
                }
                return true;

M module-services/service-cellular/ServiceCellular.cpp => module-services/service-cellular/ServiceCellular.cpp +7 -17
@@ 253,7 253,7 @@ sys::ReturnCodes ServiceCellular::InitHandler()

    const auto rawVolteSetting = settings->getValue(settings::Cellular::volteEnabled, settings::SettingsScope::Global);
    if (rawVolteSetting.empty()) {
        LOG_ERROR("VoLTE setting missing, setting to default disabled");
        LOG_ERROR("[VoLTE] setting missing in database - defaulting to disabled");
        settings->setValue(settings::Cellular::volteEnabled, "0", settings::SettingsScope::Global);
    }



@@ 607,13 607,15 @@ void ServiceCellular::registerMessageHandlers()
        auto message = static_cast<cellular::SwitchVolteOnOffRequest *>(request);
        auto channel = cmux->get(CellularMux::Channel::Commands);
        if (channel == nullptr) {
            LOG_ERROR("Failed to get channel, skipping VoLTE request!");
            LOG_ERROR("[VoLTE] failed to get channel, skipping request");
            return sys::MessageNone{};
        }
        settings->setValue(
            settings::Cellular::volteEnabled, message->enable ? "1" : "0", settings::SettingsScope::Global);
        try {
            if (not priv->volteHandler->switchVolte(*channel, message->enable)) {
            // here we always assume that VoLTE is permitted as changing the setting when not permitted is made
            // impossible by the GUI
            if (not priv->volteHandler->switchVolte(*channel, true, message->enable)) {
                auto notification =
                    std::make_shared<cellular::VolteStateNotification>(priv->volteHandler->getVolteState());
                bus.sendMulticast(std::move(notification), sys::BusChannel::ServiceCellularNotifications);


@@ 629,7 631,7 @@ void ServiceCellular::registerMessageHandlers()

    connect(typeid(cellular::msg::notification::SimReady), [&](sys::Message *) {
        if (priv->volteHandler->isFunctionalityResetNeeded()) {
            LOG_INFO("First run after VoLTE switch, functionality reset needed");
            LOG_INFO("[VoLTE] first run after switching - functionality reset needed");
            priv->modemResetHandler->performFunctionalityReset();
        }
        return sys::MessageNone{};


@@ 912,20 914,8 @@ bool ServiceCellular::handle_cellular_priv_init()

    priv->privInit(channel);

    auto enableVolte =
        settings->getValue(settings::Cellular::volteEnabled, settings::SettingsScope::Global) == "1" ? true : false;
    bool volteNeedReset = false;
    try {
        volteNeedReset    = !priv->volteHandler->switchVolte(*channel, enableVolte);
        auto notification = std::make_shared<cellular::VolteStateNotification>(priv->volteHandler->getVolteState());
        bus.sendMulticast(std::move(notification), sys::BusChannel::ServiceCellularNotifications);
    }
    catch (std::runtime_error const &exc) {
        LOG_ERROR("%s", exc.what());
    }

    auto tetheringNeedReset = !priv->tetheringHandler->configure();
    if (tetheringNeedReset || volteNeedReset) {
    if (tetheringNeedReset) {
        priv->modemResetHandler->performHardReset();
        return true;
    }

M module-services/service-cellular/service-cellular/VolteState.hpp => module-services/service-cellular/service-cellular/VolteState.hpp +1 -1
@@ 15,6 15,6 @@ namespace cellular
            SwitchingToOn,
            Undefined
        } enablement   = Enablement::Undefined;
        bool permitted = true;
        bool permitted = false;
    };
}

M module-services/service-cellular/src/ServiceCellularPriv.cpp => module-services/service-cellular/src/ServiceCellularPriv.cpp +24 -0
@@ 8,6 8,8 @@
#include <service-cellular-api>
#include <service-cellular/Constans.hpp>

#include <service-db/agents/settings/SystemSettings.hpp>

#include <volte/ImsiParserUS.hpp>
#include <volte/VolteAllowedUSList.hpp>
#include <volte/VolteCapabilityHandlerCellular.hpp>


@@ 62,6 64,28 @@ namespace cellular::internal
        using namespace cellular::msg;
        simCard->onSimReady = [this]() {
            state->set(State::ST::Ready);

            auto channel     = owner->cmux->get(CellularMux::Channel::Commands);
            auto permitVolte = volteCapability->isVolteAllowed(*channel);
            auto enableVolte =
                owner->settings->getValue(settings::Cellular::volteEnabled, settings::SettingsScope::Global) == "1"
                    ? true
                    : false;
            bool volteNeedReset = false;
            try {
                volteNeedReset    = !volteHandler->switchVolte(*channel, permitVolte, enableVolte);
                auto notification = std::make_shared<cellular::VolteStateNotification>(volteHandler->getVolteState());
                owner->bus.sendMulticast(std::move(notification), sys::BusChannel::ServiceCellularNotifications);
            }
            catch (std::runtime_error const &exc) {
                LOG_ERROR("%s", exc.what());
            }

            if (volteNeedReset) {
                modemResetHandler->performHardReset();
                return;
            }

            owner->bus.sendMulticast<notification::SimReady>();
        };
        simCard->onNeedPin = [this](unsigned int attempts) {

M module-services/service-cellular/src/VolteHandler.hpp => module-services/service-cellular/src/VolteHandler.hpp +11 -4
@@ 36,9 36,15 @@ namespace cellular::service
    template <typename CmuxChannel, typename ModemResponseParser>
    struct VolteHandler : private NonCopyable
    {
        bool switchVolte(CmuxChannel &channel, bool enable)
        bool switchVolte(CmuxChannel &channel, bool permit, bool enable)
        {
            ModemResponseParser const parser;
            if (!permit) {
                if (enable) {
                    LOG_INFO("[VoLTE] requested to enable, but not permitted for this operator - disabling");
                }

                enable = false;
            }

            if (enable) {
                // according to Quectel, this setting doesn't have to be reset when disabling


@@ 75,7 81,7 @@ namespace cellular::service

            bool alreadyConfigured;
            try {
                alreadyConfigured = parser(QcfgImsResult{imsCheckAnswer}, enable);
                alreadyConfigured = ModemResponseParser()(QcfgImsResult{imsCheckAnswer}, enable);
            }
            catch (std::runtime_error const &exc) {
                throw;


@@ 103,6 109,7 @@ namespace cellular::service
                    enable ? cellular::VolteState::Enablement::On : cellular::VolteState::Enablement::Off;
            }

            volteState.permitted = permit;
            return alreadyConfigured;
        }



@@ 140,7 147,7 @@ namespace cellular::service
                    preference_ = 0x03;
                    return;
                default:
                    throw std::logic_error("unimplemented network service domain: " +
                    throw std::logic_error("[VoLTE] unimplemented network service domain: " +
                                           std::string(magic_enum::enum_name(type)));
                }
            }

M module-services/service-cellular/src/volte/ImsiParserUS.cpp => module-services/service-cellular/src/volte/ImsiParserUS.cpp +2 -2
@@ 23,12 23,12 @@ namespace cellular::service
            mnc = imsi.substr(mccSize, mncSize);
        }
        catch (const std::out_of_range &e) {
            LOG_ERROR("IMSI parsing error: %s", e.what());
            LOG_ERROR("[VoLTE] IMSI parsing error: %s", e.what());
            return std::nullopt;
        }

        if (std::find(std::begin(usMcc), std::end(usMcc), mcc) == std::end(usMcc)) {
            LOG_ERROR("Not US MCC.");
            LOG_ERROR("[VoLTE] MCC not from USA");
            return std::nullopt;
        }


M module-services/service-cellular/src/volte/VolteAllowedUSList.cpp => module-services/service-cellular/src/volte/VolteAllowedUSList.cpp +2 -2
@@ 25,11 25,11 @@ namespace cellular::service

    auto VolteAllowedUSList::isVolteAllowed(const OperatorInfo &operatorInfo) -> bool
    {
        LOG_INFO("Trying to find MCC: %s, MNC: %s", operatorInfo.MCC.c_str(), operatorInfo.MNC.c_str());
        LOG_INFO("[VoLTE] trying to find MCC: %s, MNC: %s", operatorInfo.MCC.c_str(), operatorInfo.MNC.c_str());

        if (std::find(std::begin(allowedOperators), std::end(allowedOperators), operatorInfo) ==
            std::end(allowedOperators)) {
            LOG_ERROR("Unable to find. VoLTE not allowed.");
            LOG_ERROR("[VoLTE] unable to find MCC+MNC on list - VoLTE not allowed");
            return false;
        }
        return true;

M module-services/service-cellular/src/volte/VolteCapabilityHandler.cpp => module-services/service-cellular/src/volte/VolteCapabilityHandler.cpp +7 -16
@@ 2,8 2,10 @@
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "VolteCapabilityHandler.hpp"
#include <modem/BaseChannel.hpp>
#include <modem/mux/CellularMux.h>
#include <log/log.hpp>

namespace cellular::service
{
    VolteCapabilityHandler::VolteCapabilityHandler(std::unique_ptr<ImsiParserInteface> imsiParser,


@@ 13,31 15,20 @@ namespace cellular::service
          cellularInterface(std::move(cellularInterface))
    {}

    void VolteCapabilityHandler::setChannel(at::BaseChannel *channel)
    {
        if (cellularInterface.get() != nullptr) {
            cellularInterface->setChannel(channel);
        }
    }

    auto VolteCapabilityHandler::isVolteAllowed() -> bool
    auto VolteCapabilityHandler::isVolteAllowed(at::BaseChannel &channel) -> bool
    {
        const auto imsi = cellularInterface->getImsi();
        const auto imsi = cellularInterface->getImsi(channel);
        if (not imsi.has_value()) {
            LOG_ERROR("Failed to read IMSI, disable VoLTE.");
            LOG_ERROR("[VoLTE] failed to read IMSI - VoLTE not permitted");
            return false;
        }

        const auto operatorInfo = imsiParser->parse(imsi.value());
        if (not operatorInfo.has_value()) {
            LOG_ERROR("Failed to parse IMSI, disable VoLTE.");
            LOG_ERROR("[VoLTE] failed to parse IMSI - VoLTE not permitted");
            return false;
        }
        return allowedList->isVolteAllowed(operatorInfo.value());
    }

    auto VolteCapabilityHandler::isCellularInterfaceReady() -> bool
    {
        return cellularInterface.get() != nullptr;
        return allowedList->isVolteAllowed(operatorInfo.value());
    }
} // namespace cellular::service

M module-services/service-cellular/src/volte/VolteCapabilityHandler.hpp => module-services/service-cellular/src/volte/VolteCapabilityHandler.hpp +1 -14
@@ 8,13 8,6 @@
#include "VolteAllowedListInterface.hpp"

#include <memory>
#include <vector>

namespace at
{
    class Result;
    class BaseChannel;
} // namespace at

namespace cellular::service
{


@@ 24,20 17,14 @@ namespace cellular::service
        VolteCapabilityHandler(std::unique_ptr<ImsiParserInteface> imsiParser,
                               std::unique_ptr<VolteAllowedListInterface> allowedList,
                               std::unique_ptr<VolteCapabilityCellularInterface> cellularInterface);
        /** Set AT command channel
         * \param channel channel (or nullptr to block communication):
         */
        void setChannel(at::BaseChannel *channel);
        /** Check if it is a possibility to enable VoLTE on current operator
         * @return true when VoLTE is allowed, false when not
         */
        auto isVolteAllowed() -> bool;
        auto isVolteAllowed(at::BaseChannel &channel) -> bool;

      private:
        std::unique_ptr<ImsiParserInteface> imsiParser;
        std::unique_ptr<VolteAllowedListInterface> allowedList;
        std::unique_ptr<VolteCapabilityCellularInterface> cellularInterface;

        auto isCellularInterfaceReady() -> bool;
    };
} // namespace cellular::service

M module-services/service-cellular/src/volte/VolteCapabilityHandlerCellular.cpp => module-services/service-cellular/src/volte/VolteCapabilityHandlerCellular.cpp +4 -13
@@ 3,24 3,15 @@

#include "VolteCapabilityHandlerCellular.hpp"
#include <modem/mux/CellularMux.h>
#include <module-cellular/modem/BaseChannel.hpp>

namespace cellular::service
{
    void VolteCapabilityCellular::setChannel(at::BaseChannel *channel)
    auto VolteCapabilityCellular::getImsi(at::BaseChannel &channel) -> std::optional<std::string>
    {
        this->channel = channel;
    }

    auto VolteCapabilityCellular::getImsi() -> std::optional<std::string>
    {
        if (channel == nullptr) {
            LOG_ERROR("No channel provided. Request ignored");
            return std::nullopt;
        }

        auto result = channel->cmd(at::AT::CIMI);
        auto result = channel.cmd(at::AT::CIMI);
        if (not result) {
            LOG_ERROR("Failed to read IMSI, disable VoLTE.");
            LOG_ERROR("[VoLTE] failed to read IMSI - will disable VoLTE");
            return std::nullopt;
        }


M module-services/service-cellular/src/volte/VolteCapabilityHandlerCellular.hpp => module-services/service-cellular/src/volte/VolteCapabilityHandlerCellular.hpp +2 -9
@@ 10,20 10,13 @@

namespace at
{
    class Result;
    class BaseChannel;
} // namespace at

namespace cellular::service
{

    class VolteCapabilityCellular : public VolteCapabilityCellularInterface
    struct VolteCapabilityCellular : VolteCapabilityCellularInterface
    {
      public:
        void setChannel(at::BaseChannel *channel) final;
        auto getImsi() -> std::optional<std::string> final;

      private:
        at::BaseChannel *channel = nullptr;
        auto getImsi(at::BaseChannel &) -> std::optional<std::string> final;
    };
} // namespace cellular::service

M module-services/service-cellular/src/volte/VolteCapabiltyHandlerCellularInterface.hpp => module-services/service-cellular/src/volte/VolteCapabiltyHandlerCellularInterface.hpp +2 -5
@@ 8,18 8,15 @@

namespace at
{
    class Result;
    class BaseChannel;
} // namespace at
}

namespace cellular::service
{

    class VolteCapabilityCellularInterface
    {
      public:
        virtual ~VolteCapabilityCellularInterface()          = default;
        virtual void setChannel(at::BaseChannel *channel)    = 0;
        virtual auto getImsi() -> std::optional<std::string> = 0;
        virtual auto getImsi(at::BaseChannel &) -> std::optional<std::string> = 0;
    };
} // namespace cellular::service

M module-services/service-cellular/tests/unittest_volteCapabilityHandler.cpp => module-services/service-cellular/tests/unittest_volteCapabilityHandler.cpp +39 -12
@@ 7,9 7,42 @@
#include <module-services/service-cellular/src/volte/ImsiParserUS.hpp>
#include <module-services/service-cellular/src/volte/VolteAllowedUSList.hpp>
#include <module-services/service-cellular/src/volte/VolteCapabiltyHandlerCellularInterface.hpp>
#include <module-cellular/modem/BaseChannel.hpp>

TEST_CASE("VoLTE Capability handler")
{
    struct BaseChannelStub final : at::BaseChannel
    {
        virtual ~BaseChannelStub() = default;

        virtual auto cmd(const std::string &, std::chrono::milliseconds = at::default_timeout, size_t = 0) -> at::Result
        {
            return at::Result{};
        }
        virtual auto cmd(const at::AT &) -> at::Result
        {
            return at::Result{};
        }
        virtual auto cmd(const at::Cmd &) -> at::Result
        {
            return at::Result{};
        }
        virtual void cmdLog(std::string, const at::Result &, std::chrono::milliseconds)
        {}
        virtual void cmdInit()
        {}
        virtual void cmdSend(std::string)
        {}
        virtual size_t cmdReceive(std::uint8_t *, std::chrono::milliseconds)
        {
            return 0;
        }
        virtual void cmdPost()
        {}
    };

    static BaseChannelStub baseChannelStub;

    SECTION("ImsiParserUS success - US IMSI")
    {
        using namespace cellular::service;


@@ 86,9 119,7 @@ TEST_CASE("VoLTE Capability handler")

    class MockTMobileUS : public cellular::service::VolteCapabilityCellularInterface
    {
        void setChannel(at::BaseChannel *channel)
        {}
        auto getImsi() -> std::optional<std::string>
        auto getImsi(at::BaseChannel &) -> std::optional<std::string> override
        {
            return "310120123456789";
        }


@@ 100,15 131,13 @@ TEST_CASE("VoLTE Capability handler")
        VolteCapabilityHandler handler{std::make_unique<ImsiParserUS>(),
                                       std::make_unique<VolteAllowedUSList>(),
                                       std::make_unique<MockTMobileUS>()};
        auto result = handler.isVolteAllowed();
        auto result = handler.isVolteAllowed(baseChannelStub);
        REQUIRE(result == true);
    }

    class MockNonTMobileUS : public cellular::service::VolteCapabilityCellularInterface
    {
        void setChannel(at::BaseChannel *channel)
        {}
        auto getImsi() -> std::optional<std::string>
        auto getImsi(at::BaseChannel &) -> std::optional<std::string>
        {
            return "310999123456789";
        }


@@ 120,15 149,13 @@ TEST_CASE("VoLTE Capability handler")
        VolteCapabilityHandler handler{std::make_unique<ImsiParserUS>(),
                                       std::make_unique<VolteAllowedUSList>(),
                                       std::make_unique<MockNonTMobileUS>()};
        auto result = handler.isVolteAllowed();
        auto result = handler.isVolteAllowed(baseChannelStub);
        REQUIRE(result == false);
    }

    class MockFailedToGetImsi : public cellular::service::VolteCapabilityCellularInterface
    {
        void setChannel(at::BaseChannel *channel)
        {}
        auto getImsi() -> std::optional<std::string>
        auto getImsi(at::BaseChannel &) -> std::optional<std::string>
        {
            return std::nullopt;
        }


@@ 140,7 167,7 @@ TEST_CASE("VoLTE Capability handler")
        VolteCapabilityHandler handler{std::make_unique<ImsiParserUS>(),
                                       std::make_unique<VolteAllowedUSList>(),
                                       std::make_unique<MockFailedToGetImsi>()};
        auto result = handler.isVolteAllowed();
        auto result = handler.isVolteAllowed(baseChannelStub);
        REQUIRE(result == false);
    }
}

M module-services/service-cellular/tests/unittest_volteHandler.cpp => module-services/service-cellular/tests/unittest_volteHandler.cpp +12 -10
@@ 113,7 113,7 @@ TEST_CASE("VoLTE handler test")

        VolteHandler<CommandSequentialChannel, DummyModemResponseParser> handler;

        REQUIRE_THROWS_WITH(handler.switchVolte(channel, true), Catch::Contains("voice domain"));
        REQUIRE_THROWS_WITH(handler.switchVolte(channel, true, true), Catch::Contains("voice domain"));
    }

    SECTION("ThrowsAboutMbnAutoselectFailure_When_TryingToEnableVolte_And_AutoselectCommandRespondsNOK")


@@ 127,7 127,7 @@ TEST_CASE("VoLTE handler test")

        VolteHandler<CommandSequentialChannel, DummyModemResponseParser> handler;

        REQUIRE_THROWS_WITH(handler.switchVolte(channel, true), Catch::Contains("MBN"));
        REQUIRE_THROWS_WITH(handler.switchVolte(channel, true, true), Catch::Contains("MBN"));
    }

    SECTION("ThrowsAboutImsCheckingFailure_When_TryingToEnableVolte_And_ImsConfigurationQueryRespondsNOK")


@@ 142,7 142,7 @@ TEST_CASE("VoLTE handler test")

        VolteHandler<CommandSequentialChannel, DummyModemResponseParser> handler;

        REQUIRE_THROWS_WITH(handler.switchVolte(channel, true), Catch::Contains("IMS"));
        REQUIRE_THROWS_WITH(handler.switchVolte(channel, true, true), Catch::Contains("IMS"));
    }

    SECTION("ThrowsAboutImsCheckingFailure_When_TryingToEnableVolte_And_CantParseImsConfigurationQuery")


@@ 157,7 157,7 @@ TEST_CASE("VoLTE handler test")

        VolteHandler<CommandSequentialChannel, QcfgImsThrowingParser> handler;

        REQUIRE_THROWS_WITH(handler.switchVolte(channel, true), Catch::Contains("IMS"));
        REQUIRE_THROWS_WITH(handler.switchVolte(channel, true, true), Catch::Contains("IMS"));
    }

    SECTION("ReturnsOk_When_TryingToEnableVolte_And_AlreadyEnabled")


@@ 172,7 172,7 @@ TEST_CASE("VoLTE handler test")

        VolteHandler<CommandSequentialChannel, QcfgImsDummyParser<true>> handler;

        REQUIRE(handler.switchVolte(channel, true));
        REQUIRE(handler.switchVolte(channel, true, true));
    }

    SECTION("ReturnsOk_When_TryingToDisableVolte_And_AlreadyDisabled")


@@ 183,7 183,7 @@ TEST_CASE("VoLTE handler test")

        VolteHandler<CommandSequentialChannel, QcfgImsDummyParser<true>> handler;

        REQUIRE(handler.switchVolte(channel, false));
        REQUIRE(handler.switchVolte(channel, true, false));
    }

    SECTION("ReturnsFalse_When_TryingToDisableVolte_And_WasEnabledBefore")


@@ 197,7 197,7 @@ TEST_CASE("VoLTE handler test")

        VolteHandler<CommandSequentialChannel, QcfgImsDummyParser<false>> handler;

        REQUIRE_FALSE(handler.switchVolte(channel, false));
        REQUIRE_FALSE(handler.switchVolte(channel, true, false));
    }

    SECTION("ReturnsFalse_When_TryingToEnableVolte_And_WasEnabledBefore")


@@ 211,7 211,7 @@ TEST_CASE("VoLTE handler test")

        VolteHandler<CommandSequentialChannel, QcfgImsDummyParser<false>> handler;

        REQUIRE_FALSE(handler.switchVolte(channel, false));
        REQUIRE_FALSE(handler.switchVolte(channel, true, false));
    }

    SECTION("ThrowsAboutImsFailure_When_DisablingVolte_And_ImsSteeringCommandFailed")


@@ 225,7 225,8 @@ TEST_CASE("VoLTE handler test")

        VolteHandler<CommandSequentialChannel, QcfgImsDummyParser<false>> handler;

        REQUIRE_THROWS_WITH(handler.switchVolte(channel, false), Catch::Contains("fail") && Catch::Contains("IMS"));
        REQUIRE_THROWS_WITH(handler.switchVolte(channel, true, false),
                            Catch::Contains("fail") && Catch::Contains("IMS"));
    }

    SECTION("ThrowsAboutImsFailure_When_EnablingVolte_And_ImsSteeringCommandFailed")


@@ 241,6 242,7 @@ TEST_CASE("VoLTE handler test")

        VolteHandler<CommandSequentialChannel, QcfgImsDummyParser<false>> handler;

        REQUIRE_THROWS_WITH(handler.switchVolte(channel, true), Catch::Contains("fail") && Catch::Contains("IMS"));
        REQUIRE_THROWS_WITH(handler.switchVolte(channel, true, true),
                            Catch::Contains("fail") && Catch::Contains("IMS"));
    }
}

M pure_changelog.md => pure_changelog.md +1 -0
@@ 12,6 12,7 @@
* Fixed MTP issues
* Rebuilt SIM cards window
* Added option to provide custom GDB to crash analysis script
* Made VoLTE available for proven cellular network(s) only

### Fixed
* Fixed incorrect total CPU usage in logs