LCOV - code coverage report
Current view: top level - foo/src - call.cpp (source / functions) Hit Total Coverage
Test: jami-coverage-filtered.info Lines: 326 376 86.7 %
Date: 2026-02-28 10:41:24 Functions: 42 44 95.5 %

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

Generated by: LCOV version 1.14