Merge lp:~phablet-team/telephony-service/audio_route_manager into lp:telephony-service

Proposed by Gustavo Pichorim Boiko
Status: Superseded
Proposed branch: lp:~phablet-team/telephony-service/audio_route_manager
Merge into: lp:telephony-service
Prerequisite: lp:~phablet-team/telephony-service/allow-leave-channel-by-properties
Diff against target: 2136 lines (+1668/-86)
24 files modified
CMakeLists.txt (+2/-0)
debian/control (+1/-0)
handler/CMakeLists.txt (+12/-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 (+25/-5)
handler/handlerdbus.h (+11/-1)
handler/main.cpp (+16/-2)
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:~phablet-team/telephony-service/audio_route_manager
Reviewer Review Type Date Requested Status
Ubuntu Phablet Team Pending
Review via email: mp+316365@code.launchpad.net

This proposal has been superseded by a proposal from 2017-02-03.

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.
1242. By Renato Araujo Oliveira Filho

Parent merged.

1243. By Gustavo Pichorim Boiko

Merge parent

1244. By Gustavo Pichorim Boiko

Fix building.

1245. By Gustavo Pichorim Boiko

Build the pulseaudio stuff on all supported architectures.

1246. By Tiago Salem Herrmann

merge parent branch

1247. By Tiago Salem Herrmann

merge parent branch

1248. By Tiago Salem Herrmann

merge parent branch

Unmerged revisions

1248. By Tiago Salem Herrmann

merge parent branch

1247. By Tiago Salem Herrmann

merge parent branch

1246. By Tiago Salem Herrmann

merge parent branch

1245. By Gustavo Pichorim Boiko

Build the pulseaudio stuff on all supported architectures.

1244. By Gustavo Pichorim Boiko

Fix building.

1243. By Gustavo Pichorim Boiko

Merge parent

1242. By Renato Araujo Oliveira Filho

Parent merged.

1241. By Gustavo Pichorim Boiko

Initial code to move audio outputs to telephony-service (code by Tiago Salem Herrmann)

1240. By Tiago Salem Herrmann

Allow leave single channel by properties

1239. By Tiago Salem Herrmann

merge trunk

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

Subscribers

People subscribed via source and target branches

to all changes: