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

Generated by: LCOV version 1.14