1 /*
2  * CallModel.cpp
3  * Copyright (C) 2017  Belledonne Communications, Grenoble, France
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License
7  * as published by the Free Software Foundation; either version 2
8  * of the License, or (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18  *
19  *  Created on: February 2, 2017
20  *      Author: Ronan Abhamon
21  */
22 
23 #include <QDateTime>
24 #include <QTimer>
25 
26 #include "../../app/App.hpp"
27 #include "../../utils/LinphoneUtils.hpp"
28 #include "../../utils/Utils.hpp"
29 #include "../core/CoreManager.hpp"
30 
31 #include "CallModel.hpp"
32 
33 #define AUTO_ANSWER_OBJECT_NAME "auto-answer-timer"
34 
35 using namespace std;
36 
37 // =============================================================================
38 
CallModel(shared_ptr<linphone::Call> call)39 CallModel::CallModel (shared_ptr<linphone::Call> call) {
40   Q_CHECK_PTR(call);
41   mCall = call;
42   mCall->setData("call-model", *this);
43 
44   updateIsInConference();
45 
46   // Deal with auto-answer.
47   {
48     SettingsModel *settings = CoreManager::getInstance()->getSettingsModel();
49 
50     if (settings->getAutoAnswerStatus()) {
51       QTimer *timer = new QTimer(this);
52       timer->setInterval(settings->getAutoAnswerDelay());
53       timer->setSingleShot(true);
54       timer->setObjectName(AUTO_ANSWER_OBJECT_NAME);
55 
56       QObject::connect(timer, &QTimer::timeout, this, &CallModel::acceptWithAutoAnswerDelay);
57       timer->start();
58     }
59   }
60 
61   QObject::connect(
62     CoreManager::getInstance()->getHandlers().get(), &CoreHandlers::callStateChanged,
63     this, &CallModel::handleCallStateChanged
64   );
65 }
66 
~CallModel()67 CallModel::~CallModel () {
68   mCall->unsetData("call-model");
69 }
70 
71 // -----------------------------------------------------------------------------
72 
getSipAddress() const73 QString CallModel::getSipAddress () const {
74   return ::Utils::coreStringToAppString(mCall->getRemoteAddress()->asStringUriOnly());
75 }
76 
77 // -----------------------------------------------------------------------------
78 
setRecordFile(shared_ptr<linphone::CallParams> & callParams)79 void CallModel::setRecordFile (shared_ptr<linphone::CallParams> &callParams) {
80   callParams->setRecordFile(
81     ::Utils::appStringToCoreString(
82       QStringLiteral("%1%2.mkv")
83       .arg(CoreManager::getInstance()->getSettingsModel()->getSavedVideosFolder())
84       .arg(QDateTime::currentDateTime().toString("yyyy-MM-dd_hh:mm:ss"))
85     )
86   );
87 }
88 
updateStats(const shared_ptr<const linphone::CallStats> & callStats)89 void CallModel::updateStats (const shared_ptr<const linphone::CallStats> &callStats) {
90   switch (callStats->getType()) {
91     case linphone::StreamTypeText:
92     case linphone::StreamTypeUnknown:
93       break;
94 
95     case linphone::StreamTypeAudio:
96       updateStats(callStats, mAudioStats);
97       break;
98     case linphone::StreamTypeVideo:
99       updateStats(callStats, mVideoStats);
100       break;
101   }
102 
103   emit statsUpdated();
104 }
105 
106 // -----------------------------------------------------------------------------
107 
notifyCameraFirstFrameReceived(unsigned int width,unsigned int height)108 void CallModel::notifyCameraFirstFrameReceived (unsigned int width, unsigned int height) {
109   if (mNotifyCameraFirstFrameReceived) {
110     mNotifyCameraFirstFrameReceived = false;
111     emit cameraFirstFrameReceived(width, height);
112   }
113 }
114 
115 // -----------------------------------------------------------------------------
116 
accept()117 void CallModel::accept () {
118   stopAutoAnswerTimer();
119 
120   shared_ptr<linphone::Core> core = CoreManager::getInstance()->getCore();
121   shared_ptr<linphone::CallParams> params = core->createCallParams(mCall);
122   params->enableVideo(false);
123   setRecordFile(params);
124 
125   App::smartShowWindow(App::getInstance()->getCallsWindow());
126   mCall->acceptWithParams(params);
127 }
128 
acceptWithVideo()129 void CallModel::acceptWithVideo () {
130   stopAutoAnswerTimer();
131 
132   shared_ptr<linphone::Core> core = CoreManager::getInstance()->getCore();
133   shared_ptr<linphone::CallParams> params = core->createCallParams(mCall);
134   params->enableVideo(true);
135   setRecordFile(params);
136 
137   App::smartShowWindow(App::getInstance()->getCallsWindow());
138   mCall->acceptWithParams(params);
139 }
140 
terminate()141 void CallModel::terminate () {
142   CoreManager *core = CoreManager::getInstance();
143 
144   core->lockVideoRender();
145   mCall->terminate();
146   core->unlockVideoRender();
147 }
148 
149 // -----------------------------------------------------------------------------
150 
askForTransfer()151 void CallModel::askForTransfer () {
152   CoreManager::getInstance()->getCallsListModel()->askForTransfer(this);
153 }
154 
transferTo(const QString & sipAddress)155 bool CallModel::transferTo (const QString &sipAddress) {
156   bool status = !!mCall->transfer(::Utils::appStringToCoreString(sipAddress));
157   if (status)
158     qWarning() << QStringLiteral("Unable to transfer: `%1`.").arg(sipAddress);
159   return status;
160 }
161 
162 // -----------------------------------------------------------------------------
163 
acceptVideoRequest()164 void CallModel::acceptVideoRequest () {
165   shared_ptr<linphone::Core> core = CoreManager::getInstance()->getCore();
166   shared_ptr<linphone::CallParams> params = core->createCallParams(mCall);
167   params->enableVideo(true);
168 
169   mCall->acceptUpdate(params);
170 }
171 
rejectVideoRequest()172 void CallModel::rejectVideoRequest () {
173   mCall->acceptUpdate(mCall->getCurrentParams());
174 }
175 
takeSnapshot()176 void CallModel::takeSnapshot () {
177   static QString oldName;
178   QString newName = QDateTime::currentDateTime().toString("yyyy-MM-dd_hh:mm:ss") + ".jpg";
179 
180   if (newName == oldName) {
181     qWarning() << QStringLiteral("Unable to take snapshot. Wait one second.");
182     return;
183   }
184 
185   oldName = newName;
186 
187   qInfo() << QStringLiteral("Take snapshot of call:") << this;
188 
189   const QString filePath = CoreManager::getInstance()->getSettingsModel()->getSavedScreenshotsFolder() + newName;
190   mCall->takeVideoSnapshot(::Utils::appStringToCoreString(filePath));
191   App::getInstance()->getNotifier()->notifySnapshotWasTaken(filePath);
192 }
193 
startRecording()194 void CallModel::startRecording () {
195   if (mRecording)
196     return;
197 
198   qInfo() << QStringLiteral("Start recording call:") << this;
199 
200   mCall->startRecording();
201   mRecording = true;
202 
203   emit recordingChanged(true);
204 }
205 
stopRecording()206 void CallModel::stopRecording () {
207   if (!mRecording)
208     return;
209 
210   qInfo() << QStringLiteral("Stop recording call:") << this;
211 
212   mRecording = false;
213   mCall->stopRecording();
214 
215   App::getInstance()->getNotifier()->notifyRecordingCompleted(
216     ::Utils::coreStringToAppString(mCall->getParams()->getRecordFile())
217   );
218 
219   emit recordingChanged(false);
220 }
221 
222 // -----------------------------------------------------------------------------
223 
handleCallStateChanged(const shared_ptr<linphone::Call> & call,linphone::CallState state)224 void CallModel::handleCallStateChanged (const shared_ptr<linphone::Call> &call, linphone::CallState state) {
225   if (call != mCall)
226     return;
227 
228   updateIsInConference();
229 
230   switch (state) {
231     case linphone::CallStateError:
232     case linphone::CallStateEnd:
233       setCallErrorFromReason(call->getReason());
234       stopAutoAnswerTimer();
235       mPausedByRemote = false;
236       break;
237 
238     case linphone::CallStateConnected:
239     case linphone::CallStateRefered:
240     case linphone::CallStateReleased:
241     case linphone::CallStateStreamsRunning:
242       mPausedByRemote = false;
243       break;
244 
245     case linphone::CallStatePausedByRemote:
246       mNotifyCameraFirstFrameReceived = true;
247       mPausedByRemote = true;
248       break;
249 
250     case linphone::CallStatePausing:
251       mNotifyCameraFirstFrameReceived = true;
252       mPausedByUser = true;
253       break;
254 
255     case linphone::CallStateResuming:
256       mPausedByUser = false;
257       break;
258 
259     case linphone::CallStateUpdatedByRemote:
260       if (!mCall->getCurrentParams()->videoEnabled() && mCall->getRemoteParams()->videoEnabled()) {
261         mCall->deferUpdate();
262         emit videoRequested();
263       }
264 
265       break;
266 
267     case linphone::CallStateIdle:
268     case linphone::CallStateIncomingReceived:
269     case linphone::CallStateOutgoingInit:
270     case linphone::CallStateOutgoingProgress:
271     case linphone::CallStateOutgoingRinging:
272     case linphone::CallStateOutgoingEarlyMedia:
273     case linphone::CallStatePaused:
274     case linphone::CallStateIncomingEarlyMedia:
275     case linphone::CallStateUpdating:
276     case linphone::CallStateEarlyUpdatedByRemote:
277     case linphone::CallStateEarlyUpdating:
278       break;
279   }
280 
281   emit securityUpdated();
282   emit statusChanged(getStatus());
283 }
284 
285 // -----------------------------------------------------------------------------
286 
updateIsInConference()287 void CallModel::updateIsInConference () {
288   if (mIsInConference != mCall->getParams()->getLocalConferenceMode()) {
289     mIsInConference = !mIsInConference;
290     emit isInConferenceChanged(mIsInConference);
291   }
292 }
293 
294 // -----------------------------------------------------------------------------
295 
stopAutoAnswerTimer() const296 void CallModel::stopAutoAnswerTimer () const {
297   QTimer *timer = findChild<QTimer *>(AUTO_ANSWER_OBJECT_NAME, Qt::FindDirectChildrenOnly);
298   if (timer) {
299     timer->stop();
300     timer->deleteLater();
301   }
302 }
303 
304 // -----------------------------------------------------------------------------
305 
getStatus() const306 CallModel::CallStatus CallModel::getStatus () const {
307   switch (mCall->getState()) {
308     case linphone::CallStateConnected:
309     case linphone::CallStateStreamsRunning:
310       return CallStatusConnected;
311 
312     case linphone::CallStateEnd:
313     case linphone::CallStateError:
314     case linphone::CallStateRefered:
315     case linphone::CallStateReleased:
316       return CallStatusEnded;
317 
318     case linphone::CallStatePaused:
319     case linphone::CallStatePausedByRemote:
320     case linphone::CallStatePausing:
321     case linphone::CallStateResuming:
322       return CallStatusPaused;
323 
324     case linphone::CallStateUpdating:
325     case linphone::CallStateUpdatedByRemote:
326       return mPausedByRemote ? CallStatusPaused : CallStatusConnected;
327 
328     case linphone::CallStateEarlyUpdatedByRemote:
329     case linphone::CallStateEarlyUpdating:
330     case linphone::CallStateIdle:
331     case linphone::CallStateIncomingEarlyMedia:
332     case linphone::CallStateIncomingReceived:
333     case linphone::CallStateOutgoingEarlyMedia:
334     case linphone::CallStateOutgoingInit:
335     case linphone::CallStateOutgoingProgress:
336     case linphone::CallStateOutgoingRinging:
337       break;
338   }
339 
340   return mCall->getDir() == linphone::CallDirIncoming ? CallStatusIncoming : CallStatusOutgoing;
341 }
342 
343 // -----------------------------------------------------------------------------
344 
acceptWithAutoAnswerDelay()345 void CallModel::acceptWithAutoAnswerDelay () {
346   // Use auto-answer if activated and it's the only call.
347   CoreManager *coreManager = CoreManager::getInstance();
348   if (coreManager->getSettingsModel()->getAutoAnswerStatus() && coreManager->getCore()->getCallsNb() == 1)
349     accept();
350 }
351 
352 // -----------------------------------------------------------------------------
353 
getCallError() const354 QString CallModel::getCallError () const {
355   return mCallError;
356 }
357 
setCallErrorFromReason(linphone::Reason reason)358 void CallModel::setCallErrorFromReason (linphone::Reason reason) {
359   switch (reason) {
360     case linphone::ReasonDeclined:
361       mCallError = tr("callErrorDeclined");
362       break;
363     case linphone::ReasonNotFound:
364       mCallError = tr("callErrorNotFound");
365       break;
366     case linphone::ReasonBusy:
367       mCallError = tr("callErrorBusy");
368       break;
369     case linphone::ReasonNotAcceptable:
370       mCallError = tr("callErrorNotAcceptable");
371       break;
372     default:
373       break;
374   }
375 
376   if (!mCallError.isEmpty())
377     qInfo() << QStringLiteral("Call terminated with error (%1):").arg(mCallError) << this;
378 
379   emit callErrorChanged(mCallError);
380 }
381 
382 // -----------------------------------------------------------------------------
383 
getDuration() const384 int CallModel::getDuration () const {
385   return mCall->getDuration();
386 }
387 
getQuality() const388 float CallModel::getQuality () const {
389   return mCall->getCurrentQuality();
390 }
391 
392 // -----------------------------------------------------------------------------
393 
getMicroVu() const394 float CallModel::getMicroVu () const {
395   return LinphoneUtils::computeVu(mCall->getRecordVolume());
396 }
397 
getSpeakerVu() const398 float CallModel::getSpeakerVu () const {
399   return LinphoneUtils::computeVu(mCall->getPlayVolume());
400 }
401 
402 // -----------------------------------------------------------------------------
403 
getMicroMuted() const404 bool CallModel::getMicroMuted () const {
405   return !CoreManager::getInstance()->getCore()->micEnabled();
406 }
407 
setMicroMuted(bool status)408 void CallModel::setMicroMuted (bool status) {
409   shared_ptr<linphone::Core> core = CoreManager::getInstance()->getCore();
410 
411   if (status == core->micEnabled()) {
412     core->enableMic(!status);
413     emit microMutedChanged(status);
414   }
415 }
416 
417 // -----------------------------------------------------------------------------
418 
getPausedByUser() const419 bool CallModel::getPausedByUser () const {
420   return mPausedByUser;
421 }
422 
setPausedByUser(bool status)423 void CallModel::setPausedByUser (bool status) {
424   switch (mCall->getState()) {
425     case linphone::CallStateConnected:
426     case linphone::CallStateStreamsRunning:
427     case linphone::CallStatePaused:
428     case linphone::CallStatePausedByRemote:
429       break;
430     default: return;
431   }
432 
433   if (status) {
434     if (!mPausedByUser)
435       mCall->pause();
436 
437     return;
438   }
439 
440   if (mPausedByUser)
441     mCall->resume();
442 }
443 
444 // -----------------------------------------------------------------------------
445 
getVideoEnabled() const446 bool CallModel::getVideoEnabled () const {
447   shared_ptr<const linphone::CallParams> params = mCall->getCurrentParams();
448   return params && params->videoEnabled() && getStatus() == CallStatusConnected;
449 }
450 
setVideoEnabled(bool status)451 void CallModel::setVideoEnabled (bool status) {
452   shared_ptr<linphone::Core> core = CoreManager::getInstance()->getCore();
453   if (!core->videoSupported()) {
454     qWarning() << QStringLiteral("Unable to update video call property. (Video not supported.)");
455     return;
456   }
457 
458   switch (mCall->getState()) {
459     case linphone::CallStateConnected:
460     case linphone::CallStateStreamsRunning:
461       break;
462     default: return;
463   }
464 
465   if (status == getVideoEnabled())
466     return;
467 
468   shared_ptr<linphone::CallParams> params = core->createCallParams(mCall);
469   params->enableVideo(status);
470 
471   mCall->update(params);
472 }
473 
474 // -----------------------------------------------------------------------------
475 
getUpdating() const476 bool CallModel::getUpdating () const {
477   switch (mCall->getState()) {
478     case linphone::CallStateConnected:
479     case linphone::CallStateStreamsRunning:
480     case linphone::CallStatePaused:
481     case linphone::CallStatePausedByRemote:
482       return false;
483 
484     default:
485       break;
486   }
487 
488   return true;
489 }
490 
getRecording() const491 bool CallModel::getRecording () const {
492   return mRecording;
493 }
494 
495 // -----------------------------------------------------------------------------
496 
sendDtmf(const QString & dtmf)497 void CallModel::sendDtmf (const QString &dtmf) {
498   qInfo() << QStringLiteral("Send dtmf: `%1`.").arg(dtmf);
499   mCall->sendDtmf(dtmf.constData()[0].toLatin1());
500 }
501 
502 // -----------------------------------------------------------------------------
503 
verifyAuthenticationToken(bool verify)504 void CallModel::verifyAuthenticationToken (bool verify) {
505   mCall->setAuthenticationTokenVerified(verify);
506   emit securityUpdated();
507 }
508 
509 // -----------------------------------------------------------------------------
510 
getEncryption() const511 CallModel::CallEncryption CallModel::getEncryption () const {
512   return static_cast<CallEncryption>(mCall->getCurrentParams()->getMediaEncryption());
513 }
514 
isSecured() const515 bool CallModel::isSecured () const {
516   shared_ptr<const linphone::CallParams> params = mCall->getCurrentParams();
517   linphone::MediaEncryption encryption = params->getMediaEncryption();
518   return (
519     encryption == linphone::MediaEncryptionZRTP && mCall->getAuthenticationTokenVerified()
520   ) || encryption == linphone::MediaEncryptionSRTP || encryption == linphone::MediaEncryptionDTLS;
521 }
522 
523 // -----------------------------------------------------------------------------
524 
getLocalSas() const525 QString CallModel::getLocalSas () const {
526   QString token = ::Utils::coreStringToAppString(mCall->getAuthenticationToken());
527   return mCall->getDir() == linphone::CallDirIncoming ? token.left(2).toUpper() : token.right(2).toUpper();
528 }
529 
getRemoteSas() const530 QString CallModel::getRemoteSas () const {
531   QString token = ::Utils::coreStringToAppString(mCall->getAuthenticationToken());
532   return mCall->getDir() != linphone::CallDirIncoming ? token.left(2).toUpper() : token.right(2).toUpper();
533 }
534 
535 // -----------------------------------------------------------------------------
536 
getSecuredString() const537 QString CallModel::getSecuredString () const {
538   switch (mCall->getCurrentParams()->getMediaEncryption()) {
539     case linphone::MediaEncryptionSRTP:
540       return QStringLiteral("SRTP");
541     case linphone::MediaEncryptionZRTP:
542       return QStringLiteral("ZRTP");
543     case linphone::MediaEncryptionDTLS:
544       return QStringLiteral("DTLS");
545     case linphone::MediaEncryptionNone:
546       break;
547   }
548 
549   return QString("");
550 }
551 
552 // -----------------------------------------------------------------------------
553 
getAudioStats() const554 QVariantList CallModel::getAudioStats () const {
555   return mAudioStats;
556 }
557 
getVideoStats() const558 QVariantList CallModel::getVideoStats () const {
559   return mVideoStats;
560 }
561 
562 // -----------------------------------------------------------------------------
563 
createStat(const QString & key,const QString & value)564 inline QVariantMap createStat (const QString &key, const QString &value) {
565   QVariantMap m;
566   m["key"] = key;
567   m["value"] = value;
568   return m;
569 }
570 
updateStats(const shared_ptr<const linphone::CallStats> & callStats,QVariantList & statsList)571 void CallModel::updateStats (const shared_ptr<const linphone::CallStats> &callStats, QVariantList &statsList) {
572   shared_ptr<const linphone::CallParams> params = mCall->getCurrentParams();
573   shared_ptr<const linphone::PayloadType> payloadType;
574 
575   switch (callStats->getType()) {
576     case linphone::StreamTypeAudio:
577       payloadType = params->getUsedAudioPayloadType();
578       break;
579     case linphone::StreamTypeVideo:
580       payloadType = params->getUsedVideoPayloadType();
581       break;
582     default:
583       return;
584   }
585 
586   QString family;
587   switch (callStats->getIpFamilyOfRemote()) {
588     case linphone::AddressFamilyInet:
589       family = QStringLiteral("IPv4");
590       break;
591     case linphone::AddressFamilyInet6:
592       family = QStringLiteral("IPv6");
593       break;
594     default:
595       family = QStringLiteral("Unknown");
596       break;
597   }
598 
599   statsList.clear();
600 
601   statsList << ::createStat(tr("callStatsCodec"), payloadType
602     ? QStringLiteral("%1 / %2kHz").arg(Utils::coreStringToAppString(payloadType->getMimeType())).arg(payloadType->getClockRate() / 1000)
603     : QString(""));
604   statsList << ::createStat(tr("callStatsUploadBandwidth"), QStringLiteral("%1 kbits/s").arg(int(callStats->getUploadBandwidth())));
605   statsList << ::createStat(tr("callStatsDownloadBandwidth"), QStringLiteral("%1 kbits/s").arg(int(callStats->getDownloadBandwidth())));
606   statsList << ::createStat(tr("callStatsIceState"), iceStateToString(callStats->getIceState()));
607   statsList << ::createStat(tr("callStatsIpFamily"), family);
608   statsList << ::createStat(tr("callStatsSenderLossRate"), QStringLiteral("%1 %").arg(callStats->getSenderLossRate()));
609   statsList << ::createStat(tr("callStatsReceiverLossRate"), QStringLiteral("%1 %").arg(callStats->getReceiverLossRate()));
610 
611   switch (callStats->getType()) {
612     case linphone::StreamTypeAudio:
613       statsList << ::createStat(tr("callStatsJitterBuffer"), QStringLiteral("%1 ms").arg(callStats->getJitterBufferSizeMs()));
614       break;
615     case linphone::StreamTypeVideo: {
616       const QString sentVideoDefinitionName = ::Utils::coreStringToAppString(params->getSentVideoDefinition()->getName());
617       const QString sentVideoDefinition = QStringLiteral("%1x%2")
618         .arg(params->getSentVideoDefinition()->getWidth())
619         .arg(params->getSentVideoDefinition()->getHeight());
620 
621       statsList << ::createStat(tr("callStatsSentVideoDefinition"), sentVideoDefinition == sentVideoDefinitionName
622         ? sentVideoDefinition
623         : QStringLiteral("%1 (%2)").arg(sentVideoDefinition).arg(sentVideoDefinitionName));
624 
625       const QString receivedVideoDefinitionName = ::Utils::coreStringToAppString(params->getReceivedVideoDefinition()->getName());
626       const QString receivedVideoDefinition = QString("%1x%2")
627         .arg(params->getReceivedVideoDefinition()->getWidth())
628         .arg(params->getReceivedVideoDefinition()->getHeight());
629 
630       statsList << ::createStat(tr("callStatsReceivedVideoDefinition"), receivedVideoDefinition == receivedVideoDefinitionName
631         ? receivedVideoDefinition
632         : QString("%1 (%2)").arg(receivedVideoDefinition).arg(receivedVideoDefinitionName));
633 
634       statsList << ::createStat(tr("callStatsReceivedFramerate"), QStringLiteral("%1 FPS").arg(params->getReceivedFramerate()));
635       statsList << ::createStat(tr("callStatsSentFramerate"), QStringLiteral("%1 FPS").arg(params->getSentFramerate()));
636     } break;
637 
638     default:
639       break;
640   }
641 }
642 
643 // -----------------------------------------------------------------------------
644 
iceStateToString(linphone::IceState state) const645 QString CallModel::iceStateToString (linphone::IceState state) const {
646   switch (state) {
647     case linphone::IceStateNotActivated:
648       return tr("iceStateNotActivated");
649     case linphone::IceStateFailed:
650       return tr("iceStateFailed");
651     case linphone::IceStateInProgress:
652       return tr("iceStateInProgress");
653     case linphone::IceStateReflexiveConnection:
654       return tr("iceStateReflexiveConnection");
655     case linphone::IceStateHostConnection:
656       return tr("iceStateHostConnection");
657     case linphone::IceStateRelayConnection:
658       return tr("iceStateRelayConnection");
659   }
660 
661   return tr("iceStateInvalid");
662 }
663