~aleteoryx/muditaos

49c132c62272d7104dea976d231c9a4b377c0088 — Jakub Pyszczak 4 years ago 085bccb
[EGD-6513] Volume control feedback

Added volume control feedback during music playback over Bluetooth.
Changed volume buttons handler logic in service audio to use actions
instead of messages.
M enabled_unittests => enabled_unittests +4 -0
@@ 467,4 467,8 @@ TESTS_LIST["catch2-module-bsp"]="
    BatteryChargerUtilsTest;
"
#---------
TESTS_LIST["catch2-audio-volume-scaler"]="
    Scenario: Scale volume levels between system and bluetooth;
"
#---------


M module-apps/Application.cpp => module-apps/Application.cpp +19 -22
@@ 300,9 300,6 @@ namespace app
        else if (dynamic_cast<sevm::SIMMessage *>(msgl) != nullptr) {
            return handleSIMMessage(msgl);
        }
        else if (dynamic_cast<AudioKeyPressedResponse *>(msgl) != nullptr) {
            return handleAudioKeyMessage(msgl);
        }
        return sys::msgNotHandled();
    }



@@ 588,25 585,6 @@ namespace app
        return sys::msgHandled();
    }

    sys::MessagePointer Application::handleAudioKeyMessage(sys::Message *msgl)
    {
        using namespace gui::popup;
        const auto msg = static_cast<AudioKeyPressedResponse *>(msgl);
        if (msg->showPopup == AudioKeyPressedResponse::ShowPopup::True) {
            LOG_INFO("Playback: %s, volume: %s",
                     audio::str(msg->context.second).c_str(),
                     std::to_string(msg->volume).c_str());
            auto data = std::make_unique<gui::VolumePopupData>(msg->volume, msg->context);
            if (getCurrentWindow()->getName() == window::volume_window) {
                updateWindow(window::volume_window, std::move(data));
            }
            else {
                switchWindow(window::volume_window, std::move(data));
            }
        }
        return sys::msgHandled();
    }

    sys::ReturnCodes Application::InitHandler()
    {
        setState(State::INITIALIZING);


@@ 721,6 699,18 @@ namespace app
        }
    }

    void Application::handleVolumeChanged(audio::Volume volume, audio::Context context)
    {
        using namespace gui::popup;
        const auto popupName = resolveWindowName(gui::popup::ID::Volume);
        if (const auto currentWindowName = getCurrentWindow()->getName(); currentWindowName == popupName) {
            updateWindow(popupName, std::make_unique<gui::VolumePopupData>(volume, context));
        }
        else {
            switchWindow(popupName, std::make_unique<gui::VolumePopupData>(volume, context));
        }
    }

    void Application::attachPopups(const std::vector<gui::popup::ID> &popupsList)
    {
        using namespace gui::popup;


@@ 763,6 753,13 @@ namespace app
            auto popupParams = static_cast<const gui::PhoneModePopupRequestParams *>(params);
            handlePhoneModeChanged(popupParams->getPhoneMode());
        }
        else if (id == ID::Volume) {
            auto volumeParams = static_cast<const gui::VolumePopupRequestParams *>(params);
            LOG_INFO("Playback: %s, volume: %s",
                     audio::str(volumeParams->getAudioContext().second).c_str(),
                     std::to_string(volumeParams->getVolume()).c_str());
            handleVolumeChanged(volumeParams->getVolume(), volumeParams->getAudioContext());
        }
        else {
            switchWindow(gui::popup::resolveWindowName(id));
        }

M module-apps/Application.hpp => module-apps/Application.hpp +5 -1
@@ 185,7 185,6 @@ namespace app
        sys::MessagePointer handleGetDOM(sys::Message *msgl);
        sys::MessagePointer handleAppFocusLost(sys::Message *msgl);
        sys::MessagePointer handleSIMMessage(sys::Message *msgl);
        sys::MessagePointer handleAudioKeyMessage(sys::Message *msgl);

        virtual bool isPopupPermitted(gui::popup::ID popupId) const;



@@ 331,6 330,11 @@ namespace app
        /// @param tethering new tethering mode
        void handlePhoneModeChanged(sys::phone_modes::PhoneMode mode);

        /// Handles volume changed event
        /// @param volume current volume level
        /// @param context audio context which contains current profile and playback
        void handleVolumeChanged(audio::Volume volume, audio::Context context);

        /// @ingrup AppWindowStack
        WindowsStack windowsStack;
        WindowsFactory windowsFactory;

