LCOV - code coverage report
Current view: top level - src - call.cpp (source / functions) Hit Total Coverage
Test: jami-coverage-filtered.info Lines: 343 390 87.9 %
Date: 2024-12-21 08:56:24 Functions: 38 40 95.0 %

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

Generated by: LCOV version 1.14