~aleteoryx/muditaos

75b531f7e2265a8c5a83a2c10cdcaaea0cc1abcf — Lukasz Mastalerz 2 years ago 3ddb357
[MOS-871] Loudspeaker icon is not updated when audio routing has changed

Fix for the loudspeaker becomes active again when a headset is unplugged
if it was on while the headset was connected to the phone.
Fix typo in Bluetooth in EventType enum.
M module-apps/application-call/ApplicationCall.cpp => module-apps/application-call/ApplicationCall.cpp +11 -8
@@ 6,6 6,7 @@
#include "CallWindow.hpp"
#include "EmergencyCallWindow.hpp"
#include "EnterNumberWindow.hpp"
#include "presenter/CallPresenter.hpp"

#include <apps-common/messages/DialogMetadataMessage.hpp>
#include <apps-common/windows/Dialog.hpp>


@@ 19,6 20,7 @@
#include <service-cellular/CellularServiceAPI.hpp>
#include <time/time_conversion.hpp>
#include <WindowsPopupFilter.hpp>
#include <service-audio/AudioServiceAPI.hpp>

#include <cassert>
#include <memory>


@@ 114,7 116,8 @@ namespace app
            return actionHandled();
        });

        callModel = std::make_shared<app::call::CallModel>(this);
        callModel     = std::make_shared<app::call::CallModel>(this);
        callPresenter = std::make_unique<call::CallWindowContract::Presenter>(this->callModel);
    }

    bool ApplicationCall::conditionalReturnToPreviousView()


@@ 181,9 184,9 @@ namespace app
            return sys::MessageNone{};
        });

        connect(typeid(AudioEventRequest), [&](sys::Message *request) {
            auto message = static_cast<AudioEventRequest *>(request);
            return handleAudioMessageEvent(message);
        connect(typeid(AudioRoutingNotification), [this](sys::Message *request) {
            auto message = static_cast<AudioRoutingNotification *>(request);
            return handleRoutingNotification(message);
        });

        createUserInterface();


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


@@ 281,10 283,11 @@ namespace app
        }
    }

    sys::MessagePointer ApplicationCall::handleAudioMessageEvent(AudioEventRequest *message)
    sys::MessagePointer ApplicationCall::handleRoutingNotification(AudioRoutingNotification *message)
    {
        if (auto window = getCurrentWindow(); window->getName() == app::window::name_call) {
            static_cast<gui::CallWindow *>(window)->handleAudioEvent(*message->getEvent().get());
            const auto currentRouting = message->profileType;
            callPresenter->processCurrentRouting(currentRouting);
        }
        return sys::MessageNone{};
    }

M module-apps/application-call/include/application-call/ApplicationCall.hpp => module-apps/application-call/include/application-call/ApplicationCall.hpp +4 -3
@@ 4,7 4,7 @@
#pragma once

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

#include <application-call/presenter/CallPresenter.hpp>
#include <Timers/TimerHandle.hpp>
#include <service-cellular/CellularMessage.hpp>
#include <service-evtmgr/Constants.hpp>


@@ 67,7 67,7 @@ namespace app
        void handleCallEvent(const std::string &number, ExternalRequest isExternalRequest) override;
        void handleAddContactEvent(const std::string &number) override;

        sys::MessagePointer handleAudioMessageEvent(AudioEventRequest *message);
        sys::MessagePointer handleRoutingNotification(AudioRoutingNotification *message);

        auto showNotification(std::function<bool()> action, const std::string &icon, const std::string &text) -> bool;
        enum class NotificationType


@@ 78,7 78,8 @@ namespace app
        auto showNotificationAndRestartCallFlow(NotificationType type, const std::string &text) -> bool;

      private:
        std::shared_ptr<app::call::AbstractCallModel> callModel;
        std::shared_ptr<call::AbstractCallModel> callModel;
        std::unique_ptr<call::CallWindowContract::Presenter> callPresenter;

      protected:
        ExternalRequest externalRequest = ExternalRequest::False;

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

#include "CallModel.hpp"

