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 
22 #include "qtbridge.h"
23 #include <TelepathyQt/AccountManager>
24 #include <TelepathyQt/SimpleCallObserver>
25 #include <TelepathyQt/PendingOperation>
26 #include <TelepathyQt/PendingReady>
27 #include <TelepathyQt/PendingAccount>
28 
29 #include <list>
30 #include <mutex>
31 
32 namespace media = core::ubuntu::media;
33 
34 namespace
35 {
36 namespace impl
37 {
38 class TelepathyCallMonitor : public QObject
39 {
40  Q_OBJECT
41 public:
42  TelepathyCallMonitor(const Tp::AccountPtr& account):
43  mAccount(account),
44  mCallObserver(Tp::SimpleCallObserver::create(mAccount)) {
45  connect(mCallObserver.data(), SIGNAL(callStarted(Tp::CallChannelPtr)), SIGNAL(offHook()));
46  connect(mCallObserver.data(), SIGNAL(callEnded(Tp::CallChannelPtr,QString,QString)), SIGNAL(onHook()));
47  connect(mCallObserver.data(), SIGNAL(streamedMediaCallStarted(Tp::StreamedMediaChannelPtr)), SIGNAL(offHook()));
48  connect(mCallObserver.data(), SIGNAL(streamedMediaCallEnded(Tp::StreamedMediaChannelPtr,QString,QString)), SIGNAL(onHook()));
49  }
50 
51 Q_SIGNALS:
52  void offHook();
53  void onHook();
54 
55 private:
56  Tp::AccountPtr mAccount;
57  Tp::SimpleCallObserverPtr mCallObserver;
58 };
59 
60 
61 class TelepathyBridge : public QObject
62 {
63  Q_OBJECT
64 public:
65  TelepathyBridge():
66  QObject(0) {
67  Tp::registerTypes();
68 
69  QTimer::singleShot(0, this, SLOT(accountManagerSetup()));
70  }
71 
72  ~TelepathyBridge() {
73  for (std::list<TelepathyCallMonitor*>::iterator it = mCallMonitors.begin();
74  it != mCallMonitors.end();
75  ++it) {
76  delete *it;
77  }
78  }
79 
80  void on_change(const std::function<void(media::telephony::CallMonitor::State)>& func) {
81  std::lock_guard<std::mutex> l(cb_lock);
82  cb = func;
83  }
84 
85 private Q_SLOTS:
86  void accountManagerSetup() {
87  mAccountManager = Tp::AccountManager::create(Tp::AccountFactory::create(QDBusConnection::sessionBus(),
88  Tp::Account::FeatureCore),
89  Tp::ConnectionFactory::create(QDBusConnection::sessionBus(),
90  Tp::Connection::FeatureCore));
91  connect(mAccountManager->becomeReady(),
92  SIGNAL(finished(Tp::PendingOperation*)),
93  SLOT(accountManagerReady(Tp::PendingOperation*)));
94  }
95 
96  void accountManagerReady(Tp::PendingOperation* operation) {
97  if (operation->isError()) {
98  std::cerr << "TelepathyBridge: Operation failed (accountManagerReady)" << std::endl;
99  QTimer::singleShot(1000, this, SLOT(accountManagerSetup())); // again
100  return;
101  }
102 
103  Q_FOREACH(const Tp::AccountPtr& account, mAccountManager->allAccounts()) {
104  connect(account->becomeReady(Tp::Account::FeatureCapabilities),
105  SIGNAL(finished(Tp::PendingOperation*)),
106  SLOT(accountReady(Tp::PendingOperation*)));
107  }
108 
109  connect(mAccountManager.data(), SIGNAL(newAccount(Tp::AccountPtr)), SLOT(newAccount(Tp::AccountPtr)));
110  }
111 
112  void newAccount(const Tp::AccountPtr& account)
113  {
114  connect(account->becomeReady(Tp::Account::FeatureCapabilities),
115  SIGNAL(finished(Tp::PendingOperation*)),
116  SLOT(accountReady(Tp::PendingOperation*)));
117  }
118 
119  void accountReady(Tp::PendingOperation* operation) {
120  if (operation->isError()) {
121  std::cerr << "TelepathyAccount: Operation failed (accountReady)" << std::endl;
122  return;
123  }
124 
125  Tp::PendingReady* pendingReady = qobject_cast<Tp::PendingReady*>(operation);
126  if (pendingReady == 0) {
127  std::cerr << "Rejecting account because could not understand ready status" << std::endl;
128  return;
129  }
130 
131  checkAndAddAccount(Tp::AccountPtr::qObjectCast(pendingReady->proxy()));
132  }
133 
134  void offHook()
135  {
136  std::lock_guard<std::mutex> l(cb_lock);
137  if (cb)
138  cb(media::telephony::CallMonitor::State::OffHook);
139  }
140 
141  void onHook()
142  {
143  std::lock_guard<std::mutex> l(cb_lock);
144  if (cb)
145  cb(media::telephony::CallMonitor::State::OnHook);
146  }
147 
148 private:
149  std::mutex cb_lock;
150  std::function<void (media::telephony::CallMonitor::State)> cb;
151  Tp::AccountManagerPtr mAccountManager;
152  std::list<TelepathyCallMonitor*> mCallMonitors;
153 
154  void checkAndAddAccount(const Tp::AccountPtr& account)
155  {
156  Tp::ConnectionCapabilities caps = account->capabilities();
157  // TODO: Later on we will need to filter for the right capabilities, and also allow dynamic account detection
158  // Don't check caps for now as a workaround for https://bugs.launchpad.net/ubuntu/+source/media-hub/+bug/1409125
159  // at least until we are able to find out the root cause of it (check rev 107 for the caps check)
160  auto tcm = new TelepathyCallMonitor(account);
161  connect(tcm, SIGNAL(offHook()), SLOT(offHook()));
162  connect(tcm, SIGNAL(onHook()), SLOT(onHook()));
163  mCallMonitors.push_back(tcm);
164  }
165 };
166 
167 struct CallMonitor : public media::telephony::CallMonitor
168 {
169  CallMonitor() : mBridge{nullptr}
170  {
171  try
172  {
173  qt_world = std::move(std::thread([this]()
174  {
175  qt::core::world::build_and_run(0, nullptr, [this]()
176  {
178  {
179  std::cout << "CallMonitor: Creating TelepathyBridge" << std::endl;
180  mBridge = new TelepathyBridge();
181  cv.notify_all();
182  });
183  });
184  }));
185  } catch(const std::system_error& error) {
186  std::cerr << "exception(std::system_error) in CallMonitor thread start" << error.what() << std::endl;
187  } catch(...) {
188  std::cerr << "exception(...) in CallMonitor thread start" << std::endl;
189  }
190 
191  // Wait until telepathy bridge is set, so we can hook up the change signals
192  std::unique_lock<std::mutex> lck(mtx);
193  cv.wait_for(lck, std::chrono::seconds(3));
194 
195  if (mBridge)
196  {
197  mBridge->on_change([this](CallMonitor::State state)
198  {
199  call_state_changed(state);
200  });
201  }
202  }
203 
204  ~CallMonitor()
205  {
206  // We first clean up the bridge instance.
208  {
209  delete mBridge;
210  }).get();
211 
212  // We then request destruction of the qt world.
214 
215  // Before we finally join the worker.
216  if (qt_world.joinable())
217  qt_world.join();
218  }
219 
220  const core::Signal<media::telephony::CallMonitor::State>& on_call_state_changed() const
221  {
222  return call_state_changed;
223  }
224 
225  TelepathyBridge *mBridge;
226  core::Signal<media::telephony::CallMonitor::State> call_state_changed;
227 
228  std::thread qt_world;
229  std::mutex mtx;
230  std::condition_variable cv;
231 };
232 }
233 }
234 
235 media::telephony::CallMonitor::Ptr media::telephony::make_platform_default_call_monitor()
236 {
237  return std::make_shared<impl::CallMonitor>();
238 }
239 
240 #include "call_monitor.moc"
241 
void destroy()
Destroys the Qt core world and quits its event loop.
Definition: qtbridge.cpp:154
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:132
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:163