LCOV - code coverage report
Current view: top level - foo/src - call.cpp (source / functions) Hit Total Coverage
Test: jami-coverage-filtered.info Lines: 329 376 87.5 %
Date: 2026-01-22 10:39:23 Functions: 38 40 95.0 %

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

Generated by: LCOV version 1.14