M module-apps/application-call/ApplicationCall.cpp => module-apps/application-call/ApplicationCall.cpp +3 -0
@@ 89,6 89,9 @@ namespace app

    bool ApplicationCall::isPopupPermitted(gui::popup::ID popupId) const
    {
        if (popupId == gui::popup::ID::Volume) {
            return true;
        }
        return getState() == call::State::IDLE;
    }


M module-apps/popups/VolumeWindow.cpp => module-apps/popups/VolumeWindow.cpp +1 -1
@@ 67,7 67,7 @@ namespace gui
        }
    }

    void VolumeWindow::showProperText(const AudioContext &audioContext, const audio::Volume volume) noexcept
    void VolumeWindow::showProperText(const audio::Context &audioContext, const audio::Volume volume) noexcept
    {
        volumeText->setText(utils::translate(style::window::volume::base_title_key));
        const auto [profileType, playbackType] = audioContext;

M module-apps/popups/VolumeWindow.hpp => module-apps/popups/VolumeWindow.hpp +2 -4
@@ 36,13 36,11 @@ namespace gui
{
    class VolumeWindow : public WindowWithTimer
    {
        using AudioContext = std::pair<audio::Profile::Type, audio::PlaybackType>;

      private:
        audio::Volume volume = 0;
        AudioContext audioContext;
        audio::Context audioContext;

        void showProperText(const AudioContext &audioContext, const audio::Volume volume) noexcept;
        void showProperText(const audio::Context &audioContext, const audio::Volume volume) noexcept;
        void showMultimediaPlayback() noexcept;
        void showCalling() noexcept;
        void showMuted() noexcept;

M module-apps/popups/data/PopupData.hpp => module-apps/popups/data/PopupData.hpp +3 -5
@@ 10,10 10,8 @@ namespace gui
{
    class VolumePopupData : public SwitchData
    {
        using AudioContext = std::pair<audio::Profile::Type, audio::PlaybackType>;

      public:
        explicit VolumePopupData(const audio::Volume volume, const AudioContext audioContext)
        explicit VolumePopupData(const audio::Volume volume, const audio::Context audioContext)
            : SwitchData(), volume{volume}, audioContext{audioContext}
        {}



@@ 22,14 20,14 @@ namespace gui
            return volume;
        }

        [[nodiscard]] auto getAudioContext() const noexcept -> AudioContext
        [[nodiscard]] auto getAudioContext() const noexcept -> audio::Context
        {
            return audioContext;
        }

      private:
        const audio::Volume volume;
        const AudioContext audioContext;
        const audio::Context audioContext;
    };

    class ModesPopupData : public SwitchData

M module-apps/popups/data/PopupRequestParams.hpp => module-apps/popups/data/PopupRequestParams.hpp +23 -0
@@ 7,6 7,7 @@

#include <service-appmgr/Actions.hpp>
#include <module-sys/PhoneModes/Common.hpp>
#include <module-audio/Audio/AudioCommon.hpp>

namespace gui
{


@@ 41,4 42,26 @@ namespace gui
      private:
        sys::phone_modes::PhoneMode phoneMode;
    };

    class VolumePopupRequestParams : public PopupRequestParams
    {
      public:
        VolumePopupRequestParams(audio::Volume volume, const audio::Context audioContext)
            : PopupRequestParams{gui::popup::ID::Volume}, volume{volume}, audioContext{audioContext}
        {}

        [[nodiscard]] auto getVolume() const noexcept
        {
            return volume;
        }

        [[nodiscard]] auto getAudioContext() const noexcept
        {
            return audioContext;
        }

      private:
        const audio::Volume volume;
        const audio::Context audioContext;
    };
} // namespace gui

M module-audio/Audio/AudioCommon.hpp => module-audio/Audio/AudioCommon.hpp +3 -0
@@ 67,6 67,9 @@ namespace audio
        Last = Alarm,
    };

    /// Used to describe audio operations
    using Context = std::pair<Profile::Type, PlaybackType>;

    struct DbPathElement
    {
        Setting setting;

A module-audio/Audio/VolumeScaler.cpp => module-audio/Audio/VolumeScaler.cpp +24 -0
@@ 0,0 1,24 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "VolumeScaler.hpp"

namespace audio::volume::scaler
{
    Volume toSystemVolume(std::uint8_t avrcpVolume) noexcept
    {
        constexpr auto avrcpMaxVolume = float{0x7F}; // from AVRCP documentation
        const auto systemVolume       = (avrcpVolume / avrcpMaxVolume) * audio::maxVolume;
        // prevents conversion to 0 while in fact sound is not muted
        if (systemVolume > 0.01f && systemVolume < 1.0f) {
            return 1;
        }
        return systemVolume;
    }

    std::uint8_t toAvrcpVolume(float systemVolume) noexcept
    {
        constexpr auto avrcpMaxVolume = std::uint8_t{0x7F}; // from AVRCP documentation
        return std::round(systemVolume * avrcpMaxVolume / audio::maxVolume);
    }
} // namespace audio::volume::scaler

A module-audio/Audio/VolumeScaler.hpp => module-audio/Audio/VolumeScaler.hpp +18 -0
@@ 0,0 1,18 @@
// 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 "AudioCommon.hpp"

/// @brief Converts volume between system and bluetooth ranges.
namespace audio::volume::scaler
{
    /// @brief Takes volume level and converts it to according one for the system.
    /// @param avrcpVolume - AVRCP volume level.
    /// @return Volume level scaled to satisfy system's range [audio::minVolume, audio::maxVolume].
    Volume toSystemVolume(std::uint8_t avrcpVolume) noexcept;
    /// @brief Takes volume level and converts it to according one for the AVRCP.
    /// @param systemVolume - system volume level.
    /// @return Volume level scaled to satisfy AVRCP's range [0, 127].
    std::uint8_t toAvrcpVolume(float systemVolume) noexcept;
} // namespace audio::volume::scaler

M module-audio/Audio/test/CMakeLists.txt => module-audio/Audio/test/CMakeLists.txt +9 -0
@@ 30,4 30,13 @@ add_gtest_executable(
        module-audio
)

add_catch2_executable(
    NAME
        audio-volume-scaler
    SRCS
        unittest_scaler.cpp
    LIBS
        module-audio
)

file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/testfiles" DESTINATION "${CMAKE_BINARY_DIR}")

A module-audio/Audio/test/unittest_scaler.cpp => module-audio/Audio/test/unittest_scaler.cpp +89 -0
@@ 0,0 1,89 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#define CATCH_CONFIG_MAIN

#include <catch2/catch.hpp>

#include <module-audio/Audio/VolumeScaler.hpp>

SCENARIO("Scale volume levels between system and bluetooth")
{
    GIVEN("AVRCP volume")
    {
        WHEN("Volume is 127")
        {
            THEN("System volume is set to 10")
            {
                REQUIRE(audio::volume::scaler::toSystemVolume(127) == 10);
            }
        }
        WHEN("Volume is 100")
        {
            THEN("System volume is set to 7")
            {
                REQUIRE(audio::volume::scaler::toSystemVolume(100) == 7);
            }
        }
        WHEN("Volume is 89")
        {
            THEN("System volume is set to 7")
            {
                REQUIRE(audio::volume::scaler::toSystemVolume(89) == 7);
            }
        }
        WHEN("Volume is 88")
        {
            THEN("System volume is set to 6")
            {
                REQUIRE(audio::volume::scaler::toSystemVolume(88) == 6);
            }
        }
        WHEN("Volume is 13")
        {
            THEN("System volume is set to 1")
            {
                REQUIRE(audio::volume::scaler::toSystemVolume(13) == 1);
            }
        }
        WHEN("Volume is 12")
        {
            THEN("System volume is set to 1")
            {
                REQUIRE(audio::volume::scaler::toSystemVolume(12) == 1);
            }
        }
        WHEN("Volume is 0")
        {
            THEN("System volume is set to 0")
            {
                REQUIRE(audio::volume::scaler::toSystemVolume(0) == 0);
            }
        }
    }

    GIVEN("System volume")
    {
        WHEN("Volume is set to 10")
        {
            THEN("AVRCP volume is 127")
            {
                REQUIRE(audio::volume::scaler::toAvrcpVolume(10) == std::uint8_t{127});
            }
        }
        WHEN("System volume is set to 7")
        {
            THEN("AVRCP volume is 89")
            {
                REQUIRE(audio::volume::scaler::toAvrcpVolume(7) == std::uint8_t{89});
            }
        }
        WHEN("System volume is set to 1")
        {
            THEN("AVRCP volume is 13")
            {
                REQUIRE(audio::volume::scaler::toAvrcpVolume(1) == std::uint8_t{13});
            }
        }
    }
}

M module-audio/CMakeLists.txt => module-audio/CMakeLists.txt +1 -0
@@ 14,6 14,7 @@ set(SOURCES
        "${CMAKE_CURRENT_SOURCE_DIR}/Audio/AudioDeviceFactory.cpp"
        "${CMAKE_CURRENT_SOURCE_DIR}/Audio/AudioFormat.cpp"
        "${CMAKE_CURRENT_SOURCE_DIR}/Audio/AudioMux.cpp"
        "${CMAKE_CURRENT_SOURCE_DIR}/Audio/VolumeScaler.cpp"
        "${CMAKE_CURRENT_SOURCE_DIR}/Audio/decoder/Decoder.cpp"
        "${CMAKE_CURRENT_SOURCE_DIR}/Audio/decoder/decoderFLAC.cpp"
        "${CMAKE_CURRENT_SOURCE_DIR}/Audio/decoder/decoderMP3.cpp"

M module-bluetooth/Bluetooth/audio/BluetoothAudioDevice.cpp => module-bluetooth/Bluetooth/audio/BluetoothAudioDevice.cpp +3 -3
@@ 5,6 5,7 @@

#include <Audio/AudioCommon.hpp>
#include <interface/profiles/A2DP/AVDTP.hpp>
#include <module-audio/Audio/VolumeScaler.hpp>

#include <Audio/Stream.hpp>



@@ 45,9 46,8 @@ auto BluetoothAudioDevice::Stop() -> audio::AudioDevice::RetCode

auto BluetoothAudioDevice::OutputVolumeCtrl(float vol) -> audio::AudioDevice::RetCode
{
    constexpr auto avrcpMaxVolume = std::uint8_t{0x7F}; // from AVRCP documentation
    const auto volumeToSet        = static_cast<std::uint8_t>((vol / audio::maxVolume) * avrcpMaxVolume);
    const auto status             = avrcp_controller_set_absolute_volume(ctx->avrcp_cid, volumeToSet);
    const auto volumeToSet = audio::volume::scaler::toAvrcpVolume(vol);
    const auto status      = avrcp_controller_set_absolute_volume(ctx->avrcp_cid, volumeToSet);
    if (status != ERROR_CODE_SUCCESS) {
        LOG_ERROR("Can't set volume level. Status %x", status);
        return audio::AudioDevice::RetCode::Failure;

A module-bluetooth/doc/bt_volume_buttons_handling.puml => module-bluetooth/doc/bt_volume_buttons_handling.puml +22 -0
@@ 0,0 1,22 @@
@startuml

actor User
participant "A2DP Sink" as sink
participant "Source's AVRCP event handler" as source
participant "Service Bluetooth" as bt
participant "Service Audio" as audio
participant "Settings" as settings
participant "Application" as app 


User -> sink : Volume button pressed
==A2DP==
sink -> source : AVRCP volume changed 
== ==
source -> bt : Volume changed message
bt -> audio : Volume changed message
audio -> settings : Set appropriate value
audio -> app : Current volume changed
app -> User : Show current volume level

@enduml

A module-bluetooth/doc/bt_volume_buttons_handling.svg => module-bluetooth/doc/bt_volume_buttons_handling.svg +35 -0
@@ 0,0 1,35 @@
<?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="468px" preserveAspectRatio="none" style="width:1087px;height:468px;" version="1.1" viewBox="0 0 1087 468" width="1087px" zoomAndPan="magnify"><defs><filter height="300%" id="f1ko217yxdo7w6" 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="27" x2="27" y1="86.2969" y2="381.3594"/><line style="stroke: #A80036; stroke-width: 1.0; stroke-dasharray: 5.0,5.0;" x1="200.5" x2="200.5" y1="86.2969" y2="381.3594"/><line style="stroke: #A80036; stroke-width: 1.0; stroke-dasharray: 5.0,5.0;" x1="378" x2="378" y1="86.2969" y2="381.3594"/><line style="stroke: #A80036; stroke-width: 1.0; stroke-dasharray: 5.0,5.0;" x1="573" x2="573" y1="86.2969" y2="381.3594"/><line style="stroke: #A80036; stroke-width: 1.0; stroke-dasharray: 5.0,5.0;" x1="767.5" x2="767.5" y1="86.2969" y2="381.3594"/><line style="stroke: #A80036; stroke-width: 1.0; stroke-dasharray: 5.0,5.0;" x1="929.5" x2="929.5" y1="86.2969" y2="381.3594"/><line style="stroke: #A80036; stroke-width: 1.0; stroke-dasharray: 5.0,5.0;" x1="1023.5" x2="1023.5" y1="86.2969" y2="381.3594"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="32" x="8" y="82.9951">User</text><ellipse cx="27" cy="13" fill="#FEFECE" filter="url(#f1ko217yxdo7w6)" rx="8" ry="8" style="stroke: #A80036; stroke-width: 2.0;"/><path d="M27,21 L27,48 M14,29 L40,29 M27,48 L14,63 M27,48 L40,63 " fill="none" filter="url(#f1ko217yxdo7w6)" style="stroke: #A80036; stroke-width: 2.0;"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="32" x="8" y="393.3545">User</text><ellipse cx="27" cy="406.6563" fill="#FEFECE" filter="url(#f1ko217yxdo7w6)" rx="8" ry="8" style="stroke: #A80036; stroke-width: 2.0;"/><path d="M27,414.6563 L27,441.6563 M14,422.6563 L40,422.6563 M27,441.6563 L14,456.6563 M27,441.6563 L40,456.6563 " fill="none" filter="url(#f1ko217yxdo7w6)" style="stroke: #A80036; stroke-width: 2.0;"/><rect fill="#FEFECE" filter="url(#f1ko217yxdo7w6)" height="30.2969" style="stroke: #A80036; stroke-width: 1.5;" width="85" x="156.5" y="51"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="71" x="163.5" y="70.9951">A2DP Sink</text><rect fill="#FEFECE" filter="url(#f1ko217yxdo7w6)" height="30.2969" style="stroke: #A80036; stroke-width: 1.5;" width="85" x="156.5" y="380.3594"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="71" x="163.5" y="400.3545">A2DP Sink</text><rect fill="#FEFECE" filter="url(#f1ko217yxdo7w6)" height="30.2969" style="stroke: #A80036; stroke-width: 1.5;" width="224" x="264" y="51"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="210" x="271" y="70.9951">Source's AVRCP event handler</text><rect fill="#FEFECE" filter="url(#f1ko217yxdo7w6)" height="30.2969" style="stroke: #A80036; stroke-width: 1.5;" width="224" x="264" y="380.3594"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="210" x="271" y="400.3545">Source's AVRCP event handler</text><rect fill="#FEFECE" filter="url(#f1ko217yxdo7w6)" height="30.2969" style="stroke: #A80036; stroke-width: 1.5;" width="136" x="503" y="51"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="122" x="510" y="70.9951">Service Bluetooth</text><rect fill="#FEFECE" filter="url(#f1ko217yxdo7w6)" height="30.2969" style="stroke: #A80036; stroke-width: 1.5;" width="136" x="503" y="380.3594"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="122" x="510" y="400.3545">Service Bluetooth</text><rect fill="#FEFECE" filter="url(#f1ko217yxdo7w6)" height="30.2969" style="stroke: #A80036; stroke-width: 1.5;" width="107" x="712.5" y="51"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="93" x="719.5" y="70.9951">Service Audio</text><rect fill="#FEFECE" filter="url(#f1ko217yxdo7w6)" height="30.2969" style="stroke: #A80036; stroke-width: 1.5;" width="107" x="712.5" y="380.3594"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="93" x="719.5" y="400.3545">Service Audio</text><rect fill="#FEFECE" filter="url(#f1ko217yxdo7w6)" height="30.2969" style="stroke: #A80036; stroke-width: 1.5;" width="71" x="892.5" y="51"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="57" x="899.5" y="70.9951">Settings</text><rect fill="#FEFECE" filter="url(#f1ko217yxdo7w6)" height="30.2969" style="stroke: #A80036; stroke-width: 1.5;" width="71" x="892.5" y="380.3594"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="57" x="899.5" y="400.3545">Settings</text><rect fill="#FEFECE" filter="url(#f1ko217yxdo7w6)" height="30.2969" style="stroke: #A80036; stroke-width: 1.5;" width="89" x="977.5" y="51"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="75" x="984.5" y="70.9951">Application</text><rect fill="#FEFECE" filter="url(#f1ko217yxdo7w6)" height="30.2969" style="stroke: #A80036; stroke-width: 1.5;" width="89" x="977.5" y="380.3594"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="75" x="984.5" y="400.3545">Application</text><polygon fill="#A80036" points="189,113.4297,199,117.4297,189,121.4297,193,117.4297" style="stroke: #A80036; stroke-width: 1.0;"/><line style="stroke: #A80036; stroke-width: 1.0;" x1="27" x2="195" y1="117.4297" y2="117.4297"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacingAndGlyphs" textLength="150" x="34" y="112.3638">Volume button pressed</text><rect fill="#EEEEEE" filter="url(#f1ko217yxdo7w6)" height="3" style="stroke: #EEEEEE; stroke-width: 1.0;" width="1072.5" x="3" y="145.9961"/><line style="stroke: #000000; stroke-width: 1.0;" x1="3" x2="1075.5" y1="145.9961" y2="145.9961"/><line style="stroke: #000000; stroke-width: 1.0;" x1="3" x2="1075.5" y1="148.9961" y2="148.9961"/><rect fill="#EEEEEE" filter="url(#f1ko217yxdo7w6)" height="23.1328" style="stroke: #000000; stroke-width: 2.0;" width="54" x="512.25" y="135.4297"/><text fill="#000000" font-family="sans-serif" font-size="13" font-weight="bold" lengthAdjust="spacingAndGlyphs" textLength="40" x="518.25" y="151.4966">A2DP</text><polygon fill="#A80036" points="366,185.6953,376,189.6953,366,193.6953,370,189.6953" style="stroke: #A80036; stroke-width: 1.0;"/><line style="stroke: #A80036; stroke-width: 1.0;" x1="201" x2="372" y1="189.6953" y2="189.6953"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacingAndGlyphs" textLength="153" x="208" y="184.6294">AVRCP volume changed</text><rect fill="#EEEEEE" filter="url(#f1ko217yxdo7w6)" height="3" style="stroke: #EEEEEE; stroke-width: 1.0;" width="1072.5" x="3" y="210.6953"/><line style="stroke: #000000; stroke-width: 1.0;" x1="3" x2="1075.5" y1="210.6953" y2="210.6953"/><line style="stroke: #000000; stroke-width: 1.0;" x1="3" x2="1075.5" y1="213.6953" y2="213.6953"/><polygon fill="#A80036" points="561,242.8281,571,246.8281,561,250.8281,565,246.8281" style="stroke: #A80036; stroke-width: 1.0;"/><line style="stroke: #A80036; stroke-width: 1.0;" x1="378" x2="567" y1="246.8281" y2="246.8281"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacingAndGlyphs" textLength="171" x="385" y="241.7622">Volume changed message</text><polygon fill="#A80036" points="756,271.9609,766,275.9609,756,279.9609,760,275.9609" style="stroke: #A80036; stroke-width: 1.0;"/><line style="stroke: #A80036; stroke-width: 1.0;" x1="573" x2="762" y1="275.9609" y2="275.9609"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacingAndGlyphs" textLength="171" x="580" y="270.895">Volume changed message</text><polygon fill="#A80036" points="918,301.0938,928,305.0938,918,309.0938,922,305.0938" style="stroke: #A80036; stroke-width: 1.0;"/><line style="stroke: #A80036; stroke-width: 1.0;" x1="768" x2="924" y1="305.0938" y2="305.0938"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacingAndGlyphs" textLength="138" x="775" y="300.0278">Set appropriate value</text><polygon fill="#A80036" points="1012,330.2266,1022,334.2266,1012,338.2266,1016,334.2266" style="stroke: #A80036; stroke-width: 1.0;"/><line style="stroke: #A80036; stroke-width: 1.0;" x1="768" x2="1018" y1="334.2266" y2="334.2266"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacingAndGlyphs" textLength="158" x="775" y="329.1606">Current volume changed</text><polygon fill="#A80036" points="38,359.3594,28,363.3594,38,367.3594,34,363.3594" style="stroke: #A80036; stroke-width: 1.0;"/><line style="stroke: #A80036; stroke-width: 1.0;" x1="32" x2="1023" y1="363.3594" y2="363.3594"/><text fill="#000000" font-family="sans-serif" font-size="13" lengthAdjust="spacingAndGlyphs" textLength="168" x="44" y="358.2935">Show current volume level</text><!--
@startuml

actor User
participant "A2DP Sink" as sink
participant "Source's AVRCP event handler" as source
participant "Service Bluetooth" as bt
participant "Service Audio" as audio
participant "Settings" as settings
participant "Application" as app 


User -> sink : Volume button pressed
==A2DP==
sink -> source : AVRCP volume changed 
== ==
source -> bt : Volume changed message
bt -> audio : Volume changed message
audio -> settings : Set appropriate value
audio -> app : Current volume changed
app -> User : Show current volume level

@enduml

PlantUML version 1.2018.13(Mon Nov 26 18:11:51 CET 2018)
(GPL source distribution)
Java Runtime: OpenJDK Runtime Environment
JVM: OpenJDK 64-Bit Server VM
Java Version: 11.0.10+9-Ubuntu-0ubuntu1.20.04
Operating System: Linux
OS Version: 5.8.0-50-generic
Default Encoding: UTF-8
Language: en
Country: US
--></g></svg>
\ No newline at end of file

M module-services/service-appmgr/model/ApplicationManager.cpp => module-services/service-appmgr/model/ApplicationManager.cpp +3 -0
@@ 27,6 27,7 @@
#include <service-gui/Common.hpp>
#include <service-desktop/DesktopMessages.hpp>
#include <service-appmgr/StartupType.hpp>
#include <module-services/service-audio/service-audio/AudioMessage.hpp>

#include <algorithm>
#include <limits>


@@ 137,6 138,7 @@ namespace app::manager
        autoLockTimer = sys::TimerFactory::createSingleShotTimer(
            this, timerBlock, sys::timer::InfiniteTimeout, [this](sys::Timer &) { onPhoneLocked(); });
        bus.channels.push_back(sys::BusChannel::PhoneModeChanges);
        bus.channels.push_back(sys::BusChannel::ServiceAudioNotifications);
        registerMessageHandlers();
    }



@@ 402,6 404,7 @@ namespace app::manager
        connect(typeid(sys::TetheringQuestionRequest), convertibleToActionHandler);
        connect(typeid(sys::TetheringQuestionAbort), convertibleToActionHandler);
        connect(typeid(sys::TetheringPhoneModeChangeProhibitedMessage), convertibleToActionHandler);
        connect(typeid(VolumeChanged), convertibleToActionHandler);
    }

    sys::ReturnCodes ApplicationManager::SwitchPowerModeHandler(const sys::ServicePowerMode mode)

M module-services/service-audio/ServiceAudio.cpp => module-services/service-audio/ServiceAudio.cpp +15 -11
@@ 7,6 7,7 @@
#include <Audio/Operation/IdleOperation.hpp>
#include <Audio/Operation/PlaybackOperation.hpp>
#include <Bluetooth/audio/BluetoothAudioDevice.hpp>
#include <module-audio/Audio/VolumeScaler.hpp>
#include <service-bluetooth/Constants.hpp>
#include <service-bluetooth/ServiceBluetoothCommon.hpp>
#include <service-bluetooth/BluetoothMessage.hpp>


@@ 453,7 454,7 @@ void ServiceAudio::HandleNotification(const AudioNotificationMessage::Type &type
    }
}

auto ServiceAudio::HandleKeyPressed(const int step) -> std::unique_ptr<AudioKeyPressedResponse>
auto ServiceAudio::HandleKeyPressed(const int step) -> sys::MessagePointer
{
    auto context = getCurrentContext();



@@ 464,12 465,10 @@ auto ServiceAudio::HandleKeyPressed(const int step) -> std::unique_ptr<AudioKeyP
        // active system sounds can be only muted, no volume control is possible
        if (step < 0) {
            MuteCurrentOperation();
            return std::make_unique<AudioKeyPressedResponse>(
                audio::RetCode::Success, 0, AudioKeyPressedResponse::ShowPopup::False, context);
            return sys::msgHandled();
        }
        else {
            return std::make_unique<AudioKeyPressedResponse>(
                audio::RetCode::Success, currentVolume, AudioKeyPressedResponse::ShowPopup::False, context);
            return sys::msgHandled();
        }
    }



@@ 484,8 483,8 @@ auto ServiceAudio::HandleKeyPressed(const int step) -> std::unique_ptr<AudioKeyP
        // update volume of currently active sound
        setSetting(Setting::Volume, std::to_string(newVolume));
    }
    return std::make_unique<AudioKeyPressedResponse>(
        audio::RetCode::Success, newVolume, AudioKeyPressedResponse::ShowPopup::True, context);
    bus.sendMulticast(std::make_unique<VolumeChanged>(newVolume, context), sys::BusChannel::ServiceAudioNotifications);
    return sys::msgHandled();
}