M module-apps/application-call/presenter/CallPresenter.cpp => module-apps/application-call/presenter/CallPresenter.cpp +34 -8
@@ 8,6 8,9 @@ namespace app::call
{

    CallWindowContract::Presenter::Presenter(std::shared_ptr<app::call::AbstractCallModel> callModel) : model(callModel)
    {}

    void CallWindowContract::Presenter::Presenter::attachCallbacks()
    {
        model->attachCallStateChangeCallback([this]() { this->handleCallStateChange(); });
        model->attachDurationChangeCallback([this]() { this->handleCallDurationChange(); });


@@ 41,39 44,47 @@ namespace app::call
        auto callState = model->getState();

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

    UTF8 CallWindowContract::Presenter::getCallerId()


@@ 179,4 190,19 @@ namespace app::call
    {
        model->setState(CallState::None);
    }

    void CallWindowContract::Presenter::processCurrentRouting(const audio::Profile::Type &routingType)
    {
        if (routingType == audio::Profile::Type::RoutingLoudspeaker) {
            getView()->setSpeakerIconState(gui::SpeakerIconState::SPEAKERON);
        }
        else {
            getView()->setSpeakerIconState(gui::SpeakerIconState::SPEAKER);
        }
    }
    void CallWindowContract::Presenter::clearModel()
    {
        model->clear();
    }

} // namespace app::call

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

#pragma once


@@ 24,6 24,8 @@ namespace app::call
            virtual void setActiveCallLayout()                                   = 0;
            virtual void setCallEndedLayout(bool delayedClose = true)            = 0;
            virtual void updateNumber(const UTF8 &text)                          = 0;
            virtual gui::SpeakerIconState getSpeakerIconState()                  = 0;
            virtual void setSpeakerIconState(const gui::SpeakerIconState &icon)  = 0;

            virtual ~View() noexcept = default;
        };


@@ 53,6 55,10 @@ namespace app::call

            void handleDelayedViewClose();

            void processCurrentRouting(const audio::Profile::Type &routingType);
            void attachCallbacks();
            void clearModel();

          private:
            std::shared_ptr<app::call::AbstractCallModel> model;
            UTF8 getCallerId();

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

#pragma once


@@ 31,7 31,7 @@ namespace app::call
        {
            layoutShowed = LayoutShowed::Active;
        };
        ;

        void setCallEndedLayout(bool delayedClose = true) override
        {
            layoutShowed = LayoutShowed::Ended;


@@ 41,6 41,12 @@ namespace app::call
        {
            number = text;
        };
        gui::SpeakerIconState getSpeakerIconState() override
        {
            return gui::SpeakerIconState::SPEAKER;
        }
        void setSpeakerIconState(const gui::SpeakerIconState &icon) override
        {}

        bool windowRefreshed = false;



@@ 85,7 91,6 @@ namespace app::call
        {
            return callerId;
        };

        void hangUpCall() override
        {
            hangupCallCalled = true;

M module-apps/application-call/windows/CallWindow.cpp => module-apps/application-call/windows/CallWindow.cpp +24 -42
@@ 26,13 26,17 @@ namespace gui
    using namespace callAppStyle::callWindow;
    using namespace app::call;

    CallWindow::CallWindow(app::ApplicationCommon *app,
                           std::unique_ptr<app::call::CallWindowContract::Presenter> &&windowPresenter)
        : gui::AppWindow{app, app::window::name_call}, presenter{std::move(windowPresenter)}
    CallWindow::CallWindow(app::ApplicationCommon *app, app::call::CallWindowContract::Presenter &presenter)
        : gui::AppWindow{app, app::window::name_call}, presenter{presenter}
    {
        presenter->attach(this);
        presenter.attach(this);
        presenter.attachCallbacks();
        buildInterface();
    }
    CallWindow::~CallWindow() noexcept
    {
        presenter.clearModel();
    }

    void CallWindow::rebuild()
    {


@@ 78,7 82,7 @@ namespace gui
            microphoneIcon->setNext();
            LOG_INFO("Microphone %s", static_cast<bool>(microphoneIcon->get()) ? "activated" : "deactivated");

            microphoneIcon->get() == MicrophoneIconState::MUTED ? presenter->muteCall() : presenter->unmuteCall();
            microphoneIcon->get() == MicrophoneIconState::MUTED ? presenter.muteCall() : presenter.unmuteCall();

            return true;
        };


@@ 90,10 94,10 @@ namespace gui

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


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


@@ 150,11 154,11 @@ namespace gui

    void CallWindow::onBeforeShow(ShowMode mode, SwitchData *data)
    {
        presenter->buildLayout();
        presenter.buildLayout();

        if (auto switchData = dynamic_cast<SMSTemplateSent *>(data); switchData != nullptr) {
            presenter->hangUpCall();
            presenter->sendSms(switchData->getText());
            presenter.hangUpCall();
            presenter.sendSms(switchData->getText());
            return;
        }
    }


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



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


@@ 297,36 301,14 @@ namespace gui
        numberLabel->setText(text);
    }

    void CallWindow::handleAudioEvent(const audio::Event &event)
    gui::SpeakerIconState CallWindow::getSpeakerIconState()
    {

        switch (event.getType()) {
        case audio::EventType::BlutoothHFPDeviceState:
        case audio::EventType::BlutoothHSPDeviceState:
        case audio::EventType::JackState: {
            if (event.getDeviceState() == audio::Event::DeviceState::Connected) {
                devices_connected.insert(event.getType());
                changeSpeakerIconIfNeeded();
            }
            else {
                devices_connected.erase(event.getType());
                if (not devices_connected.empty()) {
                    changeSpeakerIconIfNeeded();
                }
            }
            break;
        }
        default: {
            break;
        }
        }
        return speakerIcon->get();
    }

    void CallWindow::changeSpeakerIconIfNeeded()
    void CallWindow::setSpeakerIconState(const gui::SpeakerIconState &icon)
    {
        if (speakerIcon->get() == SpeakerIconState::SPEAKERON) {
            speakerIcon->setNext();
        }
        speakerIcon->set(icon);
    }

} /* namespace gui */

