LCOV - code coverage report
Current view: top level - src - call.cpp (source / functions) Hit Total Coverage
Test: jami-coverage-filtered.info Lines: 343 388 88.4 %
Date: 2024-04-26 09:41:19 Functions: 38 40 95.0 %

          Line data    Source code
       1             : /*
       2             :  *  Copyright (C) 2004-2024 Savoir-faire Linux Inc.
       3             :  *
       4             :  *  Author: Yan Morin <yan.morin@savoirfairelinux.com>
       5             :  *  Author: Laurielle Lea <laurielle.lea@savoirfairelinux.com>
       6             :  *  Author: Guillaume Roguez <guillaume.roguez@savoirfairelinux.com>
       7             :  *
       8             :  *  This program is free software; you can redistribute it and/or modify
       9             :  *  it under the terms of the GNU General Public License as published by
      10             :  *  the Free Software Foundation; either version 3 of the License, or
      11             :  *  (at your option) any later version.
      12             :  *
      13             :  *  This program is distributed in the hope that it will be useful,
      14             :  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
      15             :  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      16             :  *  GNU General Public License for more details.
      17             :  *
      18             :  *  You should have received a copy of the GNU General Public License
      19             :  *  along with this program; if not, write to the Free Software
      20             :  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA.
      21             :  */
      22             : 
      23             : #include "call.h"
      24             : #include "account.h"
      25             : #include "jamidht/jamiaccount.h"
      26             : #include "manager.h"
      27             : #ifdef ENABLE_PLUGIN
      28             : #include "plugin/jamipluginmanager.h"
      29             : #include "plugin/streamdata.h"
      30             : #endif
      31             : #include "audio/ringbufferpool.h"
      32             : #include "jami/call_const.h"
      33             : #include "client/ring_signal.h"
      34             : #include "connectivity/sip_utils.h"
      35             : #include "map_utils.h"
      36             : #include "call_factory.h"
      37             : #include "string_utils.h"
      38             : #include "enumclass_utils.h"
      39             : 
      40             : #include "errno.h"
      41             : 
      42             : #include <dhtnet/ip_utils.h>
      43             : #include <opendht/thread_pool.h>
      44             : 
      45             : #include <stdexcept>
      46             : #include <system_error>
      47             : #include <algorithm>
      48             : #include <functional>
      49             : #include <utility>
      50             : 
      51             : namespace jami {
      52             : 
      53             : /// Hangup many calls with same error code, filtered by a predicate
      54             : ///
      55             : /// For each call pointer given by iterating on given \a callptr_list
      56             : /// calls the unary predicate \a pred with this call pointer and hangup the call with given error
      57             : /// code \a errcode when the predicate return true.
      58             : /// The predicate should have <code>bool(Call*) signature</code>.
      59             : template<typename T>
      60             : inline void
      61         488 : hangupCallsIf(Call::SubcallSet&& calls, int errcode, T pred)
      62             : {
      63         580 :     for (auto& call : calls) {
      64          92 :         if (not pred(call.get()))
      65          78 :             continue;
      66          28 :         dht::ThreadPool::io().run([call = std::move(call), errcode] { call->hangup(errcode); });
      67             :     }
      68         488 : }
      69             : 
      70             : /// Hangup many calls with same error code.
      71             : ///
      72             : /// Works as hangupCallsIf() with a predicate that always return true.
      73             : inline void
      74         410 : hangupCalls(Call::SubcallSet&& callptr_list, int errcode)
      75             : {
      76         424 :     hangupCallsIf(std::move(callptr_list), errcode, [](Call*) { return true; });
      77         410 : }
      78             : 
      79             : //==============================================================================
      80             : 
      81         406 : Call::Call(const std::shared_ptr<Account>& account,
      82             :            const std::string& id,
      83             :            Call::CallType type,
      84         406 :            const std::map<std::string, std::string>& details)
      85         406 :     : id_(id)
      86         406 :     , type_(type)
      87         812 :     , account_(account)
      88             : {
      89         406 :     addStateListener([this](Call::CallState call_state,
      90             :                             Call::ConnectionState cnx_state,
      91       10381 :                             int code) {
      92        2096 :         checkPendingIM();
      93        2096 :         runOnMainThread([callWkPtr = weak()] {
      94        2096 :             if (auto call = callWkPtr.lock())
      95        2096 :                 call->checkAudio();
      96        2096 :         });
      97             : 
      98             :         // if call just started ringing, schedule call timeout
      99        2096 :         if (type_ == CallType::INCOMING and cnx_state == ConnectionState::RINGING) {
     100         108 :             auto timeout = Manager::instance().getRingingTimeout();
     101         108 :             JAMI_DBG("Scheduling call timeout in %d seconds", timeout);
     102             : 
     103         216 :             Manager::instance().scheduler().scheduleIn(
     104          84 :                 [callWkPtr = weak()] {
     105          84 :                     if (auto callShPtr = callWkPtr.lock()) {
     106           3 :                         if (callShPtr->getConnectionState() == Call::ConnectionState::RINGING) {
     107           1 :                             JAMI_DBG(
     108             :                                 "Call %s is still ringing after timeout, setting state to BUSY",
     109             :                                 callShPtr->getCallId().c_str());
     110           1 :                             callShPtr->hangup(PJSIP_SC_BUSY_HERE);
     111           1 :                             Manager::instance().callFailure(*callShPtr);
     112             :                         }
     113          84 :                     }
     114          84 :                 },
     115         216 :                 std::chrono::seconds(timeout));
     116             :         }
     117             : 
     118        2096 :         if (!isSubcall()) {
     119        1481 :             if (code == static_cast<int>(std::errc::no_such_device_or_address)) {
     120           1 :                 reason_ = "no_device";
     121             :             }
     122        1481 :             if (cnx_state == ConnectionState::CONNECTED && duration_start_ == time_point::min())
     123         204 :                 duration_start_ = clock::now();
     124        1277 :             else if (cnx_state == ConnectionState::DISCONNECTED && call_state == CallState::OVER) {
     125         456 :                 if (auto jamiAccount = std::dynamic_pointer_cast<JamiAccount>(getAccount().lock())) {
     126         208 :                     if (toUsername().find('/') == std::string::npos && getCallType() == CallType::OUTGOING) {
     127         111 :                         if (auto cm = jamiAccount->convModule(true))
     128         111 :                             cm->addCallHistoryMessage(getPeerNumber(), getCallDuration().count(), reason_);
     129             :                     }
     130         208 :                     monitor();
     131         228 :                 }
     132             :             }
     133             :         }
     134             : 
     135             :         // kill pending subcalls at disconnect
     136        2096 :         if (call_state == CallState::OVER)
     137         406 :             hangupCalls(safePopSubcalls(), 0);
     138             : 
     139        2096 :         return true;
     140             :     });
     141             : 
     142         406 :     time(&timestamp_start_);
     143         406 : }
     144             : 
     145         406 : Call::~Call() {}
     146             : 
     147             : void
     148         419 : Call::removeCall()
     149             : {
     150         419 :     auto this_ = shared_from_this();
     151         419 :     Manager::instance().callFactory.removeCall(*this);
     152         419 :     setState(CallState::OVER);
     153         419 :     if (Recordable::isRecording())
     154           3 :         Recordable::stopRecording();
     155         419 :     if (auto account = account_.lock())
     156         419 :         account->detach(this_);
     157         419 : }
     158             : 
     159             : std::string
     160        2589 : Call::getAccountId() const
     161             : {
     162        2589 :     if (auto shared = account_.lock())
     163        2589 :         return shared->getAccountID();
     164           4 :     JAMI_ERR("No account detected");
     165           4 :     return {};
     166             : }
     167             : 
     168             : Call::ConnectionState
     169        8039 : Call::getConnectionState() const
     170             : {
     171        8039 :     std::lock_guard lock(callMutex_);
     172        8038 :     return connectionState_;
     173        8039 : }
     174             : 
     175             : Call::CallState
     176        9708 : Call::getState() const
     177             : {
     178        9708 :     std::lock_guard lock(callMutex_);
     179        9707 :     return callState_;
     180        9708 : }
     181             : 
     182             : bool
     183         810 : Call::validStateTransition(CallState newState)
     184             : {
     185             :     // Notice to developper:
     186             :     // - list only permitted transition (return true)
     187             :     // - let non permitted ones as default case (return false)
     188             : 
     189             :     // always permited
     190         810 :     if (newState == CallState::OVER)
     191         406 :         return true;
     192             : 
     193         404 :     switch (callState_) {
     194         383 :     case CallState::INACTIVE:
     195         383 :         switch (newState) {
     196         383 :         case CallState::ACTIVE:
     197             :         case CallState::BUSY:
     198             :         case CallState::PEER_BUSY:
     199             :         case CallState::MERROR:
     200         383 :             return true;
     201           0 :         default: // INACTIVE, HOLD
     202           0 :             return false;
     203             :         }
     204             : 
     205          16 :     case CallState::ACTIVE:
     206          16 :         switch (newState) {
     207          16 :         case CallState::BUSY:
     208             :         case CallState::PEER_BUSY:
     209             :         case CallState::HOLD:
     210             :         case CallState::MERROR:
     211          16 :             return true;
     212           0 :         default: // INACTIVE, ACTIVE
     213           0 :             return false;
     214             :         }
     215             : 
     216           3 :     case CallState::HOLD:
     217           3 :         switch (newState) {
     218           3 :         case CallState::ACTIVE:
     219             :         case CallState::MERROR:
     220           3 :             return true;
     221           0 :         default: // INACTIVE, HOLD, BUSY, PEER_BUSY, MERROR
     222           0 :             return false;
     223             :         }
     224             : 
     225           0 :     case CallState::BUSY:
     226           0 :         switch (newState) {
     227           0 :         case CallState::MERROR:
     228           0 :             return true;
     229           0 :         default: // INACTIVE, ACTIVE, HOLD, BUSY, PEER_BUSY
     230           0 :             return false;
     231             :         }
     232             : 
     233           2 :     default: // MERROR
     234           2 :         return false;
     235             :     }
     236             : }
     237             : 
     238             : bool
     239        2309 : Call::setState(CallState call_state, ConnectionState cnx_state, signed code)
     240             : {
     241        2309 :     std::unique_lock<std::recursive_mutex> lock(callMutex_);
     242        2309 :     JAMI_DBG("[call:%s] state change %u/%u, cnx %u/%u, code %d",
     243             :              id_.c_str(),
     244             :              (unsigned) callState_,
     245             :              (unsigned) call_state,
     246             :              (unsigned) connectionState_,
     247             :              (unsigned) cnx_state,
     248             :              code);
     249             : 
     250        2309 :     if (callState_ != call_state) {
     251         810 :         if (not validStateTransition(call_state)) {
     252           2 :             JAMI_ERR("[call:%s] invalid call state transition from %u to %u",
     253             :                      id_.c_str(),
     254             :                      (unsigned) callState_,
     255             :                      (unsigned) call_state);
     256           2 :             return false;
     257             :         }
     258        1499 :     } else if (connectionState_ == cnx_state)
     259         211 :         return true; // no changes as no-op
     260             : 
     261             :     // Emit client state only if changed
     262        2096 :     auto old_client_state = getStateStr();
     263        2096 :     callState_ = call_state;
     264        2096 :     connectionState_ = cnx_state;
     265        2096 :     auto new_client_state = getStateStr();
     266             : 
     267        5132 :     for (auto it = stateChangedListeners_.begin(); it != stateChangedListeners_.end();) {
     268        3036 :         if ((*it)(callState_, connectionState_, code))
     269        2947 :             ++it;
     270             :         else
     271          89 :             it = stateChangedListeners_.erase(it);
     272             :     }
     273             : 
     274        2096 :     if (old_client_state != new_client_state) {
     275        1728 :         if (not parent_) {
     276        1201 :             JAMI_DBG("[call:%s] emit client call state change %s, code %d",
     277             :                      id_.c_str(),
     278             :                      new_client_state.c_str(),
     279             :                      code);
     280        1201 :             lock.unlock();
     281        2402 :             emitSignal<libjami::CallSignal::StateChange>(getAccountId(),
     282        1201 :                                                          id_,
     283             :                                                          new_client_state,
     284             :                                                          code);
     285             :         }
     286             :     }
     287             : 
     288        2096 :     return true;
     289        2309 : }
     290             : 
     291             : bool
     292         514 : Call::setState(CallState call_state, signed code)
     293             : {
     294         514 :     std::lock_guard lock(callMutex_);
     295        1028 :     return setState(call_state, connectionState_, code);
     296         514 : }
     297             : 
     298             : bool
     299        1310 : Call::setState(ConnectionState cnx_state, signed code)
     300             : {
     301        1310 :     std::lock_guard lock(callMutex_);
     302        2620 :     return setState(callState_, cnx_state, code);
     303        1310 : }
     304             : 
     305             : std::string
     306        8954 : Call::getStateStr() const
     307             : {
     308             :     using namespace libjami::Call;
     309             : 
     310        8954 :     switch (getState()) {
     311        3898 :     case CallState::ACTIVE:
     312        3898 :         switch (getConnectionState()) {
     313         890 :         case ConnectionState::PROGRESSING:
     314         890 :             return StateEvent::CONNECTING;
     315             : 
     316         907 :         case ConnectionState::RINGING:
     317         907 :             return isIncoming() ? StateEvent::INCOMING : StateEvent::RINGING;
     318             : 
     319         700 :         case ConnectionState::DISCONNECTED:
     320         700 :             return StateEvent::HUNGUP;
     321             : 
     322        1400 :         case ConnectionState::CONNECTED:
     323             :         default:
     324        1400 :             return StateEvent::CURRENT;
     325             :         }
     326             : 
     327          39 :     case CallState::HOLD:
     328          39 :         if (getConnectionState() == ConnectionState::DISCONNECTED)
     329          12 :             return StateEvent::HUNGUP;
     330          27 :         return StateEvent::HOLD;
     331             : 
     332           4 :     case CallState::BUSY:
     333           4 :         return StateEvent::BUSY;
     334             : 
     335           4 :     case CallState::PEER_BUSY:
     336           4 :         return StateEvent::PEER_BUSY;
     337             : 
     338        3521 :     case CallState::INACTIVE:
     339        3521 :         switch (getConnectionState()) {
     340        1221 :         case ConnectionState::PROGRESSING:
     341        1221 :             return StateEvent::CONNECTING;
     342             : 
     343         587 :         case ConnectionState::RINGING:
     344         587 :             return isIncoming() ? StateEvent::INCOMING : StateEvent::RINGING;
     345             : 
     346          59 :         case ConnectionState::CONNECTED:
     347          59 :             return StateEvent::CURRENT;
     348             : 
     349        1654 :         default:
     350        1654 :             return StateEvent::INACTIVE;
     351             :         }
     352             : 
     353        1087 :     case CallState::OVER:
     354        1087 :         return StateEvent::OVER;
     355             : 
     356         400 :     case CallState::MERROR:
     357             :     default:
     358         400 :         return StateEvent::FAILURE;
     359             :     }
     360             : }
     361             : 
     362             : bool
     363          17 : Call::toggleRecording()
     364             : {
     365          17 :     const bool startRecording = Recordable::toggleRecording();
     366          17 :     return startRecording;
     367             : }
     368             : 
     369             : std::map<std::string, std::string>
     370         395 : Call::getDetails() const
     371             : {
     372         395 :     auto conference = conf_.lock();
     373             :     return {
     374         395 :         {libjami::Call::Details::CALL_TYPE, std::to_string((unsigned) type_)},
     375         395 :         {libjami::Call::Details::PEER_NUMBER, peerNumber_},
     376         395 :         {libjami::Call::Details::DISPLAY_NAME, peerDisplayName_},
     377         790 :         {libjami::Call::Details::CALL_STATE, getStateStr()},
     378         790 :         {libjami::Call::Details::CONF_ID, conference ? conference->getConfId() : ""},
     379         790 :         {libjami::Call::Details::TIMESTAMP_START, std::to_string(timestamp_start_)},
     380         790 :         {libjami::Call::Details::ACCOUNTID, getAccountId()},
     381         395 :         {libjami::Call::Details::TO_USERNAME, toUsername()},
     382             :         {libjami::Call::Details::AUDIO_MUTED,
     383         790 :          std::string(bool_to_str(isCaptureDeviceMuted(MediaType::MEDIA_AUDIO)))},
     384             :         {libjami::Call::Details::VIDEO_MUTED,
     385         790 :          std::string(bool_to_str(isCaptureDeviceMuted(MediaType::MEDIA_VIDEO)))},
     386         790 :         {libjami::Call::Details::AUDIO_ONLY, std::string(bool_to_str(not hasVideo()))},
     387        8295 :     };
     388         395 : }
     389             : 
     390             : void
     391         190 : Call::onTextMessage(std::map<std::string, std::string>&& messages)
     392             : {
     393         190 :     auto it = messages.find("application/confInfo+json");
     394         190 :     if (it != messages.end()) {
     395         184 :         setConferenceInfo(it->second);
     396         184 :         return;
     397             :     }
     398             : 
     399           6 :     it = messages.find("application/confOrder+json");
     400           6 :     if (it != messages.end()) {
     401           6 :         if (auto conf = conf_.lock())
     402           6 :             conf->onConfOrder(getCallId(), it->second);
     403           0 :         return;
     404             :     }
     405             : 
     406             :     {
     407           0 :         std::lock_guard lk {callMutex_};
     408           0 :         if (parent_) {
     409           0 :             pendingInMessages_.emplace_back(std::move(messages), "");
     410           0 :             return;
     411             :         }
     412           0 :     }
     413             : #ifdef ENABLE_PLUGIN
     414           0 :     auto& pluginChatManager = Manager::instance().getJamiPluginManager().getChatServicesManager();
     415           0 :     if (pluginChatManager.hasHandlers()) {
     416           0 :         pluginChatManager.publishMessage(
     417           0 :             std::make_shared<JamiMessage>(getAccountId(), getPeerNumber(), true, messages, false));
     418             :     }
     419             : #endif
     420           0 :     Manager::instance().incomingMessage(getAccountId(), getCallId(), getPeerNumber(), messages);
     421             : }
     422             : 
     423             : void
     424         106 : Call::peerHungup()
     425             : {
     426         106 :     const auto state = getState();
     427         106 :     const auto aborted = state == CallState::ACTIVE or state == CallState::HOLD;
     428         106 :     setState(ConnectionState::DISCONNECTED, aborted ? ECONNABORTED : ECONNREFUSED);
     429         106 : }
     430             : 
     431             : void
     432         178 : Call::addSubCall(Call& subcall)
     433             : {
     434         178 :     std::lock_guard lk {callMutex_};
     435             : 
     436             :     // Add subCall only if call is not connected or terminated
     437             :     // Because we only want to addSubCall if the peer didn't answer
     438             :     // So till it's <= RINGING
     439         178 :     if (connectionState_ == ConnectionState::CONNECTED
     440         178 :         || connectionState_ == ConnectionState::DISCONNECTED || callState_ == CallState::OVER) {
     441           0 :         subcall.removeCall();
     442           0 :         return;
     443             :     }
     444             : 
     445         178 :     if (not subcalls_.emplace(getPtr(subcall)).second) {
     446           0 :         JAMI_ERR("[call:%s] add twice subcall %s", getCallId().c_str(), subcall.getCallId().c_str());
     447           0 :         return;
     448             :     }
     449             : 
     450         178 :     JAMI_DBG("[call:%s] add subcall %s", getCallId().c_str(), subcall.getCallId().c_str());
     451         178 :     subcall.parent_ = getPtr(*this);
     452             : 
     453         178 :     for (const auto& msg : pendingOutMessages_)
     454           0 :         subcall.sendTextMessage(msg.first, msg.second);
     455             : 
     456         178 :     subcall.addStateListener(
     457         615 :         [sub = subcall.weak(), parent = weak()](Call::CallState new_state,
     458             :                                                 Call::ConnectionState new_cstate,
     459             :                                                 int /* code */) {
     460         615 :             runOnMainThread([sub, parent, new_state, new_cstate]() {
     461         615 :                 if (auto p = parent.lock()) {
     462         607 :                     if (auto s = sub.lock()) {
     463         505 :                         p->subcallStateChanged(*s, new_state, new_cstate);
     464         607 :                     }
     465         615 :                 }
     466         615 :             });
     467         615 :             return true;
     468             :         });
     469         178 : }
     470             : 
     471             : /// Called by a subcall when its states change (multidevice)
     472             : ///
     473             : /// Its purpose is to manage per device call and try to found the first responding.
     474             : /// Parent call states are managed by these subcalls.
     475             : /// \note this method may decrease the given \a subcall ref count.
     476             : void
     477         505 : Call::subcallStateChanged(Call& subcall, Call::CallState new_state, Call::ConnectionState new_cstate)
     478             : {
     479             :     {
     480             :         // This condition happens when a subcall hangups/fails after removed from parent's list.
     481             :         // This is normal to keep parent_ != nullptr on the subcall, as it's the way to flag it
     482             :         // as an subcall and not a master call.
     483             :         // XXX: having a way to unsubscribe the state listener could be better than such test
     484         505 :         std::lock_guard lk {callMutex_};
     485         505 :         auto sit = subcalls_.find(getPtr(subcall));
     486         505 :         if (sit == subcalls_.end())
     487          79 :             return;
     488         505 :     }
     489             : 
     490             :     // We found a responding device: hangup all other subcalls and merge
     491         426 :     if (new_state == CallState::ACTIVE and new_cstate == ConnectionState::CONNECTED) {
     492          78 :         JAMI_DBG("[call:%s] subcall %s answered by peer",
     493             :                  getCallId().c_str(),
     494             :                  subcall.getCallId().c_str());
     495             : 
     496         156 :         hangupCallsIf(safePopSubcalls(), 0, [&](const Call* call) { return call != &subcall; });
     497          78 :         merge(subcall);
     498          78 :         Manager::instance().peerAnsweredCall(*this);
     499          78 :         return;
     500             :     }
     501             : 
     502             :     // Hangup the call if any device hangup or send busy
     503         348 :     if ((new_state == CallState::ACTIVE or new_state == CallState::PEER_BUSY)
     504         175 :         and new_cstate == ConnectionState::DISCONNECTED) {
     505           4 :         JAMI_WARN("[call:%s] subcall %s hangup by peer",
     506             :                   getCallId().c_str(),
     507             :                   subcall.getCallId().c_str());
     508           4 :         reason_ = new_state == CallState::ACTIVE ? "declined" : "busy";
     509           4 :         hangupCalls(safePopSubcalls(), 0);
     510           4 :         Manager::instance().peerHungupCall(*this);
     511           4 :         removeCall();
     512           4 :         return;
     513             :     }
     514             : 
     515             :     // Subcall is busy or failed
     516         344 :     if (new_state >= CallState::BUSY) {
     517          86 :         if (new_state == CallState::BUSY || new_state == CallState::PEER_BUSY)
     518           0 :             JAMI_WARN("[call:%s] subcall %s busy", getCallId().c_str(), subcall.getCallId().c_str());
     519             :         else
     520          86 :             JAMI_WARN("[call:%s] subcall %s failed",
     521             :                       getCallId().c_str(),
     522             :                       subcall.getCallId().c_str());
     523          86 :         std::lock_guard lk {callMutex_};
     524          86 :         subcalls_.erase(getPtr(subcall));
     525             : 
     526             :         // Parent call fails if last subcall is busy or failed
     527          86 :         if (subcalls_.empty()) {
     528           2 :             if (new_state == CallState::BUSY) {
     529           0 :                 setState(CallState::BUSY,
     530             :                          ConnectionState::DISCONNECTED,
     531             :                          static_cast<int>(std::errc::device_or_resource_busy));
     532           2 :             } else if (new_state == CallState::PEER_BUSY) {
     533           0 :                 setState(CallState::PEER_BUSY,
     534             :                          ConnectionState::DISCONNECTED,
     535             :                          static_cast<int>(std::errc::device_or_resource_busy));
     536             :             } else {
     537             :                 // XXX: first idea was to use std::errc::host_unreachable, but it's not available on
     538             :                 // some platforms like mingw.
     539           2 :                 setState(CallState::MERROR,
     540             :                          ConnectionState::DISCONNECTED,
     541             :                          static_cast<int>(std::errc::io_error));
     542             :             }
     543           2 :             removeCall();
     544             :         } else {
     545          84 :             JAMI_DBG("[call:%s] remains %zu subcall(s)", getCallId().c_str(), subcalls_.size());
     546             :         }
     547             : 
     548          86 :         return;
     549          86 :     }
     550             : 
     551             :     // Copy call/cnx states (forward only)
     552         258 :     if (new_state == CallState::ACTIVE && callState_ == CallState::INACTIVE) {
     553          86 :         setState(new_state);
     554             :     }
     555         258 :     if (static_cast<unsigned>(connectionState_) < static_cast<unsigned>(new_cstate)
     556         169 :         and static_cast<unsigned>(new_cstate) <= static_cast<unsigned>(ConnectionState::RINGING)) {
     557         169 :         setState(new_cstate);
     558             :     }
     559             : }
     560             : 
     561             : /// Replace current call data with ones from the given \a subcall.
     562             : /// Must be called while locked by subclass
     563             : void
     564          78 : Call::merge(Call& subcall)
     565             : {
     566          78 :     JAMI_DBG("[call:%s] merge subcall %s", getCallId().c_str(), subcall.getCallId().c_str());
     567             : 
     568             :     // Merge data
     569          78 :     pendingInMessages_ = std::move(subcall.pendingInMessages_);
     570          78 :     if (peerNumber_.empty())
     571           0 :         peerNumber_ = std::move(subcall.peerNumber_);
     572          78 :     peerDisplayName_ = std::move(subcall.peerDisplayName_);
     573          78 :     setState(subcall.getState(), subcall.getConnectionState());
     574             : 
     575          78 :     std::weak_ptr<Call> subCallWeak = subcall.shared_from_this();
     576          78 :     runOnMainThread([subCallWeak] {
     577          78 :         if (auto subcall = subCallWeak.lock())
     578          78 :             subcall->removeCall();
     579          78 :     });
     580          78 : }
     581             : 
     582             : /// Handle pending IM message
     583             : ///
     584             : /// Used in multi-device context to send pending IM when the master call is connected.
     585             : void
     586        2096 : Call::checkPendingIM()
     587             : {
     588        2096 :     std::lock_guard lk {callMutex_};
     589             : 
     590        2096 :     auto state = getStateStr();
     591             :     // Let parent call handles IM after the merge
     592        2096 :     if (not parent_) {
     593        1481 :         if (state == libjami::Call::StateEvent::CURRENT) {
     594         208 :             for (const auto& msg : pendingInMessages_)
     595           0 :                 Manager::instance().incomingMessage(getAccountId(),
     596             :                                                     getCallId(),
     597             :                                                     getPeerNumber(),
     598           0 :                                                     msg.first);
     599         208 :             pendingInMessages_.clear();
     600             : 
     601         208 :             std::weak_ptr<Call> callWkPtr = shared_from_this();
     602         208 :             runOnMainThread([callWkPtr, pending = std::move(pendingOutMessages_)] {
     603         208 :                 if (auto call = callWkPtr.lock())
     604         208 :                     for (const auto& msg : pending)
     605         208 :                         call->sendTextMessage(msg.first, msg.second);
     606         208 :             });
     607         208 :         }
     608             :     }
     609        2096 : }
     610             : 
     611             : /// Handle tones for RINGING and BUSY calls
     612             : ///
     613             : void
     614        1706 : Call::checkAudio()
     615             : {
     616             :     using namespace libjami::Call;
     617             : 
     618        1706 :     auto state = getStateStr();
     619        1706 :     if (state == StateEvent::RINGING) {
     620         203 :         Manager::instance().peerRingingCall(*this);
     621        1503 :     } else if (state == StateEvent::BUSY) {
     622           1 :         Manager::instance().callBusy(*this);
     623             :     }
     624        1706 : }
     625             : 
     626             : // Helper to safely pop subcalls list
     627             : Call::SubcallSet
     628         488 : Call::safePopSubcalls()
     629             : {
     630         488 :     std::lock_guard lk {callMutex_};
     631             :     // std::exchange is C++14
     632         488 :     auto old_value = std::move(subcalls_);
     633         488 :     subcalls_.clear();
     634         976 :     return old_value;
     635         488 : }
     636             : 
     637             : void
     638         184 : Call::setConferenceInfo(const std::string& msg)
     639             : {
     640         184 :     ConfInfo newInfo;
     641         184 :     Json::Value json;
     642         184 :     std::string err;
     643         184 :     Json::CharReaderBuilder rbuilder;
     644         184 :     auto reader = std::unique_ptr<Json::CharReader>(rbuilder.newCharReader());
     645         184 :     if (reader->parse(msg.data(), msg.data() + msg.size(), &json, &err)) {
     646         184 :         if (json.isObject()) {
     647             :             // new confInfo
     648         184 :             if (json.isMember("p")) {
     649         742 :                 for (const auto& participantInfo : json["p"]) {
     650         562 :                     ParticipantInfo pInfo;
     651         562 :                     if (!participantInfo.isMember("uri"))
     652           0 :                         continue;
     653         562 :                     pInfo.fromJson(participantInfo);
     654         562 :                     newInfo.emplace_back(pInfo);
     655         562 :                 }
     656             :             }
     657         184 :             if (json.isMember("v")) {
     658         180 :                 newInfo.v = json["v"].asInt();
     659         180 :                 peerConfProtocol_ = newInfo.v;
     660             :             }
     661         184 :             if (json.isMember("w"))
     662         180 :                 newInfo.w = json["w"].asInt();
     663         184 :             if (json.isMember("h"))
     664         180 :                 newInfo.h = json["h"].asInt();
     665             :         } else {
     666             :             // old confInfo
     667           0 :             for (const auto& participantInfo : json) {
     668           0 :                 ParticipantInfo pInfo;
     669           0 :                 if (!participantInfo.isMember("uri"))
     670           0 :                     continue;
     671           0 :                 pInfo.fromJson(participantInfo);
     672           0 :                 newInfo.emplace_back(pInfo);
     673           0 :             }
     674             :         }
     675             :     }
     676             : 
     677             :     {
     678         184 :         std::lock_guard lk(confInfoMutex_);
     679         184 :         if (not isConferenceParticipant()) {
     680             :             // confID_ empty -> participant set confInfo with the received one
     681         184 :             confInfo_ = std::move(newInfo);
     682             : 
     683             :             // Create sink for each participant
     684             : #ifdef ENABLE_VIDEO
     685         184 :             createSinks(confInfo_);
     686             : #endif
     687             :             // Inform client that layout has changed
     688         184 :             jami::emitSignal<libjami::CallSignal::OnConferenceInfosUpdated>(
     689         368 :                 id_, confInfo_.toVectorMapStringString());
     690           0 :         } else if (auto conf = conf_.lock()) {
     691           0 :             conf->mergeConfInfo(newInfo, getPeerNumber());
     692           0 :         }
     693         184 :     }
     694         184 : }
     695             : 
     696             : void
     697           6 : Call::sendConfOrder(const Json::Value& root)
     698             : {
     699           6 :     std::map<std::string, std::string> messages;
     700           6 :     Json::StreamWriterBuilder wbuilder;
     701           6 :     wbuilder["commentStyle"] = "None";
     702           6 :     wbuilder["indentation"] = "";
     703           6 :     messages["application/confOrder+json"] = Json::writeString(wbuilder, root);
     704             : 
     705           6 :     auto w = getAccount();
     706           6 :     auto account = w.lock();
     707           6 :     if (account)
     708           6 :         sendTextMessage(messages, account->getFromUri());
     709           6 : }
     710             : 
     711             : void
     712         189 : Call::sendConfInfo(const std::string& json)
     713             : {
     714         189 :     std::map<std::string, std::string> messages;
     715         186 :     Json::StreamWriterBuilder wbuilder;
     716         189 :     wbuilder["commentStyle"] = "None";
     717         187 :     wbuilder["indentation"] = "";
     718         187 :     messages["application/confInfo+json"] = json;
     719             : 
     720         189 :     auto w = getAccount();
     721         186 :     auto account = w.lock();
     722         187 :     if (account)
     723         185 :         sendTextMessage(messages, account->getFromUri());
     724         188 : }
     725             : 
     726             : void
     727           4 : Call::resetConfInfo()
     728             : {
     729           4 :     sendConfInfo("{}");
     730           4 : }
     731             : 
     732             : } // namespace jami

Generated by: LCOV version 1.14