blob: 6955434a17326ce961c1609bd2a7b1307bfe8f30 [file] [log] [blame]
Sébastien Blin1f915762020-08-03 13:27:42 -04001/*
2 * Copyright (C) 2020 by Savoir-faire Linux
3 * Author: Edric Ladent Milaret <edric.ladent-milaret@savoirfairelinux.com>
4 * Author: Anthony Léonard <anthony.leonard@savoirfairelinux.com>
5 * Author: Olivier Soldano <olivier.soldano@savoirfairelinux.com>
6 * Author: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com>
7 * Author: Isa Nanic <isa.nanic@savoirfairelinux.com>
8 * Author: Mingrui Zhang <mingrui.zhang@savoirfairelinux.com>
9 *
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 3 of the License, or
13 * (at your option) any later version.
14 *
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
19 *
20 * You should have received a copy of the GNU General Public License
21 * along with this program. If not, see <http://www.gnu.org/licenses/>.
22 */
23
24#include "calladapter.h"
25
26#include "globalsystemtray.h"
27#include "utils.h"
28
29CallAdapter::CallAdapter(QObject *parent)
30 : QmlAdapterBase(parent)
31 , oneSecondTimer_(new QTimer(this))
32{}
33
34CallAdapter::~CallAdapter() {}
35
36void
37CallAdapter::initQmlObject()
38{
39 connectCallStatusChanged(LRCInstance::getCurrAccId());
40
41 connect(&LRCInstance::behaviorController(),
42 &BehaviorController::showIncomingCallView,
43 this,
44 &CallAdapter::slotShowIncomingCallView);
Sébastien Blin5f35e192020-08-06 15:24:57 -040045 connect(&LRCInstance::instance(),
46 &LRCInstance::currentAccountChanged,
47 this,
48 &CallAdapter::slotAccountChanged);
Sébastien Blin1f915762020-08-03 13:27:42 -040049 connect(&LRCInstance::behaviorController(),
50 &BehaviorController::showCallView,
51 this,
52 &CallAdapter::slotShowCallView);
53}
54
55void
Sébastien Blin5f35e192020-08-06 15:24:57 -040056CallAdapter::slotAccountChanged()
57{
58 connectCallModel(LRCInstance::getCurrAccId());
59}
60
61void
Sébastien Blin1f915762020-08-03 13:27:42 -040062CallAdapter::placeAudioOnlyCall()
63{
64 auto convInfo = LRCInstance::getCurrentConversation();
65 if (!convInfo.uid.isEmpty()) {
66 LRCInstance::getCurrentConversationModel()->placeAudioOnlyCall(convInfo.uid);
67 }
68}
69
70void
71CallAdapter::placeCall()
72{
73 auto convInfo = LRCInstance::getCurrentConversation();
74 if (!convInfo.uid.isEmpty()) {
75 LRCInstance::getCurrentConversationModel()->placeCall(convInfo.uid);
76 }
77}
78
79void
80CallAdapter::hangUpACall(const QString &accountId, const QString &convUid)
81{
82 auto convInfo = LRCInstance::getConversationFromConvUid(convUid, accountId);
83 if (!convInfo.uid.isEmpty()) {
84 LRCInstance::getAccountInfo(accountId).callModel->hangUp(convInfo.callId);
85 }
86}
87
88void
89CallAdapter::refuseACall(const QString &accountId, const QString &convUid)
90{
91 auto convInfo = LRCInstance::getConversationFromConvUid(convUid, accountId);
92 if (!convInfo.uid.isEmpty()) {
93 LRCInstance::getAccountInfo(accountId).callModel->refuse(convInfo.callId);
94 }
95}
96
97void
98CallAdapter::acceptACall(const QString &accountId, const QString &convUid)
99{
100 emit incomingCallNeedToSetupMainView(accountId, convUid);
101 auto convInfo = LRCInstance::getConversationFromConvUid(convUid, accountId);
102 if (!convInfo.uid.isEmpty()) {
103 LRCInstance::getAccountInfo(accountId).callModel->accept(convInfo.callId);
104 auto &accInfo = LRCInstance::getAccountInfo(convInfo.accountId);
105 accInfo.callModel->setCurrentCall(convInfo.callId);
106 }
107}
108
109void
110CallAdapter::slotShowIncomingCallView(const QString &accountId, const conversation::Info &convInfo)
111{
112 auto callModel = LRCInstance::getCurrentCallModel();
113
114 if (!callModel->hasCall(convInfo.callId)) {
115 /*
116 * Connection to close potential incoming call page when it is not current account.
117 */
118 auto &accInfo = LRCInstance::accountModel().getAccountInfo(accountId);
119
120 QObject::disconnect(closeIncomingCallPageConnection_);
121
122 closeIncomingCallPageConnection_
123 = QObject::connect(accInfo.callModel.get(),
124 &lrc::api::NewCallModel::callStatusChanged,
125 [this, accountId, uid = convInfo.uid](const QString &callId) {
126 auto &accInfo = LRCInstance::accountModel().getAccountInfo(
127 accountId);
128 auto &callModel = accInfo.callModel;
129 auto call = callModel->getCall(callId);
130
131 switch (call.status) {
132 case lrc::api::call::Status::INVALID:
133 case lrc::api::call::Status::INACTIVE:
134 case lrc::api::call::Status::ENDED:
135 case lrc::api::call::Status::PEER_BUSY:
136 case lrc::api::call::Status::TIMEOUT:
137 case lrc::api::call::Status::TERMINATING: {
138 if (!uid.isEmpty())
139 emit closePotentialIncomingCallPageWindow(accountId, uid);
140 break;
141 }
142 default:
143 break;
144 }
145
146 emit updateConversationSmartList();
147 QObject::disconnect(closeIncomingCallPageConnection_);
148 });
149 /*
150 * Show incoming call page only.
151 */
152 emit showIncomingCallPage(accountId, convInfo.uid);
153 return;
154 }
155
156 auto call = callModel->getCall(convInfo.callId);
157 auto isCallSelected = LRCInstance::getCurrentConvUid() == convInfo.uid;
158
159 if (call.isOutgoing) {
160 if (isCallSelected) {
161 emit showOutgoingCallPage(accountId, convInfo.uid);
162 emit showCallStack(accountId, convInfo.uid);
163 }
164 } else {
165 auto selectedAccountId = LRCInstance::getCurrentAccountInfo().id;
166 auto accountProperties = LRCInstance::accountModel().getAccountConfig(selectedAccountId);
167 if (accountProperties.autoAnswer) {
168 /*
169 * TODO: Auto answer
170 */
171 } else {
172 emit showIncomingCallPage(accountId, convInfo.uid);
173 }
174 }
175
176 emit callStatusChanged(lrc::api::call::to_string(call.status), accountId, convInfo.uid);
177
178 emit updateConversationSmartList();
179}
180
181void
182CallAdapter::slotShowCallView(const QString &accountId, const lrc::api::conversation::Info &convInfo)
183{
184 updateCall(convInfo.uid, accountId);
185}
186
187void
188CallAdapter::updateCall(const QString &convUid, const QString &accountId, bool forceCallOnly)
189{
190 accountId_ = accountId.isEmpty() ? accountId_ : accountId;
191 convUid_ = convUid.isEmpty() ? convUid_ : convUid;
192
193 auto convInfo = LRCInstance::getConversationFromConvUid(convUid_, accountId_);
194 if (convInfo.uid.isEmpty()) {
195 return;
196 }
197
198 auto call = LRCInstance::getCallInfoForConversation(convInfo, forceCallOnly);
199 if (!call) {
200 return;
201 }
202
203 if (call->isAudioOnly) {
204 emit showAudioCallPage(accountId_, convUid_);
205 } else {
206 emit showVideoCallPage(accountId_, convUid_, call->id);
207 }
208
209 updateCallOverlay(convInfo);
210
211 /*
212 * Preview.
213 */
214 emit previewVisibilityNeedToChange(shouldShowPreview(forceCallOnly));
215
216 emit showCallStack(accountId_, convUid_);
217}
218
219bool
220CallAdapter::shouldShowPreview(bool force)
221{
222 bool shouldShowPreview{false};
223 auto convInfo = LRCInstance::getConversationFromConvUid(convUid_, accountId_);
224 if (convInfo.uid.isEmpty()) {
225 return shouldShowPreview;
226 }
227 auto call = LRCInstance::getCallInfoForConversation(convInfo, force);
228 if (call) {
229 shouldShowPreview = !call->isAudioOnly && !(call->status == lrc::api::call::Status::PAUSED)
230 && !call->videoMuted && call->type != lrc::api::call::Type::CONFERENCE;
231 }
232 return shouldShowPreview;
233}
234
235void
236CallAdapter::connectCallStatusChanged(const QString &accountId)
237{
238 auto &accInfo = LRCInstance::accountModel().getAccountInfo(accountId);
239
240 QObject::disconnect(callStatusChangedConnection_);
241
242 callStatusChangedConnection_ = QObject::connect(
243 accInfo.callModel.get(),
244 &lrc::api::NewCallModel::callStatusChanged,
245 [this, accountId](const QString &callId) {
246 auto &accInfo = LRCInstance::accountModel().getAccountInfo(accountId);
247 auto &callModel = accInfo.callModel;
248 auto call = callModel->getCall(callId);
249
250 /*
251 * Change status label text.
252 */
253 auto convInfo = LRCInstance::getConversationFromCallId(callId);
254 if (!convInfo.uid.isEmpty()) {
255 emit callStatusChanged(lrc::api::call::to_string(call.status),
256 accountId,
257 convInfo.uid);
258 }
259
260 switch (call.status) {
261 case lrc::api::call::Status::INVALID:
262 case lrc::api::call::Status::INACTIVE:
263 case lrc::api::call::Status::ENDED:
264 case lrc::api::call::Status::PEER_BUSY:
265 case lrc::api::call::Status::TIMEOUT:
266 case lrc::api::call::Status::TERMINATING: {
267 LRCInstance::renderer()->removeDistantRenderer(callId);
268 if (convInfo.uid.isEmpty()) {
269 break;
270 }
271 /*
272 * If it's a conference, change the smartlist index
273 * to the next remaining participant.
274 */
275 bool forceCallOnly{false};
276 if (!convInfo.confId.isEmpty()) {
277 auto callList = LRCInstance::getAPI().getConferenceSubcalls(convInfo.confId);
278 if (callList.empty()) {
279 auto lastConferencee = LRCInstance::instance().popLastConferencee(
280 convInfo.confId);
281 callList.append(lastConferencee);
282 forceCallOnly = true;
283 }
284 for (const auto &callId : callList) {
285 if (!callModel->hasCall(callId)) {
286 continue;
287 }
288 auto otherConv = LRCInstance::getConversationFromCallId(callId);
289 if (!otherConv.uid.isEmpty() && otherConv.uid != convInfo.uid) {
290 /*
291 * Reset the call view corresponding accountId, uid.
292 */
293 LRCInstance::setSelectedConvId(otherConv.uid);
294 showCallStack(otherConv.accountId, otherConv.uid, true);
295 updateCall(otherConv.uid, otherConv.accountId, forceCallOnly);
296 }
297 }
298 } else {
299 emit closeCallStack(accountId, convInfo.uid);
300 emit closePotentialIncomingCallPageWindow(accountId, convInfo.uid);
301 }
302
303 break;
304 }
305 case lrc::api::call::Status::CONNECTED:
306 case lrc::api::call::Status::IN_PROGRESS: {
307 auto convInfo = LRCInstance::getConversationFromCallId(callId, accountId);
308 if (!convInfo.uid.isEmpty() && convInfo.uid == LRCInstance::getCurrentConvUid()) {
309 accInfo.conversationModel->selectConversation(convInfo.uid);
310 }
311 LRCInstance::renderer()->addDistantRenderer(callId);
312 updateCall();
313 LRCInstance::getAccountInfo(accountId).callModel->setCurrentCall(callId);
314 break;
315 }
316 case lrc::api::call::Status::PAUSED:
317 updateCall();
318 default:
319 break;
320 }
321
322 emit updateConversationSmartList();
323 });
324}
325
326/*
327 * For Call Overlay
328 */
329void
330CallAdapter::updateCallOverlay(const lrc::api::conversation::Info &convInfo)
331{
332 setTime(accountId_, convUid_);
333 QObject::disconnect(oneSecondTimer_);
334 QObject::connect(oneSecondTimer_, &QTimer::timeout, [this] { setTime(accountId_, convUid_); });
335 oneSecondTimer_->start(20);
336
337 auto &accInfo = LRCInstance::accountModel().getAccountInfo(accountId_);
338
339 auto call = LRCInstance::getCallInfoForConversation(convInfo);
340 if (!call) {
341 return;
342 }
343
344 bool isPaused = call->status == lrc::api::call::Status::PAUSED;
345 bool isAudioOnly = call->isAudioOnly && !isPaused;
346 bool isAudioMuted = call->audioMuted && (call->status != lrc::api::call::Status::PAUSED);
347 bool isVideoMuted = call->videoMuted && !isPaused && !call->isAudioOnly;
348 bool isRecording = accInfo.callModel->isRecording(convInfo.callId);
349
350 emit updateOverlay(isPaused,
351 isAudioOnly,
352 isAudioMuted,
353 isVideoMuted,
354 isRecording,
355 accInfo.profileInfo.type == lrc::api::profile::Type::SIP,
356 !convInfo.confId.isEmpty(),
357 Utils::bestNameForConversation(convInfo,
358 *LRCInstance::getCurrentConversationModel()));
359}
360
361void
362CallAdapter::hangUpThisCall()
363{
364 auto convInfo = LRCInstance::getConversationFromConvUid(convUid_, accountId_);
365 if (!convInfo.uid.isEmpty()) {
366 auto callModel = LRCInstance::getAccountInfo(accountId_).callModel.get();
367 if (callModel->hasCall(convInfo.callId)) {
368 /*
369 * Store the last remaining participant of the conference,
370 * so we can switch the smartlist index after termination.
371 */
372 if (!convInfo.confId.isEmpty()) {
373 auto callList = LRCInstance::getAPI().getConferenceSubcalls(convInfo.confId);
374 if (callList.size() == 2) {
375 for (const auto &cId : callList) {
376 if (cId != convInfo.callId) {
377 LRCInstance::instance().pushLastConferencee(convInfo.confId, cId);
378 }
379 }
380 }
381 }
382 callModel->hangUp(convInfo.callId);
383 }
384 }
385}
386
387void
388CallAdapter::holdThisCallToggle()
389{
390 auto callId = LRCInstance::getCallIdForConversationUid(convUid_, accountId_);
391 if (callId.isEmpty() || !LRCInstance::getCurrentCallModel()->hasCall(callId)) {
392 return;
393 }
394 auto callModel = LRCInstance::getCurrentCallModel();
395 if (callModel->hasCall(callId)) {
396 callModel->togglePause(callId);
397 }
398 emit showOnHoldLabel(true);
399}
400
401void
402CallAdapter::muteThisCallToggle()
403{
404 auto callId = LRCInstance::getCallIdForConversationUid(convUid_, accountId_);
405 if (callId.isEmpty() || !LRCInstance::getCurrentCallModel()->hasCall(callId)) {
406 return;
407 }
408 auto callModel = LRCInstance::getCurrentCallModel();
409 if (callModel->hasCall(callId)) {
410 callModel->toggleMedia(callId, lrc::api::NewCallModel::Media::AUDIO);
411 }
412}
413
414void
415CallAdapter::recordThisCallToggle()
416{
417 auto callId = LRCInstance::getCallIdForConversationUid(convUid_, accountId_);
418 if (callId.isEmpty() || !LRCInstance::getCurrentCallModel()->hasCall(callId)) {
419 return;
420 }
421 auto callModel = LRCInstance::getCurrentCallModel();
422 if (callModel->hasCall(callId)) {
423 callModel->toggleAudioRecord(callId);
424 }
425}
426
427void
428CallAdapter::videoPauseThisCallToggle()
429{
430 auto callId = LRCInstance::getCallIdForConversationUid(convUid_, accountId_);
431 if (callId.isEmpty() || !LRCInstance::getCurrentCallModel()->hasCall(callId)) {
432 return;
433 }
434 auto callModel = LRCInstance::getCurrentCallModel();
435 if (callModel->hasCall(callId)) {
436 callModel->toggleMedia(callId, lrc::api::NewCallModel::Media::VIDEO);
437 }
438 emit previewVisibilityNeedToChange(shouldShowPreview(false));
439}
440
441void
442CallAdapter::setTime(const QString &accountId, const QString &convUid)
443{
444 auto callId = LRCInstance::getCallIdForConversationUid(convUid, accountId);
445 if (callId.isEmpty() || !LRCInstance::getCurrentCallModel()->hasCall(callId)) {
446 return;
447 }
448 auto callInfo = LRCInstance::getCurrentCallModel()->getCall(callId);
449 if (callInfo.status == lrc::api::call::Status::IN_PROGRESS
450 || callInfo.status == lrc::api::call::Status::PAUSED) {
451 auto timeString = LRCInstance::getCurrentCallModel()->getFormattedCallDuration(callId);
452 emit updateTimeText(timeString);
453 }
454}