void ServiceAudio::MuteCurrentOperation()


@@ 509,7 508,7 @@ sys::MessagePointer ServiceAudio::DataReceivedHandler(sys::DataMessage *msgl, sy
{
    sys::MessagePointer responseMsg;
    const auto isBusy = IsBusy();
    auto &msgType = typeid(*msgl);
    auto &msgType     = typeid(*msgl);

    if (msgType == typeid(AudioNotificationMessage)) {
        auto *msg = static_cast<AudioNotificationMessage *>(msgl);


@@ 689,7 688,7 @@ void ServiceAudio::setSetting(const Setting &setting,
    }
}

const std::pair<audio::Profile::Type, audio::PlaybackType> ServiceAudio::getCurrentContext()
const audio::Context ServiceAudio::getCurrentContext()
{
    const auto activeInput = audioMux.GetActiveInput();
    if (!activeInput.has_value()) {


@@ 715,7 714,12 @@ void ServiceAudio::settingsChanged(const std::string &name, std::string value)
}
auto ServiceAudio::handleVolumeChangedOnBluetoothDevice(sys::Message *msgl) -> sys::MessagePointer
{
    auto *msg = static_cast<BluetoothDeviceVolumeChanged *>(msgl);
    LOG_WARN("Volume chnged on bt device to %u. Handler to be done", msg->getVolume());
    auto *msg                              = static_cast<BluetoothDeviceVolumeChanged *>(msgl);
    const auto volume                      = volume::scaler::toSystemVolume(msg->getVolume());
    const auto [profileType, playbackType] = getCurrentContext();
    settingsProvider->setValue(dbPath(Setting::Volume, playbackType, profileType), std::to_string(volume));
    settingsCache[dbPath(Setting::Volume, playbackType, profileType)] = std::to_string(volume);
    bus.sendMulticast(std::make_unique<VolumeChanged>(volume, std::make_pair(profileType, playbackType)),
                      sys::BusChannel::ServiceAudioNotifications);
    return sys::msgHandled();
}

M module-services/service-audio/service-audio/AudioMessage.hpp => module-services/service-audio/service-audio/AudioMessage.hpp +16 -14
@@ 6,6 6,10 @@
#include <Audio/AudioCommon.hpp>
#include <Audio/decoder/Decoder.hpp>
#include <MessageType.hpp>
#include <service-appmgr/service-appmgr/messages/ActionRequest.hpp>
#include <module-services/service-appmgr/service-appmgr/Actions.hpp>
#include <module-apps/popups/data/PopupRequestParams.hpp>

#include <Service/Message.hpp>

#include <memory>


@@ 251,24 255,22 @@ class AudioKeyPressedRequest : public AudioMessage
    const int step{};
};

class AudioKeyPressedResponse : public sys::DataMessage
class VolumeChanged : public sys::DataMessage, public app::manager::actions::ConvertibleToAction
{
  public:
    enum class ShowPopup : bool
    {
        True,
        False
    };
    AudioKeyPressedResponse(audio::RetCode retCode,
                            const audio::Volume &volume,
                            const ShowPopup showPopup,
                            const std::pair<audio::Profile::Type, audio::PlaybackType> &context)
        : sys::DataMessage(MessageType::AudioMessage), volume(volume), showPopup(showPopup), context(context)
    VolumeChanged(audio::Volume volume, audio::Context context)
        : sys::DataMessage{MessageType::MessageTypeUninitialized}, volume{volume}, context{context}
    {}

    const audio::Volume volume{};
    const ShowPopup showPopup = ShowPopup::False;
    std::pair<audio::Profile::Type, audio::PlaybackType> context;
    [[nodiscard]] auto toAction() const -> std::unique_ptr<app::manager::ActionRequest> override
    {
        return std::make_unique<app::manager::ActionRequest>(
            sender, app::manager::actions::ShowPopup, std::make_unique<gui::VolumePopupRequestParams>(volume, context));
    }

  private:
    const audio::Volume volume;
    audio::Context context;
};

class BluetoothDeviceVolumeChanged : public AudioMessage

M module-services/service-audio/service-audio/AudioServiceAPI.hpp => module-services/service-audio/service-audio/AudioServiceAPI.hpp +1 -1
@@ 208,7 208,7 @@ namespace AudioServiceAPI
     * @param serv - requesting service.
     * @param step - step that will be added to current volume.
     * @return True if request has been sent successfully, false otherwise
     * @note Response will come as message AudioKeyPressedResponse
     * @note If volume popup should be shown then Application Manager is informed.
     */
    bool KeyPressed(sys::Service *serv, const int step);


M module-services/service-audio/service-audio/ServiceAudio.hpp => module-services/service-audio/service-audio/ServiceAudio.hpp +2 -2
@@ 78,7 78,7 @@ class ServiceAudio : public sys::Service
    auto HandleResume(const audio::Token &token) -> std::unique_ptr<AudioResponseMessage>;
    auto HandleGetFileTags(const std::string &fileName) -> std::unique_ptr<AudioResponseMessage>;
    void HandleNotification(const AudioNotificationMessage::Type &type, const audio::Token &token);
    auto HandleKeyPressed(const int step) -> std::unique_ptr<AudioKeyPressedResponse>;
    auto HandleKeyPressed(const int step) -> sys::MessagePointer;
    void MuteCurrentOperation();
    void VibrationUpdate(const audio::PlaybackType &type               = audio::PlaybackType::None,
                         std::optional<audio::AudioMux::Input *> input = std::nullopt);


@@ 106,7 106,7 @@ class ServiceAudio : public sys::Service
                                         const audio::Profile::Type &profileType,
                                         const audio::PlaybackType &playbackType);

    const std::pair<audio::Profile::Type, audio::PlaybackType> getCurrentContext();
    const audio::Context getCurrentContext();
    void settingsChanged(const std::string &name, std::string value);
    auto handleVolumeChangedOnBluetoothDevice(sys::Message *msgl) -> sys::MessagePointer;
};