M module-apps/application-call/windows/CallWindow.hpp => module-apps/application-call/windows/CallWindow.hpp +5 -8
@@ 26,8 26,7 @@ namespace gui
        {
            return callDelayedStopTime;
        }

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

      protected:
        // used to display both number and name of contact


@@ 44,11 43,9 @@ namespace gui

        utils::PhoneNumber::View phoneNumber;

        std::set<audio::EventType> devices_connected{};

      public:
        CallWindow(app::ApplicationCommon *app,
                   std::unique_ptr<app::call::CallWindowContract::Presenter> &&windowPresenter);
        CallWindow(app::ApplicationCommon *app, app::call::CallWindowContract::Presenter &presenter);
        ~CallWindow() noexcept override;

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


@@ 69,8 66,8 @@ namespace gui
        void setActiveCallLayout() override;
        void setCallEndedLayout(bool delayedClose = true) override;
        void updateNumber(const UTF8 &text) override;
        void handleAudioEvent(const audio::Event &event);
        void changeSpeakerIconIfNeeded();
        gui::SpeakerIconState getSpeakerIconState() override;
        void setSpeakerIconState(const gui::SpeakerIconState &icon) override;
    };

} /* namespace gui */

M module-audio/Audio/Audio.hpp => module-audio/Audio/Audio.hpp +2 -2
@@ 1,4 1,4 @@
// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2023, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once


