Merge lp:~tiagosh/telephony-service/audio-route-manager into lp:telephony-service

Proposed by Tiago Salem Herrmann
Status: Needs review
Proposed branch: lp:~tiagosh/telephony-service/audio-route-manager
Merge into: lp:telephony-service
Diff against target: 2127 lines (+1661/-89)
24 files modified
CMakeLists.txt (+1/-0)
debian/control (+1/-0)
handler/CMakeLists.txt (+11/-0)
handler/Handler.xml (+23/-7)
handler/audioroutemanager.cpp (+231/-0)
handler/audioroutemanager.h (+75/-0)
handler/callhandler.cpp (+0/-11)
handler/callhandler.h (+0/-1)
handler/handlerdbus.cpp (+24/-5)
handler/handlerdbus.h (+11/-1)
handler/main.cpp (+12/-5)
handler/powerd.h (+34/-0)
handler/powerdaudiomodemediator.cpp (+63/-0)
handler/powerdaudiomodemediator.h (+46/-0)
handler/powerddbus.cpp (+43/-0)
handler/powerddbus.h (+38/-0)
handler/qpulseaudioengine.cpp (+853/-0)
handler/qpulseaudioengine.h (+126/-0)
libtelephonyservice/audiooutput.cpp (+17/-0)
libtelephonyservice/audiooutput.h (+4/-0)
libtelephonyservice/callentry.cpp (+38/-50)
libtelephonyservice/callentry.h (+1/-1)
libtelephonyservice/telepathyhelper.cpp (+9/-7)
libtelephonyservice/telepathyhelper.h (+0/-1)
To merge this branch: bzr merge lp:~tiagosh/telephony-service/audio-route-manager
Reviewer Review Type Date Requested Status
system-apps-ci-bot continuous-integration Needs Fixing
PS Jenkins bot continuous-integration Approve
Ubuntu Phablet Team Pending
Review via email: mp+283200@code.launchpad.net

Commit message

Implement AudioRouteManager in telephony-service-handler

Description of the change

Implement AudioRouteManager in telephony-service-handler

To post a comment you must log in.
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
1161. By Tiago Salem Herrmann

fix get AudioOutputs

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
1162. By Tiago Salem Herrmann

remove unused include

1163. By Tiago Salem Herrmann

add ifdef to avoid including qpulseaudioengine.h

1164. By Tiago Salem Herrmann

include qpulseaudioengine only when libpulse is installed

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
system-apps-ci-bot (system-apps-ci-bot) wrote :
review: Needs Fixing (continuous-integration)

Unmerged revisions

1164. By Tiago Salem Herrmann

include qpulseaudioengine only when libpulse is installed

1163. By Tiago Salem Herrmann

add ifdef to avoid including qpulseaudioengine.h

1162. By Tiago Salem Herrmann

remove unused include

1161. By Tiago Salem Herrmann

fix get AudioOutputs

1160. By Tiago Salem Herrmann

