Music Hub ..
A session-wide music playback service
call_monitor.cpp
Go to the documentation of this file.
1/*
2 * Copyright (C) 2014 Canonical Ltd
3 *
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU Lesser General Public License version 3 as
6 * published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU Lesser General Public License for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 * Author: Justin McPherson <justin.mcpherson@canonical.com>
17 */
18
19
20#include "call_monitor.h"
21
23
24#include "qtbridge.h"
25#include <TelepathyQt/AccountManager>
26#include <TelepathyQt/SimpleCallObserver>
27#include <TelepathyQt/PendingOperation>
28#include <TelepathyQt/PendingReady>
29#include <TelepathyQt/PendingAccount>
30
31#include <list>
32#include <mutex>
33
34namespace media = core::ubuntu::media;
35
36namespace
37{
38namespace impl
39{
40class TelepathyCallMonitor : public QObject
41{
42 Q_OBJECT
43public:
44 TelepathyCallMonitor(const Tp::AccountPtr& account):
45 mAccount(account),
46 mCallObserver(Tp::SimpleCallObserver::create(mAccount)) {
47 connect(mCallObserver.data(), SIGNAL(callStarted(Tp::CallChannelPtr)), SIGNAL(offHook()));
48 connect(mCallObserver.data(), SIGNAL(callEnded(Tp::CallChannelPtr,QString,QString)), SIGNAL(onHook()));
49 connect(mCallObserver.data(), SIGNAL(streamedMediaCallStarted(Tp::StreamedMediaChannelPtr)), SIGNAL(offHook()));
50 connect(mCallObserver.data(), SIGNAL(streamedMediaCallEnded(Tp::StreamedMediaChannelPtr,QString,QString)), SIGNAL(onHook()));
51 }
52
53Q_SIGNALS:
54 void offHook();
55 void onHook();
56
57private:
58 Tp::AccountPtr mAccount;
59 Tp::SimpleCallObserverPtr mCallObserver;
60};
61
62
63class TelepathyBridge : public QObject
64{
65 Q_OBJECT
66public:
67 TelepathyBridge():
68 QObject(0) {
69 Tp::registerTypes();
70
71 QTimer::singleShot(0, this, SLOT(accountManagerSetup()));
72 }
73
74 ~TelepathyBridge() {
75 for (std::list<TelepathyCallMonitor*>::iterator it = mCallMonitors.begin();
76 it != mCallMonitors.end();
77 ++it) {
78 delete *it;
79 }
80 }
81
82 void on_change(const std::function<void(media::telephony::CallMonitor::State)>& func) {
83 std::lock_guard<std::mutex> l(cb_lock);
84 cb = func;
85 }
86
87private Q_SLOTS:
88 void accountManagerSetup() {
89 mAccountManager = Tp::AccountManager::create(Tp::AccountFactory::create(QDBusConnection::sessionBus(),
90 Tp::Account::FeatureCore),
91 Tp::ConnectionFactory::create(QDBusConnection::sessionBus(),
92 Tp::Connection::FeatureCore));
93 connect(mAccountManager->becomeReady(),
94 SIGNAL(finished(Tp::PendingOperation*)),
95 SLOT(accountManagerReady(Tp::PendingOperation*)));
96 }
97
98 void accountManagerReady(Tp::PendingOperation* operation) {
99 static uint8_t retries = 0;
100 if (operation->isError()) {
101 MH_ERROR("TelepathyBridge: Operation failed (accountManagerReady)");
102 if (retries < 10) {
103 QTimer::singleShot(1000, this, SLOT(accountManagerSetup())); // again
104 ++retries;
105 }
106 return;
107 }
108
109 Q_FOREACH(const Tp::AccountPtr& account, mAccountManager->allAccounts()) {
110 connect(account->becomeReady(Tp::Account::FeatureCapabilities),
111 SIGNAL(finished(Tp::PendingOperation*)),
112 SLOT(accountReady(Tp::PendingOperation*)));
113 }
114
115 connect(mAccountManager.data(), SIGNAL(newAccount(Tp::AccountPtr)), SLOT(newAccount(Tp::AccountPtr)));
116 }
117
118 void newAccount(const Tp::AccountPtr& account)
119 {
120 connect(account->becomeReady(Tp::Account::FeatureCapabilities),
121 SIGNAL(finished(Tp::PendingOperation*)),
122 SLOT(accountReady(Tp::PendingOperation*)));
123 }
124
125 void accountReady(Tp::PendingOperation* operation) {
126 if (operation->isError()) {
127 MH_ERROR("TelepathyAccount: Operation failed (accountReady)");
128 return;
129 }
130
131 Tp::PendingReady* pendingReady = qobject_cast<Tp::PendingReady*>(operation);
132 if (pendingReady == 0) {
133 MH_ERROR("Rejecting account because could not understand ready status");
134 return;
135 }
136
137 checkAndAddAccount(Tp::AccountPtr::qObjectCast(pendingReady->proxy()));
138 }
139
140 void offHook()
141 {
142 std::lock_guard<std::mutex> l(cb_lock);
143 if (cb)
144 cb(media::telephony::CallMonitor::State::OffHook);
145 }
146
147 void onHook()
148 {
149 std::lock_guard<std::mutex> l(cb_lock);
150 if (cb)
151 cb(media::telephony::CallMonitor::State::OnHook);
152 }
153
154private:
155 std::mutex cb_lock;
156 std::function<void (media::telephony::CallMonitor::State)> cb;
157 Tp::AccountManagerPtr mAccountManager;
158 std::list<TelepathyCallMonitor*> mCallMonitors;
159
160 void checkAndAddAccount(const Tp::AccountPtr& account)
161 {
162 Tp::ConnectionCapabilities caps = account->capabilities();
163 // TODO: Later on we will need to filter for the right capabilities, and also allow dynamic account detection
164 // Don't check caps for now as a workaround for https://bugs.launchpad.net/ubuntu/+source/media-hub/+bug/1409125
165 // at least until we are able to find out the root cause of it (check rev 107 for the caps check)
166 auto tcm = new TelepathyCallMonitor(account);
167 connect(tcm, SIGNAL(offHook()), SLOT(offHook()));
168 connect(tcm, SIGNAL(onHook()), SLOT(onHook()));
169 mCallMonitors.push_back(tcm);
170 }
171};
172
173struct CallMonitor : public media::telephony::CallMonitor
174{
175 CallMonitor() : mBridge{nullptr}
176 {
177 try
178 {
179 qt_world = std::move(std::thread([this]()
180 {
181 qt::core::world::build_and_run(0, nullptr, [this]()
182 {
184 {
185 MH_DEBUG("CallMonitor: Creating TelepathyBridge");
186 mBridge = new TelepathyBridge();
187 cv.notify_all();
188 });
189 });
190 }));
191 } catch(const std::system_error& error) {
192 MH_ERROR("exception(std::system_error) in CallMonitor thread start %s", error.what());
193 } catch(...) {
194 MH_ERROR("exception(...) in CallMonitor thread start");
195 }
196
197 // Wait until telepathy bridge is set, so we can hook up the change signals
198 std::unique_lock<std::mutex> lck(mtx);
199 cv.wait_for(lck, std::chrono::seconds(3));
200
201 if (mBridge)
202 {
203 mBridge->on_change([this](CallMonitor::State state)
204 {
205 call_state_changed(state);
206 });
207 }
208 }
209
210 ~CallMonitor()
211 {
212 // We first clean up the bridge instance.
214 {
215 delete mBridge;
216 }).get();
217
218 // We then request destruction of the qt world.
220
221 // Before we finally join the worker.
222 if (qt_world.joinable())
223 qt_world.join();
224 }
225
226 const core::Signal<media::telephony::CallMonitor::State>& on_call_state_changed() const
227 {
228 return call_state_changed;
229 }
230
231 TelepathyBridge *mBridge;
232 core::Signal<media::telephony::CallMonitor::State> call_state_changed;
233
234 std::thread qt_world;
235 std::mutex mtx;
236 std::condition_variable cv;
237};
238}
239}
240
241media::telephony::CallMonitor::Ptr media::telephony::make_platform_default_call_monitor()
242{
243 return std::make_shared<::impl::CallMonitor>();
244}
245
246#include "call_monitor.moc"
247
#define MH_ERROR(...)
Definition: logger.h:128
#define MH_DEBUG(...)
Definition: logger.h:123
CallMonitor::Ptr make_platform_default_call_monitor()
void build_and_run(int argc, char **argv, const std::function< void()> &ready)
Sets up the Qt core world and executes its event loop. Blocks until destroy() is called.
Definition: qtbridge.cpp:130
void destroy()
Destroys the Qt core world and quits its event loop.
Definition: qtbridge.cpp:152
std::future< void > enter_with_task(const std::function< void()> &task)
Enters the Qt core world and schedules the given task for execution.
Definition: qtbridge.cpp:161