@@ 89,7 89,7 @@ namespace audio
            if (audioSinkState.isConnected(EventType::JackState)) {
                return Profile::Type::PlaybackHeadphones;
            }
            if (audioSinkState.isConnected(EventType::BlutoothA2DPDeviceState)) {
            if (audioSinkState.isConnected(EventType::BluetoothA2DPDeviceState)) {
                return Profile::Type::PlaybackBluetoothA2DP;
            }
            return Profile::Type::PlaybackLoudspeaker;

M module-audio/Audio/AudioCommon.hpp => module-audio/Audio/AudioCommon.hpp +7 -7
@@ 1,4 1,4 @@
// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2023, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once


@@ 103,11 103,11 @@ namespace audio
    enum class EventType
    {
        // HW state change notifications
        JackState,               //!< Jack input plugged / unplugged event
        MicrophoneState,         //!< Microphone presence in headset (3-pole w/o microphone or 4-pole with microphone)
        BlutoothHSPDeviceState,  //!< BT device connected / disconnected event (Headset Profile)
        BlutoothHFPDeviceState,  //!< BT device connected / disconnected event (Headset Profile)
        BlutoothA2DPDeviceState, //!< BT device connected / disconnected event (Advanced Audio Distribution Profile)
        JackState,                //!< Jack input plugged / unplugged event
        MicrophoneState,          //!< Microphone presence in headset (3-pole w/o microphone or 4-pole with microphone)
        BluetoothHSPDeviceState,  //!< BT device connected / disconnected event (Headset Profile)
        BluetoothHFPDeviceState,  //!< BT device connected / disconnected event (Headset Profile)
        BluetoothA2DPDeviceState, //!< BT device connected / disconnected event (Advanced Audio Distribution Profile)

        // call control
        CallMute,


@@ 116,7 116,7 @@ namespace audio
        CallLoudspeakerOff,
    };

    constexpr auto hwStateUpdateMaxEvent = magic_enum::enum_index(EventType::BlutoothA2DPDeviceState);
    constexpr auto hwStateUpdateMaxEvent = magic_enum::enum_index(EventType::BluetoothA2DPDeviceState);

    class Event
    {

M module-audio/Audio/Operation/PlaybackOperation.cpp => module-audio/Audio/Operation/PlaybackOperation.cpp +2 -2
@@ 1,4 1,4 @@
// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2023, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "PlaybackOperation.hpp"


@@ 164,7 164,7 @@ namespace audio
            SetProfileAvailability({Profile::Type::PlaybackHeadphones}, isAvailable);
            Operation::SwitchToPriorityProfile();
            break;
        case EventType::BlutoothA2DPDeviceState:
        case EventType::BluetoothA2DPDeviceState:
            SetProfileAvailability({Profile::Type::PlaybackBluetoothA2DP}, isAvailable);
            Operation::SwitchToPriorityProfile();
            break;

M module-audio/Audio/Operation/RecorderOperation.cpp => module-audio/Audio/Operation/RecorderOperation.cpp +2 -2
@@ 1,4 1,4 @@
// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2023, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "RecorderOperation.hpp"


@@ 132,7 132,7 @@ namespace audio
            SetProfileAvailability({Profile::Type::RecordingHeadphones}, isAvailable);
            SwitchToPriorityProfile();
            break;
        case EventType::BlutoothHSPDeviceState:
        case EventType::BluetoothHSPDeviceState:
            SetProfileAvailability({Profile::Type::RecordingBluetoothHSP}, isAvailable);
            SwitchToPriorityProfile();
            break;

M module-audio/Audio/Operation/RouterOperation.cpp => module-audio/Audio/Operation/RouterOperation.cpp +2 -2
@@ 143,11 143,11 @@ namespace audio
            setInputPathForHeadset(headsetHasMicrophone);
            SwitchToPriorityProfile();
        } break;
        case EventType::BlutoothHSPDeviceState:
        case EventType::BluetoothHSPDeviceState:
            SetProfileAvailability({Profile::Type::RoutingBluetoothHSP}, isAvailable);
            SwitchToPriorityProfile();
            break;
        case EventType::BlutoothHFPDeviceState:
        case EventType::BluetoothHFPDeviceState:
            SetProfileAvailability({Profile::Type::RoutingBluetoothHFP}, isAvailable);
            SwitchToPriorityProfile();
            break;

M module-audio/Audio/test/unittest_audio.cpp => module-audio/Audio/test/unittest_audio.cpp +6 -6
@@ 1,4 1,4 @@
// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2023, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include <catch2/catch.hpp>


@@ 524,14 524,14 @@ SCENARIO("Profile playback priorities tests")
        WHEN("All audio devices are connected")
        {
            audio.setConnected(EventType::JackState);
            audio.setConnected(EventType::BlutoothA2DPDeviceState);
            audio.setConnected(EventType::BluetoothA2DPDeviceState);
            THEN("Headphones are prioritized")
            {
                REQUIRE(audio.GetPriorityPlaybackProfile() == Profile::Type::PlaybackHeadphones);
            }
            AND_WHEN("Bluetooth is disconnected")
            {
                audio.setDisconnected(EventType::BlutoothA2DPDeviceState);
                audio.setDisconnected(EventType::BluetoothA2DPDeviceState);
                THEN("Headphones are prioritized")
                {
                    REQUIRE(audio.GetPriorityPlaybackProfile() == Profile::Type::PlaybackHeadphones);


@@ 548,7 548,7 @@ SCENARIO("Profile playback priorities tests")
        }
        WHEN("Only bluetooth device is connected")
        {
            audio.setConnected(EventType::BlutoothA2DPDeviceState);
            audio.setConnected(EventType::BluetoothA2DPDeviceState);
            THEN("Bluetooth is prioritized")
            {
                REQUIRE(audio.GetPriorityPlaybackProfile() == Profile::Type::PlaybackBluetoothA2DP);


@@ 598,7 598,7 @@ SCENARIO("Router playback priorities tests")
        WHEN("All audio devices are connected, loudspeaker is off")
        {
            routerOperation.SendEvent(
                std::make_shared<Event>(audio::EventType::BlutoothHSPDeviceState, Event::DeviceState::Connected));
                std::make_shared<Event>(audio::EventType::BluetoothHSPDeviceState, Event::DeviceState::Connected));
            routerOperation.SendEvent(
                std::make_shared<Event>(audio::EventType::JackState, Event::DeviceState::Connected));
            routerOperation.SendEvent(std::make_shared<Event>(audio::EventType::CallLoudspeakerOff));


@@ 641,7 641,7 @@ SCENARIO("Router playback priorities tests")
        WHEN("Only bluetooth HSP is connected, loudspeaker is off")
        {
            routerOperation.SendEvent(
                std::make_shared<Event>(audio::EventType::BlutoothHSPDeviceState, Event::DeviceState::Connected));
                std::make_shared<Event>(audio::EventType::BluetoothHSPDeviceState, Event::DeviceState::Connected));
            routerOperation.SendEvent(std::make_shared<Event>(audio::EventType::CallLoudspeakerOff));

            THEN("Bluetooth HSP is prioritized")

M module-bluetooth/Bluetooth/interface/profiles/A2DP/A2DP.cpp => module-bluetooth/Bluetooth/interface/profiles/A2DP/A2DP.cpp +2 -2
@@ 452,7 452,7 @@ namespace bluetooth

            sourceQueue = xQueueCreate(5, sizeof(AudioData_t));
            if (sourceQueue != nullptr) {
                sendAudioEvent(audio::EventType::BlutoothA2DPDeviceState, audio::Event::DeviceState::Connected);
                sendAudioEvent(audio::EventType::BluetoothA2DPDeviceState, audio::Event::DeviceState::Connected);
            }
            else {
                LOG_ERROR("failed to create queue!");


@@ 528,7 528,7 @@ namespace bluetooth
                cid,
                AVRCP::mediaTracker.local_seid,
                local_seid);
            sendAudioEvent(audio::EventType::BlutoothA2DPDeviceState, audio::Event::DeviceState::Disconnected);
            sendAudioEvent(audio::EventType::BluetoothA2DPDeviceState, audio::Event::DeviceState::Disconnected);
            if (cid == AVRCP::mediaTracker.a2dp_cid) {
                AVRCP::mediaTracker.stream_opened = 0;
                LOG_INFO("A2DP Source: Stream released");

M module-bluetooth/Bluetooth/interface/profiles/HFP/HFP.cpp => module-bluetooth/Bluetooth/interface/profiles/HFP/HFP.cpp +3 -3
@@ 248,13 248,13 @@ namespace bluetooth
            status = hfp_subevent_service_level_connection_established_get_status(event);
            if (status) {
                LOG_DEBUG("Connection failed, status 0x%02x", status);
                sendAudioEvent(audio::EventType::BlutoothHFPDeviceState, audio::Event::DeviceState::Disconnected);
                sendAudioEvent(audio::EventType::BluetoothHFPDeviceState, audio::Event::DeviceState::Disconnected);
                break;
            }
            aclHandle = hfp_subevent_service_level_connection_established_get_acl_handle(event);
            hfp_subevent_service_level_connection_established_get_bd_addr(event, device.address);
            LOG_DEBUG("Service level connection established to %s", bd_addr_to_str(device.address));
            sendAudioEvent(audio::EventType::BlutoothHFPDeviceState, audio::Event::DeviceState::Connected);
            sendAudioEvent(audio::EventType::BluetoothHFPDeviceState, audio::Event::DeviceState::Connected);
            {
                auto &busProxy     = const_cast<sys::Service *>(ownerService)->bus;
                device.deviceState = DeviceState::ConnectedVoice;


@@ 269,7 269,7 @@ namespace bluetooth
        case HFP_SUBEVENT_SERVICE_LEVEL_CONNECTION_RELEASED:
            LOG_DEBUG("Service level connection released");
            aclHandle = HCI_CON_HANDLE_INVALID;
            sendAudioEvent(audio::EventType::BlutoothHFPDeviceState, audio::Event::DeviceState::Disconnected);
            sendAudioEvent(audio::EventType::BluetoothHFPDeviceState, audio::Event::DeviceState::Disconnected);
            {
                auto &busProxy = const_cast<sys::Service *>(ownerService)->bus;
                busProxy.sendUnicast(std::make_shared<message::bluetooth::DisconnectResult>(device),

M module-bluetooth/Bluetooth/interface/profiles/HSP/HSP.cpp => module-bluetooth/Bluetooth/interface/profiles/HSP/HSP.cpp +5 -5
@@ 1,4 1,4 @@
// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2023, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include <log/log.hpp>


@@ 158,12 158,12 @@ namespace bluetooth
            if (hsp_subevent_rfcomm_connection_complete_get_status(event) != 0u) {
                LOG_DEBUG("RFCOMM connection establishement failed with status %u\n",
                          hsp_subevent_rfcomm_connection_complete_get_status(event));
                sendAudioEvent(audio::EventType::BlutoothHSPDeviceState, audio::Event::DeviceState::Disconnected);
                sendAudioEvent(audio::EventType::BluetoothHSPDeviceState, audio::Event::DeviceState::Disconnected);
                isConnected = false;
                break;
            }
            LOG_DEBUG("RFCOMM connection established.\n");
            sendAudioEvent(audio::EventType::BlutoothHSPDeviceState, audio::Event::DeviceState::Connected);
            sendAudioEvent(audio::EventType::BluetoothHSPDeviceState, audio::Event::DeviceState::Connected);
            {
                auto &busProxy     = const_cast<sys::Service *>(ownerService)->bus;
                device.deviceState = DeviceState::ConnectedVoice;


@@ 174,7 174,7 @@ namespace bluetooth
            break;
        case HSP_SUBEVENT_RFCOMM_DISCONNECTION_COMPLETE: {
            LOG_DEBUG("RFCOMM disconnected.\n");
            sendAudioEvent(audio::EventType::BlutoothHSPDeviceState, audio::Event::DeviceState::Disconnected);
            sendAudioEvent(audio::EventType::BluetoothHSPDeviceState, audio::Event::DeviceState::Disconnected);
            auto &busProxy = const_cast<sys::Service *>(ownerService)->bus;
            busProxy.sendUnicast(std::make_shared<message::bluetooth::DisconnectResult>(device),
                                 service::name::bluetooth);


@@ 200,7 200,7 @@ namespace bluetooth
            LOG_DEBUG("Audio connection released.\n\n");
            scoHandle    = HCI_CON_HANDLE_INVALID;
            callAnswered = false;
            sendAudioEvent(audio::EventType::BlutoothHSPDeviceState, audio::Event::DeviceState::Disconnected);
            sendAudioEvent(audio::EventType::BluetoothHSPDeviceState, audio::Event::DeviceState::Disconnected);
            break;
        case HSP_SUBEVENT_MICROPHONE_GAIN_CHANGED:
            LOG_DEBUG("Received microphone gain change %d\n", hsp_subevent_microphone_gain_changed_get_gain(event));

M module-services/service-audio/AudioServiceAPI.cpp => module-services/service-audio/AudioServiceAPI.cpp +5 -6
@@ 90,18 90,16 @@ namespace AudioServiceAPI
        return serv->bus.sendUnicast(msg, service::name::audio);
    }

    void SendEvent(sys::Service *serv, std::shared_ptr<audio::Event> evt)
    bool SendEvent(sys::Service *serv, std::shared_ptr<audio::Event> evt)
    {
        auto msg = std::make_shared<AudioEventRequest>(std::move(evt));
        serv->bus.sendMulticast(msg, sys::BusChannel::ServiceAudioNotifications);
        // return true;
        return serv->bus.sendUnicast(msg, service::name::audio);
    }

    void SendEvent(sys::Service *serv, audio::EventType eType, audio::Event::DeviceState state)
    bool SendEvent(sys::Service *serv, audio::EventType eType, audio::Event::DeviceState state)
    {
        auto msg = std::make_shared<AudioEventRequest>(eType, state);
        serv->bus.sendMulticast(msg, sys::BusChannel::ServiceAudioNotifications);
        // return true;
        return serv->bus.sendUnicast(msg, service::name::audio);
    }

    std::string GetSetting(sys::Service *serv, audio::Setting setting, audio::PlaybackType playbackType)


@@ 251,4 249,5 @@ namespace AudioServiceAPI
    {
        return serv->bus.sendUnicast(std::make_shared<HFPDeviceVolumeChanged>(volume), service::name::audio);
    }

} // namespace AudioServiceAPI

M module-services/service-audio/ServiceAudio.cpp => module-services/service-audio/ServiceAudio.cpp +22 -10
@@ 126,11 126,6 @@ ServiceAudio::ServiceAudio()
            [this](sys::Message *msg) -> sys::MessagePointer { return handleMultimediaAudioPause(); });
    connect(typeid(message::bluetooth::AudioStart),
            [this](sys::Message *msg) -> sys::MessagePointer { return handleMultimediaAudioStart(); });
    connect(typeid(AudioEventRequest), [this](sys::Message *msg) -> sys::MessagePointer {
        auto message = static_cast<AudioEventRequest *>(msg);
        HandleSendEvent(message->getEvent());
        return sys::msgHandled();
    });
}

ServiceAudio::~ServiceAudio()


@@ 454,13 449,13 @@ std::unique_ptr<AudioResponseMessage> ServiceAudio::HandleStart(const Operation:
    return std::make_unique<AudioStartRoutingResponse>(RetCode::OperationNotSet, Token::MakeBadToken());
}

sys::MessagePointer ServiceAudio::HandleSendEvent(std::shared_ptr<Event> evt)
std::unique_ptr<AudioResponseMessage> ServiceAudio::HandleSendEvent(std::shared_ptr<Event> evt)
{
    const auto eventType       = evt->getType();
    const auto deviceConnected = evt->getDeviceState() == audio::Event::DeviceState::Connected;

    switch (eventType) {
    case EventType::BlutoothA2DPDeviceState: {
    case EventType::BluetoothA2DPDeviceState: {
        LOG_DEBUG("Bluetooth A2DP connection status changed: %s", deviceConnected ? "connected" : "disconnected");

        if (!deviceConnected) {


@@ 474,8 469,8 @@ sys::MessagePointer ServiceAudio::HandleSendEvent(std::shared_ptr<Event> evt)
        HandleStop(playbacksToBeStopped, audio::Token());
    } break;

    case EventType::BlutoothHSPDeviceState:
    case EventType::BlutoothHFPDeviceState: {
    case EventType::BluetoothHSPDeviceState:
    case EventType::BluetoothHFPDeviceState: {
        if (deviceConnected != bluetoothVoiceProfileConnected) {
            LOG_DEBUG("Bluetooth voice connection status changed: %s", deviceConnected ? "connected" : "disconnected");
            bluetoothVoiceProfileConnected = deviceConnected;


@@ 497,7 492,9 @@ sys::MessagePointer ServiceAudio::HandleSendEvent(std::shared_ptr<Event> evt)
        input.audio->SendEvent(evt);
    }

    return sys::msgHandled();
    notifyAboutNewRoutingIfRouterAvailable();

    return std::make_unique<AudioEventResponse>(RetCode::Success);
}

std::unique_ptr<AudioResponseMessage> ServiceAudio::HandleStop(const std::vector<audio::PlaybackType> &stopTypes,


@@ 675,6 672,10 @@ sys::MessagePointer ServiceAudio::DataReceivedHandler(sys::DataMessage *msgl, sy
        auto *msg   = static_cast<AudioResumeRequest *>(msgl);
        responseMsg = HandleResume(msg->token);
    }
    else if (msgType == typeid(AudioEventRequest)) {
        auto *msg   = static_cast<AudioEventRequest *>(msgl);
        responseMsg = HandleSendEvent(msg->getEvent());
    }
    else if (msgType == typeid(AudioKeyPressedRequest)) {
        auto *msg   = static_cast<AudioKeyPressedRequest *>(msgl);
        responseMsg = HandleKeyPressed(msg->step);


@@ 896,3 897,14 @@ auto ServiceAudio::handleMultimediaAudioStart() -> sys::MessagePointer
    }
    return sys::msgHandled();
}

void ServiceAudio::notifyAboutNewRoutingIfRouterAvailable()
{
    for (auto &input : audioMux.GetAllInputs()) {
        if (input.audio->GetCurrentOperation().GetOperationType() == audio::Operation::Type::Router) {
            bus.sendMulticast(
                std::make_shared<AudioRoutingNotification>(input.audio->GetCurrentOperation().GetProfile()->GetType()),
                sys::BusChannel::ServiceAudioNotifications);
        }
    }
}

M module-services/service-audio/include/service-audio/AudioMessage.hpp => module-services/service-audio/include/service-audio/AudioMessage.hpp +9 -1
@@ 1,4 1,4 @@
// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2023, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once


@@ 168,6 168,14 @@ class AudioStartRoutingResponse : public AudioResponseMessage
    const audio::Token token;
};

class AudioRoutingNotification : public AudioMessage
{
  public:
    AudioRoutingNotification(const audio::Profile::Type &profileType) : profileType(profileType)
    {}
    const audio::Profile::Type profileType;
};

class AudioStartRecorderRequest : public AudioMessage
{
  public:

M module-services/service-audio/include/service-audio/AudioServiceAPI.hpp => module-services/service-audio/include/service-audio/AudioServiceAPI.hpp +8 -4
@@ 104,18 104,22 @@ namespace AudioServiceAPI
     */
    bool Resume(sys::Service *serv, const audio::Token &token);
    /**
     * @brief Sends audio event as multicast
     * @brief Sends audio event.
     * @param serv Requesting service.
     * @param evt Event to be sent.
     * @return True if request has been sent successfully, false otherwise
     *   Response will come as message AudioSendEventResponse
     */
    void SendEvent(sys::Service *serv, std::shared_ptr<audio::Event> evt);
    bool SendEvent(sys::Service *serv, std::shared_ptr<audio::Event> evt);
    /**
     * @brief Sends audio event as multicast
     * @brief Sends audio event.
     * @param serv Requesting service.
     * @param evt Event to be sent.
     * @param state Optional parameter to request.
     * @return True if request has been sent successfully, false otherwise
     *   Response will come as message AudioSendEventResponse
     */
    void SendEvent(sys::Service *serv,
    bool SendEvent(sys::Service *serv,
                   audio::EventType evt,
                   audio::Event::DeviceState state = audio::Event::DeviceState::Connected);


M module-services/service-audio/include/service-audio/ServiceAudio.hpp => module-services/service-audio/include/service-audio/ServiceAudio.hpp +3 -1
@@ 78,7 78,7 @@ class ServiceAudio : public sys::Service
    };

    auto StopInput(audio::AudioMux::Input *input, StopReason stopReason = StopReason::Other) -> audio::RetCode;
    auto HandleSendEvent(std::shared_ptr<audio::Event> evt) -> sys::MessagePointer;
    auto HandleSendEvent(std::shared_ptr<audio::Event> evt) -> std::unique_ptr<AudioResponseMessage>;
    auto HandlePause(const audio::Token &token) -> std::unique_ptr<AudioResponseMessage>;
    auto HandlePause(std::optional<audio::AudioMux::Input *> input) -> std::unique_ptr<AudioResponseMessage>;
    auto HandleResume(const audio::Token &token) -> std::unique_ptr<AudioResponseMessage>;


@@ 127,6 127,8 @@ class ServiceAudio : public sys::Service
    auto handleHFPVolumeChangedOnBluetoothDevice(sys::Message *msgl) -> sys::MessagePointer;
    auto handleMultimediaAudioPause() -> sys::MessagePointer;
    auto handleMultimediaAudioStart() -> sys::MessagePointer;

    void notifyAboutNewRoutingIfRouterAvailable();
};

namespace sys

M module-services/service-evtmgr/AppSettingsNotify.cpp => module-services/service-evtmgr/AppSettingsNotify.cpp +4 -4
@@ 1,4 1,4 @@
// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2023, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "Service/BusProxy.hpp"


@@ 9,17 9,17 @@ namespace evm::api
    void notifySettingsBluetoothAudio(sys::BusProxy &bus, std::shared_ptr<audio::Event> event)
    {
        switch (event->getType()) {
        case audio::EventType::BlutoothA2DPDeviceState: {
        case audio::EventType::BluetoothA2DPDeviceState: {
            auto message = std::make_shared<message::bluetooth::ProfileStatus>(
                bluetooth::AudioProfile::A2DP, (event->getDeviceState() == audio::Event::DeviceState::Connected));
            bus.sendUnicast(message, app::name_settings);
        } break;
        case audio::EventType::BlutoothHSPDeviceState: {
        case audio::EventType::BluetoothHSPDeviceState: {
            auto message = std::make_shared<message::bluetooth::ProfileStatus>(
                bluetooth::AudioProfile::HSP, (event->getDeviceState() == audio::Event::DeviceState::Connected));
            bus.sendUnicast(message, app::name_settings);
        } break;
        case audio::EventType::BlutoothHFPDeviceState: {
        case audio::EventType::BluetoothHFPDeviceState: {
            auto message = std::make_shared<message::bluetooth::ProfileStatus>(
                bluetooth::AudioProfile::HFP, (event->getDeviceState() == audio::Event::DeviceState::Connected));
            bus.sendUnicast(message, app::name_settings);

M pure_changelog.md => pure_changelog.md +1 -1
@@ 72,7 72,7 @@
* Fixed (removed) redundant leading zero from time representation unless exactly midnight
* Fixed inability to type characters other than digits in USSD replies
* Fixed screen ghosting after emoji selection
* Fixed incorrect loudspeaker icon in call window when headset was connected during a call
* Fixed incorrect loudspeaker icon in call window when headset was connected or disconnected during a call
* Fixed invalid screen displayed after missed call
* Fixed minor issues in the Calculator Application
* Fixed displaying usual SMS template call rejection window when no templates were defined