Implement AudioRouteManager in telephony-service-handler

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'CMakeLists.txt'
2--- CMakeLists.txt 2015-10-13 14:42:57 +0000
3+++ CMakeLists.txt 2016-01-20 11:56:12 +0000
4@@ -73,6 +73,7 @@
5 pkg_check_modules(UserMetrics REQUIRED libusermetricsinput-1)
6 pkg_check_modules(HISTORY REQUIRED history-service)
7 pkg_check_modules(GSETTINGS_QT REQUIRED gsettings-qt)
8+pkg_check_modules(PULSEAUDIO libpulse)
9
10 add_definitions(-DQT_NO_KEYWORDS)
11
12
13=== modified file 'debian/control'
14--- debian/control 2014-09-05 13:47:18 +0000
15+++ debian/control 2016-01-20 11:56:12 +0000
16@@ -18,6 +18,7 @@
17 libtelepathy-qt5-dev,
18 libubuntu-application-api-dev [armhf],
19 libprotobuf-dev,
20+ libpulse-dev [armhf],
21 pkg-config,
22 python:any,
23 qml-module-qttest,
24
25=== modified file 'handler/CMakeLists.txt'
26--- handler/CMakeLists.txt 2015-03-10 21:06:56 +0000
27+++ handler/CMakeLists.txt 2016-01-20 11:56:12 +0000
28@@ -1,10 +1,19 @@
29+if (PULSEAUDIO_FOUND)
30+ add_definitions(-DUSE_PULSEAUDIO)
31+ set(USE_PULSEAUDIO ON)
32+ set(QPULSEAUDIOENGINE_CPP qpulseaudioengine.cpp)
33+endif (PULSEAUDIO_FOUND)
34
35 set(qt_SRCS
36+ audioroutemanager.cpp
37 callhandler.cpp
38 displaynamesettings.cpp
39 handler.cpp
40 handlerdbus.cpp
41+ powerdaudiomodemediator.cpp
42+ powerddbus.cpp
43 texthandler.cpp
44+ ${QPULSEAUDIOENGINE_CPP}
45 )
46
47 set(handler_SRCS main.cpp ${qt_SRCS})
48@@ -15,6 +24,7 @@
49 ${CMAKE_SOURCE_DIR}/libtelephonyservice
50 ${CMAKE_CURRENT_BINARY_DIR}
51 ${GSETTINGS_QT_INCLUDE_DIRS}
52+ ${PULSEAUDIO_INCLUDE_DIRS}
53 )
54
55 add_executable(telephony-service-handler ${handler_SRCS} ${handler_HDRS})
56@@ -24,6 +34,7 @@
57 ${TP_QT5_LIBRARIES}
58 ${GSETTINGS_QT_LDFLAGS}
59 telephonyservice
60+ ${PULSEAUDIO_LIBRARIES}
61 )
62
63 enable_coverage(telephony-service-handler)
64
65=== modified file 'handler/Handler.xml'
66--- handler/Handler.xml 2015-11-17 17:23:54 +0000
67+++ handler/Handler.xml 2016-01-20 11:56:12 +0000
68@@ -82,13 +82,6 @@
69 <arg name="objectPath" type="s" direction="in"/>
70 <arg name="muted" type="b" direction="in"/>
71 </method>
72- <method name="SetActiveAudioOutput">
73- <dox:d><![CDATA[
74- Change the current active audio output
75- ]]></dox:d>
76- <arg name="objectPath" type="s" direction="in"/>
77- <arg name="id" type="s" direction="in"/>
78- </method>
79 <method name="SendDTMF">
80 <dox:d><![CDATA[
81 Send a DTMF to the given channel
82@@ -170,5 +163,28 @@
83 ]]></dox:d>
84 <arg name="visible" type="b"/>
85 </signal>
86+ <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="true"/>
87+ <property name="ActiveAudioOutput" type="s" access="readwrite"/>
88+ <signal name="ActiveAudioOutputChanged">
89+ <dox:d><![CDATA[
90+ The active audio output has changed
91+ ]]></dox:d>
92+ <arg name="id" type="s"/>
93+ </signal>
94+ <method name="AudioOutputs">
95+ <dox:d><![CDATA[
96+ ]]></dox:d>
97+ <arg name="outputs" type="a(sss)" direction="out"/>
98+ <annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="AudioOutputDBusList"/>
99+ <annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="AudioOutputDBusList"/>
100+ </method>
101+ <signal name="AudioOutputsChanged">
102+ <dox:d><![CDATA[
103+ The available audio outputs have changed
104+ ]]></dox:d>
105+ <arg name="outputs" type="a(sss)"/>
106+ <annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="AudioOutputDBusList"/>
107+ <annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="AudioOutputDBusList"/>
108+ </signal>
109 </interface>
110 </node>
111
112=== added file 'handler/audioroutemanager.cpp'
113--- handler/audioroutemanager.cpp 1970-01-01 00:00:00 +0000
114+++ handler/audioroutemanager.cpp 2016-01-20 11:56:12 +0000
115@@ -0,0 +1,231 @@
116+/*
117+ * Copyright (C) 2016 Canonical, Ltd.
118+ *
119+ * Authors:
120+ * Tiago Salem Herrmann <tiago.herrmann@canonical.com>
121+ *
122+ * This file is part of telephony-service.
123+ *
124+ * telephony-service is free software; you can redistribute it and/or modify
125+ * it under the terms of the GNU General Public License as published by
126+ * the Free Software Foundation; version 3.
127+ *
128+ * telephony-service is distributed in the hope that it will be useful,
129+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
130+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
131+ * GNU General Public License for more details.
132+ *
133+ * You should have received a copy of the GNU General Public License
134+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
135+ */
136+
137+#include "audioroutemanager.h"
138+#include "telepathyhelper.h"
139+#include "accountentry.h"
140+#include <TelepathyQt/Contact>
141+#include <TelepathyQt/Functors>
142+
143+
144+static void enable_earpiece()
145+{
146+#ifdef USE_PULSEAUDIO
147+ QPulseAudioEngine::instance()->setCallMode(CallActive, AudioModeBtOrWiredOrEarpiece);
148+#endif
149+}
150+
151+static void enable_normal()
152+{
153+#ifdef USE_PULSEAUDIO
154+ QTimer* timer = new QTimer();
155+ timer->setSingleShot(true);
156+ QObject::connect(timer, &QTimer::timeout, [=](){
157+ QPulseAudioEngine::instance()->setMicMute(false);
158+ QPulseAudioEngine::instance()->setCallMode(CallEnded, AudioModeWiredOrSpeaker);
159+ timer->deleteLater();
160+ });
161+ timer->start(2000);
162+#endif
163+}
164+
165+static void enable_speaker()
166+{
167+#ifdef USE_PULSEAUDIO
168+ QPulseAudioEngine::instance()->setCallMode(CallActive, AudioModeSpeaker);
169+#endif
170+}
171+
172+static void enable_ringtone()
173+{
174+#ifdef USE_PULSEAUDIO
175+ QPulseAudioEngine::instance()->setCallMode(CallRinging, AudioModeBtOrWiredOrSpeaker);
176+#endif
177+}
178+
179+AudioRouteManager *AudioRouteManager::instance()
180+{
181+ static AudioRouteManager *self = new AudioRouteManager();
182+ return self;
183+}
184+
185+AudioRouteManager::AudioRouteManager(QObject *parent) :
186+ QObject(parent), mAudioModeMediator(mPowerDDBus)
187+{
188+ TelepathyHelper::instance()->registerChannelObserver("TelephonyServiceHandlerAudioRouteManager");
189+
190+ QObject::connect(TelepathyHelper::instance()->channelObserver(), SIGNAL(callChannelAvailable(Tp::CallChannelPtr)),
191+ this, SLOT(onCallChannelAvailable(Tp::CallChannelPtr)));
192+
193+#ifdef USE_PULSEAUDIO
194+ // update audio modes
195+ QObject::connect(QPulseAudioEngine::instance(), SIGNAL(audioModeChanged(AudioMode)), SLOT(onAudioModeChanged(AudioMode)));
196+ QObject::connect(QPulseAudioEngine::instance(), SIGNAL(availableAudioModesChanged(AudioModes)), SLOT(onAvailableAudioModesChanged(AudioModes)));
197+
198+ // check if we should indeed use pulseaudio
199+ QByteArray pulseAudioDisabled = qgetenv("PA_DISABLED");
200+ mHasPulseAudio = true;
201+ if (!pulseAudioDisabled.isEmpty())
202+ mHasPulseAudio = false;
203+#endif
204+
205+ connect(this, &AudioRouteManager::activeAudioOutputChanged, Tp::memFun(&mAudioModeMediator, &PowerDAudioModeMediator::audioModeChanged));
206+ connect(this, &AudioRouteManager::lastChannelClosed, Tp::memFun(&mAudioModeMediator, &PowerDAudioModeMediator::audioOutputClosed));
207+}
208+
209+void AudioRouteManager::onCallChannelAvailable(Tp::CallChannelPtr callChannel)
210+{
211+ connect(callChannel.data(),
212+ SIGNAL(callStateChanged(Tp::CallState)),
213+ SLOT(onCallStateChanged(Tp::CallState)));
214+
215+ mChannels.append(callChannel);
216+ updateAudioRoute(true);
217+}
218+
219+void AudioRouteManager::onCallStateChanged(Tp::CallState state)
220+{
221+ Tp::CallChannelPtr channel(qobject_cast<Tp::CallChannel*>(sender()));
222+ if (!channel) {
223+ return;
224+ }
225+
226+ if (channel->callState() == Tp::CallStateEnded) {
227+ mChannels.removeOne(channel);
228+ }
229+ updateAudioRoute(false);
230+}
231+
232+void AudioRouteManager::setActiveAudioOutput(const QString &id)
233+{
234+#ifdef USE_PULSEAUDIO
235+ // fallback to earpiece/headset
236+ AudioMode mode = AudioModeWiredOrEarpiece;
237+ if (id == "bluetooth") {
238+ mode = AudioModeBluetooth;
239+ } else if (id == "speaker") {
240+ mode = AudioModeSpeaker;
241+ }
242+ if (mHasPulseAudio)
243+ QPulseAudioEngine::instance()->setCallMode(CallActive, mode);
244+#endif
245+}
246+
247+QString AudioRouteManager::activeAudioOutput()
248+{
249+ return mActiveAudioOutput;
250+}
251+
252+AudioOutputDBusList AudioRouteManager::audioOutputs() const
253+{
254+ return mAudioOutputs;
255+}
256+
257+void AudioRouteManager::updateAudioRoute(bool newCall)
258+{
259+#ifdef USE_PULSEAUDIO
260+ if (!mHasPulseAudio)
261+ return;
262+#endif
263+
264+ int currentCalls = mChannels.size();
265+ if (currentCalls != 0) {
266+ if (currentCalls == 1) {
267+ // if we have only one call, check if it's incoming and
268+ // enable speaker mode so the ringtone is audible
269+ Tp::CallChannelPtr callChannel = mChannels.first();
270+ AccountEntry *accountEntry = TelepathyHelper::instance()->accountForConnection(callChannel->connection());
271+ if (!accountEntry || !callChannel) {
272+ return;
273+ }
274+
275+ bool incoming = callChannel->initiatorContact() != accountEntry->account()->connection()->selfContact();
276+ Tp::CallState state = callChannel->callState();
277+ if (incoming && newCall) {
278+ enable_ringtone();
279+ return;
280+ }
281+ if (state == Tp::CallStateEnded) {
282+ enable_normal();
283+ return;
284+ }
285+ // if only one call and dialing, or incoming call just accepted, then default to earpiece
286+ if (newCall || (state == Tp::CallStateAccepted && incoming)) {
287+ enable_earpiece();
288+ return;
289+ }
290+ }
291+ } else {
292+ enable_normal();
293+ Q_EMIT lastChannelClosed();
294+ }
295+}
296+
297+#ifdef USE_PULSEAUDIO
298+void AudioRouteManager::onAudioModeChanged(AudioMode mode)
299+{
300+ qDebug("PulseAudio audio mode changed: 0x%x", mode);
301+
302+ if (mode == AudioModeEarpiece && mActiveAudioOutput != "earpiece") {
303+ mActiveAudioOutput = "earpiece";
304+ } else if (mode == AudioModeWiredHeadset && mActiveAudioOutput != "wired_headset") {
305+ mActiveAudioOutput = "wired_headset";
306+ } else if (mode == AudioModeSpeaker && mActiveAudioOutput != "speaker") {
307+ mActiveAudioOutput = "speaker";
308+ } else if (mode == AudioModeBluetooth && mActiveAudioOutput != "bluetooth") {
309+ mActiveAudioOutput = "bluetooth";
310+ }
311+ Q_EMIT activeAudioOutputChanged(mActiveAudioOutput);
312+}
313+
314+void AudioRouteManager::onAvailableAudioModesChanged(AudioModes modes)
315+{
316+ qDebug("PulseAudio available audio modes changed");
317+ bool defaultFound = false;
318+ mAudioOutputs.clear();
319+ Q_FOREACH(const AudioMode &mode, modes) {
320+ AudioOutputDBus output;
321+ if (mode == AudioModeBluetooth) {
322+ // there can be only one bluetooth
323+ output.id = "bluetooth";
324+ output.type = "bluetooth";
325+ // we dont support names for now, so we set a default value
326+ output.name = "bluetooth";
327+ } else if (mode == AudioModeEarpiece || mode == AudioModeWiredHeadset) {
328+ if (!defaultFound) {
329+ defaultFound = true;
330+ output.id = "default";
331+ output.type = "default";
332+ output.name = "default";
333+ } else {
334+ continue;
335+ }
336+ } else if (mode == AudioModeSpeaker) {
337+ output.id = "speaker";
338+ output.type = "speaker";
339+ output.name = "speaker";
340+ }
341+ mAudioOutputs << output;
342+ }
343+ Q_EMIT audioOutputsChanged(mAudioOutputs);
344+}
345+#endif
346+
347
348=== added file 'handler/audioroutemanager.h'
349--- handler/audioroutemanager.h 1970-01-01 00:00:00 +0000
350+++ handler/audioroutemanager.h 2016-01-20 11:56:12 +0000
351@@ -0,0 +1,75 @@
352+/*
353+ * Copyright (C) 2016 Canonical, Ltd.
354+ *
355+ * Authors:
356+ * Tiago Salem Herrmann <tiago.herrmann@canonical.com>
357+ *
358+ * This file is part of telephony-service.
359+ *
360+ * telephony-service is free software; you can redistribute it and/or modify
361+ * it under the terms of the GNU General Public License as published by
362+ * the Free Software Foundation; version 3.
363+ *
364+ * telephony-service is distributed in the hope that it will be useful,
365+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
366+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
367+ * GNU General Public License for more details.
368+ *
369+ * You should have received a copy of the GNU General Public License
370+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
371+ */
372+
373+#ifndef AUDIOROUTEMANAGER_H
374+#define AUDIOROUTEMANAGER_H
375+
376+#ifdef USE_PULSEAUDIO
377+#include "qpulseaudioengine.h"
378+#endif
379+#include "audiooutput.h"
380+#include "powerdaudiomodemediator.h"
381+#include "powerddbus.h"
382+#include <QObject>
383+#include <TelepathyQt/CallChannel>
384+
385+
386+class AudioRouteManager : public QObject
387+{
388+ Q_OBJECT
389+
390+public:
391+ static AudioRouteManager *instance();
392+ void setActiveAudioOutput(const QString &id);
393+ QString activeAudioOutput();
394+ AudioOutputDBusList audioOutputs() const;
395+ void updateAudioRoute(bool newCall = false);
396+
397+public Q_SLOTS:
398+ void onCallChannelAvailable(Tp::CallChannelPtr callChannel);
399+
400+Q_SIGNALS:
401+ void audioOutputsChanged(const AudioOutputDBusList &outputs);
402+ void activeAudioOutputChanged(const QString &id);
403+ void lastChannelClosed();
404+
405+protected Q_SLOTS:
406+ void onCallStateChanged(Tp::CallState state);
407+
408+private Q_SLOTS:
409+#ifdef USE_PULSEAUDIO
410+ void onAudioModeChanged(AudioMode mode);
411+ void onAvailableAudioModesChanged(AudioModes modes);
412+#endif
413+
414+private:
415+ explicit AudioRouteManager(QObject *parent = 0);
416+ QList<Tp::CallChannelPtr> mChannels;
417+ AudioOutputDBusList mAudioOutputs;
418+ QString mActiveAudioOutput;
419+ PowerDDBus mPowerDDBus;
420+ PowerDAudioModeMediator mAudioModeMediator;
421+#ifdef USE_PULSEAUDIO
422+ bool mHasPulseAudio;
423+#endif
424+};
425+
426+#endif // AUDIOROUTEMANAGER_H
427
428=== modified file 'handler/callhandler.cpp'
429--- handler/callhandler.cpp 2015-07-06 23:32:13 +0000
430+++ handler/callhandler.cpp 2016-01-20 11:56:12 +0000
431@@ -151,14 +151,6 @@
432 muteInterface.call("RequestMuted", muted);
433 }
434
435-void CallHandler::setActiveAudioOutput(const QString &objectPath, const QString &id)
436-{
437- Tp::CallChannelPtr channel = callFromObjectPath(objectPath);
438-
439- QDBusInterface audioOutputsInterface(channel->busName(), channel->objectPath(), CANONICAL_TELEPHONY_AUDIOOUTPUTS_IFACE);
440- audioOutputsInterface.call("SetActiveAudioOutput", id);
441-}
442-
443 void CallHandler::sendDTMF(const QString &objectPath, const QString &key)
444 {
445 bool ok;
446@@ -266,9 +258,6 @@
447 void CallHandler::onCallChannelAvailable(Tp::CallChannelPtr channel)
448 {
449 QDBusInterface callChannelIface(channel->busName(), channel->objectPath(), DBUS_PROPERTIES_IFACE);
450- QDBusMessage reply = callChannelIface.call("GetAll", CANONICAL_TELEPHONY_AUDIOOUTPUTS_IFACE);
451- QVariantList args = reply.arguments();
452- QMap<QString, QVariant> map = qdbus_cast<QMap<QString, QVariant> >(args[0]);
453 channel->setProperty("timestamp", QDateTime::currentDateTimeUtc());
454
455 if (channel->callState() == Tp::CallStateActive) {
456
457=== modified file 'handler/callhandler.h'
458--- handler/callhandler.h 2015-03-04 18:02:13 +0000
459+++ handler/callhandler.h 2016-01-20 11:56:12 +0000
460@@ -44,7 +44,6 @@
461 void hangUpCall(const QString &objectPath);
462 void setHold(const QString &objectPath, bool hold);
463 void setMuted(const QString &objectPath, bool muted);
464- void setActiveAudioOutput(const QString &objectPath, const QString &id);
465 void sendDTMF(const QString &objectPath, const QString &key);
466
467 // conference call related
468
469=== modified file 'handler/handlerdbus.cpp'
470--- handler/handlerdbus.cpp 2015-11-17 17:23:54 +0000
471+++ handler/handlerdbus.cpp 2016-01-20 11:56:12 +0000
472@@ -21,6 +21,8 @@
473 * along with this program. If not, see <http://www.gnu.org/licenses/>.
474 */
475
476+#include "audiooutput.h"
477+#include "audioroutemanager.h"
478 #include "callhandler.h"
479 #include "handlerdbus.h"
480 #include "handleradaptor.h"
481@@ -44,6 +46,13 @@
482 connect(CallHandler::instance(),
483 SIGNAL(conferenceCallRequestFinished(bool)),
484 SIGNAL(ConferenceCallRequestFinished(bool)));
485+ connect(AudioRouteManager::instance(),
486+ SIGNAL(audioOutputsChanged(AudioOutputDBusList)),
487+ SIGNAL(AudioOutputsChanged(AudioOutputDBusList)));
488+ connect(AudioRouteManager::instance(),
489+ SIGNAL(activeAudioOutputChanged(QString)),
490+ SIGNAL(ActiveAudioOutputChanged(QString)));
491+
492 }
493
494 HandlerDBus::~HandlerDBus()
495@@ -81,6 +90,21 @@
496 Q_EMIT CallIndicatorVisibleChanged(visible);
497 }
498
499+void HandlerDBus::setActiveAudioOutput(const QString &id)
500+{
501+ AudioRouteManager::instance()->setActiveAudioOutput(id);
502+}
503+
504+QString HandlerDBus::activeAudioOutput() const
505+{
506+ return AudioRouteManager::instance()->activeAudioOutput();
507+}
508+
509+AudioOutputDBusList HandlerDBus::AudioOutputs() const
510+{
511+ return AudioRouteManager::instance()->audioOutputs();
512+}
513+
514 bool HandlerDBus::connectToBus()
515 {
516 bool ok = QDBusConnection::sessionBus().registerService(DBUS_SERVICE);
517@@ -138,11 +162,6 @@
518 CallHandler::instance()->setMuted(objectPath, muted);
519 }
520
521-void HandlerDBus::SetActiveAudioOutput(const QString &objectPath, const QString &id)
522-{
523- CallHandler::instance()->setActiveAudioOutput(objectPath, id);
524-}
525-
526 void HandlerDBus::SendDTMF(const QString &objectPath, const QString &key)
527 {
528 CallHandler::instance()->sendDTMF(objectPath, key);
529
530=== modified file 'handler/handlerdbus.h'
531--- handler/handlerdbus.h 2015-11-17 17:23:54 +0000
532+++ handler/handlerdbus.h 2016-01-20 11:56:12 +0000
533@@ -28,6 +28,7 @@
534 #include <QtDBus/QDBusContext>
535 #include "chatmanager.h"
536 #include "dbustypes.h"
537+#include "audiooutput.h"
538
539 /**
540 * DBus interface for the phone approver
541@@ -40,6 +41,11 @@
542 WRITE setCallIndicatorVisible
543 NOTIFY CallIndicatorVisibleChanged)
544
545+ Q_PROPERTY(QString ActiveAudioOutput
546+ READ activeAudioOutput
547+ WRITE setActiveAudioOutput
548+ NOTIFY ActiveAudioOutputChanged)
549+
550 public:
551 HandlerDBus(QObject* parent=0);
552 ~HandlerDBus();
553@@ -50,6 +56,8 @@
554 bool IsReady();
555 bool callIndicatorVisible() const;
556 void setCallIndicatorVisible(bool visible);
557+ QString activeAudioOutput() const;
558+ void setActiveAudioOutput(const QString &id);
559
560 public Q_SLOTS:
561 bool connectToBus();
562@@ -66,13 +74,13 @@
563 Q_NOREPLY void HangUpCall(const QString &objectPath);
564 Q_NOREPLY void SetHold(const QString &objectPath, bool hold);
565 Q_NOREPLY void SetMuted(const QString &objectPath, bool muted);
566- Q_NOREPLY void SetActiveAudioOutput(const QString &objectPath, const QString &id);
567 Q_NOREPLY void SendDTMF(const QString &objectPath, const QString &key);
568
569 // conference call related
570 Q_NOREPLY void CreateConferenceCall(const QStringList &objectPaths);
571 Q_NOREPLY void MergeCall(const QString &conferenceObjectPath, const QString &callObjectPath);
572 Q_NOREPLY void SplitCall(const QString &objectPath);
573+ AudioOutputDBusList AudioOutputs() const;
574
575 Q_SIGNALS:
576 void onMessageSent(const QString &number, const QString &message);
577@@ -80,6 +88,8 @@
578 void CallIndicatorVisibleChanged(bool visible);
579 void ConferenceCallRequestFinished(bool succeeded);
580 void CallHoldingFailed(const QString &objectPath);
581+ void ActiveAudioOutputChanged(const QString &id);
582+ void AudioOutputsChanged(const AudioOutputDBusList &audioOutputs);
583
584 private:
585 bool mCallIndicatorVisible;
586
587=== modified file 'handler/main.cpp'
588--- handler/main.cpp 2015-01-08 01:05:51 +0000
589+++ handler/main.cpp 2016-01-20 11:56:12 +0000
590@@ -31,6 +31,9 @@
591 #include <TelepathyQt/AbstractClient>
592 #include <TelepathyQt/AccountManager>
593 #include <TelepathyQt/Contact>
594+#include <TelepathyQt/CallChannel>
595+
596+Q_DECLARE_METATYPE(Tp::CallChannelPtr)
597
598 int main(int argc, char **argv)
599 {
600@@ -38,6 +41,12 @@
601 QCoreApplication::setApplicationName("telephony-service-handler");
602
603 Tp::registerTypes();
604+ qRegisterMetaType<Tp::CallChannelPtr>();
605+ qRegisterMetaType<AudioOutputDBus>();
606+ qRegisterMetaType<AudioOutputDBusList>();
607+
608+ qDBusRegisterMetaType<AudioOutputDBus>();
609+ qDBusRegisterMetaType<AudioOutputDBusList>();
610
611 // check if there is already an instance of the handler running
612 if (ApplicationUtils::checkApplicationRunning(TP_QT_IFACE_CLIENT + ".TelephonyServiceHandler")) {
613@@ -45,9 +54,11 @@
614 return 1;
615 }
616
617+ HandlerDBus dbus;
618 Handler *handler = new Handler();
619- QObject::connect(TelepathyHelper::instance(), &TelepathyHelper::setupReady, [handler]() {
620+ QObject::connect(TelepathyHelper::instance(), &TelepathyHelper::setupReady, [&]() {
621 TelepathyHelper::instance()->registerClient(handler, "TelephonyServiceHandler");
622+ dbus.connectToBus();
623 });
624
625 QObject::connect(handler, SIGNAL(callChannelAvailable(Tp::CallChannelPtr)),
626@@ -55,10 +66,6 @@
627 QObject::connect(handler, SIGNAL(textChannelAvailable(Tp::TextChannelPtr)),
628 TextHandler::instance(), SLOT(onTextChannelAvailable(Tp::TextChannelPtr)));
629
630- HandlerDBus dbus;
631-
632- QObject::connect(TelepathyHelper::instance(), SIGNAL(setupReady()),
633- &dbus, SLOT(connectToBus()));
634
635 // instanciate the display name settings singleton, it will work by itself
636 DisplayNameSettings::instance();
637
638=== added file 'handler/powerd.h'
639--- handler/powerd.h 1970-01-01 00:00:00 +0000
640+++ handler/powerd.h 2016-01-20 11:56:12 +0000
641@@ -0,0 +1,34 @@
642+/**
643+ * Copyright (C) 2014 Canonical, Ltd.
644+ *
645+ * This program is free software: you can redistribute it and/or modify it under
646+ * the terms of the GNU Lesser General Public License version 3, as published by
647+ * the Free Software Foundation.
648+ *
649+ * This program is distributed in the hope that it will be useful, but WITHOUT
650+ * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
651+ * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
652+ * Lesser General Public License for more details.
653+ *
654+ * You should have received a copy of the GNU Lesser General Public License
655+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
656+ *
657+ * Authors: Andreas Pokorny <andreas.pokorny@canonical.com>
658+ */
659+
660+#ifndef POWERD_H
661+#define POWERD_H
662+
663+class PowerD
664+{
665+public:
666+ PowerD() = default;
667+ virtual ~PowerD() = default;
668+ PowerD(PowerD const&) = delete;
669+ PowerD& operator=(PowerD const&) = delete;
670+
671+ virtual void enableProximityHandling() = 0;
672+ virtual void disableProximityHandling() = 0;
673+};
674+
675+#endif // POWERD_H
676
677=== added file 'handler/powerdaudiomodemediator.cpp'
678--- handler/powerdaudiomodemediator.cpp 1970-01-01 00:00:00 +0000
679+++ handler/powerdaudiomodemediator.cpp 2016-01-20 11:56:12 +0000
680@@ -0,0 +1,63 @@
681+/**
682+ * Copyright (C) 2014 Canonical, Ltd.
683+ *
684+ * This program is free software: you can redistribute it and/or modify it under
685+ * the terms of the GNU Lesser General Public License version 3, as published by
686+ * the Free Software Foundation.
687+ *
688+ * This program is distributed in the hope that it will be useful, but WITHOUT
689+ * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
690+ * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
691+ * Lesser General Public License for more details.
692+ *
693+ * You should have received a copy of the GNU Lesser General Public License
694+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
695+ *
696+ * Authors: Andreas Pokorny <andreas.pokorny@canonical.com>
697+ */
698+
699+#include <QDBusInterface>
700+#include "powerdaudiomodemediator.h"
701+
702+PowerDAudioModeMediator::PowerDAudioModeMediator(PowerD &powerd)
703+ : powerd(powerd)
704+{
705+}
706+
707+void PowerDAudioModeMediator::audioModeChanged(const QString &mode)
708+{
709+ bool enableProximity = !(mode == "speaker" || mode == "bluetooth" || mode == "wired_headset");
710+
711+ if (mProximityEnabled != enableProximity)
712+ {
713+ mProximityEnabled = enableProximity;
714+ apply();
715+ }
716+}
717+
718+void PowerDAudioModeMediator::apply() const
719+{
720+ if (mProximityEnabled) {
721+ powerd.enableProximityHandling();
722+ } else {
723+ // we need to power the screen on before disabling the proximity handling
724+ QDBusInterface unityIface("com.canonical.Unity.Screen",
725+ "/com/canonical/Unity/Screen",
726+ "com.canonical.Unity.Screen",
727+ QDBusConnection::systemBus());
728+ QList<QVariant> args;
729+ args.append("on");
730+ args.append(3);
731+ unityIface.callWithArgumentList(QDBus::NoBlock, "setScreenPowerMode", args);
732+ powerd.disableProximityHandling();
733+ }
734+}
735+
736+void PowerDAudioModeMediator::audioOutputClosed()
737+{
738+ if (mProximityEnabled)
739+ {
740+ mProximityEnabled = false;
741+ apply();
742+ }
743+}
744
745=== added file 'handler/powerdaudiomodemediator.h'
746--- handler/powerdaudiomodemediator.h 1970-01-01 00:00:00 +0000
747+++ handler/powerdaudiomodemediator.h 2016-01-20 11:56:12 +0000
748@@ -0,0 +1,46 @@
749+/****************************************************************************
750+**
751+** Copyright (C) 2014 Canonical, Ltd.
752+**
753+** Authors:
754+** Andreas Pokorny <andreas.pokorny@canonical.com>
755+**
756+** GNU Lesser General Public License Usage
757+** Alternatively, this file may be used under the terms of the GNU Lesser
758+** General Public License version 2.1 as published by the Free Software
759+** Foundation and appearing in the file LICENSE.LGPL included in the
760+** packaging of this file. Please review the following information to
761+** ensure the GNU Lesser General Public License version 2.1 requirements
762+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
763+**
764+****************************************************************************/
765+
766+#ifndef POWERDAUDIOMODEMEDIATOR_H
767+#define POWERDAUDIOMODEMEDIATOR_H
768+
769+#include "powerd.h"
770+
771+#include <QString>
772+#include <fstream>
773+#include <memory>
774+
775+class PowerD;
776+/*!
777+ * \brief PowerDAudioModeMediator is responsible for configuring proximity
778+ * handling of powerd during different call states and used audio outputs.
779+ * In General that mean enabling sreen blanking on proximity events, when
780+ * a call is active and neither a bluetooth headset nor the speakers are used.
781+ */
782+class PowerDAudioModeMediator
783+{
784+public:
785+ PowerDAudioModeMediator(PowerD &powerd);
786+ void audioModeChanged(const QString &mode);
787+ void audioOutputClosed();
788+private:
789+ void apply() const;
790+ PowerD &powerd;
791+ bool mProximityEnabled{false};
792+};
793+
794+#endif
795
796=== added file 'handler/powerddbus.cpp'
797--- handler/powerddbus.cpp 1970-01-01 00:00:00 +0000
798+++ handler/powerddbus.cpp 2016-01-20 11:56:12 +0000
799@@ -0,0 +1,43 @@
800+/**
801+ * Copyright (C) 2014 Canonical, Ltd.
802+ *
803+ * This program is free software: you can redistribute it and/or modify it under
804+ * the terms of the GNU Lesser General Public License version 3, as published by
805+ * the Free Software Foundation.
806+ *
807+ * This program is distributed in the hope that it will be useful, but WITHOUT
808+ * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
809+ * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
810+ * Lesser General Public License for more details.
811+ *
812+ * You should have received a copy of the GNU Lesser General Public License
813+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
814+ *
815+ * Authors: Andreas Pokorny <andreas.pokorny@canonical.com>
816+ */
817+
818+#include "powerddbus.h"
819+
820+#include <QDBusConnection>
821+#include <QDBusInterface>
822+#include <QDBusReply>
823+
824+PowerDDBus::PowerDDBus()
825+ : mPowerDIface{
826+ new QDBusInterface(
827+ "com.canonical.powerd",
828+ "/com/canonical/powerd",
829+ "com.canonical.powerd",
830+ QDBusConnection::systemBus())}
831+{
832+}
833+
834+void PowerDDBus::enableProximityHandling()
835+{
836+ mPowerDIface->call("enableProximityHandling", "telephony-service-handler");
837+}
838+
839+void PowerDDBus::disableProximityHandling()
840+{
841+ mPowerDIface->call("disableProximityHandling", "telephony-service-handler");
842+}
843
844=== added file 'handler/powerddbus.h'
845--- handler/powerddbus.h 1970-01-01 00:00:00 +0000
846+++ handler/powerddbus.h 2016-01-20 11:56:12 +0000
847@@ -0,0 +1,38 @@
848+/**
849+ * Copyright (C) 2014 Canonical, Ltd.
850+ *
851+ * This program is free software: you can redistribute it and/or modify it under
852+ * the terms of the GNU Lesser General Public License version 3, as published by
853+ * the Free Software Foundation.
854+ *
855+ * This program is distributed in the hope that it will be useful, but WITHOUT
856+ * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
857+ * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
858+ * Lesser General Public License for more details.
859+ *
860+ * You should have received a copy of the GNU Lesser General Public License
861+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
862+ *
863+ * Authors: Andreas Pokorny <andreas.pokorny@canonical.com>
864+ */
865+
866+#ifndef POWERD_DUBS_H
867+#define POWERD_DBUS_H
868+
869+#include "powerd.h"
870+
871+#include <memory>
872+
873+class QDBusInterface;
874+
875+class PowerDDBus : public PowerD
876+{
877+public:
878+ PowerDDBus();
879+ void enableProximityHandling() override;
880+ void disableProximityHandling() override;
881+private:
882+ std::unique_ptr<QDBusInterface> mPowerDIface;
883+};
884+
885+#endif
886
887=== added file 'handler/qpulseaudioengine.cpp'
888--- handler/qpulseaudioengine.cpp 1970-01-01 00:00:00 +0000
889+++ handler/qpulseaudioengine.cpp 2016-01-20 11:56:12 +0000
890@@ -0,0 +1,853 @@
891+/****************************************************************************
892+**
893+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
894+** Contact: http://www.qt-project.org/legal
895+**
896+** This file was taken from qt5 and modified by
897+** David Henningsson <david.henningsson@canonical.com> for usage in
898+** telepathy-ofono.
899+**
900+** GNU Lesser General Public License Usage
901+** Alternatively, this file may be used under the terms of the GNU Lesser
902+** General Public License version 2.1 as published by the Free Software
903+** Foundation and appearing in the file LICENSE.LGPL included in the
904+** packaging of this file. Please review the following information to
905+** ensure the GNU Lesser General Public License version 2.1 requirements
906+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
907+**
908+****************************************************************************/
909+
910+#include <QtCore/qdebug.h>
911+
912+#include "qpulseaudioengine.h"
913+#include <sys/types.h>
914+#include <unistd.h>
915+
916+#define PULSEAUDIO_PROFILE_HSP "headset_head_unit"
917+#define PULSEAUDIO_PROFILE_A2DP "a2dp_sink"
918+
919+QT_BEGIN_NAMESPACE
920+
921+static void contextStateCallbackInit(pa_context *context, void *userdata)
922+{
923+ Q_UNUSED(context);
924+ QPulseAudioEngineWorker *pulseEngine = reinterpret_cast<QPulseAudioEngineWorker*>(userdata);
925+ pa_threaded_mainloop_signal(pulseEngine->mainloop(), 0);
926+}
927+
928+static void contextStateCallback(pa_context *context, void *userdata)
929+{
930+ Q_UNUSED(userdata);
931+ Q_UNUSED(context);
932+}
933+
934+static void success_cb(pa_context *context, int success, void *userdata)
935+{
936+ QPulseAudioEngineWorker *pulseEngine = reinterpret_cast<QPulseAudioEngineWorker*>(userdata);
937+ pa_threaded_mainloop_signal(pulseEngine->mainloop(), 0);
938+}
939+
940+/* Callbacks used when handling events from PulseAudio */
941+static void plug_card_cb(pa_context *c, const pa_card_info *info, int isLast, void *userdata)
942+{
943+ QPulseAudioEngineWorker *pulseEngine = static_cast<QPulseAudioEngineWorker*>(userdata);
944+ if (isLast != 0 || !pulseEngine || !info) {
945+ pa_threaded_mainloop_signal(pulseEngine->mainloop(), 0);
946+ return;
947+ }
948+ pulseEngine->plugCardCallback(info);
949+}
950+
951+static void update_card_cb(pa_context *c, const pa_card_info *info, int isLast, void *userdata)
952+{
953+ QPulseAudioEngineWorker *pulseEngine = static_cast<QPulseAudioEngineWorker*>(userdata);
954+ if (isLast != 0 || !pulseEngine || !info) {
955+ pa_threaded_mainloop_signal(pulseEngine->mainloop(), 0);
956+ return;
957+ }
958+ pulseEngine->updateCardCallback(info);
959+}
960+
961+static void unplug_card_cb(pa_context *c, const pa_card_info *info, int isLast, void *userdata)
962+{
963+ QPulseAudioEngineWorker *pulseEngine = static_cast<QPulseAudioEngineWorker*>(userdata);
964+ if (!pulseEngine) {
965+ pa_threaded_mainloop_signal(pulseEngine->mainloop(), 0);
966+ return;
967+ }
968+
969+ if (info == NULL) {
970+ /* That means that the card used to query card_info was removed */
971+ pulseEngine->unplugCardCallback();
972+ }
973+}
974+
975+static void subscribeCallback(pa_context *context, pa_subscription_event_type_t t, uint32_t idx, void *userdata)
976+{
977+ /* For card change events (slot plug/unplug and add/remove card) */
978+ if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_CARD) {
979+ if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_CHANGE) {
980+ QMetaObject::invokeMethod((QPulseAudioEngineWorker *) userdata, "handleCardEvent",
981+ Qt::QueuedConnection, Q_ARG(int, PA_SUBSCRIPTION_EVENT_CHANGE), Q_ARG(unsigned int, idx));
982+ } else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) {
983+ QMetaObject::invokeMethod((QPulseAudioEngineWorker *) userdata, "handleCardEvent",
984+ Qt::QueuedConnection, Q_ARG(int, PA_SUBSCRIPTION_EVENT_NEW), Q_ARG(unsigned int, idx));
985+ } else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
986+ QMetaObject::invokeMethod((QPulseAudioEngineWorker *) userdata, "handleCardEvent",
987+ Qt::QueuedConnection, Q_ARG(int, PA_SUBSCRIPTION_EVENT_REMOVE), Q_ARG(unsigned int, idx));
988+ }
989+ }
990+}
991+
992+QPulseAudioEngineWorker::QPulseAudioEngineWorker(QObject *parent)
993+ : QObject(parent)
994+ , m_mainLoopApi(0)
995+ , m_context(0)
996+ , m_callstatus(CallEnded)
997+ , m_audiomode(AudioModeSpeaker)
998+ , m_micmute(false)
999+ , m_defaultsink("sink.primary")
1000+ , m_defaultsource("source.primary")
1001+ , m_voicecallcard("")
1002+ , m_voicecallhighest("")
1003+ , m_voicecallprofile("")
1004+ , m_bt_hsp("")
1005+ , m_bt_hsp_a2dp("")
1006+ , m_default_bt_card_fallback("")
1007+
1008+{
1009+ m_mainLoop = pa_threaded_mainloop_new();
1010+ if (m_mainLoop == 0) {
1011+ qWarning("Unable to create pulseaudio mainloop");
1012+ return;
1013+ }
1014+
1015+ if (pa_threaded_mainloop_start(m_mainLoop) != 0) {
1016+ qWarning("Unable to start pulseaudio mainloop");
1017+ pa_threaded_mainloop_free(m_mainLoop);
1018+ m_mainLoop = 0;
1019+ return;
1020+ }
1021+
1022+ createPulseContext();
1023+}
1024+
1025+bool QPulseAudioEngineWorker::createPulseContext()
1026+{
1027+ bool keepGoing = true;
1028+ bool ok = true;
1029+
1030+ if (m_context)
1031+ return true;
1032+
1033+ m_mainLoopApi = pa_threaded_mainloop_get_api(m_mainLoop);
1034+
1035+ pa_threaded_mainloop_lock(m_mainLoop);
1036+
1037+ m_context = pa_context_new(m_mainLoopApi, QString(QLatin1String("QtmPulseContext:%1")).arg(::getpid()).toLatin1().constData());
1038+ pa_context_set_state_callback(m_context, contextStateCallbackInit, this);
1039+
1040+ if (!m_context) {
1041+ qWarning("Unable to create new pulseaudio context");
1042+ pa_threaded_mainloop_unlock(m_mainLoop);
1043+ return false;
1044+ }
1045+
1046+ if (pa_context_connect(m_context, NULL, PA_CONTEXT_NOAUTOSPAWN, NULL) < 0) {
1047+ qWarning("Unable to create a connection to the pulseaudio context");
1048+ pa_threaded_mainloop_unlock(m_mainLoop);
1049+ releasePulseContext();
1050+ return false;
1051+ }
1052+
1053+ pa_threaded_mainloop_wait(m_mainLoop);
1054+
1055+ while (keepGoing) {
1056+ switch (pa_context_get_state(m_context)) {
1057+ case PA_CONTEXT_CONNECTING:
1058+ case PA_CONTEXT_AUTHORIZING:
1059+ case PA_CONTEXT_SETTING_NAME:
1060+ break;
1061+
1062+ case PA_CONTEXT_READY:
1063+ qDebug("Pulseaudio connection established.");
1064+ keepGoing = false;
1065+ break;
1066+
1067+ case PA_CONTEXT_TERMINATED:
1068+ qCritical("Pulseaudio context terminated.");
1069+ keepGoing = false;
1070+ ok = false;
1071+ break;
1072+
1073+ case PA_CONTEXT_FAILED:
1074+ default:
1075+ qCritical() << QString("Pulseaudio connection failure: %1").arg(pa_strerror(pa_context_errno(m_context)));
1076+ keepGoing = false;
1077+ ok = false;
1078+ }
1079+
1080+ if (keepGoing) {
1081+ pa_threaded_mainloop_wait(m_mainLoop);
1082+ }
1083+ }
1084+
1085+ if (ok) {
1086+ pa_context_set_state_callback(m_context, contextStateCallback, this);
1087+ pa_context_set_subscribe_callback(m_context, subscribeCallback, this);
1088+ pa_context_subscribe(m_context, PA_SUBSCRIPTION_MASK_CARD, NULL, this);
1089+ } else {
1090+ if (m_context) {
1091+ pa_context_unref(m_context);
1092+ m_context = 0;
1093+ }
1094+ }
1095+
1096+ pa_threaded_mainloop_unlock(m_mainLoop);
1097+ return true;
1098+}
1099+
1100+
1101+void QPulseAudioEngineWorker::releasePulseContext()
1102+{
1103+ if (m_context) {
1104+ pa_threaded_mainloop_lock(m_mainLoop);
1105+ pa_context_disconnect(m_context);
1106+ pa_context_unref(m_context);
1107+ pa_threaded_mainloop_unlock(m_mainLoop);
1108+ m_context = 0;
1109+ }
1110+
1111+}
1112+
1113+QPulseAudioEngineWorker::~QPulseAudioEngineWorker()
1114+{
1115+ releasePulseContext();
1116+
1117+ if (m_mainLoop) {
1118+ pa_threaded_mainloop_stop(m_mainLoop);
1119+ pa_threaded_mainloop_free(m_mainLoop);
1120+ m_mainLoop = 0;
1121+ }
1122+}
1123+
1124+void QPulseAudioEngineWorker::cardInfoCallback(const pa_card_info *info)
1125+{
1126+ pa_card_profile_info2 *voice_call = NULL, *highest = NULL;
1127+ pa_card_profile_info2 *hsp = NULL, *a2dp = NULL;
1128+
1129+ /* For now we only support one card with the voicecall feature */
1130+ for (int i = 0; i < info->n_profiles; i++) {
1131+ if (!highest || info->profiles2[i]->priority > highest->priority)
1132+ highest = info->profiles2[i];
1133+ if (!strcmp(info->profiles2[i]->name, "voicecall"))
1134+ voice_call = info->profiles2[i];
1135+ else if (!strcmp(info->profiles2[i]->name, PULSEAUDIO_PROFILE_HSP) &&
1136+ info->profiles2[i]->available != 0)
1137+ hsp = info->profiles2[i];
1138+ else if (!strcmp(info->profiles2[i]->name, PULSEAUDIO_PROFILE_A2DP) &&
1139+ info->profiles2[i]->available != 0)
1140+ a2dp = info->profiles2[i];
1141+ }
1142+
1143+ /* Record the card that supports voicecall (default one to be used) */
1144+ if (voice_call) {
1145+ qDebug("Found card that supports voicecall: '%s'", info->name);
1146+ m_voicecallcard = info->name;
1147+ m_voicecallhighest = highest->name;
1148+ qDebug("1");
1149+ m_voicecallprofile = voice_call->name;
1150+ qDebug("2");
1151+ }
1152+
1153+ /* Handle the use cases needed for bluetooth */
1154+ if (hsp && a2dp) {
1155+ qDebug("Found card that supports hsp and a2dp: '%s'", info->name);
1156+ m_bt_hsp_a2dp = info->name;
1157+ } else if (hsp && (a2dp == NULL)) {
1158+ /* This card only provides the hsp profile */
1159+ qDebug("Found card that supports only hsp: '%s'", info->name);
1160+ m_bt_hsp = info->name;
1161+ }
1162+ qDebug("3");
1163+}
1164+
1165+void QPulseAudioEngineWorker::sinkInfoCallback(const pa_sink_info *info)
1166+{
1167+ pa_sink_port_info *earpiece = NULL, *speaker = NULL;
1168+ pa_sink_port_info *wired_headset = NULL, *wired_headphone = NULL;
1169+ pa_sink_port_info *preferred = NULL;
1170+ pa_sink_port_info *bluetooth_sco = NULL;
1171+ pa_sink_port_info *speaker_and_wired_headphone = NULL;
1172+ AudioMode audiomodetoset;
1173+ AudioModes modes;
1174+
1175+ for (int i = 0; i < info->n_ports; i++) {
1176+ if (!strcmp(info->ports[i]->name, "output-earpiece"))
1177+ earpiece = info->ports[i];
1178+ else if (!strcmp(info->ports[i]->name, "output-wired_headset") &&
1179+ (info->ports[i]->available != PA_PORT_AVAILABLE_NO))
1180+ wired_headset = info->ports[i];
1181+ else if (!strcmp(info->ports[i]->name, "output-wired_headphone") &&
1182+ (info->ports[i]->available != PA_PORT_AVAILABLE_NO))
1183+ wired_headphone = info->ports[i];
1184+ else if (!strcmp(info->ports[i]->name, "output-speaker"))
1185+ speaker = info->ports[i];
1186+ else if (!strcmp(info->ports[i]->name, "output-bluetooth_sco"))
1187+ bluetooth_sco = info->ports[i];
1188+ else if (!strcmp(info->ports[i]->name, "output-speaker+wired_headphone"))
1189+ speaker_and_wired_headphone = info->ports[i];
1190+ }
1191+
1192+ if (!earpiece || !speaker)
1193+ return; /* Not the right sink */
1194+
1195+ /* Refresh list of available audio modes */
1196+ modes.append(AudioModeEarpiece);
1197+ modes.append(AudioModeSpeaker);
1198+ if (wired_headset || wired_headphone)
1199+ modes.append(AudioModeWiredHeadset);
1200+ if (bluetooth_sco && ((m_bt_hsp != "") || (m_bt_hsp_a2dp != "")))
1201+ modes.append(AudioModeBluetooth);
1202+
1203+ /* Check if the requested mode is available (earpiece*/
1204+ if (((m_audiomode == AudioModeWiredHeadset) && !modes.contains(AudioModeWiredHeadset)) ||
1205+ ((m_audiomode == AudioModeBluetooth) && !modes.contains(AudioModeBluetooth)))
1206+ return;
1207+
1208+ /* Now to decide which output to be used, depending on the active mode */
1209+ if (m_audiomode & AudioModeEarpiece) {
1210+ preferred = earpiece;
1211+ audiomodetoset = AudioModeEarpiece;
1212+ }
1213+ if (m_audiomode & AudioModeSpeaker) {
1214+ preferred = speaker;
1215+ audiomodetoset = AudioModeSpeaker;
1216+ }
1217+ if ((m_audiomode & AudioModeWiredHeadset) && (modes.contains(AudioModeWiredHeadset))) {
1218+ preferred = wired_headset ? wired_headset : wired_headphone;
1219+ audiomodetoset = AudioModeWiredHeadset;
1220+ }
1221+ if (m_callstatus == CallRinging && speaker_and_wired_headphone) {
1222+ preferred = speaker_and_wired_headphone;
1223+ }
1224+ if ((m_audiomode & AudioModeBluetooth) && (modes.contains(AudioModeBluetooth))) {
1225+ preferred = bluetooth_sco;
1226+ audiomodetoset = AudioModeBluetooth;
1227+ }
1228+
1229+ m_audiomode = audiomodetoset;
1230+
1231+ m_nametoset = info->name;
1232+ if (info->active_port != preferred)
1233+ m_valuetoset = preferred->name;
1234+
1235+ if (modes != m_availableAudioModes)
1236+ m_availableAudioModes = modes;
1237+}
1238+
1239+void QPulseAudioEngineWorker::sourceInfoCallback(const pa_source_info *info)
1240+{
1241+ pa_source_port_info *builtin_mic = NULL, *preferred = NULL;
1242+ pa_source_port_info *wired_headset = NULL, *bluetooth_sco = NULL;
1243+
1244+ if (info->monitor_of_sink != PA_INVALID_INDEX)
1245+ return; /* Not the right source */
1246+
1247+ for (int i = 0; i < info->n_ports; i++) {
1248+ if (!strcmp(info->ports[i]->name, "input-builtin_mic"))
1249+ builtin_mic = info->ports[i];
1250+ else if (!strcmp(info->ports[i]->name, "input-wired_headset") &&
1251+ (info->ports[i]->available != PA_PORT_AVAILABLE_NO))
1252+ wired_headset = info->ports[i];
1253+ else if (!strcmp(info->ports[i]->name, "input-bluetooth_sco_headset"))
1254+ bluetooth_sco = info->ports[i];
1255+ }
1256+
1257+ if (!builtin_mic)
1258+ return; /* Not the right source */
1259+
1260+ /* Now to decide which output to be used, depending on the active mode */
1261+ if ((m_audiomode & AudioModeEarpiece) || (m_audiomode & AudioModeSpeaker))
1262+ preferred = builtin_mic;
1263+ if ((m_audiomode & AudioModeWiredHeadset) && (m_availableAudioModes.contains(AudioModeWiredHeadset)))
1264+ preferred = wired_headset ? wired_headset : builtin_mic;
1265+ if ((m_audiomode & AudioModeBluetooth) && (m_availableAudioModes.contains(AudioModeBluetooth)))
1266+ preferred = bluetooth_sco;
1267+
1268+ m_nametoset = info->name;
1269+ if (info->active_port != preferred)
1270+ m_valuetoset = preferred->name;
1271+}
1272+
1273+void QPulseAudioEngineWorker::serverInfoCallback(const pa_server_info *info)
1274+{
1275+ /* Saving default sink/source to restore after call hangup */
1276+ m_defaultsink = info->default_sink_name;
1277+ m_defaultsource = info->default_source_name;
1278+
1279+ /* In the case of a server callback we need to signal the mainloop */
1280+ pa_threaded_mainloop_signal(mainloop(), 0);
1281+}
1282+
1283+static void cardinfo_cb(pa_context *context, const pa_card_info *info, int isLast, void *userdata)
1284+{
1285+ QPulseAudioEngineWorker *pulseEngine = static_cast<QPulseAudioEngineWorker*>(userdata);
1286+ if (isLast != 0 || !pulseEngine || !info) {
1287+ pa_threaded_mainloop_signal(pulseEngine->mainloop(), 0);
1288+ return;
1289+ }
1290+ pulseEngine->cardInfoCallback(info);
1291+}
1292+
1293+static void sinkinfo_cb(pa_context *context, const pa_sink_info *info, int isLast, void *userdata)
1294+{
1295+ QPulseAudioEngineWorker *pulseEngine = static_cast<QPulseAudioEngineWorker*>(userdata);
1296+ if (isLast != 0 || !pulseEngine || !info) {
1297+ pa_threaded_mainloop_signal(pulseEngine->mainloop(), 0);
1298+ return;
1299+ }
1300+ pulseEngine->sinkInfoCallback(info);
1301+}
1302+
1303+static void sourceinfo_cb(pa_context *context, const pa_source_info *info, int isLast, void *userdata)
1304+{
1305+ QPulseAudioEngineWorker *pulseEngine = static_cast<QPulseAudioEngineWorker*>(userdata);
1306+ if (isLast != 0 || !pulseEngine || !info) {
1307+ pa_threaded_mainloop_signal(pulseEngine->mainloop(), 0);
1308+ return;
1309+ }
1310+ pulseEngine->sourceInfoCallback(info);
1311+}
1312+
1313+static void serverinfo_cb(pa_context *context, const pa_server_info *info, void *userdata)
1314+{
1315+ QPulseAudioEngineWorker *pulseEngine = static_cast<QPulseAudioEngineWorker*>(userdata);
1316+ if (!pulseEngine || !info) {
1317+ pa_threaded_mainloop_signal(pulseEngine->mainloop(), 0);
1318+ return;
1319+ }
1320+ pulseEngine->serverInfoCallback(info);
1321+}
1322+
1323+bool QPulseAudioEngineWorker::handleOperation(pa_operation *operation, const char *func_name)
1324+{
1325+ if (!operation) {
1326+ qCritical("'%s' failed (lost PulseAudio connection?)", func_name);
1327+ /* Free resources so it can retry a new connection during next operation */
1328+ pa_threaded_mainloop_unlock(m_mainLoop);
1329+ releasePulseContext();
1330+ return false;
1331+ }
1332+
1333+ while (pa_operation_get_state(operation) == PA_OPERATION_RUNNING)
1334+ pa_threaded_mainloop_wait(m_mainLoop);
1335+ pa_operation_unref(operation);
1336+ return true;
1337+}
1338+
1339+int QPulseAudioEngineWorker::setupVoiceCall()
1340+{
1341+ pa_operation *o;
1342+
1343+ qDebug("Setting up pulseaudio for voice call");
1344+
1345+ pa_threaded_mainloop_lock(m_mainLoop);
1346+
1347+ /* Get and set the default sink/source to be restored later */
1348+ o = pa_context_get_server_info(m_context, serverinfo_cb, this);
1349+ if (!handleOperation(o, "pa_context_get_server_info"))
1350+ return -1;
1351+
1352+ qDebug("Recorded default sink: %s default source: %s",
1353+ m_defaultsink.c_str(), m_defaultsource.c_str());
1354+
1355+ /* Walk through the list of devices, find the voice call capable card and
1356+ * identify if we have bluetooth capable devices (hsp and a2dp) */
1357+ m_voicecallcard = m_voicecallhighest = m_voicecallprofile = "";
1358+ m_bt_hsp = m_bt_hsp_a2dp = "";
1359+ o = pa_context_get_card_info_list(m_context, cardinfo_cb, this);
1360+ if (!handleOperation(o, "pa_context_get_card_info_list"))
1361+ return -1;
1362+ /* In case we have only one bt device that provides hsp and a2dp, we need
1363+ * to make sure we switch the default profile for that card (to hsp) */
1364+ if ((m_bt_hsp_a2dp != "") && (m_bt_hsp == "")) {
1365+ qDebug("Setting PulseAudio card '%s' profile '%s'",
1366+ m_bt_hsp_a2dp.c_str(), PULSEAUDIO_PROFILE_HSP);
1367+ o = pa_context_set_card_profile_by_name(m_context,
1368+ m_bt_hsp_a2dp.c_str(), PULSEAUDIO_PROFILE_HSP, success_cb, this);
1369+ if (!handleOperation(o, "pa_context_set_card_profile_by_name"))
1370+ return -1;
1371+ }
1372+
1373+ pa_threaded_mainloop_unlock(m_mainLoop);
1374+
1375+ return 0;
1376+}
1377+
1378+void QPulseAudioEngineWorker::restoreVoiceCall()
1379+{
1380+ pa_operation *o;
1381+
1382+ qDebug("Restoring pulseaudio previous state");
1383+
1384+ /* Then restore previous settings */
1385+ pa_threaded_mainloop_lock(m_mainLoop);
1386+
1387+ /* See if we need to restore any HSP+AD2P device state */
1388+ if ((m_bt_hsp_a2dp != "") && (m_bt_hsp == "")) {
1389+ qDebug("Restoring PulseAudio card '%s' to profile '%s'",
1390+ m_bt_hsp_a2dp.c_str(), PULSEAUDIO_PROFILE_A2DP);
1391+ o = pa_context_set_card_profile_by_name(m_context,
1392+ m_bt_hsp_a2dp.c_str(), PULSEAUDIO_PROFILE_A2DP, success_cb, this);
1393+ if (!handleOperation(o, "pa_context_set_card_profile_by_name"))
1394+ return;
1395+ }
1396+
1397+ /* Restore default sink/source */
1398+ if (m_defaultsink != "") {
1399+ qDebug("Restoring PulseAudio default sink to '%s'", m_defaultsink.c_str());
1400+ o = pa_context_set_default_sink(m_context, m_defaultsink.c_str(), success_cb, this);
1401+ if (!handleOperation(o, "pa_context_set_default_sink"))
1402+ return;
1403+ }
1404+ if (m_defaultsource != "") {
1405+ qDebug("Restoring PulseAudio default source to '%s'", m_defaultsource.c_str());
1406+ o = pa_context_set_default_source(m_context, m_defaultsource.c_str(), success_cb, this);
1407+ if (!handleOperation(o, "pa_context_set_default_source"))
1408+ return;
1409+ }
1410+
1411+ pa_threaded_mainloop_unlock(m_mainLoop);
1412+}
1413+
1414+void QPulseAudioEngineWorker::setCallMode(CallStatus callstatus, AudioMode audiomode)
1415+{
1416+ if (!createPulseContext()) {
1417+ return;
1418+ }
1419+ CallStatus p_callstatus = m_callstatus;
1420+ AudioMode p_audiomode = m_audiomode;
1421+ AudioModes p_availableAudioModes = m_availableAudioModes;
1422+ pa_operation *o;
1423+
1424+ /* Check if we need to save the current pulseaudio state (e.g. when starting a call) */
1425+ if ((callstatus != CallEnded) && (p_callstatus == CallEnded)) {
1426+ if (setupVoiceCall() < 0) {
1427+ qCritical("Failed to setup PulseAudio for Voice Call");
1428+ return;
1429+ }
1430+ }
1431+
1432+ /* If we have an active call, update internal state (used later when updating sink/source ports) */
1433+ m_callstatus = callstatus;
1434+ m_audiomode = audiomode;
1435+
1436+ pa_threaded_mainloop_lock(m_mainLoop);
1437+
1438+ /* Switch the virtual card mode when call is active and not active
1439+ * This needs to be done before sink/source gets updated, because after changing mode
1440+ * it will automatically move to input/output-parking */
1441+ if ((m_callstatus == CallActive) && (p_callstatus != CallActive) &&
1442+ (m_voicecallcard != "") && (m_voicecallprofile != "")) {
1443+ qDebug("Setting PulseAudio card '%s' profile '%s'",
1444+ m_voicecallcard.c_str(), m_voicecallprofile.c_str());
1445+ o = pa_context_set_card_profile_by_name(m_context,
1446+ m_voicecallcard.c_str(), m_voicecallprofile.c_str(), success_cb, this);
1447+ if (!handleOperation(o, "pa_context_set_card_profile_by_name"))
1448+ return;
1449+ } else if ((m_callstatus == CallEnded) && (m_voicecallcard != "") && (m_voicecallhighest != "")) {
1450+ /* If using droid, make sure to restore to the profile that has the highest score */
1451+ qDebug("Restoring PulseAudio card '%s' to profile '%s'",
1452+ m_voicecallcard.c_str(), m_voicecallhighest.c_str());
1453+ o = pa_context_set_card_profile_by_name(m_context,
1454+ m_voicecallcard.c_str(), m_voicecallhighest.c_str(), success_cb, this);
1455+ if (!handleOperation(o, "pa_context_set_card_profile_by_name"))
1456+ return;
1457+ }
1458+
1459+ /* Find highest compatible sink/source elements from the voicecall
1460+ compatible card (on touch this means the pulse droid element) */
1461+ m_nametoset = m_valuetoset = "";
1462+ o = pa_context_get_sink_info_list(m_context, sinkinfo_cb, this);
1463+ if (!handleOperation(o, "pa_context_get_sink_info_list"))
1464+ return;
1465+ if ((m_nametoset != "") && (m_nametoset != m_defaultsink)) {
1466+ qDebug("Setting PulseAudio default sink to '%s'", m_nametoset.c_str());
1467+ o = pa_context_set_default_sink(m_context, m_nametoset.c_str(), success_cb, this);
1468+ if (!handleOperation(o, "pa_context_set_default_sink"))
1469+ return;
1470+ }
1471+ if (m_valuetoset != "") {
1472+ qDebug("Setting PulseAudio sink '%s' port '%s'",
1473+ m_nametoset.c_str(), m_valuetoset.c_str());
1474+ o = pa_context_set_sink_port_by_name(m_context, m_nametoset.c_str(),
1475+ m_valuetoset.c_str(), success_cb, this);
1476+ if (!handleOperation(o, "pa_context_set_sink_port_by_name"))
1477+ return;
1478+ }
1479+
1480+ /* Same for source */
1481+ m_nametoset = m_valuetoset = "";
1482+ o = pa_context_get_source_info_list(m_context, sourceinfo_cb, this);
1483+ if (!handleOperation(o, "pa_context_get_source_info_list"))
1484+ return;
1485+ if ((m_nametoset != "") && (m_nametoset != m_defaultsource)) {
1486+ qDebug("Setting PulseAudio default source to '%s'", m_nametoset.c_str());
1487+ o = pa_context_set_default_source(m_context, m_nametoset.c_str(), success_cb, this);
1488+ if (!handleOperation(o, "pa_context_set_default_source"))
1489+ return;
1490+ }
1491+ if (m_valuetoset != "") {
1492+ qDebug("Setting PulseAudio source '%s' port '%s'",
1493+ m_nametoset.c_str(), m_valuetoset.c_str());
1494+ o = pa_context_set_source_port_by_name(m_context, m_nametoset.c_str(),
1495+ m_valuetoset.c_str(), success_cb, this);
1496+ if (!handleOperation(o, "pa_context_set_source_port_by_name"))
1497+ return;
1498+ }
1499+
1500+ pa_threaded_mainloop_unlock(m_mainLoop);
1501+
1502+ /* Notify if the list of audio modes changed */
1503+ if (p_availableAudioModes != m_availableAudioModes)
1504+ Q_EMIT availableAudioModesChanged(m_availableAudioModes);
1505+
1506+ /* Notify if call mode changed */
1507+ if (p_audiomode != m_audiomode) {
1508+ Q_EMIT audioModeChanged(m_audiomode);
1509+ }
1510+
1511+ /* If no more active voicecall, restore previous saved pulseaudio state */
1512+ if (callstatus == CallEnded) {
1513+ restoreVoiceCall();
1514+ }
1515+
1516+ /* In case the app had set mute when the call wasn't active, make sure we reflect it here */
1517+ if (m_callstatus != CallEnded)
1518+ setMicMute(m_micmute);
1519+}
1520+
1521+void QPulseAudioEngineWorker::setMicMute(bool muted)
1522+{
1523+ if (!createPulseContext()) {
1524+ return;
1525+ }
1526+
1527+ m_micmute = muted;
1528+
1529+ if (m_callstatus == CallEnded)
1530+ return;
1531+
1532+ pa_threaded_mainloop_lock(m_mainLoop);
1533+
1534+ m_nametoset = "";
1535+ pa_operation *o = pa_context_get_source_info_list(m_context, sourceinfo_cb, this);
1536+ if (!handleOperation(o, "pa_context_get_source_info_list"))
1537+ return;
1538+
1539+ if (m_nametoset != "") {
1540+ int m = m_micmute ? 1 : 0;
1541+ qDebug("Setting PulseAudio source '%s' muted '%d'", m_nametoset.c_str(), m);
1542+ o = pa_context_set_source_mute_by_name(m_context,
1543+ m_nametoset.c_str(), m, success_cb, this);
1544+ if (!handleOperation(o, "pa_context_set_source_mute_by_name"))
1545+ return;
1546+ }
1547+
1548+ pa_threaded_mainloop_unlock(m_mainLoop);
1549+}
1550+
1551+void QPulseAudioEngineWorker::plugCardCallback(const pa_card_info *info)
1552+{
1553+ qDebug("Notified about card (%s) add event from PulseAudio", info->name);
1554+
1555+ /* Check if it's indeed a BT device (with at least one hsp profile) */
1556+ pa_card_profile_info2 *hsp = NULL, *a2dp = NULL;
1557+ for (int i = 0; i < info->n_profiles; i++) {
1558+ if (!strcmp(info->profiles2[i]->name, PULSEAUDIO_PROFILE_HSP))
1559+ hsp = info->profiles2[i];
1560+ else if (!strcmp(info->profiles2[i]->name, PULSEAUDIO_PROFILE_A2DP) &&
1561+ info->profiles2[i]->available != 0) {
1562+ qDebug("Found a2dp");
1563+ a2dp = info->profiles2[i];
1564+ }
1565+ qDebug("%s", info->profiles2[i]->name);
1566+ }
1567+
1568+ if ((!info->active_profile || !strcmp(info->active_profile->name, "off")) && a2dp) {
1569+ qDebug("No profile set");
1570+ m_default_bt_card_fallback = info->name;
1571+ }
1572+
1573+ /* We only care about BT (HSP) devices, and if one is not already available */
1574+ if ((m_callstatus != CallEnded) && ((m_bt_hsp == "") || (m_bt_hsp_a2dp == ""))) {
1575+ if (hsp)
1576+ m_handleevent = true;
1577+ }
1578+}
1579+
1580+void QPulseAudioEngineWorker::updateCardCallback(const pa_card_info *info)
1581+{
1582+ qDebug("Notified about card (%s) changes event from PulseAudio", info->name);
1583+
1584+ /* Check if it's indeed a BT device (with at least one hsp profile) */
1585+ pa_card_profile_info2 *hsp = NULL, *a2dp = NULL;
1586+ for (int i = 0; i < info->n_profiles; i++) {
1587+ if (!strcmp(info->profiles2[i]->name, PULSEAUDIO_PROFILE_HSP))
1588+ hsp = info->profiles2[i];
1589+ else if (!strcmp(info->profiles2[i]->name, PULSEAUDIO_PROFILE_A2DP) &&
1590+ info->profiles2[i]->available != 0) {
1591+ qDebug("Found a2dp");
1592+ a2dp = info->profiles2[i];
1593+ }
1594+ qDebug("%s", info->profiles2[i]->name);
1595+ }
1596+
1597+ if ((!info->active_profile || !strcmp(info->active_profile->name, "off")) && a2dp) {
1598+ qDebug("No profile set");
1599+ m_default_bt_card_fallback = info->name;
1600+ }
1601+
1602+
1603+ /* We only care if the card event for the voicecall capable card */
1604+ if ((m_callstatus == CallActive) && (!strcmp(info->name, m_voicecallcard.c_str()))) {
1605+ if (m_audiomode == AudioModeWiredHeadset) {
1606+ /* If previous mode is wired, it means it got unplugged */
1607+ m_handleevent = true;
1608+ m_audiomodetoset = AudioModeBtOrWiredOrEarpiece;
1609+ } else if ((m_audiomode == AudioModeEarpiece) || ((m_audiomode == AudioModeSpeaker))) {
1610+ /* Now only trigger the event in case wired headset/headphone is now available */
1611+ pa_card_port_info *port_info = NULL;
1612+ for (int i = 0; i < info->n_ports; i++) {
1613+ if (info->ports[i] && (info->ports[i]->available == PA_PORT_AVAILABLE_YES) && (
1614+ !strcmp(info->ports[i]->name, "output-wired_headset") ||
1615+ !strcmp(info->ports[i]->name, "output-wired_headphone"))) {
1616+ m_handleevent = true;
1617+ m_audiomodetoset = AudioModeWiredOrEarpiece;
1618+ }
1619+ }
1620+ } else if (m_audiomode == AudioModeBluetooth) {
1621+ /* Handle the event so we can update the audiomodes */
1622+ m_handleevent = true;
1623+ m_audiomodetoset = AudioModeBluetooth;
1624+ }
1625+ }
1626+}
1627+
1628+void QPulseAudioEngineWorker::unplugCardCallback()
1629+{
1630+ if (m_callstatus != CallEnded) {
1631+ m_handleevent = true;
1632+ }
1633+}
1634+
1635+void QPulseAudioEngineWorker::handleCardEvent(const int evt, const unsigned int idx)
1636+{
1637+ pa_operation *o = NULL;
1638+
1639+ /* Internal state var used to know if we need to update our internal state */
1640+ m_handleevent = false;
1641+
1642+ if (evt == PA_SUBSCRIPTION_EVENT_NEW) {
1643+ o = pa_context_get_card_info_by_index(m_context, idx, plug_card_cb, this);
1644+ if (!handleOperation(o, "pa_context_get_card_info_by_index"))
1645+ return;
1646+
1647+ if (m_default_bt_card_fallback != "") {
1648+ o = pa_context_set_card_profile_by_name(m_context,
1649+ m_default_bt_card_fallback.c_str(), PULSEAUDIO_PROFILE_A2DP, success_cb, this);
1650+ if (!handleOperation(o, "pa_context_set_card_profile_by_name"))
1651+ return;
1652+ m_default_bt_card_fallback = "";
1653+ }
1654+
1655+ if (m_handleevent) {
1656+ qDebug("Adding new BT-HSP capable device");
1657+ /* In case A2DP is available, switch to HSP */
1658+ if (setupVoiceCall() < 0)
1659+ return;
1660+ /* Enable the HSP output port */
1661+ setCallMode(m_callstatus, AudioModeBluetooth);
1662+ }
1663+ } else if (evt == PA_SUBSCRIPTION_EVENT_CHANGE) {
1664+ o = pa_context_get_card_info_by_index(m_context, idx, update_card_cb, this);
1665+ if (!handleOperation(o, "pa_context_get_card_info_by_index"))
1666+ return;
1667+
1668+ if (m_default_bt_card_fallback != "") {
1669+ o = pa_context_set_card_profile_by_name(m_context,
1670+ m_default_bt_card_fallback.c_str(), PULSEAUDIO_PROFILE_A2DP, success_cb, this);
1671+ if (!handleOperation(o, "pa_context_set_card_profile_by_name"))
1672+ return;
1673+ m_default_bt_card_fallback = "";
1674+ }
1675+
1676+ if (m_handleevent) {
1677+ /* In this case it means the handset state changed */
1678+ qDebug("Notifying card changes for the voicecall capable card");
1679+ setCallMode(m_callstatus, m_audiomodetoset);
1680+ }
1681+ } else if (evt == PA_SUBSCRIPTION_EVENT_REMOVE) {
1682+ /* Check if the main HSP card was removed */
1683+ if (m_bt_hsp != "") {
1684+ o = pa_context_get_card_info_by_name(m_context, m_bt_hsp.c_str(), unplug_card_cb, this);
1685+ if (!handleOperation(o, "pa_context_get_sink_info_by_name"))
1686+ return;
1687+ }
1688+ if (m_bt_hsp_a2dp != "") {
1689+ o = pa_context_get_card_info_by_name(m_context, m_bt_hsp_a2dp.c_str(), unplug_card_cb, this);
1690+ if (!handleOperation(o, "pa_context_get_sink_info_by_name"))
1691+ return;
1692+ }
1693+ if (m_handleevent) {
1694+ qDebug("Notifying about BT-HSP card removal");
1695+ /* Needed in order to save the default sink/source */
1696+ if (setupVoiceCall() < 0)
1697+ return;
1698+ /* Enable the default handset output port */
1699+ setCallMode(m_callstatus, AudioModeWiredOrEarpiece);
1700+ }
1701+ }
1702+}
1703+
1704+Q_GLOBAL_STATIC(QPulseAudioEngine, pulseEngine);
1705+
1706+QPulseAudioEngine::QPulseAudioEngine(QObject *parent) :
1707+ QObject(parent)
1708+{
1709+ qRegisterMetaType<CallStatus>();
1710+ qRegisterMetaType<AudioMode>();
1711+ qRegisterMetaType<AudioModes>();
1712+ mWorker = new QPulseAudioEngineWorker();
1713+ QObject::connect(mWorker, SIGNAL(audioModeChanged(const AudioMode)), this, SIGNAL(audioModeChanged(const AudioMode)), Qt::QueuedConnection);
1714+ QObject::connect(mWorker, SIGNAL(availableAudioModesChanged(const AudioModes)), this, SIGNAL(availableAudioModesChanged(const AudioModes)), Qt::QueuedConnection);
1715+ mWorker->createPulseContext();
1716+ mWorker->moveToThread(&mThread);
1717+ mThread.start();
1718+}
1719+
1720+QPulseAudioEngine::~QPulseAudioEngine()
1721+{
1722+ mThread.quit();
1723+ mThread.wait();
1724+}
1725+
1726+QPulseAudioEngine *QPulseAudioEngine::instance()
1727+{
1728+ QPulseAudioEngine *engine = pulseEngine();
1729+ return engine;
1730+}
1731+
1732+void QPulseAudioEngine::setCallMode(CallStatus callstatus, AudioMode audiomode)
1733+{
1734+ QMetaObject::invokeMethod(mWorker, "setCallMode", Qt::QueuedConnection, Q_ARG(CallStatus, callstatus), Q_ARG(AudioMode, audiomode));
1735+}
1736+
1737+void QPulseAudioEngine::setMicMute(bool muted)
1738+{
1739+ QMetaObject::invokeMethod(mWorker, "setMicMute", Qt::QueuedConnection, Q_ARG(bool, muted));
1740+}
1741+
1742+QT_END_NAMESPACE
1743+
1744
1745=== added file 'handler/qpulseaudioengine.h'
1746--- handler/qpulseaudioengine.h 1970-01-01 00:00:00 +0000
1747+++ handler/qpulseaudioengine.h 2016-01-20 11:56:12 +0000
1748@@ -0,0 +1,126 @@
1749+/****************************************************************************
1750+**
1751+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
1752+** Contact: http://www.qt-project.org/legal
1753+**
1754+** This file was taken from qt5 and modified by
1755+** David Henningsson <david.henningsson@canonical.com> for usage in
1756+** telepathy-ofono.
1757+**
1758+** GNU Lesser General Public License Usage
1759+** Alternatively, this file may be used under the terms of the GNU Lesser
1760+** General Public License version 2.1 as published by the Free Software
1761+** Foundation and appearing in the file LICENSE.LGPL included in the
1762+** packaging of this file. Please review the following information to
1763+** ensure the GNU Lesser General Public License version 2.1 requirements
1764+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
1765+**
1766+****************************************************************************/
1767+
1768+#ifndef QPULSEAUDIOENGINE_H
1769+#define QPULSEAUDIOENGINE_H
1770+
1771+#include <QtCore/qmap.h>
1772+#include <QtCore/qbytearray.h>
1773+#include <QThread>
1774+#include <pulse/pulseaudio.h>
1775+
1776+enum AudioMode {
1777+ AudioModeEarpiece = 0x0001,
1778+ AudioModeWiredHeadset = 0x0002,
1779+ AudioModeSpeaker = 0x0004,
1780+ AudioModeBluetooth = 0x0008,
1781+ AudioModeBtOrWiredOrEarpiece = AudioModeBluetooth | AudioModeWiredHeadset | AudioModeEarpiece,
1782+ AudioModeWiredOrEarpiece = AudioModeWiredHeadset | AudioModeEarpiece,
1783+ AudioModeWiredOrSpeaker = AudioModeWiredHeadset | AudioModeSpeaker,
1784+ AudioModeBtOrWiredOrSpeaker = AudioModeBluetooth | AudioModeWiredOrSpeaker
1785+};
1786+
1787+Q_DECLARE_METATYPE(AudioMode)
1788+
1789+typedef QList<AudioMode> AudioModes;
1790+Q_DECLARE_METATYPE(AudioModes)
1791+
1792+enum CallStatus {
1793+ CallRinging,
1794+ CallActive,
1795+ CallEnded
1796+};
1797+
1798+Q_DECLARE_METATYPE(CallStatus)
1799+
1800+QT_BEGIN_NAMESPACE
1801+
1802+class QPulseAudioEngineWorker : public QObject
1803+{
1804+ Q_OBJECT
1805+
1806+public:
1807+ QPulseAudioEngineWorker(QObject *parent = 0);
1808+ ~QPulseAudioEngineWorker();
1809+
1810+ pa_threaded_mainloop *mainloop() { return m_mainLoop; }
1811+ pa_context *context() { return m_context; }
1812+ bool createPulseContext(void);
1813+ int setupVoiceCall(void);
1814+ void restoreVoiceCall(void);
1815+ /* Callbacks to be used internally */
1816+ void cardInfoCallback(const pa_card_info *card);
1817+ void sinkInfoCallback(const pa_sink_info *sink);
1818+ void sourceInfoCallback(const pa_source_info *source);
1819+ void serverInfoCallback(const pa_server_info *server);
1820+ void plugCardCallback(const pa_card_info *card);
1821+ void updateCardCallback(const pa_card_info *card);
1822+ void unplugCardCallback();
1823+
1824+Q_SIGNALS:
1825+ void audioModeChanged(const AudioMode mode);
1826+ void availableAudioModesChanged(const AudioModes modes);
1827+
1828+public Q_SLOTS:
1829+ void handleCardEvent(const int evt, const unsigned int idx);
1830+ void setCallMode(CallStatus callstatus, AudioMode audiomode);
1831+ void setMicMute(bool muted); /* True if muted, false if unmuted */
1832+
1833+private:
1834+ pa_mainloop_api *m_mainLoopApi;
1835+ pa_threaded_mainloop *m_mainLoop;
1836+ pa_context *m_context;
1837+
1838+ AudioModes m_availableAudioModes;
1839+ CallStatus m_callstatus;
1840+ AudioMode m_audiomode;
1841+ AudioMode m_audiomodetoset;
1842+ bool m_micmute, m_handleevent;
1843+ std::string m_nametoset, m_valuetoset;
1844+ std::string m_defaultsink, m_defaultsource;
1845+ std::string m_bt_hsp, m_bt_hsp_a2dp;
1846+ std::string m_default_bt_card_fallback;
1847+ std::string m_voicecallcard, m_voicecallhighest, m_voicecallprofile;
1848+
1849+ bool handleOperation(pa_operation *operation, const char *func_name);
1850+ void releasePulseContext(void);
1851+};
1852+
1853+class QPulseAudioEngine : public QObject
1854+{
1855+ Q_OBJECT
1856+public:
1857+ explicit QPulseAudioEngine(QObject *parent = 0);
1858+ ~QPulseAudioEngine();
1859+ static QPulseAudioEngine *instance();
1860+
1861+ void setCallMode(CallStatus callstatus, AudioMode audiomode);
1862+ void setMicMute(bool muted); /* True if muted, false if unmuted */
1863+
1864+Q_SIGNALS:
1865+ void audioModeChanged(const AudioMode mode);
1866+ void availableAudioModesChanged(const AudioModes modes);
1867+private:
1868+ QPulseAudioEngineWorker *mWorker;
1869+ QThread mThread;
1870+};
1871+
1872+QT_END_NAMESPACE
1873+
1874+#endif
1875
1876=== modified file 'libtelephonyservice/audiooutput.cpp'
1877--- libtelephonyservice/audiooutput.cpp 2014-08-25 14:49:53 +0000
1878+++ libtelephonyservice/audiooutput.cpp 2016-01-20 11:56:12 +0000
1879@@ -21,6 +21,23 @@
1880
1881 #include "audiooutput.h"
1882
1883+QDBusArgument &operator<<(QDBusArgument &argument, const AudioOutputDBus &output)
1884+{
1885+ argument.beginStructure();
1886+ argument << output.id << output.type << output.name;
1887+ argument.endStructure();
1888+ return argument;
1889+}
1890+
1891+const QDBusArgument &operator>>(const QDBusArgument &argument, AudioOutputDBus &output)
1892+{
1893+ argument.beginStructure();
1894+ argument >> output.id >> output.type >> output.name;
1895+ argument.endStructure();
1896+ return argument;
1897+}
1898+
1899+
1900 AudioOutput::AudioOutput(const QString& id, const QString& name, const QString& type, QObject *parent) :
1901 QObject(parent), mId(id), mName(name), mType(type)
1902 {
1903
1904=== modified file 'libtelephonyservice/audiooutput.h'
1905--- libtelephonyservice/audiooutput.h 2014-08-25 14:49:53 +0000
1906+++ libtelephonyservice/audiooutput.h 2016-01-20 11:56:12 +0000
1907@@ -23,6 +23,7 @@
1908 #define AUDIOOUTPUT_H
1909
1910 #include <QObject>
1911+#include <QDBusArgument>
1912
1913 struct AudioOutputDBus {
1914 QString id;
1915@@ -53,4 +54,7 @@
1916 QString mType;
1917 };
1918
1919+QDBusArgument &operator<<(QDBusArgument &argument, const AudioOutputDBus &output);
1920+const QDBusArgument &operator>>(const QDBusArgument &argument, AudioOutputDBus &output);
1921+
1922 #endif // AUDIOOUTPUT_H
1923
1924=== modified file 'libtelephonyservice/callentry.cpp'
1925--- libtelephonyservice/callentry.cpp 2015-06-09 21:59:29 +0000
1926+++ libtelephonyservice/callentry.cpp 2016-01-20 11:56:12 +0000
1927@@ -38,30 +38,16 @@
1928 #define PROPERTY_AUDIO_OUTPUTS "AudioOutputs"
1929 #define PROPERTY_ACTIVE_AUDIO_OUTPUT "ActiveAudioOutput"
1930
1931-QDBusArgument &operator<<(QDBusArgument &argument, const AudioOutputDBus &output)
1932-{
1933- argument.beginStructure();
1934- argument << output.id << output.type << output.name;
1935- argument.endStructure();
1936- return argument;
1937-}
1938-
1939-const QDBusArgument &operator>>(const QDBusArgument &argument, AudioOutputDBus &output)
1940-{
1941- argument.beginStructure();
1942- argument >> output.id >> output.type >> output.name;
1943- argument.endStructure();
1944- return argument;
1945-}
1946-
1947 CallEntry::CallEntry(const Tp::CallChannelPtr &channel, QObject *parent) :
1948 QObject(parent),
1949 mChannel(channel),
1950 mVoicemail(false),
1951 mLocalMuteState(false),
1952- mMuteInterface(channel->busName(), channel->objectPath(), TELEPATHY_MUTE_IFACE),
1953- mAudioOutputsInterface(channel->busName(), channel->objectPath(), CANONICAL_TELEPHONY_AUDIOOUTPUTS_IFACE)
1954+ mMuteInterface(channel->busName(), channel->objectPath(), TELEPATHY_MUTE_IFACE)
1955 {
1956+ qRegisterMetaType<AudioOutputDBus>();
1957+ qRegisterMetaType<AudioOutputDBusList>();
1958+
1959 qDBusRegisterMetaType<AudioOutputDBus>();
1960 qDBusRegisterMetaType<AudioOutputDBusList>();
1961
1962@@ -77,12 +63,32 @@
1963 SIGNAL(CallHoldingFailed(QString)),
1964 SLOT(onCallHoldingFailed(QString)));
1965
1966+ connect(TelepathyHelper::instance()->handlerInterface(),
1967+ SIGNAL(ActiveAudioOutputChanged(QString)),
1968+ SLOT(onActiveAudioOutputChanged(QString)));
1969+
1970+ QDBusConnection::sessionBus().connect(TelepathyHelper::instance()->handlerInterface()->service(),
1971+ TelepathyHelper::instance()->handlerInterface()->path(),
1972+ TelepathyHelper::instance()->handlerInterface()->interface(),
1973+ "AudioOutputsChanged",
1974+ this,
1975+ SLOT(onAudioOutputsChanged(AudioOutputDBusList)));
1976+
1977 // in case the account is an ofono account, we can check the voicemail number
1978 OfonoAccountEntry *ofonoAccount = qobject_cast<OfonoAccountEntry*>(mAccount);
1979 if (ofonoAccount && !ofonoAccount->voicemailNumber().isEmpty()) {
1980 setVoicemail(phoneNumber() == ofonoAccount->voicemailNumber());
1981 }
1982
1983+ QDBusInterface *phoneAppHandler = TelepathyHelper::instance()->handlerInterface();
1984+
1985+ QDBusMessage reply = phoneAppHandler->call(PROPERTY_AUDIO_OUTPUTS);
1986+ AudioOutputDBusList audioOutputList = qdbus_cast<AudioOutputDBusList>(reply.arguments().first());
1987+ onAudioOutputsChanged(audioOutputList);
1988+
1989+ QString activeAudioOutput = phoneAppHandler->property(PROPERTY_ACTIVE_AUDIO_OUTPUT).toString();
1990+ onActiveAudioOutputChanged(activeAudioOutput);
1991+
1992 Q_EMIT incomingChanged();
1993 }
1994
1995@@ -105,7 +111,8 @@
1996
1997 void CallEntry::setActiveAudioOutput(const QString &id)
1998 {
1999- mAudioOutputsInterface.call("SetActiveAudioOutput", id);
2000+ QDBusInterface *phoneAppHandler = TelepathyHelper::instance()->handlerInterface();
2001+ phoneAppHandler->setProperty("ActiveAudioOutput", id);
2002 }
2003
2004 void CallEntry::onActiveAudioOutputChanged(const QString &id)
2005@@ -198,13 +205,6 @@
2006
2007 refreshProperties();
2008
2009- QDBusConnection::sessionBus().connect(mChannel->busName(), mChannel->objectPath(),
2010- CANONICAL_TELEPHONY_AUDIOOUTPUTS_IFACE,
2011- "AudioOutputsChanged",
2012- this,
2013- SLOT(onAudioOutputsChanged(AudioOutputDBusList)));
2014- connect(&mAudioOutputsInterface, SIGNAL(ActiveAudioOutputChanged(QString)), SLOT(onActiveAudioOutputChanged(QString)));
2015-
2016 onCallStateChanged(mChannel->callState());
2017
2018 Q_EMIT heldChanged();
2019@@ -248,30 +248,18 @@
2020
2021 void CallEntry::refreshProperties()
2022 {
2023- QDBusInterface callChannelIface(mChannel->busName(), mChannel->objectPath(), DBUS_PROPERTIES_IFACE);
2024-
2025- QDBusMessage reply = callChannelIface.call("GetAll", TELEPATHY_CALL_IFACE);
2026- QVariantList args = reply.arguments();
2027- QMap<QString, QVariant> map = qdbus_cast<QMap<QString, QVariant> >(args[0]);
2028-
2029- reply = callChannelIface.call("GetAll", CANONICAL_TELEPHONY_AUDIOOUTPUTS_IFACE);
2030- args = reply.arguments();
2031- QMap<QString, QVariant> map2 = qdbus_cast<QMap<QString, QVariant> >(args[0]);
2032-
2033- mProperties.clear();
2034- QMapIterator<QString, QVariant> i(map);
2035- while(i.hasNext()) {
2036- i.next();
2037- mProperties[i.key()] = i.value();
2038- }
2039- QMapIterator<QString, QVariant> i2(map2);
2040- while(i2.hasNext()) {
2041- i2.next();
2042- mProperties[i2.key()] = i2.value();
2043- }
2044-
2045- onAudioOutputsChanged(qdbus_cast<AudioOutputDBusList>(mProperties[PROPERTY_AUDIO_OUTPUTS]));
2046- onActiveAudioOutputChanged(mProperties[PROPERTY_ACTIVE_AUDIO_OUTPUT].toString());
2047+ QDBusInterface callChannelIface(mChannel->busName(), mChannel->objectPath(), DBUS_PROPERTIES_IFACE);
2048+
2049+ QDBusMessage reply = callChannelIface.call("GetAll", TELEPATHY_CALL_IFACE);
2050+ QVariantList args = reply.arguments();
2051+ QMap<QString, QVariant> map = qdbus_cast<QMap<QString, QVariant> >(args[0]);
2052+
2053+ mProperties.clear();
2054+ QMapIterator<QString, QVariant> i(map);
2055+ while(i.hasNext()) {
2056+ i.next();
2057+ mProperties[i.key()] = i.value();
2058+ }
2059 }
2060
2061 bool CallEntry::dialing() const
2062
2063=== modified file 'libtelephonyservice/callentry.h'
2064--- libtelephonyservice/callentry.h 2015-03-04 18:02:13 +0000
2065+++ libtelephonyservice/callentry.h 2016-01-20 11:56:12 +0000
2066@@ -29,6 +29,7 @@
2067 #include <TelepathyQt/CallChannel>
2068 #include "audiooutput.h"
2069
2070+
2071 class AccountEntry;
2072
2073 class CallEntry : public QObject
2074@@ -179,7 +180,6 @@
2075 AccountEntry *mAccount;
2076 Tp::CallChannelPtr mChannel;
2077 QDBusInterface mMuteInterface;
2078- QDBusInterface mAudioOutputsInterface;
2079 QMap<QString, QVariant> mProperties;
2080 bool mVoicemail;
2081 bool mLocalMuteState;
2082
2083=== modified file 'libtelephonyservice/telepathyhelper.cpp'
2084--- libtelephonyservice/telepathyhelper.cpp 2015-11-23 20:01:58 +0000
2085+++ libtelephonyservice/telepathyhelper.cpp 2016-01-20 11:56:12 +0000
2086@@ -243,7 +243,6 @@
2087 if (name.isEmpty()) {
2088 name = "TelephonyPluginObserver";
2089 }
2090-
2091 if (mChannelObserver) {
2092 unregisterClient(mChannelObserver);
2093 }
2094@@ -251,13 +250,16 @@
2095 mChannelObserver = new ChannelObserver(this);
2096 mChannelObserverPtr = Tp::AbstractClientPtr(mChannelObserver);
2097 if (registerClient(mChannelObserver, name)) {
2098- // messages
2099- connect(mChannelObserver, SIGNAL(textChannelAvailable(Tp::TextChannelPtr)),
2100- ChatManager::instance(), SLOT(onTextChannelAvailable(Tp::TextChannelPtr)));
2101+ // we don't connect managers in handler, as they query the handler and cause a deadlock
2102+ if (QCoreApplication::applicationName() != "telephony-service-handler") {
2103+ // messages
2104+ connect(mChannelObserver, SIGNAL(textChannelAvailable(Tp::TextChannelPtr)),
2105+ ChatManager::instance(), SLOT(onTextChannelAvailable(Tp::TextChannelPtr)));
2106
2107- // calls
2108- connect(mChannelObserver, SIGNAL(callChannelAvailable(Tp::CallChannelPtr)),
2109- CallManager::instance(), SLOT(onCallChannelAvailable(Tp::CallChannelPtr)));
2110+ // calls
2111+ connect(mChannelObserver, SIGNAL(callChannelAvailable(Tp::CallChannelPtr)),
2112+ CallManager::instance(), SLOT(onCallChannelAvailable(Tp::CallChannelPtr)));
2113+ }
2114
2115 Q_EMIT channelObserverCreated(mChannelObserver);
2116 }
2117
2118=== modified file 'libtelephonyservice/telepathyhelper.h'
2119--- libtelephonyservice/telepathyhelper.h 2015-11-23 20:01:58 +0000
2120+++ libtelephonyservice/telepathyhelper.h 2016-01-20 11:56:12 +0000
2121@@ -33,7 +33,6 @@
2122 #include "channelobserver.h"
2123
2124 #define CANONICAL_TELEPHONY_VOICEMAIL_IFACE "com.canonical.Telephony.Voicemail"
2125-#define CANONICAL_TELEPHONY_AUDIOOUTPUTS_IFACE "com.canonical.Telephony.AudioOutputs"
2126 #define CANONICAL_TELEPHONY_USSD_IFACE "com.canonical.Telephony.USSD"
2127 #define CANONICAL_TELEPHONY_EMERGENCYMODE_IFACE "com.canonical.Telephony.EmergencyMode"
2128

Subscribers

People subscribed via source and target branches