1 #include <tests/lib/test.h>
2 
3 #include <tests/lib/glib-helpers/test-conn-helper.h>
4 
5 #include <tests/lib/glib/call/conn.h>
6 
7 #define TP_QT_ENABLE_LOWLEVEL_API
8 
9 #include <TelepathyQt/Connection>
10 #include <TelepathyQt/ConnectionLowlevel>
11 #include <TelepathyQt/ContactManager>
12 #include <TelepathyQt/PendingChannel>
13 #include <TelepathyQt/PendingContacts>
14 #include <TelepathyQt/PendingReady>
15 #include <TelepathyQt/CallChannel>
16 
17 #include <telepathy-glib/debug.h>
18 
19 using namespace Tp;
20 
21 class TestCallChannel : public Test
22 {
23     Q_OBJECT
24 
25 public:
TestCallChannel(QObject * parent=0)26     TestCallChannel(QObject *parent = 0)
27         : Test(parent), mConn(0)
28     { }
29 
30 protected Q_SLOTS:
31     void expectRequestContentFinished(Tp::PendingOperation *op);
32     void expectSuccessfulRequestReceiving(Tp::PendingOperation *op);
33 
34     void onContentRemoved(const Tp::CallContentPtr &content,
35             const Tp::CallStateReason &reason);
36     void onCallStateChanged(Tp::CallState newState);
37     void onCallFlagsChanged(Tp::CallFlags newFlags);
38     void onRemoteMemberFlagsChanged(
39             const QHash<Tp::ContactPtr, Tp::CallMemberFlags> &remoteMemberFlags,
40             const Tp::CallStateReason &reason);
41     void onRemoteMembersRemoved(const Tp::Contacts &remoteMembers,
42             const Tp::CallStateReason &reason);
43     void onLocalSendingStateChanged(Tp::SendingState localSendingState,
44             const Tp::CallStateReason &reason);
45     void onRemoteSendingStateChanged(
46             const QHash<Tp::ContactPtr, Tp::SendingState> &remoteSendingStates,
47             const Tp::CallStateReason &reason);
48 
49     void onLocalHoldStateChanged(Tp::LocalHoldState state, Tp::LocalHoldStateReason reason);
50 
51     void onNewChannels(const Tp::ChannelDetailsList &details);
52 
53 private Q_SLOTS:
54     void initTestCase();
55     void init();
56 
57     void testOutgoingCall();
58     void testIncomingCall();
59     void testHold();
60     void testHangup();
61     void testCallMembers();
62     void testDTMF();
63     void testFeatureCore();
64 
65     void cleanup();
66     void cleanupTestCase();
67 
68 private:
69     TestConnHelper *mConn;
70 
71     CallChannelPtr mChan;
72     CallContentPtr mRequestContentReturn;
73     CallContentPtr mContentRemoved;
74     CallStateReason mCallStateReason;
75     CallState mCallState;
76     CallFlags mCallFlags;
77     QHash<ContactPtr, CallMemberFlags> mRemoteMemberFlags;
78     Contacts mRemoteMembersRemoved;
79     SendingState mLSSCReturn;
80     QQueue<uint> mLocalHoldStates;
81     QQueue<uint> mLocalHoldStateReasons;
82 
83     // Remote sending state changed state-machine
84     enum {
85         RSSCStateInitial,
86         RSSCStatePendingSend,
87         RSSCStateSending,
88         RSSCStateDone
89     } mRSSCState;
90     int mSuccessfulRequestReceivings;
91 };
92 
expectRequestContentFinished(Tp::PendingOperation * op)93 void TestCallChannel::expectRequestContentFinished(Tp::PendingOperation *op)
94 {
95     if (!op->isFinished()) {
96         qWarning() << "unfinished";
97         mLoop->exit(1);
98         return;
99     }
100 
101     if (op->isError()) {
102         qWarning().nospace() << op->errorName()
103             << ": " << op->errorMessage();
104         mLoop->exit(2);
105         return;
106     }
107 
108     if (!op->isValid()) {
109         qWarning() << "inconsistent results";
110         mLoop->exit(3);
111         return;
112     }
113 
114     PendingCallContent *pmc = qobject_cast<PendingCallContent*>(op);
115     mRequestContentReturn = pmc->content();
116     mLoop->exit(0);
117 }
118 
onLocalSendingStateChanged(Tp::SendingState state,const Tp::CallStateReason & reason)119 void TestCallChannel::onLocalSendingStateChanged(Tp::SendingState state,
120         const Tp::CallStateReason &reason)
121 {
122     qDebug() << "local sending state changed";
123     mLSSCReturn = state;
124     mCallStateReason = reason;
125     mLoop->exit(0);
126 }
127 
expectSuccessfulRequestReceiving(Tp::PendingOperation * op)128 void TestCallChannel::expectSuccessfulRequestReceiving(Tp::PendingOperation *op)
129 {
130     if (!op->isFinished()) {
131         qWarning() << "unfinished";
132         mLoop->exit(1);
133         return;
134     }
135 
136     if (op->isError()) {
137         qWarning().nospace() << op->errorName()
138             << ": " << op->errorMessage();
139         mLoop->exit(2);
140         return;
141     }
142 
143     if (!op->isValid()) {
144         qWarning() << "inconsistent results";
145         mLoop->exit(3);
146         return;
147     }
148 
149     if (++mSuccessfulRequestReceivings == 2 && mRSSCState == RSSCStateDone) {
150         mLoop->exit(0);
151     }
152 }
153 
onContentRemoved(const CallContentPtr & content,const Tp::CallStateReason & reason)154 void TestCallChannel::onContentRemoved(const CallContentPtr &content,
155         const Tp::CallStateReason &reason)
156 {
157     mContentRemoved = content;
158     mCallStateReason = reason;
159     mLoop->exit(0);
160 }
161 
onCallStateChanged(CallState newState)162 void TestCallChannel::onCallStateChanged(CallState newState)
163 {
164     mCallState = newState;
165     mLoop->exit(0);
166 }
167 
onCallFlagsChanged(CallFlags newFlags)168 void TestCallChannel::onCallFlagsChanged(CallFlags newFlags)
169 {
170     mCallFlags = newFlags;
171 }
172 
onRemoteMemberFlagsChanged(const QHash<ContactPtr,CallMemberFlags> & remoteMemberFlags,const CallStateReason & reason)173 void TestCallChannel::onRemoteMemberFlagsChanged(
174         const QHash<ContactPtr, CallMemberFlags> &remoteMemberFlags,
175         const CallStateReason &reason)
176 {
177     mRemoteMemberFlags = remoteMemberFlags;
178     mLoop->exit(0);
179 }
180 
onRemoteMembersRemoved(const Tp::Contacts & remoteMembers,const Tp::CallStateReason & reason)181 void TestCallChannel::onRemoteMembersRemoved(const Tp::Contacts &remoteMembers,
182         const Tp::CallStateReason &reason)
183 {
184     mRemoteMembersRemoved = remoteMembers;
185 }
186 
onRemoteSendingStateChanged(const QHash<Tp::ContactPtr,SendingState> & states,const Tp::CallStateReason & reason)187 void TestCallChannel::onRemoteSendingStateChanged(
188         const QHash<Tp::ContactPtr, SendingState> &states,
189         const Tp::CallStateReason &reason)
190 {
191     // There should be no further events
192     QVERIFY(mRSSCState != RSSCStateDone);
193 
194     QCOMPARE(states.size(), 1);
195     Tp::ContactPtr otherContact = states.keys().first();
196 
197     CallContentPtr content = mChan->contentsForType(Tp::MediaStreamTypeVideo).first();
198     QVERIFY(content);
199 
200     CallStreamPtr stream = content->streams().first();
201     QVERIFY(stream);
202 
203     if (mRSSCState == RSSCStateInitial) {
204         QCOMPARE(states[otherContact], SendingStatePendingSend);
205         mRSSCState = RSSCStatePendingSend;
206     } else if (mRSSCState == RSSCStatePendingSend) {
207         QCOMPARE(states[otherContact], SendingStateSending);
208         mRSSCState = RSSCStateSending;
209 
210         QVERIFY(connect(stream->requestReceiving(otherContact, false),
211                     SIGNAL(finished(Tp::PendingOperation*)),
212                     SLOT(expectSuccessfulRequestReceiving(Tp::PendingOperation*))));
213     } else if (mRSSCState == RSSCStateSending) {
214         QCOMPARE(states[otherContact], SendingStateNone);
215         mRSSCState = RSSCStateDone;
216 
217         if (mSuccessfulRequestReceivings == 2) {
218             mLoop->exit(0);
219         }
220     }
221 
222     qDebug() << "remote sending state changed to" << states[otherContact];
223 }
224 
onLocalHoldStateChanged(Tp::LocalHoldState localHoldState,Tp::LocalHoldStateReason localHoldStateReason)225 void TestCallChannel::onLocalHoldStateChanged(Tp::LocalHoldState localHoldState,
226         Tp::LocalHoldStateReason localHoldStateReason)
227 {
228     mLocalHoldStates.append(localHoldState);
229     mLocalHoldStateReasons.append(localHoldStateReason);
230     mLoop->exit(0);
231 }
232 
onNewChannels(const Tp::ChannelDetailsList & channels)233 void TestCallChannel::onNewChannels(const Tp::ChannelDetailsList &channels)
234 {
235     qDebug() << "new channels";
236     Q_FOREACH (const Tp::ChannelDetails &details, channels) {
237         QString channelType = details.properties.value(TP_QT_IFACE_CHANNEL + QLatin1String(".ChannelType")).toString();
238         bool requested = details.properties.value(TP_QT_IFACE_CHANNEL + QLatin1String(".Requested")).toBool();
239         qDebug() << " channelType:" << channelType;
240         qDebug() << " requested  :" << requested;
241 
242         if (channelType == TP_QT_IFACE_CHANNEL_TYPE_CALL && !requested) {
243             mChan = CallChannel::create(mConn->client(), details.channel.path(), details.properties);
244             mLoop->exit(0);
245         }
246     }
247 }
248 
initTestCase()249 void TestCallChannel::initTestCase()
250 {
251     initTestCaseImpl();
252 
253     g_type_init();
254     g_set_prgname("call-channel");
255     tp_debug_set_flags("all");
256     dbus_g_bus_get(DBUS_BUS_STARTER, 0);
257 
258     mConn = new TestConnHelper(this,
259             EXAMPLE_TYPE_CALL_CONNECTION,
260             "account", "me@example.com",
261             "protocol", "example",
262             "simulation-delay", 1,
263             NULL);
264     QCOMPARE(mConn->connect(Connection::FeatureSelfContact), true);
265 }
266 
init()267 void TestCallChannel::init()
268 {
269     initImpl();
270 
271     mRequestContentReturn.reset();
272     mContentRemoved.reset();
273     mCallStateReason = CallStateReason();
274     mCallState = CallStateUnknown;
275     mCallFlags = (CallFlags) 0;
276     mRemoteMemberFlags.clear();
277     mRemoteMembersRemoved.clear();
278     mLSSCReturn = (Tp::SendingState) -1;
279     mLocalHoldStates.clear();
280     mLocalHoldStateReasons.clear();
281 }
282 
testOutgoingCall()283 void TestCallChannel::testOutgoingCall()
284 {
285     qDebug() << "requesting contact for alice";
286 
287     QList<ContactPtr> contacts = mConn->contacts(QStringList() << QLatin1String("alice"));
288     QCOMPARE(contacts.size(), 1);
289 
290     ContactPtr otherContact = contacts.at(0);
291     QVERIFY(otherContact);
292 
293     qDebug() << "creating the channel";
294 
295     QVariantMap request;
296     request.insert(TP_QT_IFACE_CHANNEL + QLatin1String(".ChannelType"),
297                    TP_QT_IFACE_CHANNEL_TYPE_CALL);
298     request.insert(TP_QT_IFACE_CHANNEL + QLatin1String(".TargetHandleType"),
299                    (uint) Tp::HandleTypeContact);
300     request.insert(TP_QT_IFACE_CHANNEL + QLatin1String(".TargetHandle"),
301                    otherContact->handle()[0]);
302     request.insert(TP_QT_IFACE_CHANNEL_TYPE_CALL + QLatin1String(".InitialAudio"),
303                    true);
304     mChan = CallChannelPtr::qObjectCast(mConn->createChannel(request));
305     QVERIFY(mChan);
306 
307     qDebug() << "making the channel ready";
308 
309     Features features;
310     features << CallChannel::FeatureCallState
311              << CallChannel::FeatureContents;
312 
313     QVERIFY(connect(mChan->becomeReady(features),
314                     SIGNAL(finished(Tp::PendingOperation*)),
315                     SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
316     QCOMPARE(mLoop->exec(), 0);
317     QVERIFY(mChan->isReady(Tp::CallChannel::FeatureCallState));
318     QVERIFY(mChan->isReady(Tp::CallChannel::FeatureContents));
319 
320     QCOMPARE(mChan->callState(), CallStatePendingInitiator);
321 
322     QVERIFY(connect(mChan->accept(),
323                     SIGNAL(finished(Tp::PendingOperation*)),
324                     SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
325     QCOMPARE(mLoop->exec(), 0);
326     QCOMPARE(mChan->callState(), CallStateInitialised);
327     QCOMPARE(mChan->callStateReason().reason, (uint) CallStateChangeReasonUserRequested);
328 
329     QVERIFY(connect(mChan.data(),
330                     SIGNAL(callStateChanged(Tp::CallState)),
331                     SLOT(onCallStateChanged(Tp::CallState))));
332     QCOMPARE(mLoop->exec(), 0);
333     QCOMPARE(mCallState, CallStateAccepted);
334     QCOMPARE(mChan->callState(), CallStateAccepted);
335 
336     QCOMPARE(mChan->contents().size(), 1);
337 
338     Tp::CallContentPtr content;
339     content = mChan->contents().first();
340     QVERIFY(content);
341     QCOMPARE(content->name(), QString::fromLatin1("audio"));
342     QCOMPARE(content->type(), Tp::MediaStreamTypeAudio);
343     QCOMPARE(content->disposition(), Tp::CallContentDispositionInitial);
344     QVERIFY(mChan->contentByName(QLatin1String("audio")));
345 
346 /*
347     QCOMPARE(mChan->groupContacts().size(), 2);
348     QCOMPARE(mChan->groupLocalPendingContacts().size(), 0);
349     QCOMPARE(mChan->groupRemotePendingContacts().size(), 0);
350     QVERIFY(mChan->groupContacts().contains(mConn->client()->selfContact()));
351     QVERIFY(mChan->groupContacts().contains(otherContact));
352 */
353 
354     qDebug() << "calling requestContent with a bad type";
355     // RequestContent with bad type must fail
356     QVERIFY(connect(mChan->requestContent(QLatin1String("content1"), (Tp::MediaStreamType) -1,
357                                           Tp::MediaStreamDirectionNone),
358                     SIGNAL(finished(Tp::PendingOperation*)),
359                     SLOT(expectRequestContentFinished(Tp::PendingOperation*))));
360     QCOMPARE(mLoop->exec(), 2);
361     QVERIFY(!mRequestContentReturn);
362 
363     qDebug() << "calling requestContent with Audio";
364     QCOMPARE(mChan->contentsForType(Tp::MediaStreamTypeAudio).size(), 1);
365 
366     mRequestContentReturn.reset();
367     QVERIFY(connect(mChan->requestContent(QLatin1String("content1"), Tp::MediaStreamTypeAudio,
368                                           Tp::MediaStreamDirectionBidirectional),
369                     SIGNAL(finished(Tp::PendingOperation*)),
370                     SLOT(expectRequestContentFinished(Tp::PendingOperation*))));
371     QCOMPARE(mLoop->exec(), 0);
372     QVERIFY(!mRequestContentReturn.isNull());
373     QCOMPARE(mRequestContentReturn->name(), QString(QLatin1String("content1")));
374     QCOMPARE(mRequestContentReturn->type(), Tp::MediaStreamTypeAudio);
375     QCOMPARE(mRequestContentReturn->disposition(), Tp::CallContentDispositionNone);
376 
377     QCOMPARE(mChan->contentsForType(Tp::MediaStreamTypeAudio).size(), 2);
378 
379     qDebug() << "calling requestContent with Video";
380     mRequestContentReturn.reset();
381     QVERIFY(connect(mChan->requestContent(QLatin1String("content2"), Tp::MediaStreamTypeVideo,
382                                           Tp::MediaStreamDirectionBidirectional),
383                     SIGNAL(finished(Tp::PendingOperation*)),
384                     SLOT(expectRequestContentFinished(Tp::PendingOperation*))));
385     QCOMPARE(mLoop->exec(), 0);
386     QVERIFY(!mRequestContentReturn.isNull());
387     QCOMPARE(mRequestContentReturn->name(), QString(QLatin1String("content2")));
388     QCOMPARE(mRequestContentReturn->type(), Tp::MediaStreamTypeVideo);
389 
390     // test content removal
391     QCOMPARE(mChan->contentsForType(Tp::MediaStreamTypeAudio).size(), 2);
392 
393     content = mChan->contentsForType(Tp::MediaStreamTypeAudio).first();
394     QVERIFY(content);
395 
396     QVERIFY(connect(mChan.data(),
397                     SIGNAL(contentRemoved(Tp::CallContentPtr,Tp::CallStateReason)),
398                     SLOT(onContentRemoved(Tp::CallContentPtr,Tp::CallStateReason))));
399     QVERIFY(connect(content->remove(),
400                     SIGNAL(finished(Tp::PendingOperation*)),
401                     SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
402     while (mContentRemoved.isNull()) {
403         QCOMPARE(mLoop->exec(), 0);
404     }
405     QCOMPARE(mChan->contentsForType(Tp::MediaStreamTypeAudio).size(), 1);
406     QCOMPARE(mContentRemoved, content);
407     QCOMPARE(mCallStateReason.reason, (uint) Tp::CallStateChangeReasonUserRequested);
408 
409     // test content sending changed
410     content = mChan->contentsForType(Tp::MediaStreamTypeVideo).first();
411     Tp::CallStreamPtr stream = content->streams().first();
412     QVERIFY(content);
413 
414     QVERIFY(connect(stream.data(),
415                     SIGNAL(localSendingStateChanged(Tp::SendingState,Tp::CallStateReason)),
416                     SLOT(onLocalSendingStateChanged(Tp::SendingState,Tp::CallStateReason))));
417 
418     qDebug() << "stopping sending";
419 
420     QCOMPARE(stream->localSendingState(), Tp::SendingStateSending);
421     QVERIFY(stream->remoteMembers().contains(otherContact));
422 
423     QVERIFY(connect(stream->requestSending(false),
424                     SIGNAL(finished(Tp::PendingOperation*)),
425                     SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
426     QCOMPARE(mLoop->exec(), 0);
427 
428     qDebug() << "stopping receiving";
429     QVERIFY(connect(stream->requestReceiving(otherContact, false),
430                     SIGNAL(finished(Tp::PendingOperation*)),
431                     SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
432     QCOMPARE(mLoop->exec(), 0);
433 
434     qDebug() << "waiting until we're not sending and not receiving";
435 
436     while (stream->localSendingState() != Tp::SendingStateNone
437             || stream->remoteSendingState(otherContact) != Tp::SendingStateNone) {
438         qDebug() << "re-entering mainloop to wait for local and remote SSC -> None";
439         // wait local and remote sending state change
440         QCOMPARE(mLoop->exec(), 0);
441     }
442     QCOMPARE(mLSSCReturn, Tp::SendingStateNone);
443     QCOMPARE(stream->localSendingState(), Tp::SendingStateNone);
444     QCOMPARE(stream->remoteSendingState(otherContact), Tp::SendingStateNone);
445 
446     qDebug() << "re-enabling sending";
447 
448     mLSSCReturn = (Tp::SendingState) -1;
449 
450     QVERIFY(connect(stream->requestSending(true),
451                     SIGNAL(finished(Tp::PendingOperation*)),
452                     SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
453     QCOMPARE(mLoop->exec(), 0);
454     while (mLSSCReturn == (Tp::SendingState) -1) {
455         qDebug() << "re-entering mainloop to wait for SSC -> Sending";
456         // wait sending state change
457         QCOMPARE(mLoop->exec(), 0);
458     }
459     QCOMPARE(mLSSCReturn, Tp::SendingStateSending);
460 
461     qDebug() << "flushing D-Bus events";
462     processDBusQueue(mChan.data());
463 
464     qDebug() << "enabling receiving";
465 
466     mRSSCState = RSSCStateInitial;
467     mSuccessfulRequestReceivings = 0;
468 
469     QVERIFY(connect(stream.data(),
470                     SIGNAL(remoteSendingStateChanged(QHash<Tp::ContactPtr,Tp::SendingState>,Tp::CallStateReason)),
471                     SLOT(onRemoteSendingStateChanged(QHash<Tp::ContactPtr,Tp::SendingState>,Tp::CallStateReason))));
472 
473     // test content receiving changed
474     QVERIFY(connect(stream->requestReceiving(otherContact, true),
475                     SIGNAL(finished(Tp::PendingOperation*)),
476                     SLOT(expectSuccessfulRequestReceiving(Tp::PendingOperation*))));
477     QCOMPARE(mLoop->exec(), 0);
478     QCOMPARE(static_cast<uint>(mRSSCState), static_cast<uint>(RSSCStateDone));
479 }
480 
testIncomingCall()481 void TestCallChannel::testIncomingCall()
482 {
483     mConn->client()->lowlevel()->setSelfPresence(QLatin1String("away"), QLatin1String("preparing for a test"));
484 
485     Tp::Client::ConnectionInterfaceRequestsInterface *connRequestsInterface =
486         mConn->client()->optionalInterface<Tp::Client::ConnectionInterfaceRequestsInterface>();
487     QVERIFY(connect(connRequestsInterface,
488                     SIGNAL(NewChannels(const Tp::ChannelDetailsList&)),
489                     SLOT(onNewChannels(const Tp::ChannelDetailsList&))));
490 
491     mConn->client()->lowlevel()->setSelfPresence(QLatin1String("available"), QLatin1String("call me?"));
492     QCOMPARE(mLoop->exec(), 0);
493 
494     QVERIFY(mChan);
495     QCOMPARE(mChan->contents().size(), 0);
496 
497     Features features;
498     features << CallChannel::FeatureCallState
499              << CallChannel::FeatureContents;
500 
501     QVERIFY(connect(mChan->becomeReady(features),
502                     SIGNAL(finished(Tp::PendingOperation*)),
503                     SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
504     QCOMPARE(mLoop->exec(), 0);
505     QVERIFY(mChan->isReady(Tp::CallChannel::FeatureCallState));
506     QVERIFY(mChan->isReady(Tp::CallChannel::FeatureContents));
507 
508     Tp::ContactPtr otherContact = mChan->initiatorContact();
509     QVERIFY(otherContact);
510 
511     QCOMPARE(mChan->callState(), CallStateInitialised);
512 
513     QVERIFY(connect(mChan.data(),
514                     SIGNAL(callFlagsChanged(Tp::CallFlags)),
515                     SLOT(onCallFlagsChanged(Tp::CallFlags))));
516 
517     QVERIFY(connect(mChan->setRinging(),
518                     SIGNAL(finished(Tp::PendingOperation*)),
519                     SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
520     QCOMPARE(mLoop->exec(), 0);
521     QVERIFY(mCallFlags.testFlag(CallFlagLocallyRinging));
522     QVERIFY(mChan->callFlags().testFlag(CallFlagLocallyRinging));
523 
524     QVERIFY(connect(mChan->setQueued(),
525                     SIGNAL(finished(Tp::PendingOperation*)),
526                     SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
527     QCOMPARE(mLoop->exec(), 0);
528     QVERIFY(mCallFlags.testFlag(CallFlagLocallyQueued));
529     QVERIFY(mChan->callFlags().testFlag(CallFlagLocallyQueued));
530 
531     QVERIFY(connect(mChan->accept(),
532                     SIGNAL(finished(Tp::PendingOperation*)),
533                     SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
534     QCOMPARE(mLoop->exec(), 0);
535     QCOMPARE(mChan->callState(), CallStateAccepted);
536     QVERIFY(!mCallFlags.testFlag(CallFlagLocallyQueued));
537     QVERIFY(!mCallFlags.testFlag(CallFlagLocallyRinging));
538     QVERIFY(!mChan->callFlags().testFlag(CallFlagLocallyRinging));
539     QVERIFY(!mChan->callFlags().testFlag(CallFlagLocallyQueued));
540 
541 /*
542     QCOMPARE(mChan->groupContacts().size(), 2);
543     QCOMPARE(mChan->groupLocalPendingContacts().size(), 0);
544     QCOMPARE(mChan->groupRemotePendingContacts().size(), 0);
545     QVERIFY(mChan->groupContacts().contains(mConn->selfContact()));
546 */
547 
548     QCOMPARE(mChan->contents().size(), 1);
549     Tp::CallContentPtr content = mChan->contents().first();
550     QCOMPARE(content->channel(), mChan);
551     QCOMPARE(content->type(), Tp::MediaStreamTypeAudio);
552 
553     qDebug() << "requesting a video stream";
554 
555     // Request video stream
556     QVERIFY(connect(mChan->requestContent(QLatin1String("video_content"), Tp::MediaStreamTypeVideo,
557                                           Tp::MediaStreamDirectionBidirectional),
558                     SIGNAL(finished(Tp::PendingOperation*)),
559                     SLOT(expectRequestContentFinished(Tp::PendingOperation*))));
560     QCOMPARE(mLoop->exec(), 0);
561     content = mRequestContentReturn;
562     QCOMPARE(content->type(), Tp::MediaStreamTypeVideo);
563 
564     QCOMPARE(mChan->contents().size(), 2);
565     QVERIFY(mChan->contents().contains(content));
566 
567     QCOMPARE(mChan->contentsForType(Tp::MediaStreamTypeAudio).size(), 1);
568     QCOMPARE(mChan->contentsForType(Tp::MediaStreamTypeVideo).size(), 1);
569 
570     // test content removal
571     content = mChan->contentsForType(Tp::MediaStreamTypeAudio).first();
572     QVERIFY(content);
573 
574     qDebug() << "removing the audio content";
575 
576     // call does not have the concept of removing streams, it will remove the content the stream
577     // belongs
578     QVERIFY(connect(mChan.data(),
579                     SIGNAL(contentRemoved(Tp::CallContentPtr,Tp::CallStateReason)),
580                     SLOT(onContentRemoved(Tp::CallContentPtr,Tp::CallStateReason))));
581 
582     QVERIFY(connect(content->remove(),
583                     SIGNAL(finished(Tp::PendingOperation*)),
584                     SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
585     while (mContentRemoved.isNull()) {
586         QCOMPARE(mLoop->exec(), 0);
587     }
588     QCOMPARE(mContentRemoved, content);
589 }
590 
testHold()591 void TestCallChannel::testHold()
592 {
593     QList<ContactPtr> contacts = mConn->contacts(QStringList() << QLatin1String("bob"));
594     QCOMPARE(contacts.size(), 1);
595 
596     ContactPtr otherContact = contacts.at(0);
597     QVERIFY(otherContact);
598 
599     QVariantMap request;
600     request.insert(TP_QT_IFACE_CHANNEL + QLatin1String(".ChannelType"),
601                    TP_QT_IFACE_CHANNEL_TYPE_CALL);
602     request.insert(TP_QT_IFACE_CHANNEL + QLatin1String(".TargetHandleType"),
603                    (uint) Tp::HandleTypeContact);
604     request.insert(TP_QT_IFACE_CHANNEL + QLatin1String(".TargetHandle"),
605                    otherContact->handle()[0]);
606     request.insert(TP_QT_IFACE_CHANNEL_TYPE_CALL + QLatin1String(".InitialAudio"),
607                    true);
608     mChan = CallChannelPtr::qObjectCast(mConn->createChannel(request));
609     QVERIFY(mChan);
610 
611     QVERIFY(connect(mChan->becomeReady(Tp::CallChannel::FeatureLocalHoldState),
612                     SIGNAL(finished(Tp::PendingOperation*)),
613                     SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
614     QCOMPARE(mLoop->exec(), 0);
615     QVERIFY(mChan->isReady(Tp::CallChannel::FeatureLocalHoldState));
616 
617     QCOMPARE(static_cast<uint>(mChan->localHoldState()), static_cast<uint>(Tp::LocalHoldStateUnheld));
618     QCOMPARE(static_cast<uint>(mChan->localHoldStateReason()), static_cast<uint>(Tp::LocalHoldStateReasonNone));
619 
620     QVERIFY(connect(mChan.data(),
621                     SIGNAL(localHoldStateChanged(Tp::LocalHoldState, Tp::LocalHoldStateReason)),
622                     SLOT(onLocalHoldStateChanged(Tp::LocalHoldState, Tp::LocalHoldStateReason))));
623     // Request hold
624     QVERIFY(connect(mChan->requestHold(true),
625                     SIGNAL(finished(Tp::PendingOperation*)),
626                     SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
627     QCOMPARE(mLoop->exec(), 0);
628     while (mLocalHoldStates.size() != 2) {
629         QCOMPARE(mLoop->exec(), 0);
630     }
631     QCOMPARE(mLocalHoldStates.first(), static_cast<uint>(Tp::LocalHoldStatePendingHold));
632     QCOMPARE(mLocalHoldStateReasons.first(), static_cast<uint>(Tp::LocalHoldStateReasonRequested));
633     QCOMPARE(mLocalHoldStates.last(), static_cast<uint>(Tp::LocalHoldStateHeld));
634     QCOMPARE(mLocalHoldStateReasons.last(), static_cast<uint>(Tp::LocalHoldStateReasonRequested));
635     QCOMPARE(static_cast<uint>(mChan->localHoldState()), static_cast<uint>(Tp::LocalHoldStateHeld));
636     QCOMPARE(static_cast<uint>(mChan->localHoldStateReason()), static_cast<uint>(Tp::LocalHoldStateReasonRequested));
637 
638     mLocalHoldStates.clear();
639     mLocalHoldStateReasons.clear();
640 
641     // Request unhold
642     QVERIFY(connect(mChan->requestHold(false),
643                     SIGNAL(finished(Tp::PendingOperation*)),
644                     SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
645     QCOMPARE(mLoop->exec(), 0);
646     while (mLocalHoldStates.size() != 2) {
647         QCOMPARE(mLoop->exec(), 0);
648     }
649     QCOMPARE(mLocalHoldStates.first(), static_cast<uint>(Tp::LocalHoldStatePendingUnhold));
650     QCOMPARE(mLocalHoldStateReasons.first(), static_cast<uint>(Tp::LocalHoldStateReasonRequested));
651     QCOMPARE(mLocalHoldStates.last(), static_cast<uint>(Tp::LocalHoldStateUnheld));
652     QCOMPARE(mLocalHoldStateReasons.last(), static_cast<uint>(Tp::LocalHoldStateReasonRequested));
653     QCOMPARE(static_cast<uint>(mChan->localHoldState()), static_cast<uint>(Tp::LocalHoldStateUnheld));
654     QCOMPARE(static_cast<uint>(mChan->localHoldStateReason()), static_cast<uint>(Tp::LocalHoldStateReasonRequested));
655 }
656 
testHangup()657 void TestCallChannel::testHangup()
658 {
659     qDebug() << "requesting contact for alice";
660 
661     QList<ContactPtr> contacts = mConn->contacts(QStringList() << QLatin1String("alice"));
662     QCOMPARE(contacts.size(), 1);
663 
664     ContactPtr otherContact = contacts.at(0);
665     QVERIFY(otherContact);
666 
667     qDebug() << "creating the channel";
668 
669     QVariantMap request;
670     request.insert(TP_QT_IFACE_CHANNEL + QLatin1String(".ChannelType"),
671                    TP_QT_IFACE_CHANNEL_TYPE_CALL);
672     request.insert(TP_QT_IFACE_CHANNEL + QLatin1String(".TargetHandleType"),
673                    (uint) Tp::HandleTypeContact);
674     request.insert(TP_QT_IFACE_CHANNEL + QLatin1String(".TargetHandle"),
675                    otherContact->handle()[0]);
676     request.insert(TP_QT_IFACE_CHANNEL_TYPE_CALL + QLatin1String(".InitialVideo"),
677                    true);
678     mChan = CallChannelPtr::qObjectCast(mConn->createChannel(request));
679     QVERIFY(mChan);
680 
681     qDebug() << "making the channel ready";
682 
683     QVERIFY(connect(mChan->becomeReady(CallChannel::FeatureCallState),
684                     SIGNAL(finished(Tp::PendingOperation*)),
685                     SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
686     QCOMPARE(mLoop->exec(), 0);
687     QVERIFY(mChan->isReady(Tp::CallChannel::FeatureCallState));
688 
689     QCOMPARE(mChan->callState(), CallStatePendingInitiator);
690 
691     QVERIFY(connect(mChan->hangup(),
692                     SIGNAL(finished(Tp::PendingOperation*)),
693                     SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
694     QCOMPARE(mLoop->exec(), 0);
695     QCOMPARE(mChan->callState(), CallStateEnded);
696     QCOMPARE(mChan->callStateReason().reason, (uint) CallStateChangeReasonUserRequested);
697 }
698 
testCallMembers()699 void TestCallChannel::testCallMembers()
700 {
701     qDebug() << "requesting contact for john";
702 
703     QList<ContactPtr> contacts = mConn->contacts(QStringList() << QLatin1String("john"));
704     QCOMPARE(contacts.size(), 1);
705 
706     ContactPtr otherContact = contacts.at(0);
707     QVERIFY(otherContact);
708 
709     qDebug() << "creating the channel";
710 
711     QVariantMap request;
712     request.insert(TP_QT_IFACE_CHANNEL + QLatin1String(".ChannelType"),
713                    TP_QT_IFACE_CHANNEL_TYPE_CALL);
714     request.insert(TP_QT_IFACE_CHANNEL + QLatin1String(".TargetHandleType"),
715                    (uint) Tp::HandleTypeContact);
716     request.insert(TP_QT_IFACE_CHANNEL + QLatin1String(".TargetHandle"),
717                    otherContact->handle()[0]);
718     request.insert(TP_QT_IFACE_CHANNEL_TYPE_CALL + QLatin1String(".InitialVideo"),
719                    true);
720     mChan = CallChannelPtr::qObjectCast(mConn->createChannel(request));
721     QVERIFY(mChan);
722 
723     qDebug() << "making the channel ready";
724 
725     Features features;
726     features << CallChannel::FeatureCallState
727              << CallChannel::FeatureCallMembers
728              << CallChannel::FeatureContents;
729 
730     QVERIFY(connect(mChan->becomeReady(features),
731                     SIGNAL(finished(Tp::PendingOperation*)),
732                     SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
733     QCOMPARE(mLoop->exec(), 0);
734     QVERIFY(mChan->isReady(Tp::CallChannel::FeatureCallMembers));
735     QVERIFY(mChan->isReady(Tp::CallChannel::FeatureContents));
736 
737     qDebug() << "accepting the call";
738 
739     QCOMPARE(mChan->callState(), CallStatePendingInitiator);
740     QCOMPARE(mChan->remoteMembers().size(), 1);
741 
742     QVERIFY(connect(mChan->accept(),
743                     SIGNAL(finished(Tp::PendingOperation*)),
744                     SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
745     QCOMPARE(mLoop->exec(), 0);
746     QCOMPARE(mChan->callState(), CallStateInitialised);
747     QCOMPARE(mChan->callStateReason().reason, (uint) CallStateChangeReasonUserRequested);
748 
749     qDebug() << "ringing on the remote side";
750 
751     QVERIFY(connect(mChan.data(),
752                     SIGNAL(remoteMemberFlagsChanged(QHash<Tp::ContactPtr,Tp::CallMemberFlags>,Tp::CallStateReason)),
753                     SLOT(onRemoteMemberFlagsChanged(QHash<Tp::ContactPtr,Tp::CallMemberFlags>,Tp::CallStateReason))));
754     QCOMPARE(mLoop->exec(), 0);
755 
756     QCOMPARE(mChan->callState(), CallStateInitialised);
757     QCOMPARE(mRemoteMemberFlags.size(), 1);
758     QCOMPARE(mChan->remoteMembers().size(), 1);
759     QVERIFY(mRemoteMemberFlags.constBegin().value().testFlag(CallMemberFlagRinging));
760     QVERIFY(mChan->remoteMemberFlags(otherContact).testFlag(CallMemberFlagRinging));
761 
762     QVERIFY(disconnect(mChan.data(),
763                        SIGNAL(remoteMemberFlagsChanged(QHash<Tp::ContactPtr,Tp::CallMemberFlags>,Tp::CallStateReason)),
764                        this,
765                        SLOT(onRemoteMemberFlagsChanged(QHash<Tp::ContactPtr,Tp::CallMemberFlags>,Tp::CallStateReason))));
766 
767     qDebug() << "remote contact answers";
768 
769     QVERIFY(connect(mChan.data(),
770                     SIGNAL(callStateChanged(Tp::CallState)),
771                     SLOT(onCallStateChanged(Tp::CallState))));
772     QCOMPARE(mLoop->exec(), 0);
773 
774     QCOMPARE(mCallState, CallStateAccepted);
775     QCOMPARE(mChan->callState(), CallStateAccepted);
776 
777     QVERIFY(disconnect(mChan.data(),
778                        SIGNAL(callStateChanged(Tp::CallState)),
779                        this,
780                        SLOT(onCallStateChanged(Tp::CallState))));
781 
782     qDebug() << "testing members";
783 
784     QCOMPARE(mChan->contents().size(), 1);
785 
786     CallContentPtr content = mChan->contents().at(0);
787     QCOMPARE(content->streams().size(), 1);
788 
789     QCOMPARE(mChan->remoteMembers().size(), 1);
790     QCOMPARE(content->streams().at(0)->remoteMembers().size(), 1);
791 
792     ContactPtr contact1 = *mChan->remoteMembers().constBegin();
793     ContactPtr contact2 = *content->streams().at(0)->remoteMembers().constBegin();
794 
795     QCOMPARE(contact1->id(), QString::fromLatin1("john"));
796     QCOMPARE(contact2->id(), QString::fromLatin1("john"));
797 
798     qDebug() << "hanging up";
799 
800     QVERIFY(connect(mChan.data(),
801                     SIGNAL(remoteMembersRemoved(Tp::Contacts,Tp::CallStateReason)),
802                     SLOT(onRemoteMembersRemoved(Tp::Contacts,Tp::CallStateReason))));
803 
804     QVERIFY(connect(mChan->hangup(),
805                     SIGNAL(finished(Tp::PendingOperation*)),
806                     SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
807     QCOMPARE(mLoop->exec(), 0);
808     QCOMPARE(mChan->callState(), CallStateEnded);
809     QCOMPARE(mChan->callStateReason().reason, (uint) CallStateChangeReasonUserRequested);
810 
811     QCOMPARE(mRemoteMembersRemoved.size(), 1);
812     QCOMPARE((*mRemoteMembersRemoved.constBegin())->id(), QString::fromLatin1("john"));
813     QCOMPARE(mChan->remoteMembers().size(), 0);
814     QCOMPARE(mChan->contents().size(), 0);
815 }
816 
testDTMF()817 void TestCallChannel::testDTMF()
818 {
819     mConn->client()->lowlevel()->setSelfPresence(QLatin1String("away"), QLatin1String("preparing for a test"));
820 
821     Tp::Client::ConnectionInterfaceRequestsInterface *connRequestsInterface =
822         mConn->client()->optionalInterface<Tp::Client::ConnectionInterfaceRequestsInterface>();
823     QVERIFY(connect(connRequestsInterface,
824                     SIGNAL(NewChannels(const Tp::ChannelDetailsList&)),
825                     SLOT(onNewChannels(const Tp::ChannelDetailsList&))));
826 
827     mConn->client()->lowlevel()->setSelfPresence(QLatin1String("available"), QLatin1String("call me?"));
828     QCOMPARE(mLoop->exec(), 0);
829     QVERIFY(mChan);
830 
831     qDebug() << "making the channel ready";
832 
833     QVERIFY(connect(mChan->becomeReady(CallChannel::FeatureContents),
834                     SIGNAL(finished(Tp::PendingOperation*)),
835                     SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
836     QCOMPARE(mLoop->exec(), 0);
837     QVERIFY(mChan->isReady(Tp::CallChannel::FeatureContents));
838 
839     QVERIFY(connect(mChan->accept(),
840                     SIGNAL(finished(Tp::PendingOperation*)),
841                     SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
842     QCOMPARE(mLoop->exec(), 0);
843 
844     QCOMPARE(mChan->contents().size(), 1);
845     Tp::CallContentPtr content = mChan->contents().first();
846     QCOMPARE(content->channel(), mChan);
847     QCOMPARE(content->type(), Tp::MediaStreamTypeAudio);
848 
849 /*  TODO enable when/if the example call CM actually supports DTMF
850     QVERIFY(content->supportsDTMF());
851     QVERIFY(connect(content->startDTMFTone(DTMFEventDigit0),
852                     SIGNAL(finished(Tp::PendingOperation*)),
853                     SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
854     QCOMPARE(mLoop->exec(), 0);
855 
856     QVERIFY(connect(content->stopDTMFTone(),
857                     SIGNAL(finished(Tp::PendingOperation*)),
858                     SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
859     QCOMPARE(mLoop->exec(), 0);
860 
861     QVERIFY(connect(content->startDTMFTone((DTMFEvent) 1234),
862                     SIGNAL(finished(Tp::PendingOperation*)),
863                     SLOT(expectFailure(Tp::PendingOperation*))));
864     QCOMPARE(mLoop->exec(), 0);
865     QCOMPARE(mLastError, QString(TP_QT_ERROR_INVALID_ARGUMENT));
866 */
867 
868     qDebug() << "requesting video content";
869 
870     QVERIFY(connect(mChan->requestContent(QLatin1String("video_content"),
871                                           Tp::MediaStreamTypeVideo,
872                                           Tp::MediaStreamDirectionBidirectional),
873                     SIGNAL(finished(Tp::PendingOperation*)),
874                     SLOT(expectRequestContentFinished(Tp::PendingOperation*))));
875     QCOMPARE(mLoop->exec(), 0);
876     content = mRequestContentReturn;
877     QCOMPARE(content->type(), Tp::MediaStreamTypeVideo);
878 
879     QVERIFY(!content->supportsDTMF());
880     QVERIFY(connect(content->startDTMFTone(DTMFEventDigit0),
881                     SIGNAL(finished(Tp::PendingOperation*)),
882                     SLOT(expectFailure(Tp::PendingOperation*))));
883     QCOMPARE(mLoop->exec(), 0);
884     QCOMPARE(mLastError, QString(TP_QT_ERROR_NOT_IMPLEMENTED));
885 
886     QVERIFY(connect(content->stopDTMFTone(),
887                     SIGNAL(finished(Tp::PendingOperation*)),
888                     SLOT(expectFailure(Tp::PendingOperation*))));
889     QCOMPARE(mLoop->exec(), 0);
890     QCOMPARE(mLastError, QString(TP_QT_ERROR_NOT_IMPLEMENTED));
891 }
892 
testFeatureCore()893 void TestCallChannel::testFeatureCore()
894 {
895     qDebug() << "requesting contact for alice";
896 
897     QList<ContactPtr> contacts = mConn->contacts(QStringList() << QLatin1String("alice"));
898     QCOMPARE(contacts.size(), 1);
899 
900     ContactPtr otherContact = contacts.at(0);
901     QVERIFY(otherContact);
902 
903     qDebug() << "creating the channel";
904 
905     QVariantMap request;
906     request.insert(TP_QT_IFACE_CHANNEL + QLatin1String(".ChannelType"),
907                    TP_QT_IFACE_CHANNEL_TYPE_CALL);
908     request.insert(TP_QT_IFACE_CHANNEL + QLatin1String(".TargetHandleType"),
909                    (uint) Tp::HandleTypeContact);
910     request.insert(TP_QT_IFACE_CHANNEL + QLatin1String(".TargetHandle"),
911                    otherContact->handle()[0]);
912     request.insert(TP_QT_IFACE_CHANNEL_TYPE_CALL + QLatin1String(".InitialAudio"),
913                    true);
914     mChan = CallChannelPtr::qObjectCast(mConn->createChannel(request));
915     QVERIFY(mChan);
916 
917     QVERIFY(connect(mChan->becomeReady(),
918                     SIGNAL(finished(Tp::PendingOperation*)),
919                     SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
920     QCOMPARE(mLoop->exec(), 0);
921     QVERIFY(mChan->isReady(Tp::CallChannel::FeatureCore));
922 
923     QVERIFY(mChan->hasInitialAudio());
924     QCOMPARE(mChan->initialAudioName(), QString::fromLatin1("audio"));
925     QVERIFY(!mChan->hasInitialVideo());
926     QCOMPARE(mChan->initialVideoName(), QString::fromLatin1("video"));
927     QVERIFY(mChan->hasMutableContents());
928     QVERIFY(mChan->handlerStreamingRequired());
929 
930     qDebug() << "creating second CallChannel object";
931 
932     //this object is not passed immutable properties on
933     //the constructor, so it will have to introspect them.
934     CallChannelPtr chan2 = CallChannel::create(mConn->client(), mChan->objectPath(), QVariantMap());
935 
936     QVERIFY(connect(chan2->becomeReady(),
937                     SIGNAL(finished(Tp::PendingOperation*)),
938                     SLOT(expectSuccessfulCall(Tp::PendingOperation*))));
939     QCOMPARE(mLoop->exec(), 0);
940     QVERIFY(chan2->isReady(Tp::CallChannel::FeatureCore));
941 
942     QVERIFY(chan2->hasInitialAudio());
943     QCOMPARE(chan2->initialAudioName(), QString::fromLatin1("audio"));
944     QVERIFY(!chan2->hasInitialVideo());
945     QCOMPARE(chan2->initialVideoName(), QString::fromLatin1("video"));
946     QVERIFY(chan2->hasMutableContents());
947     QVERIFY(chan2->handlerStreamingRequired());
948 }
949 
cleanup()950 void TestCallChannel::cleanup()
951 {
952     mChan.reset();
953     cleanupImpl();
954 }
955 
cleanupTestCase()956 void TestCallChannel::cleanupTestCase()
957 {
958     QCOMPARE(mConn->disconnect(), true);
959     delete mConn;
960 
961     cleanupTestCaseImpl();
962 }
963 
964 QTEST_MAIN(TestCallChannel)
965 #include "_gen/call-channel.cpp.moc.hpp"
966