LCOV - code coverage report
Current view: top level - foo/src - call.cpp (source / functions) Hit Total Coverage
Test: jami-coverage-filtered.info Lines: 334 381 87.7 %
Date: 2025-08-24 09:11:10 Functions: 38 40 95.0 %

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

Generated by: LCOV version 1.14