~aleteoryx/muditaos

ddf5342b7548a16854c981a7236e8c3a63e09e2b — Adam Dobrowolski 4 years ago 301fddd
[EGD-8208] Fixed race condition on ring within call management

Now we get request for call and to end call in proper order
M module-apps/application-call/ApplicationCall.cpp => module-apps/application-call/ApplicationCall.cpp +0 -6
@@ 88,7 88,6 @@ namespace app
                switchWindow(window::name_call, std::make_unique<app::CallAbortData>());
            }
            else if (state == Application::State::ACTIVE_BACKGROUND) {
                stopAudio();
                setCallState(call::State::IDLE);
                manager::Controller::finish(this);
            }


@@ 265,11 264,6 @@ namespace app
        }
    }

    void ApplicationCall::stopAudio()
    {
        AudioServiceAPI::StopAll(this);
    }

    void ApplicationCall::startAudioRouting()
    {
        AudioServiceAPI::RoutingStart(this);

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

#pragma once


@@ 45,7 45,6 @@ namespace app
            LoudspeakerOff
        };

        virtual void stopAudio()                           = 0;
        virtual void startAudioRouting()                   = 0;
        virtual void sendAudioEvent(AudioEvent audioEvent) = 0;



@@ 120,7 119,6 @@ namespace app
            this->callState = state;
        }

        void stopAudio() override;
        void startAudioRouting() override;
        void sendAudioEvent(AudioEvent audioEvent) override;


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

#include "ApplicationCall.hpp"


@@ 174,7 174,6 @@ namespace gui
            }
        } break;
        case State::CALL_ENDED: {
            interface->stopAudio();
            stopCallTimer();
            navBar->setActive(gui::nav_bar::Side::Left, false);
            navBar->setActive(gui::nav_bar::Side::Center, false);

M module-apps/apps-common/notifications/NotificationsConfiguration.cpp => module-apps/apps-common/notifications/NotificationsConfiguration.cpp +1 -12
@@ 1,4 1,4 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "NotificationsConfiguration.hpp"


@@ 18,11 18,6 @@ void NotificationsConfiguration::updateCurrentCall(CallNotificationPolicy &polic
    policy.updateCurrentCall(phoneModeObserver->getCurrentPhoneMode());
}

void NotificationsConfiguration::callNumberCheck(CallNotificationPolicy &policy, bool contactInFavourites)
{
    policy.numberCheck(getDNDCallsFromFavourites(), contactInFavourites);
}

void NotificationsConfiguration::updateCurrentList(NotificationsListPolicy &policy)
{
    policy.updateCurrentList(phoneModeObserver->getCurrentPhoneMode(),


@@ 40,9 35,3 @@ auto NotificationsConfiguration::getDNDNotificationsOnLockedScreen() const noexc
    return utils::getNumericValue<bool>(
        settings->getValue(settings::Offline::notificationsWhenLocked, settings::SettingsScope::Global));
}

auto NotificationsConfiguration::getDNDCallsFromFavourites() const noexcept -> bool
{
    return utils::getNumericValue<bool>(
        settings->getValue(settings::Offline::callsFromFavorites, settings::SettingsScope::Global));
}

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

#pragma once


@@ 19,7 19,6 @@ namespace notifications
                                   std::shared_ptr<settings::Settings> settings,
                                   const locks::PhoneLockHandler &phoneLockHandler);
        void updateCurrentCall(CallNotificationPolicy &policy);
        void callNumberCheck(CallNotificationPolicy &policy, bool contactInFavourites);
        void updateCurrentList(NotificationsListPolicy &policy);
        void updateCurrentSMS(SMSNotificationPolicy &policy);


M module-apps/apps-common/notifications/NotificationsHandler.cpp => module-apps/apps-common/notifications/NotificationsHandler.cpp +1 -20
@@ 1,4 1,4 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "NotificationsHandler.hpp"


@@ 43,7 43,6 @@ void NotificationsHandler::incomingCallHandler(sys::Message *request)
        app::manager::Controller::sendAction(parentService,
                                             app::manager::actions::HandleIncomingCall,
                                             std::make_unique<app::manager::actions::CallParams>(msg->number));
        playbackCallRingtone();
    }
}



