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 
34 namespace media = core::ubuntu::media;
35 
36 namespace
37 {
38 namespace impl
39 {
40 class TelepathyCallMonitor : public QObject
41 {
42  Q_OBJECT
43 public:
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 
53 Q_SIGNALS:
54  void offHook();
55  void onHook();
56 
57 private:
58  Tp::AccountPtr mAccount;
59  Tp::SimpleCallObserverPtr mCallObserver;
60 };
61 
62 
63 class TelepathyBridge : public QObject
64 {
65  Q_OBJECT
66 public:
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 
87 private 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 
154 private:
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 
173 struct 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 
241 media::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 
void destroy()
Destroys the Qt core world and quits its event loop.
Definition: qtbridge.cpp:152
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
#define MH_ERROR(...)
Definition: logger.h:128
#define MH_DEBUG(...)
Definition: logger.h:123
CallMonitor::Ptr make_platform_default_call_monitor()
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