1 /*
2 * This file is part of TelepathyQt
3 *
4 * @copyright Copyright (C) 2010-2012 Collabora Ltd. <http://www.collabora.co.uk/>
5 * @copyright Copyright (C) 2012 Nokia Corporation
6 * @license LGPL 2.1
7 *
8 * This library is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Lesser General Public
10 * License as published by the Free Software Foundation; either
11 * version 2.1 of the License, or (at your option) any later version.
12 *
13 * This library is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * Lesser General Public License for more details.
17 *
18 * You should have received a copy of the GNU Lesser General Public
19 * License along with this library; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
21 */
22
23 #include <TelepathyQt/CallContent>
24
25 #include "TelepathyQt/_gen/call-content.moc.hpp"
26
27 #include "TelepathyQt/_gen/cli-call-content-body.hpp"
28 #include "TelepathyQt/_gen/cli-call-content.moc.hpp"
29
30 #include <TelepathyQt/debug-internal.h>
31
32 #include <TelepathyQt/CallChannel>
33 #include <TelepathyQt/DBus>
34 #include <TelepathyQt/PendingReady>
35 #include <TelepathyQt/PendingVoid>
36 #include <TelepathyQt/PendingVariantMap>
37 #include <TelepathyQt/ReadinessHelper>
38
39 namespace Tp
40 {
41
42 /* ====== CallContent ====== */
43 struct TP_QT_NO_EXPORT CallContent::Private
44 {
45 Private(CallContent *parent, const CallChannelPtr &channel);
46
47 static void introspectMainProperties(Private *self);
48 void checkIntrospectionCompleted();
49
50 CallStreamPtr addStream(const QDBusObjectPath &streamPath);
51 CallStreamPtr lookupStream(const QDBusObjectPath &streamPath);
52
53 // Public object
54 CallContent *parent;
55
56 WeakPtr<CallChannel> channel;
57
58 // Mandatory proxies
59 Client::CallContentInterface *contentInterface;
60
61 ReadinessHelper *readinessHelper;
62
63 // Introspection
64 QString name;
65 uint type;
66 uint disposition;
67 CallStreams streams;
68 CallStreams incompleteStreams;
69 };
70
Private(CallContent * parent,const CallChannelPtr & channel)71 CallContent::Private::Private(CallContent *parent, const CallChannelPtr &channel)
72 : parent(parent),
73 channel(channel.data()),
74 contentInterface(parent->interface<Client::CallContentInterface>()),
75 readinessHelper(parent->readinessHelper())
76 {
77 ReadinessHelper::Introspectables introspectables;
78
79 ReadinessHelper::Introspectable introspectableCore(
80 QSet<uint>() << 0, // makesSenseForStatuses
81 Features(), // dependsOnFeatures
82 QStringList(), // dependsOnInterfaces
83 (ReadinessHelper::IntrospectFunc) &Private::introspectMainProperties,
84 this);
85 introspectables[FeatureCore] = introspectableCore;
86
87 readinessHelper->addIntrospectables(introspectables);
88 readinessHelper->becomeReady(FeatureCore);
89 }
90
introspectMainProperties(CallContent::Private * self)91 void CallContent::Private::introspectMainProperties(CallContent::Private *self)
92 {
93 CallContent *parent = self->parent;
94 CallChannelPtr channel = parent->channel();
95
96 parent->connect(self->contentInterface,
97 SIGNAL(StreamsAdded(Tp::ObjectPathList)),
98 SLOT(onStreamsAdded(Tp::ObjectPathList)));
99 parent->connect(self->contentInterface,
100 SIGNAL(StreamsRemoved(Tp::ObjectPathList,Tp::CallStateReason)),
101 SLOT(onStreamsRemoved(Tp::ObjectPathList,Tp::CallStateReason)));
102
103 parent->connect(self->contentInterface->requestAllProperties(),
104 SIGNAL(finished(Tp::PendingOperation*)),
105 SLOT(gotMainProperties(Tp::PendingOperation*)));
106 }
107
checkIntrospectionCompleted()108 void CallContent::Private::checkIntrospectionCompleted()
109 {
110 if (!parent->isReady(FeatureCore) && incompleteStreams.size() == 0) {
111 readinessHelper->setIntrospectCompleted(FeatureCore, true);
112 }
113 }
114
addStream(const QDBusObjectPath & streamPath)115 CallStreamPtr CallContent::Private::addStream(const QDBusObjectPath &streamPath)
116 {
117 CallStreamPtr stream = CallStreamPtr(
118 new CallStream(CallContentPtr(parent), streamPath));
119 incompleteStreams.append(stream);
120 parent->connect(stream->becomeReady(),
121 SIGNAL(finished(Tp::PendingOperation*)),
122 SLOT(onStreamReady(Tp::PendingOperation*)));
123 return stream;
124 }
125
lookupStream(const QDBusObjectPath & streamPath)126 CallStreamPtr CallContent::Private::lookupStream(const QDBusObjectPath &streamPath)
127 {
128 foreach (const CallStreamPtr &stream, streams) {
129 if (stream->objectPath() == streamPath.path()) {
130 return stream;
131 }
132 }
133 foreach (const CallStreamPtr &stream, incompleteStreams) {
134 if (stream->objectPath() == streamPath.path()) {
135 return stream;
136 }
137 }
138 return CallStreamPtr();
139 }
140
141 /**
142 * \class CallContent
143 * \ingroup clientchannel
144 * \headerfile TelepathyQt/call-content.h <TelepathyQt/CallContent>
145 *
146 * \brief The CallContent class provides an object representing a Telepathy
147 * Call.Content.
148 *
149 * Instances of this class cannot be constructed directly; the only way to get
150 * one is via CallChannel.
151 *
152 * See \ref async_model
153 */
154
155 /**
156 * Feature representing the core that needs to become ready to make the
157 * CallContent object usable.
158 *
159 * Note that this feature must be enabled in order to use most CallContent
160 * methods. See specific methods documentation for more details.
161 *
162 * When calling isReady(), becomeReady(), this feature is implicitly added
163 * to the requested features.
164 */
165 const Feature CallContent::FeatureCore = Feature(QLatin1String(CallContent::staticMetaObject.className()), 0);
166
167 /**
168 * Construct a new CallContent object.
169 *
170 * \param channel The channel owning this media content.
171 * \param name The object path of this media content.
172 */
CallContent(const CallChannelPtr & channel,const QDBusObjectPath & objectPath)173 CallContent::CallContent(const CallChannelPtr &channel, const QDBusObjectPath &objectPath)
174 : StatefulDBusProxy(channel->dbusConnection(), channel->busName(),
175 objectPath.path(), FeatureCore),
176 OptionalInterfaceFactory<CallContent>(this),
177 mPriv(new Private(this, channel))
178 {
179 }
180
181 /**
182 * Class destructor.
183 */
~CallContent()184 CallContent::~CallContent()
185 {
186 delete mPriv;
187 }
188
189 /**
190 * Return the channel owning this media content.
191 *
192 * \return The channel owning this media content.
193 */
channel() const194 CallChannelPtr CallContent::channel() const
195 {
196 return CallChannelPtr(mPriv->channel);
197 }
198
199 /**
200 * Return the name of this media content.
201 *
202 * \return The name of this media content.
203 */
name() const204 QString CallContent::name() const
205 {
206 return mPriv->name;
207 }
208
209 /**
210 * Return the type of this media content.
211 *
212 * \return The type of this media content.
213 */
type() const214 MediaStreamType CallContent::type() const
215 {
216 return (MediaStreamType) mPriv->type;
217 }
218
219 /**
220 * Return the disposition of this media content.
221 *
222 * \return The disposition of this media content.
223 */
disposition() const224 CallContentDisposition CallContent::disposition() const
225 {
226 return (CallContentDisposition) mPriv->disposition;
227 }
228
229 /**
230 * Return the media streams of this media content.
231 *
232 * \return A list of media streams of this media content.
233 * \sa streamAdded(), streamRemoved()
234 */
streams() const235 CallStreams CallContent::streams() const
236 {
237 return mPriv->streams;
238 }
239
240 /**
241 * Removes this media content from the call.
242 *
243 * \return A PendingOperation which will emit PendingOperation::finished
244 * when the call has finished.
245 */
remove()246 PendingOperation *CallContent::remove()
247 {
248 return new PendingVoid(mPriv->contentInterface->Remove(), CallContentPtr(this));
249 }
250
251 /**
252 * Return whether sending DTMF events is supported on this content.
253 * DTMF is only supported on audio contents that implement the
254 * #TP_QT_IFACE_CALL_CONTENT_INTERFACE_DTMF interface.
255 *
256 * \returns \c true if DTMF is supported, or \c false otherwise.
257 */
supportsDTMF() const258 bool CallContent::supportsDTMF() const
259 {
260 return hasInterface(TP_QT_IFACE_CALL_CONTENT_INTERFACE_DTMF);
261 }
262
263 /**
264 * Start sending a DTMF tone on this media stream.
265 *
266 * Where possible, the tone will continue until stopDTMFTone() is called.
267 * On certain protocols, it may only be possible to send events with a predetermined
268 * length. In this case, the implementation may emit a fixed-length tone,
269 * and the stopDTMFTone() method call should return #TP_QT_ERROR_NOT_AVAILABLE.
270 *
271 * If this content does not support the #TP_QT_IFACE_CALL_CONTENT_INTERFACE_DTMF
272 * interface, the resulting PendingOperation will fail with error code
273 * #TP_QT_ERROR_NOT_IMPLEMENTED.
274 *
275 * \param event A numeric event code from the #DTMFEvent enum.
276 * \return A PendingOperation which will emit PendingOperation::finished
277 * when the request finishes.
278 * \sa stopDTMFTone(), supportsDTMF()
279 */
startDTMFTone(DTMFEvent event)280 PendingOperation *CallContent::startDTMFTone(DTMFEvent event)
281 {
282 if (!supportsDTMF()) {
283 warning() << "CallContent::startDTMFTone() used with no dtmf interface";
284 return new PendingFailure(TP_QT_ERROR_NOT_IMPLEMENTED,
285 QLatin1String("This CallContent does not support the dtmf interface"),
286 CallContentPtr(this));
287 }
288
289 Client::CallContentInterfaceDTMFInterface *dtmfInterface =
290 interface<Client::CallContentInterfaceDTMFInterface>();
291 return new PendingVoid(dtmfInterface->StartTone(event), CallContentPtr(this));
292 }
293
294 /**
295 * Stop sending any DTMF tone which has been started using the startDTMFTone()
296 * method.
297 *
298 * If there is no current tone, the resulting PendingOperation will
299 * finish successfully.
300 *
301 * If this content does not support the #TP_QT_IFACE_CALL_CONTENT_INTERFACE_DTMF
302 * interface, the resulting PendingOperation will fail with error code
303 * #TP_QT_ERROR_NOT_IMPLEMENTED.
304 *
305 * \return A PendingOperation which will emit PendingOperation::finished
306 * when the request finishes.
307 * \sa startDTMFTone(), supportsDTMF()
308 */
stopDTMFTone()309 PendingOperation *CallContent::stopDTMFTone()
310 {
311 if (!supportsDTMF()) {
312 warning() << "CallContent::stopDTMFTone() used with no dtmf interface";
313 return new PendingFailure(TP_QT_ERROR_NOT_IMPLEMENTED,
314 QLatin1String("This CallContent does not support the dtmf interface"),
315 CallContentPtr(this));
316 }
317
318 Client::CallContentInterfaceDTMFInterface *dtmfInterface =
319 interface<Client::CallContentInterfaceDTMFInterface>();
320 return new PendingVoid(dtmfInterface->StopTone(), CallContentPtr(this));
321 }
322
gotMainProperties(PendingOperation * op)323 void CallContent::gotMainProperties(PendingOperation *op)
324 {
325 if (op->isError()) {
326 warning().nospace() << "CallContentInterface::requestAllProperties() failed with" <<
327 op->errorName() << ": " << op->errorMessage();
328 mPriv->readinessHelper->setIntrospectCompleted(FeatureCore, false,
329 op->errorName(), op->errorMessage());
330 return;
331 }
332
333 debug() << "Got reply to CallContentInterface::requestAllProperties()";
334
335 PendingVariantMap *pvm = qobject_cast<PendingVariantMap*>(op);
336 Q_ASSERT(pvm);
337
338 QVariantMap props = pvm->result();
339
340 mPriv->name = qdbus_cast<QString>(props[QLatin1String("Name")]);
341 mPriv->type = qdbus_cast<uint>(props[QLatin1String("Type")]);
342 mPriv->disposition = qdbus_cast<uint>(props[QLatin1String("Disposition")]);
343 setInterfaces(qdbus_cast<QStringList>(props[QLatin1String("Interfaces")]));
344
345 ObjectPathList streamsPaths = qdbus_cast<ObjectPathList>(props[QLatin1String("Streams")]);
346 if (streamsPaths.size() != 0) {
347 foreach (const QDBusObjectPath &streamPath, streamsPaths) {
348 CallStreamPtr stream = mPriv->lookupStream(streamPath);
349 if (!stream) {
350 mPriv->addStream(streamPath);
351 }
352 }
353 } else {
354 mPriv->readinessHelper->setIntrospectCompleted(FeatureCore, true);
355 }
356 }
357
onStreamsAdded(const ObjectPathList & streamsPaths)358 void CallContent::onStreamsAdded(const ObjectPathList &streamsPaths)
359 {
360 foreach (const QDBusObjectPath &streamPath, streamsPaths) {
361 debug() << "Received Call::Content::StreamAdded for stream" << streamPath.path();
362
363 if (mPriv->lookupStream(streamPath)) {
364 debug() << "Stream already exists, ignoring";
365 return;
366 }
367
368 mPriv->addStream(streamPath);
369 }
370 }
371
onStreamsRemoved(const ObjectPathList & streamsPaths,const CallStateReason & reason)372 void CallContent::onStreamsRemoved(const ObjectPathList &streamsPaths,
373 const CallStateReason &reason)
374 {
375 foreach (const QDBusObjectPath &streamPath, streamsPaths) {
376 debug() << "Received Call::Content::StreamRemoved for stream" << streamPath.path();
377
378 CallStreamPtr stream = mPriv->lookupStream(streamPath);
379 if (!stream) {
380 debug() << "Stream does not exist, ignoring";
381 return;
382 }
383
384 bool incomplete = mPriv->incompleteStreams.contains(stream);
385 if (incomplete) {
386 mPriv->incompleteStreams.removeOne(stream);
387 } else {
388 mPriv->streams.removeOne(stream);
389 }
390
391 if (isReady(FeatureCore) && !incomplete) {
392 emit streamRemoved(stream, reason);
393 }
394
395 mPriv->checkIntrospectionCompleted();
396 }
397 }
398
onStreamReady(PendingOperation * op)399 void CallContent::onStreamReady(PendingOperation *op)
400 {
401 PendingReady *pr = qobject_cast<PendingReady*>(op);
402 CallStreamPtr stream = CallStreamPtr::qObjectCast(pr->proxy());
403
404 if (op->isError() || !mPriv->incompleteStreams.contains(stream)) {
405 mPriv->incompleteStreams.removeOne(stream);
406 mPriv->checkIntrospectionCompleted();
407 return;
408 }
409
410 mPriv->incompleteStreams.removeOne(stream);
411 mPriv->streams.append(stream);
412
413 if (isReady(FeatureCore)) {
414 emit streamAdded(stream);
415 }
416
417 mPriv->checkIntrospectionCompleted();
418 }
419
420 /**
421 * \fn void CallContent::streamAdded(const Tp::CallStreamPtr &stream);
422 *
423 * This signal is emitted when a new media stream is added to this media
424 * content.
425 *
426 * \param stream The media stream that was added.
427 * \sa streams()
428 */
429
430 /**
431 * \fn void CallContent::streamRemoved(const Tp::CallStreamPtr &stream, const Tp::CallStateReason &reason);
432 *
433 * This signal is emitted when a new media stream is removed from this media
434 * content.
435 *
436 * \param stream The media stream that was removed.
437 * \param reason The reason for this removal.
438 * \sa streams()
439 */
440
441
442 /* ====== PendingCallContent ====== */
443 struct TP_QT_NO_EXPORT PendingCallContent::Private
444 {
PrivateTp::PendingCallContent::Private445 Private(PendingCallContent *parent, const CallChannelPtr &channel)
446 : parent(parent),
447 channel(channel)
448 {
449 }
450
451 PendingCallContent *parent;
452 CallChannelPtr channel;
453 CallContentPtr content;
454 };
455
PendingCallContent(const CallChannelPtr & channel,const QString & name,MediaStreamType type,MediaStreamDirection direction)456 PendingCallContent::PendingCallContent(const CallChannelPtr &channel,
457 const QString &name, MediaStreamType type, MediaStreamDirection direction)
458 : PendingOperation(channel),
459 mPriv(new Private(this, channel))
460 {
461 Client::ChannelTypeCallInterface *callInterface =
462 channel->interface<Client::ChannelTypeCallInterface>();
463 QDBusPendingCallWatcher *watcher =
464 new QDBusPendingCallWatcher(
465 callInterface->AddContent(name, type, direction), this);
466 connect(watcher,
467 SIGNAL(finished(QDBusPendingCallWatcher*)),
468 SLOT(gotContent(QDBusPendingCallWatcher*)));
469 }
470
~PendingCallContent()471 PendingCallContent::~PendingCallContent()
472 {
473 delete mPriv;
474 }
475
content() const476 CallContentPtr PendingCallContent::content() const
477 {
478 if (!isFinished() || !isValid()) {
479 return CallContentPtr();
480 }
481
482 return mPriv->content;
483 }
484
gotContent(QDBusPendingCallWatcher * watcher)485 void PendingCallContent::gotContent(QDBusPendingCallWatcher *watcher)
486 {
487 QDBusPendingReply<QDBusObjectPath> reply = *watcher;
488 if (reply.isError()) {
489 warning().nospace() << "Call::AddContent failed with " <<
490 reply.error().name() << ": " << reply.error().message();
491 setFinishedWithError(reply.error());
492 watcher->deleteLater();
493 return;
494 }
495
496 QDBusObjectPath contentPath = reply.value();
497 CallChannelPtr channel(mPriv->channel);
498 CallContentPtr content = channel->lookupContent(contentPath);
499 if (!content) {
500 content = channel->addContent(contentPath);
501 }
502
503 connect(content->becomeReady(),
504 SIGNAL(finished(Tp::PendingOperation*)),
505 SLOT(onContentReady(Tp::PendingOperation*)));
506 connect(channel.data(),
507 SIGNAL(contentRemoved(Tp::CallContentPtr,Tp::CallStateReason)),
508 SLOT(onContentRemoved(Tp::CallContentPtr)));
509
510 mPriv->content = content;
511
512 watcher->deleteLater();
513 }
514
onContentReady(PendingOperation * op)515 void PendingCallContent::onContentReady(PendingOperation *op)
516 {
517 if (op->isError()) {
518 setFinishedWithError(op->errorName(), op->errorMessage());
519 return;
520 }
521
522 setFinished();
523 }
524
onContentRemoved(const CallContentPtr & content)525 void PendingCallContent::onContentRemoved(const CallContentPtr &content)
526 {
527 if (isFinished()) {
528 return;
529 }
530
531 if (mPriv->content == content) {
532 // the content was removed before becoming ready
533 setFinishedWithError(TP_QT_ERROR_CANCELLED,
534 QLatin1String("Content removed before ready"));
535 }
536 }
537
538 } // Tp
539