@@ 51,40 50,22 @@ void NotificationsHandler::callerIdHandler(sys::Message *request)
{
    auto msg = static_cast<CellularCallerIdMessage *>(request);

    if (currentCallPolicy.isNumberCheckRequired()) {
        policyNumberCheck(msg->number);
    }
    if (currentCallPolicy.isPopupAllowed()) {
        app::manager::Controller::sendAction(parentService,
                                             app::manager::actions::HandleCallerId,
                                             std::make_unique<app::manager::actions::CallParams>(msg->number));
        playbackCallRingtone();
    }
    else {
        CellularServiceAPI::DismissCall(parentService, currentCallPolicy.isDismissedCallNotificationAllowed());
    }
}

void NotificationsHandler::policyNumberCheck(const utils::PhoneNumber::View &number)
{
    auto isContactInFavourites = DBServiceAPI::IsContactInFavourites(parentService, number);
    notifcationConfig.callNumberCheck(currentCallPolicy, isContactInFavourites);
}

void NotificationsHandler::incomingSMSHandler()
{
    notifcationConfig.updateCurrentSMS(currentSMSPolicy);
    playbackSMSRingtone();
}

void NotificationsHandler::playbackCallRingtone()
{
    if (currentCallPolicy.isRingtoneAllowed()) {
        const auto filePath = AudioServiceAPI::GetSound(parentService, audio::PlaybackType::CallRingtone);
        AudioServiceAPI::PlaybackStart(parentService, audio::PlaybackType::CallRingtone, filePath);
    }
}

void NotificationsHandler::playbackSMSRingtone()
{
    if (currentSMSPolicy.isRingtoneAllowed()) {

M module-apps/apps-common/notifications/policies/CallNotificationPolicy.cpp => module-apps/apps-common/notifications/policies/CallNotificationPolicy.cpp +1 -6
@@ 1,4 1,4 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "CallNotificationPolicy.hpp"


@@ 42,11 42,6 @@ bool CallNotificationPolicy::isDismissedCallNotificationAllowed() const noexcept
    return dismissedCallNotification;
}

bool CallNotificationPolicy::isNumberCheckRequired() const noexcept
{
    return numberCheckNeeded;
}

void CallNotificationPolicy::numberCheck(bool callsFromFavouritesSetting, bool isNumberinFavourites)
{
    numberCheckNeeded = false;

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

#pragma once


@@ 15,7 15,6 @@ namespace notifications
        bool isPopupAllowed() const noexcept;
        bool isRingtoneAllowed() const noexcept;
        bool isDismissedCallNotificationAllowed() const noexcept;
        bool isNumberCheckRequired() const noexcept;
        void numberCheck(bool callsFromFavouritesSetting, bool isNumberInFavourites);

      private:

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

#include <catch2/catch.hpp>


@@ 16,7 16,6 @@ TEST_CASE("Connected Mode notifications - calls policy test")
    callPolicy.updateCurrentCall(sys::phone_modes::PhoneMode::Connected);
    REQUIRE(callPolicy.isPopupAllowed());
    REQUIRE(callPolicy.isRingtoneAllowed());
    REQUIRE(!callPolicy.isNumberCheckRequired());
}

TEST_CASE("Connected Mode notifications - sms policy test")


@@ 63,7 62,6 @@ TEST_CASE("DoNotDisturb Mode notifications  - calls policy test")
    callPolicy.updateCurrentCall(sys::phone_modes::PhoneMode::DoNotDisturb);
    REQUIRE(!callPolicy.isPopupAllowed());
    REQUIRE(!callPolicy.isRingtoneAllowed());
    REQUIRE(callPolicy.isNumberCheckRequired());
    REQUIRE(callPolicy.isDismissedCallNotificationAllowed());

    SECTION("Number in/not in Favourites")


@@ 146,6 144,5 @@ TEST_CASE("Offline Mode notifications - calls policy test")
    callPolicy.updateCurrentCall(sys::phone_modes::PhoneMode::Offline);
    REQUIRE(!callPolicy.isPopupAllowed());
    REQUIRE(!callPolicy.isRingtoneAllowed());
    REQUIRE(!callPolicy.isNumberCheckRequired());
    REQUIRE(!callPolicy.isDismissedCallNotificationAllowed());
}

M module-services/service-cellular/CMakeLists.txt => module-services/service-cellular/CMakeLists.txt +2 -0
@@ 17,6 17,8 @@ set(SOURCES
    src/CSQHandler.cpp

    CellularCall.cpp
    CallAudio.cpp
    CallRingGuard.cpp
    CellularServiceAPI.cpp
    CellularUrcHandler.cpp
    checkSmsCenter.cpp

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

#include "CallAudio.hpp"
#include <service-audio/AudioServiceAPI.hpp>

CallRingAudio::CallRingAudio(sys::Service &s) : owner(s)
{}

void CallRingAudio::play()
{
    const auto filePath = AudioServiceAPI::GetSound(&owner, audio::PlaybackType::CallRingtone);
    AudioServiceAPI::PlaybackStart(&owner, audio::PlaybackType::CallRingtone, filePath);
}

void CallRingAudio::stop()
{
    /// NOTE: we should fix it to stop actual played ringtone...
    /// but we would require async response from PlayBack start or API in audio to be able to
    /// just stop Ringtone by path
    AudioServiceAPI::StopAll(&owner);
}

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

#pragma once

namespace sys
{
    class Service;
}

class CallRingAudio
{
    sys::Service &owner;

  public:
    explicit CallRingAudio(sys::Service &);

    void play();
    void stop();
};

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

#include "CallRingGuard.hpp"
#include "service-cellular/CellularCall.hpp"

bool callRingGuard(CellularCall::CellularCall &call)
{
    return call.mode == sys::phone_modes::PhoneMode::Connected ||
           (call.mode == sys::phone_modes::PhoneMode::DoNotDisturb && call.operations.areCallsFromFavouritesEnabled() &&
            call.operations.isNumberInFavourites());
}

M module-services/service-cellular/CellularCall.cpp => module-services/service-cellular/CellularCall.cpp +33 -1
@@ 1,7 1,10 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "service-cellular/CellularCall.hpp"
#include "service-cellular/ServiceCellular.hpp"
#include "service-cellular/CallRingGuard.hpp"
#include "service-db/agents/settings/SystemSettings.hpp"

#include <CalllogRecord.hpp>
#include <PhoneNumber.hpp>


@@ 18,8 21,26 @@

namespace CellularCall
{
    CellularCall::CellularCall(ServiceCellular &owner) : owner(owner), audio(owner)
    {
        utils::PhoneNumber::View number = utils::PhoneNumber::View();
        const CallType type             = CallType::CT_NONE;
        const time_t date               = 0;
        const time_t duration           = 0;

        clear();

        this->call.phoneNumber = number;
        this->call.date        = date;
        this->call.duration    = duration;
        this->call.type        = type;
        this->call.name        = number.getEntered(); // temporary set number as name
    }

    bool CellularCall::startCall(const utils::PhoneNumber::View &number, const CallType type)
    {
        callRingGuard(*this) ? audio.play() : void();

        LOG_INFO("starting call");

        if (isValid()) {


@@ 62,6 83,7 @@ namespace CellularCall

    bool CellularCall::endCall(Forced forced)
    {
        audio.stop();
        LOG_INFO("ending call");
        if (!isValid()) {
            LOG_ERROR("no call to end");


@@ 122,4 144,14 @@ namespace CellularCall
        cpuSentinel = std::move(sentinel);
    }

    bool CellularCall::Operations::areCallsFromFavouritesEnabled()
    {
        return owner.owner.areCallsFromFavouritesEnabled();
    }

    bool CellularCall::Operations::isNumberInFavourites()
    {
        return DBServiceAPI::IsContactInFavourites(&owner.owner, owner.call.phoneNumber);
    }

} // namespace CellularCall

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

#include "CellularUrcHandler.hpp"


@@ 29,7 29,7 @@ void CellularUrcHandler::Handle(Clip &urc)

void CellularUrcHandler::Handle(Ring &urc)
{
    response = std::make_unique<CellularRingNotification>("");
    response = std::make_unique<CellularRingNotification>();
    urc.setHandled(true);
}


M module-services/service-cellular/ServiceCellular.cpp => module-services/service-cellular/ServiceCellular.cpp +11 -2
@@ 300,7 300,9 @@ void ServiceCellular::registerMessageHandlers()
        if (priv->simCard->isSimCardSelected()) {
            connectionManager->onPhoneModeChange(mode);
        }
        ongoingCall.setMode(mode);
    });
    ongoingCall.setMode(phoneModeObserver->getCurrentPhoneMode());
    phoneModeObserver->subscribe([&](sys::phone_modes::Tethering tethering) {
        if (tethering == sys::phone_modes::Tethering::On) {
            priv->tetheringHandler->enable();


@@ 2206,9 2208,9 @@ auto ServiceCellular::handleCellularRingNotification(sys::Message *msg) -> std::
        return std::make_shared<CellularResponseMessage>(hangUpCall());
    }

    /// NOTE: the code below should be investigated as there is something weird in this flow
    if (!callManager.isIncomingCallPropagated()) {
        auto message = static_cast<CellularRingNotification *>(msg);
        bus.sendMulticast(std::make_shared<CellularIncominCallMessage>(message->getNubmer()),
        bus.sendMulticast(std::make_shared<CellularIncominCallMessage>(""),
                          sys::BusChannel::ServiceCellularNotifications);
        callManager.ring();
    }


@@ 2345,6 2347,13 @@ auto ServiceCellular::logTetheringCalls() -> void
        tetheringCalllog.clear();
    }
}

bool ServiceCellular::areCallsFromFavouritesEnabled()
{
    return utils::getNumericValue<bool>(
        settings->getValue(settings::Offline::callsFromFavorites, settings::SettingsScope::Global));
}

TaskHandle_t ServiceCellular::getTaskHandle()
{
    return xTaskGetCurrentTaskHandle();

M module-services/service-cellular/service-cellular/CellularCall.hpp => module-services/service-cellular/service-cellular/CellularCall.hpp +29 -12
@@ 1,9 1,12 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once

#include "CallAudio.hpp"
#include "PhoneModes/PhoneMode.hpp"
#include <Interface/CalllogRecord.hpp>
#include <SystemManager/CpuSentinel.hpp>
#include <PhoneNumber.hpp>
#include <Tables/CalllogTable.hpp>
#include <time/time_conversion.hpp>


@@ 11,11 14,13 @@
#include <Utils.hpp>

#include <cstdint>
#include <memory>
#include <functional>
#include <iosfwd>
#include <string>
#include <sys/types.h>
#include <SystemManager/CpuSentinel.hpp>

class ServiceCellular;

namespace CellularCall
{


@@ 52,20 57,18 @@ namespace CellularCall
            startActiveTime.set_time(0);
        }

        ServiceCellular &owner;
        CallRingAudio audio;

      public:
        CellularCall(utils::PhoneNumber::View number = utils::PhoneNumber::View(),
                     const CallType type             = CallType::CT_NONE,
                     const time_t date               = 0,
                     const time_t duration           = 0)
        void setMode(sys::phone_modes::PhoneMode mode)
        {
            clear();
            this->call.phoneNumber = number;
            this->call.date        = date;
            this->call.duration    = duration;
            this->call.type        = type;
            this->call.name        = number.getEntered(); // temporary set number as name
            this->mode = mode;
        }

        mutable sys::phone_modes::PhoneMode mode;
        CellularCall(ServiceCellular &owner);

        void setStartCallAction(const std::function<CalllogRecord(const CalllogRecord &rec)> callAction)
        {
            startCallAction = callAction;


@@ 98,5 101,19 @@ namespace CellularCall
        }

        void setCpuSentinel(std::shared_ptr<sys::CpuSentinel> sentinel);

        struct Operations
        {
            bool areCallsFromFavouritesEnabled();
            bool isNumberInFavourites();

          private:
            friend CellularCall;
            CellularCall &owner;
            explicit Operations(CellularCall &owner) : owner(owner)
            {}
        };
        Operations operations = Operations(*this);
        friend Operations;
    };
} // namespace CellularCall

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

#pragma once


@@ 883,23 883,8 @@ class CellularCallRejectedByOfflineNotification : public CellularResponseMessage
class CellularRingNotification : public CellularNotificationMessage
{
  public:
    explicit CellularRingNotification(const utils::PhoneNumber::View &number)
        : CellularNotificationMessage(CellularNotificationMessage::Content::Ring), number(number)
    {}
    explicit CellularRingNotification(const utils::PhoneNumber &number)
        : CellularNotificationMessage(CellularNotificationMessage::Content::Ring), number(number.getView())
    CellularRingNotification() : CellularNotificationMessage(CellularNotificationMessage::Content::Ring)
    {}
    explicit CellularRingNotification(const std::string &e164number)
        : CellularNotificationMessage(CellularNotificationMessage::Content::Ring),
          number(utils::PhoneNumber::parse(e164number))
    {}
    auto getNubmer() const -> utils::PhoneNumber::View
    {
        return number;
    }

  private:
    utils::PhoneNumber::View number;
};

class CellularCallerIdNotification : public CellularNotificationMessage

M module-services/service-cellular/service-cellular/ServiceCellular.hpp => module-services/service-cellular/service-cellular/ServiceCellular.hpp +3 -3
@@ 103,6 103,8 @@ class ServiceCellular : public sys::Service
    bool getIMSI(std::string &destination, bool fullNumber = false);
    std::vector<std::string> getNetworkInfo();

    auto areCallsFromFavouritesEnabled() -> bool;

  private:
    at::ATURCStream atURCStream;
    std::unique_ptr<CellularMux> cmux = std::make_unique<CellularMux>(PortSpeed_e::PS115200, this);


@@ 138,7 140,7 @@ class ServiceCellular : public sys::Service

    std::vector<std::string> messageParts;

    CellularCall::CellularCall ongoingCall;
    CellularCall::CellularCall ongoingCall = CellularCall::CellularCall(*this);
    std::vector<CalllogRecord> tetheringCalllog;

    ussd::State ussdState = ussd::State::none;


@@ 316,8 318,6 @@ class ServiceCellular : public sys::Service
    auto tetheringTurnOffURC() -> bool;
    auto tetheringTurnOnURC() -> bool;
    auto logTetheringCalls() -> void;

  private:
    std::unique_ptr<cellular::internal::ServiceCellularPriv> priv;
    cellular::internal::SimpleCallManager callManager;
    TaskHandle_t getTaskHandle();

M module-sys/PhoneModes/include/PhoneModes/Common.hpp => module-sys/PhoneModes/include/PhoneModes/Common.hpp +2 -8
@@ 1,19 1,13 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once

#include <Service/Message.hpp>
#include "PhoneMode.hpp"

namespace sys::phone_modes
{
    enum class PhoneMode
    {
        Connected,
        DoNotDisturb,
        Offline
    };

    enum class Tethering
    {
        Off,

A module-sys/PhoneModes/include/PhoneModes/PhoneMode.hpp => module-sys/PhoneModes/include/PhoneModes/PhoneMode.hpp +14 -0
@@ 0,0 1,14 @@
// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once

namespace sys::phone_modes
{
    enum class PhoneMode
    {
        Connected,
        DoNotDisturb,
        Offline
    };
}