Merge lp:~phablet-team/telephony-service/multiple_performance_improvements into lp:telephony-service/staging

Proposed by Tiago Salem Herrmann
Status: Needs review
Proposed branch: lp:~phablet-team/telephony-service/multiple_performance_improvements
Merge into: lp:telephony-service/staging
Prerequisite: lp:~phablet-team/telephony-service/audio_route_manager
Diff against target: 3337 lines (+41/-1537)
13 files modified
handler/audioroutemanager.cpp (+0/-231)
handler/audioroutemanager.h (+0/-75)
handler/powerd.h (+0/-34)
handler/powerdaudiomodemediator.cpp (+0/-63)
handler/powerdaudiomodemediator.h (+0/-46)
handler/powerddbus.cpp (+0/-43)
handler/powerddbus.h (+0/-38)
handler/qpulseaudioengine.cpp (+0/-853)
handler/qpulseaudioengine.h (+0/-126)
libtelephonyservice/chatentry.cpp (+26/-16)
libtelephonyservice/chatentry.h (+1/-0)
libtelephonyservice/contactwatcher.cpp (+1/-7)
tests/Ubuntu.Telephony/ContactWatcherTest.cpp (+13/-5)
To merge this branch: bzr merge lp:~phablet-team/telephony-service/multiple_performance_improvements
Reviewer Review Type Date Requested Status
Ubuntu Phablet Team Pending
Review via email: mp+316375@code.launchpad.net

Commit message

Multiple performance improvements on Roles Interfaces and ContactWatcher

Description of the change

Multiple performance improvements on Roles Interfaces and ContactWatcher

To post a comment you must log in.
1242. By Tiago Salem Herrmann

merge parent branch

Unmerged revisions

1242. By Tiago Salem Herrmann

merge parent branch

1241. By Tiago Salem Herrmann

merge parent

1240. By Tiago Salem Herrmann

Multiple performance fixes for contact watcher and roles interface

1239. By Tiago Salem Herrmann

merge parent

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== added file 'handler/audioroutemanager.cpp'
--- handler/audioroutemanager.cpp 1970-01-01 00:00:00 +0000
+++ handler/audioroutemanager.cpp 2017-02-06 13:26:33 +0000
@@ -0,0 +1,231 @@
1/*
2 * Copyright (C) 2016 Canonical, Ltd.
3 *
4 * Authors:
5 * Tiago Salem Herrmann <tiago.herrmann@canonical.com>
6 *
7 * This file is part of telephony-service.
8 *
9 * telephony-service is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; version 3.
12 *
13 * telephony-service is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program. If not, see <http://www.gnu.org/licenses/>.
20 */
21
22#include "audioroutemanager.h"
23#include "telepathyhelper.h"
24#include "accountentry.h"
25#include <TelepathyQt/Contact>
26#include <TelepathyQt/Functors>
27
28
29static void enable_earpiece()
30{
31#ifdef USE_PULSEAUDIO
32 QPulseAudioEngine::instance()->setCallMode(CallActive, AudioModeBtOrWiredOrEarpiece);
33#endif
34}
35
36static void enable_normal()
37{
38#ifdef USE_PULSEAUDIO
39 QTimer* timer = new QTimer();
40 timer->setSingleShot(true);
41 QObject::connect(timer, &QTimer::timeout, [=](){
42 QPulseAudioEngine::instance()->setMicMute(false);
43 QPulseAudioEngine::instance()->setCallMode(CallEnded, AudioModeWiredOrSpeaker);
44 timer->deleteLater();
45 });
46 timer->start(2000);
47#endif
48}
49
50static void enable_speaker()
51{
52#ifdef USE_PULSEAUDIO
53 QPulseAudioEngine::instance()->setCallMode(CallActive, AudioModeSpeaker);
54#endif
55}
56
57static void enable_ringtone()
58{
59#ifdef USE_PULSEAUDIO
60 QPulseAudioEngine::instance()->setCallMode(CallRinging, AudioModeBtOrWiredOrSpeaker);
61#endif
62}
63
64AudioRouteManager *AudioRouteManager::instance()
65{
66 static AudioRouteManager *self = new AudioRouteManager();
67 return self;
68}
69
70AudioRouteManager::AudioRouteManager(QObject *parent) :
71 QObject(parent), mAudioModeMediator(mPowerDDBus)
72{
73 TelepathyHelper::instance()->registerChannelObserver("TelephonyServiceHandlerAudioRouteManager");
74
75 QObject::connect(TelepathyHelper::instance()->channelObserver(), SIGNAL(callChannelAvailable(Tp::CallChannelPtr)),
76 this, SLOT(onCallChannelAvailable(Tp::CallChannelPtr)));
77
78#ifdef USE_PULSEAUDIO
79 // update audio modes
80 QObject::connect(QPulseAudioEngine::instance(), SIGNAL(audioModeChanged(AudioMode)), SLOT(onAudioModeChanged(AudioMode)));
81 QObject::connect(QPulseAudioEngine::instance(), SIGNAL(availableAudioModesChanged(AudioModes)), SLOT(onAvailableAudioModesChanged(AudioModes)));
82
83 // check if we should indeed use pulseaudio
84 QByteArray pulseAudioDisabled = qgetenv("PA_DISABLED");
85 mHasPulseAudio = true;
86 if (!pulseAudioDisabled.isEmpty())
87 mHasPulseAudio = false;
88#endif
89
90 connect(this, &AudioRouteManager::activeAudioOutputChanged, Tp::memFun(&mAudioModeMediator, &PowerDAudioModeMediator::audioModeChanged));
91 connect(this, &AudioRouteManager::lastChannelClosed, Tp::memFun(&mAudioModeMediator, &PowerDAudioModeMediator::audioOutputClosed));
92}
93
94void AudioRouteManager::onCallChannelAvailable(Tp::CallChannelPtr callChannel)
95{
96 connect(callChannel.data(),
97 SIGNAL(callStateChanged(Tp::CallState)),
98 SLOT(onCallStateChanged(Tp::CallState)));
99
100 mChannels.append(callChannel);
101 updateAudioRoute(true);
102}
103
104void AudioRouteManager::onCallStateChanged(Tp::CallState state)
105{
106 Tp::CallChannelPtr channel(qobject_cast<Tp::CallChannel*>(sender()));
107 if (!channel) {
108 return;
109 }
110
111 if (channel->callState() == Tp::CallStateEnded) {
112 mChannels.removeOne(channel);
113 }
114 updateAudioRoute(false);
115}
116
117void AudioRouteManager::setActiveAudioOutput(const QString &id)
118{
119#ifdef USE_PULSEAUDIO
120 // fallback to earpiece/headset
121 AudioMode mode = AudioModeWiredOrEarpiece;
122 if (id == "bluetooth") {
123 mode = AudioModeBluetooth;
124 } else if (id == "speaker") {
125 mode = AudioModeSpeaker;
126 }
127 if (mHasPulseAudio)
128 QPulseAudioEngine::instance()->setCallMode(CallActive, mode);
129#endif
130}
131
132QString AudioRouteManager::activeAudioOutput()
133{
134 return mActiveAudioOutput;
135}
136
137AudioOutputDBusList AudioRouteManager::audioOutputs() const
138{
139 return mAudioOutputs;
140}
141
142void AudioRouteManager::updateAudioRoute(bool newCall)
143{
144#ifdef USE_PULSEAUDIO
145 if (!mHasPulseAudio)
146 return;
147#endif
148
149 int currentCalls = mChannels.size();
150 if (currentCalls != 0) {
151 if (currentCalls == 1) {
152 // if we have only one call, check if it's incoming and
153 // enable speaker mode so the ringtone is audible
154 Tp::CallChannelPtr callChannel = mChannels.first();
155 AccountEntry *accountEntry = TelepathyHelper::instance()->accountForConnection(callChannel->connection());
156 if (!accountEntry || !callChannel) {
157 return;
158 }
159
160 bool incoming = callChannel->initiatorContact() != accountEntry->account()->connection()->selfContact();
161 Tp::CallState state = callChannel->callState();
162 if (incoming && newCall) {
163 enable_ringtone();
164 return;
165 }
166 if (state == Tp::CallStateEnded) {
167 enable_normal();
168 return;
169 }
170 // if only one call and dialing, or incoming call just accepted, then default to earpiece
171 if (newCall || (state == Tp::CallStateAccepted && incoming)) {
172 enable_earpiece();
173 return;
174 }
175 }
176 } else {
177 enable_normal();
178 Q_EMIT lastChannelClosed();
179 }
180}
181
182#ifdef USE_PULSEAUDIO
183void AudioRouteManager::onAudioModeChanged(AudioMode mode)
184{
185 qDebug("PulseAudio audio mode changed: 0x%x", mode);
186
187 if (mode == AudioModeEarpiece && mActiveAudioOutput != "earpiece") {
188 mActiveAudioOutput = "earpiece";
189 } else if (mode == AudioModeWiredHeadset && mActiveAudioOutput != "wired_headset") {
190 mActiveAudioOutput = "wired_headset";
191 } else if (mode == AudioModeSpeaker && mActiveAudioOutput != "speaker") {
192 mActiveAudioOutput = "speaker";
193 } else if (mode == AudioModeBluetooth && mActiveAudioOutput != "bluetooth") {
194 mActiveAudioOutput = "bluetooth";
195 }
196 Q_EMIT activeAudioOutputChanged(mActiveAudioOutput);
197}
198
199void AudioRouteManager::onAvailableAudioModesChanged(AudioModes modes)
200{
201 qDebug("PulseAudio available audio modes changed");
202 bool defaultFound = false;
203 mAudioOutputs.clear();
204 Q_FOREACH(const AudioMode &mode, modes) {
205 AudioOutputDBus output;
206 if (mode == AudioModeBluetooth) {
207 // there can be only one bluetooth
208 output.id = "bluetooth";
209 output.type = "bluetooth";
210 // we dont support names for now, so we set a default value
211 output.name = "bluetooth";
212 } else if (mode == AudioModeEarpiece || mode == AudioModeWiredHeadset) {
213 if (!defaultFound) {
214 defaultFound = true;
215 output.id = "default";
216 output.type = "default";
217 output.name = "default";
218 } else {
219 continue;
220 }
221 } else if (mode == AudioModeSpeaker) {
222 output.id = "speaker";
223 output.type = "speaker";
224 output.name = "speaker";
225 }
226 mAudioOutputs << output;
227 }
228 Q_EMIT audioOutputsChanged(mAudioOutputs);
229}
230#endif
231
0232
=== removed file 'handler/audioroutemanager.cpp'
--- handler/audioroutemanager.cpp 2017-02-06 13:26:33 +0000
+++ handler/audioroutemanager.cpp 1970-01-01 00:00:00 +0000
@@ -1,231 +0,0 @@
1/*
2 * Copyright (C) 2016 Canonical, Ltd.
3 *
4 * Authors:
5 * Tiago Salem Herrmann <tiago.herrmann@canonical.com>
6 *
7 * This file is part of telephony-service.
8 *
9 * telephony-service is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; version 3.
12 *
13 * telephony-service is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program. If not, see <http://www.gnu.org/licenses/>.
20 */
21
22#include "audioroutemanager.h"
23#include "telepathyhelper.h"
24#include "accountentry.h"
25#include <TelepathyQt/Contact>
26#include <TelepathyQt/Functors>
27
28
29static void enable_earpiece()
30{
31#ifdef USE_PULSEAUDIO
32 QPulseAudioEngine::instance()->setCallMode(CallActive, AudioModeBtOrWiredOrEarpiece);
33#endif
34}
35
36static void enable_normal()
37{
38#ifdef USE_PULSEAUDIO
39 QTimer* timer = new QTimer();
40 timer->setSingleShot(true);
41 QObject::connect(timer, &QTimer::timeout, [=](){
42 QPulseAudioEngine::instance()->setMicMute(false);
43 QPulseAudioEngine::instance()->setCallMode(CallEnded, AudioModeWiredOrSpeaker);
44 timer->deleteLater();
45 });
46 timer->start(2000);
47#endif
48}
49
50static void enable_speaker()
51{
52#ifdef USE_PULSEAUDIO
53 QPulseAudioEngine::instance()->setCallMode(CallActive, AudioModeSpeaker);
54#endif
55}
56
57static void enable_ringtone()
58{
59#ifdef USE_PULSEAUDIO
60 QPulseAudioEngine::instance()->setCallMode(CallRinging, AudioModeBtOrWiredOrSpeaker);
61#endif
62}
63
64AudioRouteManager *AudioRouteManager::instance()
65{
66 static AudioRouteManager *self = new AudioRouteManager();
67 return self;
68}
69
70AudioRouteManager::AudioRouteManager(QObject *parent) :
71 QObject(parent), mAudioModeMediator(mPowerDDBus)
72{
73 TelepathyHelper::instance()->registerChannelObserver("TelephonyServiceHandlerAudioRouteManager");
74
75 QObject::connect(TelepathyHelper::instance()->channelObserver(), SIGNAL(callChannelAvailable(Tp::CallChannelPtr)),
76 this, SLOT(onCallChannelAvailable(Tp::CallChannelPtr)));
77
78#ifdef USE_PULSEAUDIO
79 // update audio modes
80 QObject::connect(QPulseAudioEngine::instance(), SIGNAL(audioModeChanged(AudioMode)), SLOT(onAudioModeChanged(AudioMode)));
81 QObject::connect(QPulseAudioEngine::instance(), SIGNAL(availableAudioModesChanged(AudioModes)), SLOT(onAvailableAudioModesChanged(AudioModes)));
82
83 // check if we should indeed use pulseaudio
84 QByteArray pulseAudioDisabled = qgetenv("PA_DISABLED");
85 mHasPulseAudio = true;
86 if (!pulseAudioDisabled.isEmpty())
87 mHasPulseAudio = false;
88#endif
89
90 connect(this, &AudioRouteManager::activeAudioOutputChanged, Tp::memFun(&mAudioModeMediator, &PowerDAudioModeMediator::audioModeChanged));
91 connect(this, &AudioRouteManager::lastChannelClosed, Tp::memFun(&mAudioModeMediator, &PowerDAudioModeMediator::audioOutputClosed));
92}
93
94void AudioRouteManager::onCallChannelAvailable(Tp::CallChannelPtr callChannel)
95{
96 connect(callChannel.data(),
97 SIGNAL(callStateChanged(Tp::CallState)),
98 SLOT(onCallStateChanged(Tp::CallState)));
99
100 mChannels.append(callChannel);
101 updateAudioRoute(true);
102}
103
104void AudioRouteManager::onCallStateChanged(Tp::CallState state)
105{
106 Tp::CallChannelPtr channel(qobject_cast<Tp::CallChannel*>(sender()));
107 if (!channel) {
108 return;
109 }
110
111 if (channel->callState() == Tp::CallStateEnded) {
112 mChannels.removeOne(channel);
113 }
114 updateAudioRoute(false);
115}
116
117void AudioRouteManager::setActiveAudioOutput(const QString &id)
118{
119#ifdef USE_PULSEAUDIO
120 // fallback to earpiece/headset
121 AudioMode mode = AudioModeWiredOrEarpiece;
122 if (id == "bluetooth") {
123 mode = AudioModeBluetooth;
124 } else if (id == "speaker") {
125 mode = AudioModeSpeaker;
126 }
127 if (mHasPulseAudio)
128 QPulseAudioEngine::instance()->setCallMode(CallActive, mode);
129#endif
130}
131
132QString AudioRouteManager::activeAudioOutput()
133{
134 return mActiveAudioOutput;
135}
136
137AudioOutputDBusList AudioRouteManager::audioOutputs() const
138{
139 return mAudioOutputs;
140}
141
142void AudioRouteManager::updateAudioRoute(bool newCall)
143{
144#ifdef USE_PULSEAUDIO
145 if (!mHasPulseAudio)
146 return;
147#endif
148
149 int currentCalls = mChannels.size();
150 if (currentCalls != 0) {
151 if (currentCalls == 1) {
152 // if we have only one call, check if it's incoming and
153 // enable speaker mode so the ringtone is audible
154 Tp::CallChannelPtr callChannel = mChannels.first();
155 AccountEntry *accountEntry = TelepathyHelper::instance()->accountForConnection(callChannel->connection());
156 if (!accountEntry || !callChannel) {
157 return;
158 }
159
160 bool incoming = callChannel->initiatorContact() != accountEntry->account()->connection()->selfContact();
161 Tp::CallState state = callChannel->callState();
162 if (incoming && newCall) {
163 enable_ringtone();
164 return;
165 }
166 if (state == Tp::CallStateEnded) {
167 enable_normal();
168 return;
169 }
170 // if only one call and dialing, or incoming call just accepted, then default to earpiece
171 if (newCall || (state == Tp::CallStateAccepted && incoming)) {
172 enable_earpiece();
173 return;
174 }
175 }
176 } else {
177 enable_normal();
178 Q_EMIT lastChannelClosed();
179 }
180}
181
182#ifdef USE_PULSEAUDIO
183void AudioRouteManager::onAudioModeChanged(AudioMode mode)
184{
185 qDebug("PulseAudio audio mode changed: 0x%x", mode);
186
187 if (mode == AudioModeEarpiece && mActiveAudioOutput != "earpiece") {
188 mActiveAudioOutput = "earpiece";
189 } else if (mode == AudioModeWiredHeadset && mActiveAudioOutput != "wired_headset") {
190 mActiveAudioOutput = "wired_headset";
191 } else if (mode == AudioModeSpeaker && mActiveAudioOutput != "speaker") {
192 mActiveAudioOutput = "speaker";
193 } else if (mode == AudioModeBluetooth && mActiveAudioOutput != "bluetooth") {
194 mActiveAudioOutput = "bluetooth";
195 }
196 Q_EMIT activeAudioOutputChanged(mActiveAudioOutput);
197}
198
199void AudioRouteManager::onAvailableAudioModesChanged(AudioModes modes)
200{
201 qDebug("PulseAudio available audio modes changed");
202 bool defaultFound = false;
203 mAudioOutputs.clear();
204 Q_FOREACH(const AudioMode &mode, modes) {
205 AudioOutputDBus output;
206 if (mode == AudioModeBluetooth) {
207 // there can be only one bluetooth
208 output.id = "bluetooth";
209 output.type = "bluetooth";
210 // we dont support names for now, so we set a default value
211 output.name = "bluetooth";
212 } else if (mode == AudioModeEarpiece || mode == AudioModeWiredHeadset) {
213 if (!defaultFound) {
214 defaultFound = true;
215 output.id = "default";
216 output.type = "default";
217 output.name = "default";
218 } else {
219 continue;
220 }
221 } else if (mode == AudioModeSpeaker) {
222 output.id = "speaker";
223 output.type = "speaker";
224 output.name = "speaker";
225 }
226 mAudioOutputs << output;
227 }
228 Q_EMIT audioOutputsChanged(mAudioOutputs);
229}
230#endif
231
2320
=== added file 'handler/audioroutemanager.h'
--- handler/audioroutemanager.h 1970-01-01 00:00:00 +0000
+++ handler/audioroutemanager.h 2017-02-06 13:26:33 +0000
@@ -0,0 +1,75 @@
1/*
2 * Copyright (C) 2016 Canonical, Ltd.
3 *
4 * Authors:
5 * Tiago Salem Herrmann <tiago.herrmann@canonical.com>
6 *
7 * This file is part of telephony-service.
8 *
9 * telephony-service is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; version 3.
12 *
13 * telephony-service is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program. If not, see <http://www.gnu.org/licenses/>.
20 */
21
22#ifndef AUDIOROUTEMANAGER_H
23#define AUDIOROUTEMANAGER_H
24
25#ifdef USE_PULSEAUDIO
26#include "qpulseaudioengine.h"
27#endif
28#include "audiooutput.h"
29#include "powerdaudiomodemediator.h"
30#include "powerddbus.h"
31#include <QObject>
32#include <TelepathyQt/CallChannel>
33
34
35class AudioRouteManager : public QObject
36{
37 Q_OBJECT
38
39public:
40 static AudioRouteManager *instance();
41 void setActiveAudioOutput(const QString &id);
42 QString activeAudioOutput();
43 AudioOutputDBusList audioOutputs() const;
44 void updateAudioRoute(bool newCall = false);
45
46public Q_SLOTS:
47 void onCallChannelAvailable(Tp::CallChannelPtr callChannel);
48
49Q_SIGNALS:
50 void audioOutputsChanged(const AudioOutputDBusList &outputs);
51 void activeAudioOutputChanged(const QString &id);
52 void lastChannelClosed();
53
54protected Q_SLOTS:
55 void onCallStateChanged(Tp::CallState state);
56
57private Q_SLOTS:
58#ifdef USE_PULSEAUDIO
59 void onAudioModeChanged(AudioMode mode);
60 void onAvailableAudioModesChanged(AudioModes modes);
61#endif
62
63private:
64 explicit AudioRouteManager(QObject *parent = 0);
65 QList<Tp::CallChannelPtr> mChannels;
66 AudioOutputDBusList mAudioOutputs;
67 QString mActiveAudioOutput;
68 PowerDDBus mPowerDDBus;
69 PowerDAudioModeMediator mAudioModeMediator;
70#ifdef USE_PULSEAUDIO
71 bool mHasPulseAudio;
72#endif
73};
74
75#endif // AUDIOROUTEMANAGER_H
076
=== removed file 'handler/audioroutemanager.h'
--- handler/audioroutemanager.h 2017-02-06 13:26:33 +0000
+++ handler/audioroutemanager.h 1970-01-01 00:00:00 +0000
@@ -1,75 +0,0 @@
1/*
2 * Copyright (C) 2016 Canonical, Ltd.
3 *
4 * Authors:
5 * Tiago Salem Herrmann <tiago.herrmann@canonical.com>
6 *
7 * This file is part of telephony-service.
8 *
9 * telephony-service is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; version 3.
12 *
13 * telephony-service is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program. If not, see <http://www.gnu.org/licenses/>.
20 */
21
22#ifndef AUDIOROUTEMANAGER_H
23#define AUDIOROUTEMANAGER_H
24
25#ifdef USE_PULSEAUDIO
26#include "qpulseaudioengine.h"
27#endif
28#include "audiooutput.h"
29#include "powerdaudiomodemediator.h"
30#include "powerddbus.h"
31#include <QObject>
32#include <TelepathyQt/CallChannel>
33
34
35class AudioRouteManager : public QObject
36{
37 Q_OBJECT
38
39public:
40 static AudioRouteManager *instance();
41 void setActiveAudioOutput(const QString &id);
42 QString activeAudioOutput();
43 AudioOutputDBusList audioOutputs() const;
44 void updateAudioRoute(bool newCall = false);
45
46public Q_SLOTS:
47 void onCallChannelAvailable(Tp::CallChannelPtr callChannel);
48
49Q_SIGNALS:
50 void audioOutputsChanged(const AudioOutputDBusList &outputs);
51 void activeAudioOutputChanged(const QString &id);
52 void lastChannelClosed();
53
54protected Q_SLOTS:
55 void onCallStateChanged(Tp::CallState state);
56
57private Q_SLOTS:
58#ifdef USE_PULSEAUDIO
59 void onAudioModeChanged(AudioMode mode);
60 void onAvailableAudioModesChanged(AudioModes modes);
61#endif
62
63private:
64 explicit AudioRouteManager(QObject *parent = 0);
65 QList<Tp::CallChannelPtr> mChannels;
66 AudioOutputDBusList mAudioOutputs;
67 QString mActiveAudioOutput;
68 PowerDDBus mPowerDDBus;
69 PowerDAudioModeMediator mAudioModeMediator;
70#ifdef USE_PULSEAUDIO
71 bool mHasPulseAudio;
72#endif
73};
74
75#endif // AUDIOROUTEMANAGER_H
760
=== added file 'handler/powerd.h'
--- handler/powerd.h 1970-01-01 00:00:00 +0000
+++ handler/powerd.h 2017-02-06 13:26:33 +0000
@@ -0,0 +1,34 @@
1/**
2 * Copyright (C) 2014 Canonical, Ltd.
3 *
4 * This program is free software: you can redistribute it and/or modify it under
5 * the terms of the GNU Lesser General Public License version 3, as published by
6 * the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful, but WITHOUT
9 * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
10 * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 * Lesser General Public License for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 * Authors: Andreas Pokorny <andreas.pokorny@canonical.com>
17 */
18
19#ifndef POWERD_H
20#define POWERD_H
21
22class PowerD
23{
24public:
25 PowerD() = default;
26 virtual ~PowerD() = default;
27 PowerD(PowerD const&) = delete;
28 PowerD& operator=(PowerD const&) = delete;
29
30 virtual void enableProximityHandling() = 0;
31 virtual void disableProximityHandling() = 0;
32};
33
34#endif // POWERD_H
035
=== removed file 'handler/powerd.h'
--- handler/powerd.h 2017-02-06 13:26:33 +0000
+++ handler/powerd.h 1970-01-01 00:00:00 +0000
@@ -1,34 +0,0 @@
1/**
2 * Copyright (C) 2014 Canonical, Ltd.
3 *
4 * This program is free software: you can redistribute it and/or modify it under
5 * the terms of the GNU Lesser General Public License version 3, as published by
6 * the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful, but WITHOUT
9 * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
10 * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 * Lesser General Public License for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 * Authors: Andreas Pokorny <andreas.pokorny@canonical.com>
17 */
18
19#ifndef POWERD_H
20#define POWERD_H
21
22class PowerD
23{
24public:
25 PowerD() = default;
26 virtual ~PowerD() = default;
27 PowerD(PowerD const&) = delete;
28 PowerD& operator=(PowerD const&) = delete;
29
30 virtual void enableProximityHandling() = 0;
31 virtual void disableProximityHandling() = 0;
32};
33
34#endif // POWERD_H
350
=== added file 'handler/powerdaudiomodemediator.cpp'
--- handler/powerdaudiomodemediator.cpp 1970-01-01 00:00:00 +0000
+++ handler/powerdaudiomodemediator.cpp 2017-02-06 13:26:33 +0000
@@ -0,0 +1,63 @@
1/**
2 * Copyright (C) 2014 Canonical, Ltd.
3 *
4 * This program is free software: you can redistribute it and/or modify it under
5 * the terms of the GNU Lesser General Public License version 3, as published by
6 * the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful, but WITHOUT
9 * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
10 * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 * Lesser General Public License for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 * Authors: Andreas Pokorny <andreas.pokorny@canonical.com>
17 */
18
19#include <QDBusInterface>
20#include "powerdaudiomodemediator.h"
21
22PowerDAudioModeMediator::PowerDAudioModeMediator(PowerD &powerd)
23 : powerd(powerd)
24{
25}
26
27void PowerDAudioModeMediator::audioModeChanged(const QString &mode)
28{
29 bool enableProximity = !(mode == "speaker" || mode == "bluetooth" || mode == "wired_headset");
30
31 if (mProximityEnabled != enableProximity)
32 {
33 mProximityEnabled = enableProximity;
34 apply();
35 }
36}
37
38void PowerDAudioModeMediator::apply() const
39{
40 if (mProximityEnabled) {
41 powerd.enableProximityHandling();
42 } else {
43 // we need to power the screen on before disabling the proximity handling
44 QDBusInterface unityIface("com.canonical.Unity.Screen",
45 "/com/canonical/Unity/Screen",
46 "com.canonical.Unity.Screen",
47 QDBusConnection::systemBus());
48 QList<QVariant> args;
49 args.append("on");
50 args.append(3);
51 unityIface.callWithArgumentList(QDBus::NoBlock, "setScreenPowerMode", args);
52 powerd.disableProximityHandling();
53 }
54}
55
56void PowerDAudioModeMediator::audioOutputClosed()
57{
58 if (mProximityEnabled)
59 {
60 mProximityEnabled = false;
61 apply();
62 }
63}
064
=== removed file 'handler/powerdaudiomodemediator.cpp'
--- handler/powerdaudiomodemediator.cpp 2017-02-06 13:26:33 +0000
+++ handler/powerdaudiomodemediator.cpp 1970-01-01 00:00:00 +0000
@@ -1,63 +0,0 @@
1/**
2 * Copyright (C) 2014 Canonical, Ltd.
3 *
4 * This program is free software: you can redistribute it and/or modify it under
5 * the terms of the GNU Lesser General Public License version 3, as published by
6 * the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful, but WITHOUT
9 * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
10 * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 * Lesser General Public License for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 * Authors: Andreas Pokorny <andreas.pokorny@canonical.com>
17 */
18
19#include <QDBusInterface>
20#include "powerdaudiomodemediator.h"
21
22PowerDAudioModeMediator::PowerDAudioModeMediator(PowerD &powerd)
23 : powerd(powerd)
24{
25}
26
27void PowerDAudioModeMediator::audioModeChanged(const QString &mode)
28{
29 bool enableProximity = !(mode == "speaker" || mode == "bluetooth" || mode == "wired_headset");
30
31 if (mProximityEnabled != enableProximity)
32 {
33 mProximityEnabled = enableProximity;
34 apply();
35 }
36}
37
38void PowerDAudioModeMediator::apply() const
39{
40 if (mProximityEnabled) {
41 powerd.enableProximityHandling();
42 } else {
43 // we need to power the screen on before disabling the proximity handling
44 QDBusInterface unityIface("com.canonical.Unity.Screen",
45 "/com/canonical/Unity/Screen",
46 "com.canonical.Unity.Screen",
47 QDBusConnection::systemBus());
48 QList<QVariant> args;
49 args.append("on");
50 args.append(3);
51 unityIface.callWithArgumentList(QDBus::NoBlock, "setScreenPowerMode", args);
52 powerd.disableProximityHandling();
53 }
54}
55
56void PowerDAudioModeMediator::audioOutputClosed()
57{
58 if (mProximityEnabled)
59 {
60 mProximityEnabled = false;
61 apply();
62 }
63}
640
=== added file 'handler/powerdaudiomodemediator.h'
--- handler/powerdaudiomodemediator.h 1970-01-01 00:00:00 +0000
+++ handler/powerdaudiomodemediator.h 2017-02-06 13:26:33 +0000
@@ -0,0 +1,46 @@
1/****************************************************************************
2**
3** Copyright (C) 2014 Canonical, Ltd.
4**
5** Authors:
6** Andreas Pokorny <andreas.pokorny@canonical.com>
7**
8** GNU Lesser General Public License Usage
9** Alternatively, this file may be used under the terms of the GNU Lesser
10** General Public License version 2.1 as published by the Free Software
11** Foundation and appearing in the file LICENSE.LGPL included in the
12** packaging of this file. Please review the following information to
13** ensure the GNU Lesser General Public License version 2.1 requirements
14** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
15**
16****************************************************************************/
17
18#ifndef POWERDAUDIOMODEMEDIATOR_H
19#define POWERDAUDIOMODEMEDIATOR_H
20
21#include "powerd.h"
22
23#include <QString>
24#include <fstream>
25#include <memory>
26
27class PowerD;
28/*!
29 * \brief PowerDAudioModeMediator is responsible for configuring proximity
30 * handling of powerd during different call states and used audio outputs.
31 * In General that mean enabling sreen blanking on proximity events, when
32 * a call is active and neither a bluetooth headset nor the speakers are used.
33 */
34class PowerDAudioModeMediator
35{
36public:
37 PowerDAudioModeMediator(PowerD &powerd);
38 void audioModeChanged(const QString &mode);
39 void audioOutputClosed();
40private:
41 void apply() const;
42 PowerD &powerd;
43 bool mProximityEnabled{false};
44};
45
46#endif
047
=== removed file 'handler/powerdaudiomodemediator.h'
--- handler/powerdaudiomodemediator.h 2017-02-06 13:26:33 +0000
+++ handler/powerdaudiomodemediator.h 1970-01-01 00:00:00 +0000
@@ -1,46 +0,0 @@
1/****************************************************************************
2**
3** Copyright (C) 2014 Canonical, Ltd.
4**
5** Authors:
6** Andreas Pokorny <andreas.pokorny@canonical.com>
7**
8** GNU Lesser General Public License Usage
9** Alternatively, this file may be used under the terms of the GNU Lesser
10** General Public License version 2.1 as published by the Free Software
11** Foundation and appearing in the file LICENSE.LGPL included in the
12** packaging of this file. Please review the following information to
13** ensure the GNU Lesser General Public License version 2.1 requirements
14** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
15**
16****************************************************************************/
17
18#ifndef POWERDAUDIOMODEMEDIATOR_H
19#define POWERDAUDIOMODEMEDIATOR_H
20
21#include "powerd.h"
22
23#include <QString>
24#include <fstream>
25#include <memory>
26
27class PowerD;
28/*!
29 * \brief PowerDAudioModeMediator is responsible for configuring proximity
30 * handling of powerd during different call states and used audio outputs.
31 * In General that mean enabling sreen blanking on proximity events, when
32 * a call is active and neither a bluetooth headset nor the speakers are used.
33 */
34class PowerDAudioModeMediator
35{
36public:
37 PowerDAudioModeMediator(PowerD &powerd);
38 void audioModeChanged(const QString &mode);
39 void audioOutputClosed();
40private:
41 void apply() const;
42 PowerD &powerd;
43 bool mProximityEnabled{false};
44};
45
46#endif
470
=== added file 'handler/powerddbus.cpp'
--- handler/powerddbus.cpp 1970-01-01 00:00:00 +0000
+++ handler/powerddbus.cpp 2017-02-06 13:26:33 +0000
@@ -0,0 +1,43 @@
1/**
2 * Copyright (C) 2014 Canonical, Ltd.
3 *
4 * This program is free software: you can redistribute it and/or modify it under
5 * the terms of the GNU Lesser General Public License version 3, as published by
6 * the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful, but WITHOUT
9 * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
10 * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 * Lesser General Public License for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 * Authors: Andreas Pokorny <andreas.pokorny@canonical.com>
17 */
18
19#include "powerddbus.h"
20
21#include <QDBusConnection>
22#include <QDBusInterface>
23#include <QDBusReply>
24
25PowerDDBus::PowerDDBus()
26 : mPowerDIface{
27 new QDBusInterface(
28 "com.canonical.powerd",
29 "/com/canonical/powerd",
30 "com.canonical.powerd",
31 QDBusConnection::systemBus())}
32{
33}
34
35void PowerDDBus::enableProximityHandling()
36{
37 mPowerDIface->call("enableProximityHandling", "telephony-service-handler");
38}
39
40void PowerDDBus::disableProximityHandling()
41{
42 mPowerDIface->call("disableProximityHandling", "telephony-service-handler");
43}
044
=== removed file 'handler/powerddbus.cpp'
--- handler/powerddbus.cpp 2017-02-06 13:26:33 +0000
+++ handler/powerddbus.cpp 1970-01-01 00:00:00 +0000
@@ -1,43 +0,0 @@
1/**
2 * Copyright (C) 2014 Canonical, Ltd.
3 *
4 * This program is free software: you can redistribute it and/or modify it under
5 * the terms of the GNU Lesser General Public License version 3, as published by
6 * the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful, but WITHOUT
9 * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
10 * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 * Lesser General Public License for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 * Authors: Andreas Pokorny <andreas.pokorny@canonical.com>
17 */
18
19#include "powerddbus.h"
20
21#include <QDBusConnection>
22#include <QDBusInterface>
23#include <QDBusReply>
24
25PowerDDBus::PowerDDBus()
26 : mPowerDIface{
27 new QDBusInterface(
28 "com.canonical.powerd",
29 "/com/canonical/powerd",
30 "com.canonical.powerd",
31 QDBusConnection::systemBus())}
32{
33}
34
35void PowerDDBus::enableProximityHandling()
36{
37 mPowerDIface->call("enableProximityHandling", "telephony-service-handler");
38}
39
40void PowerDDBus::disableProximityHandling()
41{
42 mPowerDIface->call("disableProximityHandling", "telephony-service-handler");
43}
440
=== added file 'handler/powerddbus.h'
--- handler/powerddbus.h 1970-01-01 00:00:00 +0000
+++ handler/powerddbus.h 2017-02-06 13:26:33 +0000
@@ -0,0 +1,38 @@
1/**
2 * Copyright (C) 2014 Canonical, Ltd.
3 *
4 * This program is free software: you can redistribute it and/or modify it under
5 * the terms of the GNU Lesser General Public License version 3, as published by
6 * the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful, but WITHOUT
9 * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
10 * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 * Lesser General Public License for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 * Authors: Andreas Pokorny <andreas.pokorny@canonical.com>
17 */
18
19#ifndef POWERD_DUBS_H
20#define POWERD_DBUS_H
21
22#include "powerd.h"
23
24#include <memory>
25
26class QDBusInterface;
27
28class PowerDDBus : public PowerD
29{
30public:
31 PowerDDBus();
32 void enableProximityHandling() override;
33 void disableProximityHandling() override;
34private:
35 std::unique_ptr<QDBusInterface> mPowerDIface;
36};
37
38#endif
039
=== removed file 'handler/powerddbus.h'
--- handler/powerddbus.h 2017-02-06 13:26:33 +0000
+++ handler/powerddbus.h 1970-01-01 00:00:00 +0000
@@ -1,38 +0,0 @@
1/**
2 * Copyright (C) 2014 Canonical, Ltd.
3 *
4 * This program is free software: you can redistribute it and/or modify it under
5 * the terms of the GNU Lesser General Public License version 3, as published by
6 * the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful, but WITHOUT
9 * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
10 * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 * Lesser General Public License for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 * Authors: Andreas Pokorny <andreas.pokorny@canonical.com>
17 */
18
19#ifndef POWERD_DUBS_H
20#define POWERD_DBUS_H
21
22#include "powerd.h"
23
24#include <memory>
25
26class QDBusInterface;
27
28class PowerDDBus : public PowerD
29{
30public:
31 PowerDDBus();
32 void enableProximityHandling() override;
33 void disableProximityHandling() override;
34private:
35 std::unique_ptr<QDBusInterface> mPowerDIface;
36};
37
38#endif
390
=== added file 'handler/qpulseaudioengine.cpp'
--- handler/qpulseaudioengine.cpp 1970-01-01 00:00:00 +0000
+++ handler/qpulseaudioengine.cpp 2017-02-06 13:26:33 +0000
@@ -0,0 +1,853 @@
1/****************************************************************************
2**
3** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
4** Contact: http://www.qt-project.org/legal
5**
6** This file was taken from qt5 and modified by
7** David Henningsson <david.henningsson@canonical.com> for usage in
8** telepathy-ofono.
9**
10** GNU Lesser General Public License Usage
11** Alternatively, this file may be used under the terms of the GNU Lesser
12** General Public License version 2.1 as published by the Free Software
13** Foundation and appearing in the file LICENSE.LGPL included in the
14** packaging of this file. Please review the following information to
15** ensure the GNU Lesser General Public License version 2.1 requirements
16** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
17**
18****************************************************************************/
19
20#include <QtCore/qdebug.h>
21
22#include "qpulseaudioengine.h"
23#include <sys/types.h>
24#include <unistd.h>
25
26#define PULSEAUDIO_PROFILE_HSP "headset_head_unit"
27#define PULSEAUDIO_PROFILE_A2DP "a2dp_sink"
28
29QT_BEGIN_NAMESPACE
30
31static void contextStateCallbackInit(pa_context *context, void *userdata)
32{
33 Q_UNUSED(context);
34 QPulseAudioEngineWorker *pulseEngine = reinterpret_cast<QPulseAudioEngineWorker*>(userdata);
35 pa_threaded_mainloop_signal(pulseEngine->mainloop(), 0);
36}
37
38static void contextStateCallback(pa_context *context, void *userdata)
39{
40 Q_UNUSED(userdata);
41 Q_UNUSED(context);
42}
43
44static void success_cb(pa_context *context, int success, void *userdata)
45{
46 QPulseAudioEngineWorker *pulseEngine = reinterpret_cast<QPulseAudioEngineWorker*>(userdata);
47 pa_threaded_mainloop_signal(pulseEngine->mainloop(), 0);
48}
49
50/* Callbacks used when handling events from PulseAudio */
51static void plug_card_cb(pa_context *c, const pa_card_info *info, int isLast, void *userdata)
52{
53 QPulseAudioEngineWorker *pulseEngine = static_cast<QPulseAudioEngineWorker*>(userdata);
54 if (isLast != 0 || !pulseEngine || !info) {
55 pa_threaded_mainloop_signal(pulseEngine->mainloop(), 0);
56 return;
57 }
58 pulseEngine->plugCardCallback(info);
59}
60
61static void update_card_cb(pa_context *c, const pa_card_info *info, int isLast, void *userdata)
62{
63 QPulseAudioEngineWorker *pulseEngine = static_cast<QPulseAudioEngineWorker*>(userdata);
64 if (isLast != 0 || !pulseEngine || !info) {
65 pa_threaded_mainloop_signal(pulseEngine->mainloop(), 0);
66 return;
67 }
68 pulseEngine->updateCardCallback(info);
69}
70
71static void unplug_card_cb(pa_context *c, const pa_card_info *info, int isLast, void *userdata)
72{
73 QPulseAudioEngineWorker *pulseEngine = static_cast<QPulseAudioEngineWorker*>(userdata);
74 if (!pulseEngine) {
75 pa_threaded_mainloop_signal(pulseEngine->mainloop(), 0);
76 return;
77 }
78
79 if (info == NULL) {
80 /* That means that the card used to query card_info was removed */
81 pulseEngine->unplugCardCallback();
82 }
83}
84
85static void subscribeCallback(pa_context *context, pa_subscription_event_type_t t, uint32_t idx, void *userdata)
86{
87 /* For card change events (slot plug/unplug and add/remove card) */
88 if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_CARD) {
89 if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_CHANGE) {
90 QMetaObject::invokeMethod((QPulseAudioEngineWorker *) userdata, "handleCardEvent",
91 Qt::QueuedConnection, Q_ARG(int, PA_SUBSCRIPTION_EVENT_CHANGE), Q_ARG(unsigned int, idx));
92 } else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) {
93 QMetaObject::invokeMethod((QPulseAudioEngineWorker *) userdata, "handleCardEvent",
94 Qt::QueuedConnection, Q_ARG(int, PA_SUBSCRIPTION_EVENT_NEW), Q_ARG(unsigned int, idx));
95 } else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
96 QMetaObject::invokeMethod((QPulseAudioEngineWorker *) userdata, "handleCardEvent",
97 Qt::QueuedConnection, Q_ARG(int, PA_SUBSCRIPTION_EVENT_REMOVE), Q_ARG(unsigned int, idx));
98 }
99 }
100}
101
102QPulseAudioEngineWorker::QPulseAudioEngineWorker(QObject *parent)
103 : QObject(parent)
104 , m_mainLoopApi(0)
105 , m_context(0)
106 , m_callstatus(CallEnded)
107 , m_audiomode(AudioModeSpeaker)
108 , m_micmute(false)
109 , m_defaultsink("sink.primary")
110 , m_defaultsource("source.primary")
111 , m_voicecallcard("")
112 , m_voicecallhighest("")
113 , m_voicecallprofile("")
114 , m_bt_hsp("")
115 , m_bt_hsp_a2dp("")
116 , m_default_bt_card_fallback("")
117
118{
119 m_mainLoop = pa_threaded_mainloop_new();
120 if (m_mainLoop == 0) {
121 qWarning("Unable to create pulseaudio mainloop");
122 return;
123 }
124
125 if (pa_threaded_mainloop_start(m_mainLoop) != 0) {
126 qWarning("Unable to start pulseaudio mainloop");
127 pa_threaded_mainloop_free(m_mainLoop);
128 m_mainLoop = 0;
129 return;
130 }
131
132 createPulseContext();
133}
134
135bool QPulseAudioEngineWorker::createPulseContext()
136{
137 bool keepGoing = true;
138 bool ok = true;
139
140 if (m_context)
141 return true;
142
143 m_mainLoopApi = pa_threaded_mainloop_get_api(m_mainLoop);
144
145 pa_threaded_mainloop_lock(m_mainLoop);
146
147 m_context = pa_context_new(m_mainLoopApi, QString(QLatin1String("QtmPulseContext:%1")).arg(::getpid()).toLatin1().constData());
148 pa_context_set_state_callback(m_context, contextStateCallbackInit, this);
149
150 if (!m_context) {
151 qWarning("Unable to create new pulseaudio context");
152 pa_threaded_mainloop_unlock(m_mainLoop);
153 return false;
154 }
155
156 if (pa_context_connect(m_context, NULL, PA_CONTEXT_NOAUTOSPAWN, NULL) < 0) {
157 qWarning("Unable to create a connection to the pulseaudio context");
158 pa_threaded_mainloop_unlock(m_mainLoop);
159 releasePulseContext();
160 return false;
161 }
162
163 pa_threaded_mainloop_wait(m_mainLoop);
164
165 while (keepGoing) {
166 switch (pa_context_get_state(m_context)) {
167 case PA_CONTEXT_CONNECTING:
168 case PA_CONTEXT_AUTHORIZING:
169 case PA_CONTEXT_SETTING_NAME:
170 break;
171
172 case PA_CONTEXT_READY:
173 qDebug("Pulseaudio connection established.");
174 keepGoing = false;
175 break;
176
177 case PA_CONTEXT_TERMINATED:
178 qCritical("Pulseaudio context terminated.");
179 keepGoing = false;
180 ok = false;
181 break;
182
183 case PA_CONTEXT_FAILED:
184 default:
185 qCritical() << QString("Pulseaudio connection failure: %1").arg(pa_strerror(pa_context_errno(m_context)));
186 keepGoing = false;
187 ok = false;
188 }
189
190 if (keepGoing) {
191 pa_threaded_mainloop_wait(m_mainLoop);
192 }
193 }
194
195 if (ok) {
196 pa_context_set_state_callback(m_context, contextStateCallback, this);
197 pa_context_set_subscribe_callback(m_context, subscribeCallback, this);
198 pa_context_subscribe(m_context, PA_SUBSCRIPTION_MASK_CARD, NULL, this);
199 } else {
200 if (m_context) {
201 pa_context_unref(m_context);
202 m_context = 0;
203 }
204 }
205
206 pa_threaded_mainloop_unlock(m_mainLoop);
207 return true;
208}
209
210
211void QPulseAudioEngineWorker::releasePulseContext()
212{
213 if (m_context) {
214 pa_threaded_mainloop_lock(m_mainLoop);
215 pa_context_disconnect(m_context);
216 pa_context_unref(m_context);
217 pa_threaded_mainloop_unlock(m_mainLoop);
218 m_context = 0;
219 }
220
221}
222
223QPulseAudioEngineWorker::~QPulseAudioEngineWorker()
224{
225 releasePulseContext();
226
227 if (m_mainLoop) {
228 pa_threaded_mainloop_stop(m_mainLoop);
229 pa_threaded_mainloop_free(m_mainLoop);
230 m_mainLoop = 0;
231 }
232}
233
234void QPulseAudioEngineWorker::cardInfoCallback(const pa_card_info *info)
235{
236 pa_card_profile_info2 *voice_call = NULL, *highest = NULL;
237 pa_card_profile_info2 *hsp = NULL, *a2dp = NULL;
238
239 /* For now we only support one card with the voicecall feature */
240 for (int i = 0; i < info->n_profiles; i++) {
241 if (!highest || info->profiles2[i]->priority > highest->priority)
242 highest = info->profiles2[i];
243 if (!strcmp(info->profiles2[i]->name, "voicecall"))
244 voice_call = info->profiles2[i];
245 else if (!strcmp(info->profiles2[i]->name, PULSEAUDIO_PROFILE_HSP) &&
246 info->profiles2[i]->available != 0)
247 hsp = info->profiles2[i];
248 else if (!strcmp(info->profiles2[i]->name, PULSEAUDIO_PROFILE_A2DP) &&
249 info->profiles2[i]->available != 0)
250 a2dp = info->profiles2[i];
251 }
252
253 /* Record the card that supports voicecall (default one to be used) */
254 if (voice_call) {
255 qDebug("Found card that supports voicecall: '%s'", info->name);
256 m_voicecallcard = info->name;
257 m_voicecallhighest = highest->name;
258 qDebug("1");
259 m_voicecallprofile = voice_call->name;
260 qDebug("2");
261 }
262
263 /* Handle the use cases needed for bluetooth */
264 if (hsp && a2dp) {
265 qDebug("Found card that supports hsp and a2dp: '%s'", info->name);
266 m_bt_hsp_a2dp = info->name;
267 } else if (hsp && (a2dp == NULL)) {
268 /* This card only provides the hsp profile */
269 qDebug("Found card that supports only hsp: '%s'", info->name);
270 m_bt_hsp = info->name;
271 }
272 qDebug("3");
273}
274
275void QPulseAudioEngineWorker::sinkInfoCallback(const pa_sink_info *info)
276{
277 pa_sink_port_info *earpiece = NULL, *speaker = NULL;
278 pa_sink_port_info *wired_headset = NULL, *wired_headphone = NULL;
279 pa_sink_port_info *preferred = NULL;
280 pa_sink_port_info *bluetooth_sco = NULL;
281 pa_sink_port_info *speaker_and_wired_headphone = NULL;
282 AudioMode audiomodetoset;
283 AudioModes modes;
284
285 for (int i = 0; i < info->n_ports; i++) {
286 if (!strcmp(info->ports[i]->name, "output-earpiece"))
287 earpiece = info->ports[i];
288 else if (!strcmp(info->ports[i]->name, "output-wired_headset") &&
289 (info->ports[i]->available != PA_PORT_AVAILABLE_NO))
290 wired_headset = info->ports[i];
291 else if (!strcmp(info->ports[i]->name, "output-wired_headphone") &&
292 (info->ports[i]->available != PA_PORT_AVAILABLE_NO))
293 wired_headphone = info->ports[i];
294 else if (!strcmp(info->ports[i]->name, "output-speaker"))
295 speaker = info->ports[i];
296 else if (!strcmp(info->ports[i]->name, "output-bluetooth_sco"))
297 bluetooth_sco = info->ports[i];
298 else if (!strcmp(info->ports[i]->name, "output-speaker+wired_headphone"))
299 speaker_and_wired_headphone = info->ports[i];
300 }
301
302 if (!earpiece || !speaker)
303 return; /* Not the right sink */
304
305 /* Refresh list of available audio modes */
306 modes.append(AudioModeEarpiece);
307 modes.append(AudioModeSpeaker);
308 if (wired_headset || wired_headphone)
309 modes.append(AudioModeWiredHeadset);
310 if (bluetooth_sco && ((m_bt_hsp != "") || (m_bt_hsp_a2dp != "")))
311 modes.append(AudioModeBluetooth);
312
313 /* Check if the requested mode is available (earpiece*/
314 if (((m_audiomode == AudioModeWiredHeadset) && !modes.contains(AudioModeWiredHeadset)) ||
315 ((m_audiomode == AudioModeBluetooth) && !modes.contains(AudioModeBluetooth)))
316 return;
317
318 /* Now to decide which output to be used, depending on the active mode */
319 if (m_audiomode & AudioModeEarpiece) {
320 preferred = earpiece;
321 audiomodetoset = AudioModeEarpiece;
322 }
323 if (m_audiomode & AudioModeSpeaker) {
324 preferred = speaker;
325 audiomodetoset = AudioModeSpeaker;
326 }
327 if ((m_audiomode & AudioModeWiredHeadset) && (modes.contains(AudioModeWiredHeadset))) {
328 preferred = wired_headset ? wired_headset : wired_headphone;
329 audiomodetoset = AudioModeWiredHeadset;
330 }
331 if (m_callstatus == CallRinging && speaker_and_wired_headphone) {
332 preferred = speaker_and_wired_headphone;
333 }
334 if ((m_audiomode & AudioModeBluetooth) && (modes.contains(AudioModeBluetooth))) {
335 preferred = bluetooth_sco;
336 audiomodetoset = AudioModeBluetooth;
337 }
338
339 m_audiomode = audiomodetoset;
340
341 m_nametoset = info->name;
342 if (info->active_port != preferred)
343 m_valuetoset = preferred->name;
344
345 if (modes != m_availableAudioModes)
346 m_availableAudioModes = modes;
347}
348
349void QPulseAudioEngineWorker::sourceInfoCallback(const pa_source_info *info)
350{
351 pa_source_port_info *builtin_mic = NULL, *preferred = NULL;
352 pa_source_port_info *wired_headset = NULL, *bluetooth_sco = NULL;
353
354 if (info->monitor_of_sink != PA_INVALID_INDEX)
355 return; /* Not the right source */
356
357 for (int i = 0; i < info->n_ports; i++) {
358 if (!strcmp(info->ports[i]->name, "input-builtin_mic"))
359 builtin_mic = info->ports[i];
360 else if (!strcmp(info->ports[i]->name, "input-wired_headset") &&
361 (info->ports[i]->available != PA_PORT_AVAILABLE_NO))
362 wired_headset = info->ports[i];
363 else if (!strcmp(info->ports[i]->name, "input-bluetooth_sco_headset"))
364 bluetooth_sco = info->ports[i];
365 }
366
367 if (!builtin_mic)
368 return; /* Not the right source */
369
370 /* Now to decide which output to be used, depending on the active mode */
371 if ((m_audiomode & AudioModeEarpiece) || (m_audiomode & AudioModeSpeaker))
372 preferred = builtin_mic;
373 if ((m_audiomode & AudioModeWiredHeadset) && (m_availableAudioModes.contains(AudioModeWiredHeadset)))
374 preferred = wired_headset ? wired_headset : builtin_mic;
375 if ((m_audiomode & AudioModeBluetooth) && (m_availableAudioModes.contains(AudioModeBluetooth)))
376 preferred = bluetooth_sco;
377
378 m_nametoset = info->name;
379 if (info->active_port != preferred)
380 m_valuetoset = preferred->name;
381}
382
383void QPulseAudioEngineWorker::serverInfoCallback(const pa_server_info *info)
384{
385 /* Saving default sink/source to restore after call hangup */
386 m_defaultsink = info->default_sink_name;
387 m_defaultsource = info->default_source_name;
388
389 /* In the case of a server callback we need to signal the mainloop */
390 pa_threaded_mainloop_signal(mainloop(), 0);
391}
392
393static void cardinfo_cb(pa_context *context, const pa_card_info *info, int isLast, void *userdata)
394{
395 QPulseAudioEngineWorker *pulseEngine = static_cast<QPulseAudioEngineWorker*>(userdata);
396 if (isLast != 0 || !pulseEngine || !info) {
397 pa_threaded_mainloop_signal(pulseEngine->mainloop(), 0);
398 return;
399 }
400 pulseEngine->cardInfoCallback(info);
401}
402
403static void sinkinfo_cb(pa_context *context, const pa_sink_info *info, int isLast, void *userdata)
404{
405 QPulseAudioEngineWorker *pulseEngine = static_cast<QPulseAudioEngineWorker*>(userdata);
406 if (isLast != 0 || !pulseEngine || !info) {
407 pa_threaded_mainloop_signal(pulseEngine->mainloop(), 0);
408 return;
409 }
410 pulseEngine->sinkInfoCallback(info);
411}
412
413static void sourceinfo_cb(pa_context *context, const pa_source_info *info, int isLast, void *userdata)
414{
415 QPulseAudioEngineWorker *pulseEngine = static_cast<QPulseAudioEngineWorker*>(userdata);
416 if (isLast != 0 || !pulseEngine || !info) {
417 pa_threaded_mainloop_signal(pulseEngine->mainloop(), 0);
418 return;
419 }
420 pulseEngine->sourceInfoCallback(info);
421}
422
423static void serverinfo_cb(pa_context *context, const pa_server_info *info, void *userdata)
424{
425 QPulseAudioEngineWorker *pulseEngine = static_cast<QPulseAudioEngineWorker*>(userdata);
426 if (!pulseEngine || !info) {
427 pa_threaded_mainloop_signal(pulseEngine->mainloop(), 0);
428 return;
429 }
430 pulseEngine->serverInfoCallback(info);
431}
432
433bool QPulseAudioEngineWorker::handleOperation(pa_operation *operation, const char *func_name)
434{
435 if (!operation) {
436 qCritical("'%s' failed (lost PulseAudio connection?)", func_name);
437 /* Free resources so it can retry a new connection during next operation */
438 pa_threaded_mainloop_unlock(m_mainLoop);
439 releasePulseContext();
440 return false;
441 }
442
443 while (pa_operation_get_state(operation) == PA_OPERATION_RUNNING)
444 pa_threaded_mainloop_wait(m_mainLoop);
445 pa_operation_unref(operation);
446 return true;
447}
448
449int QPulseAudioEngineWorker::setupVoiceCall()
450{
451 pa_operation *o;
452
453 qDebug("Setting up pulseaudio for voice call");
454
455 pa_threaded_mainloop_lock(m_mainLoop);
456
457 /* Get and set the default sink/source to be restored later */
458 o = pa_context_get_server_info(m_context, serverinfo_cb, this);
459 if (!handleOperation(o, "pa_context_get_server_info"))
460 return -1;
461
462 qDebug("Recorded default sink: %s default source: %s",
463 m_defaultsink.c_str(), m_defaultsource.c_str());
464
465 /* Walk through the list of devices, find the voice call capable card and
466 * identify if we have bluetooth capable devices (hsp and a2dp) */
467 m_voicecallcard = m_voicecallhighest = m_voicecallprofile = "";
468 m_bt_hsp = m_bt_hsp_a2dp = "";
469 o = pa_context_get_card_info_list(m_context, cardinfo_cb, this);
470 if (!handleOperation(o, "pa_context_get_card_info_list"))
471 return -1;
472 /* In case we have only one bt device that provides hsp and a2dp, we need
473 * to make sure we switch the default profile for that card (to hsp) */
474 if ((m_bt_hsp_a2dp != "") && (m_bt_hsp == "")) {
475 qDebug("Setting PulseAudio card '%s' profile '%s'",
476 m_bt_hsp_a2dp.c_str(), PULSEAUDIO_PROFILE_HSP);
477 o = pa_context_set_card_profile_by_name(m_context,
478 m_bt_hsp_a2dp.c_str(), PULSEAUDIO_PROFILE_HSP, success_cb, this);
479 if (!handleOperation(o, "pa_context_set_card_profile_by_name"))
480 return -1;
481 }
482
483 pa_threaded_mainloop_unlock(m_mainLoop);
484
485 return 0;
486}
487
488void QPulseAudioEngineWorker::restoreVoiceCall()
489{
490 pa_operation *o;
491
492 qDebug("Restoring pulseaudio previous state");
493
494 /* Then restore previous settings */
495 pa_threaded_mainloop_lock(m_mainLoop);
496
497 /* See if we need to restore any HSP+AD2P device state */
498 if ((m_bt_hsp_a2dp != "") && (m_bt_hsp == "")) {
499 qDebug("Restoring PulseAudio card '%s' to profile '%s'",
500 m_bt_hsp_a2dp.c_str(), PULSEAUDIO_PROFILE_A2DP);
501 o = pa_context_set_card_profile_by_name(m_context,
502 m_bt_hsp_a2dp.c_str(), PULSEAUDIO_PROFILE_A2DP, success_cb, this);
503 if (!handleOperation(o, "pa_context_set_card_profile_by_name"))
504 return;
505 }
506
507 /* Restore default sink/source */
508 if (m_defaultsink != "") {
509 qDebug("Restoring PulseAudio default sink to '%s'", m_defaultsink.c_str());
510 o = pa_context_set_default_sink(m_context, m_defaultsink.c_str(), success_cb, this);
511 if (!handleOperation(o, "pa_context_set_default_sink"))
512 return;
513 }
514 if (m_defaultsource != "") {
515 qDebug("Restoring PulseAudio default source to '%s'", m_defaultsource.c_str());
516 o = pa_context_set_default_source(m_context, m_defaultsource.c_str(), success_cb, this);
517 if (!handleOperation(o, "pa_context_set_default_source"))
518 return;
519 }
520
521 pa_threaded_mainloop_unlock(m_mainLoop);
522}
523
524void QPulseAudioEngineWorker::setCallMode(CallStatus callstatus, AudioMode audiomode)
525{
526 if (!createPulseContext()) {
527 return;
528 }
529 CallStatus p_callstatus = m_callstatus;
530 AudioMode p_audiomode = m_audiomode;
531 AudioModes p_availableAudioModes = m_availableAudioModes;
532 pa_operation *o;
533
534 /* Check if we need to save the current pulseaudio state (e.g. when starting a call) */
535 if ((callstatus != CallEnded) && (p_callstatus == CallEnded)) {
536 if (setupVoiceCall() < 0) {
537 qCritical("Failed to setup PulseAudio for Voice Call");
538 return;
539 }
540 }
541
542 /* If we have an active call, update internal state (used later when updating sink/source ports) */
543 m_callstatus = callstatus;
544 m_audiomode = audiomode;
545
546 pa_threaded_mainloop_lock(m_mainLoop);
547
548 /* Switch the virtual card mode when call is active and not active
549 * This needs to be done before sink/source gets updated, because after changing mode
550 * it will automatically move to input/output-parking */
551 if ((m_callstatus == CallActive) && (p_callstatus != CallActive) &&
552 (m_voicecallcard != "") && (m_voicecallprofile != "")) {
553 qDebug("Setting PulseAudio card '%s' profile '%s'",
554 m_voicecallcard.c_str(), m_voicecallprofile.c_str());
555 o = pa_context_set_card_profile_by_name(m_context,
556 m_voicecallcard.c_str(), m_voicecallprofile.c_str(), success_cb, this);
557 if (!handleOperation(o, "pa_context_set_card_profile_by_name"))
558 return;
559 } else if ((m_callstatus == CallEnded) && (m_voicecallcard != "") && (m_voicecallhighest != "")) {
560 /* If using droid, make sure to restore to the profile that has the highest score */
561 qDebug("Restoring PulseAudio card '%s' to profile '%s'",
562 m_voicecallcard.c_str(), m_voicecallhighest.c_str());
563 o = pa_context_set_card_profile_by_name(m_context,
564 m_voicecallcard.c_str(), m_voicecallhighest.c_str(), success_cb, this);
565 if (!handleOperation(o, "pa_context_set_card_profile_by_name"))
566 return;
567 }
568
569 /* Find highest compatible sink/source elements from the voicecall
570 compatible card (on touch this means the pulse droid element) */
571 m_nametoset = m_valuetoset = "";
572 o = pa_context_get_sink_info_list(m_context, sinkinfo_cb, this);
573 if (!handleOperation(o, "pa_context_get_sink_info_list"))
574 return;
575 if ((m_nametoset != "") && (m_nametoset != m_defaultsink)) {
576 qDebug("Setting PulseAudio default sink to '%s'", m_nametoset.c_str());
577 o = pa_context_set_default_sink(m_context, m_nametoset.c_str(), success_cb, this);
578 if (!handleOperation(o, "pa_context_set_default_sink"))
579 return;
580 }
581 if (m_valuetoset != "") {
582 qDebug("Setting PulseAudio sink '%s' port '%s'",
583 m_nametoset.c_str(), m_valuetoset.c_str());
584 o = pa_context_set_sink_port_by_name(m_context, m_nametoset.c_str(),
585 m_valuetoset.c_str(), success_cb, this);
586 if (!handleOperation(o, "pa_context_set_sink_port_by_name"))
587 return;
588 }
589
590 /* Same for source */
591 m_nametoset = m_valuetoset = "";
592 o = pa_context_get_source_info_list(m_context, sourceinfo_cb, this);
593 if (!handleOperation(o, "pa_context_get_source_info_list"))
594 return;
595 if ((m_nametoset != "") && (m_nametoset != m_defaultsource)) {
596 qDebug("Setting PulseAudio default source to '%s'", m_nametoset.c_str());
597 o = pa_context_set_default_source(m_context, m_nametoset.c_str(), success_cb, this);
598 if (!handleOperation(o, "pa_context_set_default_source"))
599 return;
600 }
601 if (m_valuetoset != "") {
602 qDebug("Setting PulseAudio source '%s' port '%s'",
603 m_nametoset.c_str(), m_valuetoset.c_str());
604 o = pa_context_set_source_port_by_name(m_context, m_nametoset.c_str(),
605 m_valuetoset.c_str(), success_cb, this);
606 if (!handleOperation(o, "pa_context_set_source_port_by_name"))
607 return;
608 }
609
610 pa_threaded_mainloop_unlock(m_mainLoop);
611
612 /* Notify if the list of audio modes changed */
613 if (p_availableAudioModes != m_availableAudioModes)
614 Q_EMIT availableAudioModesChanged(m_availableAudioModes);
615
616 /* Notify if call mode changed */
617 if (p_audiomode != m_audiomode) {
618 Q_EMIT audioModeChanged(m_audiomode);
619 }
620
621 /* If no more active voicecall, restore previous saved pulseaudio state */
622 if (callstatus == CallEnded) {
623 restoreVoiceCall();
624 }
625
626 /* In case the app had set mute when the call wasn't active, make sure we reflect it here */
627 if (m_callstatus != CallEnded)
628 setMicMute(m_micmute);
629}
630
631void QPulseAudioEngineWorker::setMicMute(bool muted)
632{
633 if (!createPulseContext()) {
634 return;
635 }
636
637 m_micmute = muted;
638
639 if (m_callstatus == CallEnded)
640 return;
641
642 pa_threaded_mainloop_lock(m_mainLoop);
643
644 m_nametoset = "";
645 pa_operation *o = pa_context_get_source_info_list(m_context, sourceinfo_cb, this);
646 if (!handleOperation(o, "pa_context_get_source_info_list"))
647 return;
648
649 if (m_nametoset != "") {
650 int m = m_micmute ? 1 : 0;
651 qDebug("Setting PulseAudio source '%s' muted '%d'", m_nametoset.c_str(), m);
652 o = pa_context_set_source_mute_by_name(m_context,
653 m_nametoset.c_str(), m, success_cb, this);
654 if (!handleOperation(o, "pa_context_set_source_mute_by_name"))
655 return;
656 }
657
658 pa_threaded_mainloop_unlock(m_mainLoop);
659}
660
661void QPulseAudioEngineWorker::plugCardCallback(const pa_card_info *info)
662{
663 qDebug("Notified about card (%s) add event from PulseAudio", info->name);
664
665 /* Check if it's indeed a BT device (with at least one hsp profile) */
666 pa_card_profile_info2 *hsp = NULL, *a2dp = NULL;
667 for (int i = 0; i < info->n_profiles; i++) {
668 if (!strcmp(info->profiles2[i]->name, PULSEAUDIO_PROFILE_HSP))
669 hsp = info->profiles2[i];
670 else if (!strcmp(info->profiles2[i]->name, PULSEAUDIO_PROFILE_A2DP) &&
671 info->profiles2[i]->available != 0) {
672 qDebug("Found a2dp");
673 a2dp = info->profiles2[i];
674 }
675 qDebug("%s", info->profiles2[i]->name);
676 }
677
678 if ((!info->active_profile || !strcmp(info->active_profile->name, "off")) && a2dp) {
679 qDebug("No profile set");
680 m_default_bt_card_fallback = info->name;
681 }
682
683 /* We only care about BT (HSP) devices, and if one is not already available */
684 if ((m_callstatus != CallEnded) && ((m_bt_hsp == "") || (m_bt_hsp_a2dp == ""))) {
685 if (hsp)
686 m_handleevent = true;
687 }
688}
689
690void QPulseAudioEngineWorker::updateCardCallback(const pa_card_info *info)
691{
692 qDebug("Notified about card (%s) changes event from PulseAudio", info->name);
693
694 /* Check if it's indeed a BT device (with at least one hsp profile) */
695 pa_card_profile_info2 *hsp = NULL, *a2dp = NULL;
696 for (int i = 0; i < info->n_profiles; i++) {
697 if (!strcmp(info->profiles2[i]->name, PULSEAUDIO_PROFILE_HSP))
698 hsp = info->profiles2[i];
699 else if (!strcmp(info->profiles2[i]->name, PULSEAUDIO_PROFILE_A2DP) &&
700 info->profiles2[i]->available != 0) {
701 qDebug("Found a2dp");
702 a2dp = info->profiles2[i];
703 }
704 qDebug("%s", info->profiles2[i]->name);
705 }
706
707 if ((!info->active_profile || !strcmp(info->active_profile->name, "off")) && a2dp) {
708 qDebug("No profile set");
709 m_default_bt_card_fallback = info->name;
710 }
711
712
713 /* We only care if the card event for the voicecall capable card */
714 if ((m_callstatus == CallActive) && (!strcmp(info->name, m_voicecallcard.c_str()))) {
715 if (m_audiomode == AudioModeWiredHeadset) {
716 /* If previous mode is wired, it means it got unplugged */
717 m_handleevent = true;
718 m_audiomodetoset = AudioModeBtOrWiredOrEarpiece;
719 } else if ((m_audiomode == AudioModeEarpiece) || ((m_audiomode == AudioModeSpeaker))) {
720 /* Now only trigger the event in case wired headset/headphone is now available */
721 pa_card_port_info *port_info = NULL;
722 for (int i = 0; i < info->n_ports; i++) {
723 if (info->ports[i] && (info->ports[i]->available == PA_PORT_AVAILABLE_YES) && (
724 !strcmp(info->ports[i]->name, "output-wired_headset") ||
725 !strcmp(info->ports[i]->name, "output-wired_headphone"))) {
726 m_handleevent = true;
727 m_audiomodetoset = AudioModeWiredOrEarpiece;
728 }
729 }
730 } else if (m_audiomode == AudioModeBluetooth) {
731 /* Handle the event so we can update the audiomodes */
732 m_handleevent = true;
733 m_audiomodetoset = AudioModeBluetooth;
734 }
735 }
736}
737
738void QPulseAudioEngineWorker::unplugCardCallback()
739{
740 if (m_callstatus != CallEnded) {
741 m_handleevent = true;
742 }
743}
744
745void QPulseAudioEngineWorker::handleCardEvent(const int evt, const unsigned int idx)
746{
747 pa_operation *o = NULL;
748
749 /* Internal state var used to know if we need to update our internal state */
750 m_handleevent = false;
751
752 if (evt == PA_SUBSCRIPTION_EVENT_NEW) {
753 o = pa_context_get_card_info_by_index(m_context, idx, plug_card_cb, this);
754 if (!handleOperation(o, "pa_context_get_card_info_by_index"))
755 return;
756
757 if (m_default_bt_card_fallback != "") {
758 o = pa_context_set_card_profile_by_name(m_context,
759 m_default_bt_card_fallback.c_str(), PULSEAUDIO_PROFILE_A2DP, success_cb, this);
760 if (!handleOperation(o, "pa_context_set_card_profile_by_name"))
761 return;
762 m_default_bt_card_fallback = "";
763 }
764
765 if (m_handleevent) {
766 qDebug("Adding new BT-HSP capable device");
767 /* In case A2DP is available, switch to HSP */
768 if (setupVoiceCall() < 0)
769 return;
770 /* Enable the HSP output port */
771 setCallMode(m_callstatus, AudioModeBluetooth);
772 }
773 } else if (evt == PA_SUBSCRIPTION_EVENT_CHANGE) {
774 o = pa_context_get_card_info_by_index(m_context, idx, update_card_cb, this);
775 if (!handleOperation(o, "pa_context_get_card_info_by_index"))
776 return;
777
778 if (m_default_bt_card_fallback != "") {
779 o = pa_context_set_card_profile_by_name(m_context,
780 m_default_bt_card_fallback.c_str(), PULSEAUDIO_PROFILE_A2DP, success_cb, this);
781 if (!handleOperation(o, "pa_context_set_card_profile_by_name"))
782 return;
783 m_default_bt_card_fallback = "";
784 }
785
786 if (m_handleevent) {
787 /* In this case it means the handset state changed */
788 qDebug("Notifying card changes for the voicecall capable card");
789 setCallMode(m_callstatus, m_audiomodetoset);
790 }
791 } else if (evt == PA_SUBSCRIPTION_EVENT_REMOVE) {
792 /* Check if the main HSP card was removed */
793 if (m_bt_hsp != "") {
794 o = pa_context_get_card_info_by_name(m_context, m_bt_hsp.c_str(), unplug_card_cb, this);
795 if (!handleOperation(o, "pa_context_get_sink_info_by_name"))
796 return;
797 }
798 if (m_bt_hsp_a2dp != "") {
799 o = pa_context_get_card_info_by_name(m_context, m_bt_hsp_a2dp.c_str(), unplug_card_cb, this);
800 if (!handleOperation(o, "pa_context_get_sink_info_by_name"))
801 return;
802 }
803 if (m_handleevent) {
804 qDebug("Notifying about BT-HSP card removal");
805 /* Needed in order to save the default sink/source */
806 if (setupVoiceCall() < 0)
807 return;
808 /* Enable the default handset output port */
809 setCallMode(m_callstatus, AudioModeWiredOrEarpiece);
810 }
811 }
812}
813
814Q_GLOBAL_STATIC(QPulseAudioEngine, pulseEngine);
815
816QPulseAudioEngine::QPulseAudioEngine(QObject *parent) :
817 QObject(parent)
818{
819 qRegisterMetaType<CallStatus>();
820 qRegisterMetaType<AudioMode>();
821 qRegisterMetaType<AudioModes>();
822 mWorker = new QPulseAudioEngineWorker();
823 QObject::connect(mWorker, SIGNAL(audioModeChanged(const AudioMode)), this, SIGNAL(audioModeChanged(const AudioMode)), Qt::QueuedConnection);
824 QObject::connect(mWorker, SIGNAL(availableAudioModesChanged(const AudioModes)), this, SIGNAL(availableAudioModesChanged(const AudioModes)), Qt::QueuedConnection);
825 mWorker->createPulseContext();
826 mWorker->moveToThread(&mThread);
827 mThread.start();
828}
829
830QPulseAudioEngine::~QPulseAudioEngine()
831{
832 mThread.quit();
833 mThread.wait();
834}
835
836QPulseAudioEngine *QPulseAudioEngine::instance()
837{
838 QPulseAudioEngine *engine = pulseEngine();
839 return engine;
840}
841
842void QPulseAudioEngine::setCallMode(CallStatus callstatus, AudioMode audiomode)
843{
844 QMetaObject::invokeMethod(mWorker, "setCallMode", Qt::QueuedConnection, Q_ARG(CallStatus, callstatus), Q_ARG(AudioMode, audiomode));
845}
846
847void QPulseAudioEngine::setMicMute(bool muted)
848{
849 QMetaObject::invokeMethod(mWorker, "setMicMute", Qt::QueuedConnection, Q_ARG(bool, muted));
850}
851
852QT_END_NAMESPACE
853
0854
=== removed file 'handler/qpulseaudioengine.cpp'
--- handler/qpulseaudioengine.cpp 2017-02-06 13:26:33 +0000
+++ handler/qpulseaudioengine.cpp 1970-01-01 00:00:00 +0000
@@ -1,853 +0,0 @@
1/****************************************************************************
2**
3** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
4** Contact: http://www.qt-project.org/legal
5**
6** This file was taken from qt5 and modified by
7** David Henningsson <david.henningsson@canonical.com> for usage in
8** telepathy-ofono.
9**
10** GNU Lesser General Public License Usage
11** Alternatively, this file may be used under the terms of the GNU Lesser
12** General Public License version 2.1 as published by the Free Software
13** Foundation and appearing in the file LICENSE.LGPL included in the
14** packaging of this file. Please review the following information to
15** ensure the GNU Lesser General Public License version 2.1 requirements
16** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
17**
18****************************************************************************/
19
20#include <QtCore/qdebug.h>
21
22#include "qpulseaudioengine.h"
23#include <sys/types.h>
24#include <unistd.h>
25
26#define PULSEAUDIO_PROFILE_HSP "headset_head_unit"
27#define PULSEAUDIO_PROFILE_A2DP "a2dp_sink"
28
29QT_BEGIN_NAMESPACE
30
31static void contextStateCallbackInit(pa_context *context, void *userdata)
32{
33 Q_UNUSED(context);
34 QPulseAudioEngineWorker *pulseEngine = reinterpret_cast<QPulseAudioEngineWorker*>(userdata);
35 pa_threaded_mainloop_signal(pulseEngine->mainloop(), 0);
36}
37
38static void contextStateCallback(pa_context *context, void *userdata)
39{
40 Q_UNUSED(userdata);
41 Q_UNUSED(context);
42}
43
44static void success_cb(pa_context *context, int success, void *userdata)
45{
46 QPulseAudioEngineWorker *pulseEngine = reinterpret_cast<QPulseAudioEngineWorker*>(userdata);
47 pa_threaded_mainloop_signal(pulseEngine->mainloop(), 0);
48}
49
50/* Callbacks used when handling events from PulseAudio */
51static void plug_card_cb(pa_context *c, const pa_card_info *info, int isLast, void *userdata)
52{
53 QPulseAudioEngineWorker *pulseEngine = static_cast<QPulseAudioEngineWorker*>(userdata);
54 if (isLast != 0 || !pulseEngine || !info) {
55 pa_threaded_mainloop_signal(pulseEngine->mainloop(), 0);
56 return;
57 }
58 pulseEngine->plugCardCallback(info);
59}
60
61static void update_card_cb(pa_context *c, const pa_card_info *info, int isLast, void *userdata)
62{
63 QPulseAudioEngineWorker *pulseEngine = static_cast<QPulseAudioEngineWorker*>(userdata);
64 if (isLast != 0 || !pulseEngine || !info) {
65 pa_threaded_mainloop_signal(pulseEngine->mainloop(), 0);
66 return;
67 }
68 pulseEngine->updateCardCallback(info);
69}
70
71static void unplug_card_cb(pa_context *c, const pa_card_info *info, int isLast, void *userdata)
72{
73 QPulseAudioEngineWorker *pulseEngine = static_cast<QPulseAudioEngineWorker*>(userdata);
74 if (!pulseEngine) {
75 pa_threaded_mainloop_signal(pulseEngine->mainloop(), 0);
76 return;
77 }
78
79 if (info == NULL) {
80 /* That means that the card used to query card_info was removed */
81 pulseEngine->unplugCardCallback();
82 }
83}
84
85static void subscribeCallback(pa_context *context, pa_subscription_event_type_t t, uint32_t idx, void *userdata)
86{
87 /* For card change events (slot plug/unplug and add/remove card) */
88 if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_CARD) {
89 if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_CHANGE) {
90 QMetaObject::invokeMethod((QPulseAudioEngineWorker *) userdata, "handleCardEvent",
91 Qt::QueuedConnection, Q_ARG(int, PA_SUBSCRIPTION_EVENT_CHANGE), Q_ARG(unsigned int, idx));
92 } else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) {
93 QMetaObject::invokeMethod((QPulseAudioEngineWorker *) userdata, "handleCardEvent",
94 Qt::QueuedConnection, Q_ARG(int, PA_SUBSCRIPTION_EVENT_NEW), Q_ARG(unsigned int, idx));
95 } else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
96 QMetaObject::invokeMethod((QPulseAudioEngineWorker *) userdata, "handleCardEvent",
97 Qt::QueuedConnection, Q_ARG(int, PA_SUBSCRIPTION_EVENT_REMOVE), Q_ARG(unsigned int, idx));
98 }
99 }
100}
101
102QPulseAudioEngineWorker::QPulseAudioEngineWorker(QObject *parent)
103 : QObject(parent)
104 , m_mainLoopApi(0)
105 , m_context(0)
106 , m_callstatus(CallEnded)
107 , m_audiomode(AudioModeSpeaker)
108 , m_micmute(false)
109 , m_defaultsink("sink.primary")
110 , m_defaultsource("source.primary")
111 , m_voicecallcard("")
112 , m_voicecallhighest("")
113 , m_voicecallprofile("")
114 , m_bt_hsp("")
115 , m_bt_hsp_a2dp("")
116 , m_default_bt_card_fallback("")
117
118{
119 m_mainLoop = pa_threaded_mainloop_new();
120 if (m_mainLoop == 0) {
121 qWarning("Unable to create pulseaudio mainloop");
122 return;
123 }
124
125 if (pa_threaded_mainloop_start(m_mainLoop) != 0) {
126 qWarning("Unable to start pulseaudio mainloop");
127 pa_threaded_mainloop_free(m_mainLoop);
128 m_mainLoop = 0;
129 return;
130 }
131
132 createPulseContext();
133}
134
135bool QPulseAudioEngineWorker::createPulseContext()
136{
137 bool keepGoing = true;
138 bool ok = true;
139
140 if (m_context)
141 return true;
142
143 m_mainLoopApi = pa_threaded_mainloop_get_api(m_mainLoop);
144
145 pa_threaded_mainloop_lock(m_mainLoop);
146
147 m_context = pa_context_new(m_mainLoopApi, QString(QLatin1String("QtmPulseContext:%1")).arg(::getpid()).toLatin1().constData());
148 pa_context_set_state_callback(m_context, contextStateCallbackInit, this);
149
150 if (!m_context) {
151 qWarning("Unable to create new pulseaudio context");
152 pa_threaded_mainloop_unlock(m_mainLoop);
153 return false;
154 }
155
156 if (pa_context_connect(m_context, NULL, PA_CONTEXT_NOAUTOSPAWN, NULL) < 0) {
157 qWarning("Unable to create a connection to the pulseaudio context");
158 pa_threaded_mainloop_unlock(m_mainLoop);
159 releasePulseContext();
160 return false;
161 }
162
163 pa_threaded_mainloop_wait(m_mainLoop);
164
165 while (keepGoing) {
166 switch (pa_context_get_state(m_context)) {
167 case PA_CONTEXT_CONNECTING:
168 case PA_CONTEXT_AUTHORIZING:
169 case PA_CONTEXT_SETTING_NAME:
170 break;
171
172 case PA_CONTEXT_READY:
173 qDebug("Pulseaudio connection established.");
174 keepGoing = false;
175 break;
176
177 case PA_CONTEXT_TERMINATED:
178 qCritical("Pulseaudio context terminated.");
179 keepGoing = false;
180 ok = false;
181 break;
182
183 case PA_CONTEXT_FAILED:
184 default:
185 qCritical() << QString("Pulseaudio connection failure: %1").arg(pa_strerror(pa_context_errno(m_context)));
186 keepGoing = false;
187 ok = false;
188 }
189
190 if (keepGoing) {
191 pa_threaded_mainloop_wait(m_mainLoop);
192 }
193 }
194
195 if (ok) {
196 pa_context_set_state_callback(m_context, contextStateCallback, this);
197 pa_context_set_subscribe_callback(m_context, subscribeCallback, this);
198 pa_context_subscribe(m_context, PA_SUBSCRIPTION_MASK_CARD, NULL, this);
199 } else {
200 if (m_context) {
201 pa_context_unref(m_context);
202 m_context = 0;
203 }
204 }
205
206 pa_threaded_mainloop_unlock(m_mainLoop);
207 return true;
208}
209
210
211void QPulseAudioEngineWorker::releasePulseContext()
212{
213 if (m_context) {
214 pa_threaded_mainloop_lock(m_mainLoop);
215 pa_context_disconnect(m_context);
216 pa_context_unref(m_context);
217 pa_threaded_mainloop_unlock(m_mainLoop);
218 m_context = 0;
219 }
220
221}
222
223QPulseAudioEngineWorker::~QPulseAudioEngineWorker()
224{
225 releasePulseContext();
226
227 if (m_mainLoop) {
228 pa_threaded_mainloop_stop(m_mainLoop);
229 pa_threaded_mainloop_free(m_mainLoop);
230 m_mainLoop = 0;
231 }
232}
233
234void QPulseAudioEngineWorker::cardInfoCallback(const pa_card_info *info)
235{
236 pa_card_profile_info2 *voice_call = NULL, *highest = NULL;
237 pa_card_profile_info2 *hsp = NULL, *a2dp = NULL;
238
239 /* For now we only support one card with the voicecall feature */
240 for (int i = 0; i < info->n_profiles; i++) {
241 if (!highest || info->profiles2[i]->priority > highest->priority)
242 highest = info->profiles2[i];
243 if (!strcmp(info->profiles2[i]->name, "voicecall"))
244 voice_call = info->profiles2[i];
245 else if (!strcmp(info->profiles2[i]->name, PULSEAUDIO_PROFILE_HSP) &&
246 info->profiles2[i]->available != 0)
247 hsp = info->profiles2[i];
248 else if (!strcmp(info->profiles2[i]->name, PULSEAUDIO_PROFILE_A2DP) &&
249 info->profiles2[i]->available != 0)
250 a2dp = info->profiles2[i];
251 }
252
253 /* Record the card that supports voicecall (default one to be used) */
254 if (voice_call) {
255 qDebug("Found card that supports voicecall: '%s'", info->name);
256 m_voicecallcard = info->name;
257 m_voicecallhighest = highest->name;
258 qDebug("1");
259 m_voicecallprofile = voice_call->name;
260 qDebug("2");
261 }
262
263 /* Handle the use cases needed for bluetooth */
264 if (hsp && a2dp) {
265 qDebug("Found card that supports hsp and a2dp: '%s'", info->name);
266 m_bt_hsp_a2dp = info->name;
267 } else if (hsp && (a2dp == NULL)) {
268 /* This card only provides the hsp profile */
269 qDebug("Found card that supports only hsp: '%s'", info->name);
270 m_bt_hsp = info->name;
271 }
272 qDebug("3");
273}
274
275void QPulseAudioEngineWorker::sinkInfoCallback(const pa_sink_info *info)
276{
277 pa_sink_port_info *earpiece = NULL, *speaker = NULL;
278 pa_sink_port_info *wired_headset = NULL, *wired_headphone = NULL;
279 pa_sink_port_info *preferred = NULL;
280 pa_sink_port_info *bluetooth_sco = NULL;
281 pa_sink_port_info *speaker_and_wired_headphone = NULL;
282 AudioMode audiomodetoset;
283 AudioModes modes;
284
285 for (int i = 0; i < info->n_ports; i++) {
286 if (!strcmp(info->ports[i]->name, "output-earpiece"))
287 earpiece = info->ports[i];
288 else if (!strcmp(info->ports[i]->name, "output-wired_headset") &&
289 (info->ports[i]->available != PA_PORT_AVAILABLE_NO))
290 wired_headset = info->ports[i];
291 else if (!strcmp(info->ports[i]->name, "output-wired_headphone") &&
292 (info->ports[i]->available != PA_PORT_AVAILABLE_NO))
293 wired_headphone = info->ports[i];
294 else if (!strcmp(info->ports[i]->name, "output-speaker"))
295 speaker = info->ports[i];
296 else if (!strcmp(info->ports[i]->name, "output-bluetooth_sco"))
297 bluetooth_sco = info->ports[i];
298 else if (!strcmp(info->ports[i]->name, "output-speaker+wired_headphone"))
299 speaker_and_wired_headphone = info->ports[i];
300 }
301
302 if (!earpiece || !speaker)
303 return; /* Not the right sink */
304
305 /* Refresh list of available audio modes */
306 modes.append(AudioModeEarpiece);
307 modes.append(AudioModeSpeaker);
308 if (wired_headset || wired_headphone)
309 modes.append(AudioModeWiredHeadset);
310 if (bluetooth_sco && ((m_bt_hsp != "") || (m_bt_hsp_a2dp != "")))
311 modes.append(AudioModeBluetooth);
312
313 /* Check if the requested mode is available (earpiece*/
314 if (((m_audiomode == AudioModeWiredHeadset) && !modes.contains(AudioModeWiredHeadset)) ||
315 ((m_audiomode == AudioModeBluetooth) && !modes.contains(AudioModeBluetooth)))
316 return;
317
318 /* Now to decide which output to be used, depending on the active mode */
319 if (m_audiomode & AudioModeEarpiece) {
320 preferred = earpiece;
321 audiomodetoset = AudioModeEarpiece;
322 }
323 if (m_audiomode & AudioModeSpeaker) {
324 preferred = speaker;
325 audiomodetoset = AudioModeSpeaker;
326 }
327 if ((m_audiomode & AudioModeWiredHeadset) && (modes.contains(AudioModeWiredHeadset))) {
328 preferred = wired_headset ? wired_headset : wired_headphone;
329 audiomodetoset = AudioModeWiredHeadset;
330 }
331 if (m_callstatus == CallRinging && speaker_and_wired_headphone) {
332 preferred = speaker_and_wired_headphone;
333 }
334 if ((m_audiomode & AudioModeBluetooth) && (modes.contains(AudioModeBluetooth))) {
335 preferred = bluetooth_sco;
336 audiomodetoset = AudioModeBluetooth;
337 }
338
339 m_audiomode = audiomodetoset;
340
341 m_nametoset = info->name;
342 if (info->active_port != preferred)
343 m_valuetoset = preferred->name;
344
345 if (modes != m_availableAudioModes)
346 m_availableAudioModes = modes;
347}
348
349void QPulseAudioEngineWorker::sourceInfoCallback(const pa_source_info *info)
350{
351 pa_source_port_info *builtin_mic = NULL, *preferred = NULL;
352 pa_source_port_info *wired_headset = NULL, *bluetooth_sco = NULL;
353
354 if (info->monitor_of_sink != PA_INVALID_INDEX)
355 return; /* Not the right source */
356
357 for (int i = 0; i < info->n_ports; i++) {
358 if (!strcmp(info->ports[i]->name, "input-builtin_mic"))
359 builtin_mic = info->ports[i];
360 else if (!strcmp(info->ports[i]->name, "input-wired_headset") &&
361 (info->ports[i]->available != PA_PORT_AVAILABLE_NO))
362 wired_headset = info->ports[i];
363 else if (!strcmp(info->ports[i]->name, "input-bluetooth_sco_headset"))
364 bluetooth_sco = info->ports[i];
365 }
366
367 if (!builtin_mic)
368 return; /* Not the right source */
369
370 /* Now to decide which output to be used, depending on the active mode */
371 if ((m_audiomode & AudioModeEarpiece) || (m_audiomode & AudioModeSpeaker))
372 preferred = builtin_mic;
373 if ((m_audiomode & AudioModeWiredHeadset) && (m_availableAudioModes.contains(AudioModeWiredHeadset)))
374 preferred = wired_headset ? wired_headset : builtin_mic;
375 if ((m_audiomode & AudioModeBluetooth) && (m_availableAudioModes.contains(AudioModeBluetooth)))
376 preferred = bluetooth_sco;
377
378 m_nametoset = info->name;
379 if (info->active_port != preferred)
380 m_valuetoset = preferred->name;
381}
382
383void QPulseAudioEngineWorker::serverInfoCallback(const pa_server_info *info)
384{
385 /* Saving default sink/source to restore after call hangup */
386 m_defaultsink = info->default_sink_name;
387 m_defaultsource = info->default_source_name;
388
389 /* In the case of a server callback we need to signal the mainloop */
390 pa_threaded_mainloop_signal(mainloop(), 0);
391}
392
393static void cardinfo_cb(pa_context *context, const pa_card_info *info, int isLast, void *userdata)
394{
395 QPulseAudioEngineWorker *pulseEngine = static_cast<QPulseAudioEngineWorker*>(userdata);
396 if (isLast != 0 || !pulseEngine || !info) {
397 pa_threaded_mainloop_signal(pulseEngine->mainloop(), 0);
398 return;
399 }
400 pulseEngine->cardInfoCallback(info);
401}
402
403static void sinkinfo_cb(pa_context *context, const pa_sink_info *info, int isLast, void *userdata)
404{
405 QPulseAudioEngineWorker *pulseEngine = static_cast<QPulseAudioEngineWorker*>(userdata);
406 if (isLast != 0 || !pulseEngine || !info) {
407 pa_threaded_mainloop_signal(pulseEngine->mainloop(), 0);
408 return;
409 }
410 pulseEngine->sinkInfoCallback(info);
411}
412
413static void sourceinfo_cb(pa_context *context, const pa_source_info *info, int isLast, void *userdata)
414{
415 QPulseAudioEngineWorker *pulseEngine = static_cast<QPulseAudioEngineWorker*>(userdata);
416 if (isLast != 0 || !pulseEngine || !info) {
417 pa_threaded_mainloop_signal(pulseEngine->mainloop(), 0);
418 return;
419 }
420 pulseEngine->sourceInfoCallback(info);
421}
422
423static void serverinfo_cb(pa_context *context, const pa_server_info *info, void *userdata)
424{
425 QPulseAudioEngineWorker *pulseEngine = static_cast<QPulseAudioEngineWorker*>(userdata);
426 if (!pulseEngine || !info) {
427 pa_threaded_mainloop_signal(pulseEngine->mainloop(), 0);
428 return;
429 }
430 pulseEngine->serverInfoCallback(info);
431}
432
433bool QPulseAudioEngineWorker::handleOperation(pa_operation *operation, const char *func_name)
434{
435 if (!operation) {
436 qCritical("'%s' failed (lost PulseAudio connection?)", func_name);
437 /* Free resources so it can retry a new connection during next operation */
438 pa_threaded_mainloop_unlock(m_mainLoop);
439 releasePulseContext();
440 return false;
441 }
442
443 while (pa_operation_get_state(operation) == PA_OPERATION_RUNNING)
444 pa_threaded_mainloop_wait(m_mainLoop);
445 pa_operation_unref(operation);
446 return true;
447}
448
449int QPulseAudioEngineWorker::setupVoiceCall()
450{
451 pa_operation *o;
452
453 qDebug("Setting up pulseaudio for voice call");
454
455 pa_threaded_mainloop_lock(m_mainLoop);
456
457 /* Get and set the default sink/source to be restored later */
458 o = pa_context_get_server_info(m_context, serverinfo_cb, this);
459 if (!handleOperation(o, "pa_context_get_server_info"))
460 return -1;
461
462 qDebug("Recorded default sink: %s default source: %s",
463 m_defaultsink.c_str(), m_defaultsource.c_str());
464
465 /* Walk through the list of devices, find the voice call capable card and
466 * identify if we have bluetooth capable devices (hsp and a2dp) */
467 m_voicecallcard = m_voicecallhighest = m_voicecallprofile = "";
468 m_bt_hsp = m_bt_hsp_a2dp = "";
469 o = pa_context_get_card_info_list(m_context, cardinfo_cb, this);
470 if (!handleOperation(o, "pa_context_get_card_info_list"))
471 return -1;
472 /* In case we have only one bt device that provides hsp and a2dp, we need
473 * to make sure we switch the default profile for that card (to hsp) */
474 if ((m_bt_hsp_a2dp != "") && (m_bt_hsp == "")) {
475 qDebug("Setting PulseAudio card '%s' profile '%s'",
476 m_bt_hsp_a2dp.c_str(), PULSEAUDIO_PROFILE_HSP);
477 o = pa_context_set_card_profile_by_name(m_context,
478 m_bt_hsp_a2dp.c_str(), PULSEAUDIO_PROFILE_HSP, success_cb, this);
479 if (!handleOperation(o, "pa_context_set_card_profile_by_name"))
480 return -1;
481 }
482
483 pa_threaded_mainloop_unlock(m_mainLoop);
484
485 return 0;
486}
487
488void QPulseAudioEngineWorker::restoreVoiceCall()
489{
490 pa_operation *o;
491
492 qDebug("Restoring pulseaudio previous state");
493
494 /* Then restore previous settings */
495 pa_threaded_mainloop_lock(m_mainLoop);
496
497 /* See if we need to restore any HSP+AD2P device state */
498 if ((m_bt_hsp_a2dp != "") && (m_bt_hsp == "")) {
499 qDebug("Restoring PulseAudio card '%s' to profile '%s'",
500 m_bt_hsp_a2dp.c_str(), PULSEAUDIO_PROFILE_A2DP);
501 o = pa_context_set_card_profile_by_name(m_context,
502 m_bt_hsp_a2dp.c_str(), PULSEAUDIO_PROFILE_A2DP, success_cb, this);
503 if (!handleOperation(o, "pa_context_set_card_profile_by_name"))
504 return;
505 }
506
507 /* Restore default sink/source */
508 if (m_defaultsink != "") {
509 qDebug("Restoring PulseAudio default sink to '%s'", m_defaultsink.c_str());
510 o = pa_context_set_default_sink(m_context, m_defaultsink.c_str(), success_cb, this);
511 if (!handleOperation(o, "pa_context_set_default_sink"))
512 return;
513 }
514 if (m_defaultsource != "") {
515 qDebug("Restoring PulseAudio default source to '%s'", m_defaultsource.c_str());
516 o = pa_context_set_default_source(m_context, m_defaultsource.c_str(), success_cb, this);
517 if (!handleOperation(o, "pa_context_set_default_source"))
518 return;
519 }
520
521 pa_threaded_mainloop_unlock(m_mainLoop);
522}
523
524void QPulseAudioEngineWorker::setCallMode(CallStatus callstatus, AudioMode audiomode)
525{
526 if (!createPulseContext()) {
527 return;
528 }
529 CallStatus p_callstatus = m_callstatus;
530 AudioMode p_audiomode = m_audiomode;
531 AudioModes p_availableAudioModes = m_availableAudioModes;
532 pa_operation *o;
533
534 /* Check if we need to save the current pulseaudio state (e.g. when starting a call) */
535 if ((callstatus != CallEnded) && (p_callstatus == CallEnded)) {
536 if (setupVoiceCall() < 0) {
537 qCritical("Failed to setup PulseAudio for Voice Call");
538 return;
539 }
540 }
541
542 /* If we have an active call, update internal state (used later when updating sink/source ports) */
543 m_callstatus = callstatus;
544 m_audiomode = audiomode;
545
546 pa_threaded_mainloop_lock(m_mainLoop);
547
548 /* Switch the virtual card mode when call is active and not active
549 * This needs to be done before sink/source gets updated, because after changing mode
550 * it will automatically move to input/output-parking */
551 if ((m_callstatus == CallActive) && (p_callstatus != CallActive) &&
552 (m_voicecallcard != "") && (m_voicecallprofile != "")) {
553 qDebug("Setting PulseAudio card '%s' profile '%s'",
554 m_voicecallcard.c_str(), m_voicecallprofile.c_str());
555 o = pa_context_set_card_profile_by_name(m_context,
556 m_voicecallcard.c_str(), m_voicecallprofile.c_str(), success_cb, this);
557 if (!handleOperation(o, "pa_context_set_card_profile_by_name"))
558 return;
559 } else if ((m_callstatus == CallEnded) && (m_voicecallcard != "") && (m_voicecallhighest != "")) {
560 /* If using droid, make sure to restore to the profile that has the highest score */
561 qDebug("Restoring PulseAudio card '%s' to profile '%s'",
562 m_voicecallcard.c_str(), m_voicecallhighest.c_str());
563 o = pa_context_set_card_profile_by_name(m_context,
564 m_voicecallcard.c_str(), m_voicecallhighest.c_str(), success_cb, this);
565 if (!handleOperation(o, "pa_context_set_card_profile_by_name"))
566 return;
567 }
568
569 /* Find highest compatible sink/source elements from the voicecall
570 compatible card (on touch this means the pulse droid element) */
571 m_nametoset = m_valuetoset = "";
572 o = pa_context_get_sink_info_list(m_context, sinkinfo_cb, this);
573 if (!handleOperation(o, "pa_context_get_sink_info_list"))
574 return;
575 if ((m_nametoset != "") && (m_nametoset != m_defaultsink)) {
576 qDebug("Setting PulseAudio default sink to '%s'", m_nametoset.c_str());
577 o = pa_context_set_default_sink(m_context, m_nametoset.c_str(), success_cb, this);
578 if (!handleOperation(o, "pa_context_set_default_sink"))
579 return;
580 }
581 if (m_valuetoset != "") {
582 qDebug("Setting PulseAudio sink '%s' port '%s'",
583 m_nametoset.c_str(), m_valuetoset.c_str());
584 o = pa_context_set_sink_port_by_name(m_context, m_nametoset.c_str(),
585 m_valuetoset.c_str(), success_cb, this);
586 if (!handleOperation(o, "pa_context_set_sink_port_by_name"))
587 return;
588 }
589
590 /* Same for source */
591 m_nametoset = m_valuetoset = "";
592 o = pa_context_get_source_info_list(m_context, sourceinfo_cb, this);
593 if (!handleOperation(o, "pa_context_get_source_info_list"))
594 return;
595 if ((m_nametoset != "") && (m_nametoset != m_defaultsource)) {
596 qDebug("Setting PulseAudio default source to '%s'", m_nametoset.c_str());
597 o = pa_context_set_default_source(m_context, m_nametoset.c_str(), success_cb, this);
598 if (!handleOperation(o, "pa_context_set_default_source"))
599 return;
600 }
601 if (m_valuetoset != "") {
602 qDebug("Setting PulseAudio source '%s' port '%s'",
603 m_nametoset.c_str(), m_valuetoset.c_str());
604 o = pa_context_set_source_port_by_name(m_context, m_nametoset.c_str(),
605 m_valuetoset.c_str(), success_cb, this);
606 if (!handleOperation(o, "pa_context_set_source_port_by_name"))
607 return;
608 }
609
610 pa_threaded_mainloop_unlock(m_mainLoop);
611
612 /* Notify if the list of audio modes changed */
613 if (p_availableAudioModes != m_availableAudioModes)
614 Q_EMIT availableAudioModesChanged(m_availableAudioModes);
615
616 /* Notify if call mode changed */
617 if (p_audiomode != m_audiomode) {
618 Q_EMIT audioModeChanged(m_audiomode);
619 }
620
621 /* If no more active voicecall, restore previous saved pulseaudio state */
622 if (callstatus == CallEnded) {
623 restoreVoiceCall();
624 }
625
626 /* In case the app had set mute when the call wasn't active, make sure we reflect it here */
627 if (m_callstatus != CallEnded)
628 setMicMute(m_micmute);
629}
630
631void QPulseAudioEngineWorker::setMicMute(bool muted)
632{
633 if (!createPulseContext()) {
634 return;
635 }
636
637 m_micmute = muted;
638
639 if (m_callstatus == CallEnded)
640 return;
641
642 pa_threaded_mainloop_lock(m_mainLoop);
643
644 m_nametoset = "";
645 pa_operation *o = pa_context_get_source_info_list(m_context, sourceinfo_cb, this);
646 if (!handleOperation(o, "pa_context_get_source_info_list"))
647 return;
648
649 if (m_nametoset != "") {
650 int m = m_micmute ? 1 : 0;
651 qDebug("Setting PulseAudio source '%s' muted '%d'", m_nametoset.c_str(), m);
652 o = pa_context_set_source_mute_by_name(m_context,
653 m_nametoset.c_str(), m, success_cb, this);
654 if (!handleOperation(o, "pa_context_set_source_mute_by_name"))
655 return;
656 }
657
658 pa_threaded_mainloop_unlock(m_mainLoop);
659}
660
661void QPulseAudioEngineWorker::plugCardCallback(const pa_card_info *info)
662{
663 qDebug("Notified about card (%s) add event from PulseAudio", info->name);
664
665 /* Check if it's indeed a BT device (with at least one hsp profile) */
666 pa_card_profile_info2 *hsp = NULL, *a2dp = NULL;
667 for (int i = 0; i < info->n_profiles; i++) {
668 if (!strcmp(info->profiles2[i]->name, PULSEAUDIO_PROFILE_HSP))
669 hsp = info->profiles2[i];
670 else if (!strcmp(info->profiles2[i]->name, PULSEAUDIO_PROFILE_A2DP) &&
671 info->profiles2[i]->available != 0) {
672 qDebug("Found a2dp");
673 a2dp = info->profiles2[i];
674 }
675 qDebug("%s", info->profiles2[i]->name);
676 }
677
678 if ((!info->active_profile || !strcmp(info->active_profile->name, "off")) && a2dp) {
679 qDebug("No profile set");
680 m_default_bt_card_fallback = info->name;
681 }
682
683 /* We only care about BT (HSP) devices, and if one is not already available */
684 if ((m_callstatus != CallEnded) && ((m_bt_hsp == "") || (m_bt_hsp_a2dp == ""))) {
685 if (hsp)
686 m_handleevent = true;
687 }
688}
689
690void QPulseAudioEngineWorker::updateCardCallback(const pa_card_info *info)
691{
692 qDebug("Notified about card (%s) changes event from PulseAudio", info->name);
693
694 /* Check if it's indeed a BT device (with at least one hsp profile) */
695 pa_card_profile_info2 *hsp = NULL, *a2dp = NULL;
696 for (int i = 0; i < info->n_profiles; i++) {
697 if (!strcmp(info->profiles2[i]->name, PULSEAUDIO_PROFILE_HSP))
698 hsp = info->profiles2[i];
699 else if (!strcmp(info->profiles2[i]->name, PULSEAUDIO_PROFILE_A2DP) &&
700 info->profiles2[i]->available != 0) {
701 qDebug("Found a2dp");
702 a2dp = info->profiles2[i];
703 }
704 qDebug("%s", info->profiles2[i]->name);
705 }
706
707 if ((!info->active_profile || !strcmp(info->active_profile->name, "off")) && a2dp) {
708 qDebug("No profile set");
709 m_default_bt_card_fallback = info->name;
710 }
711
712
713 /* We only care if the card event for the voicecall capable card */
714 if ((m_callstatus == CallActive) && (!strcmp(info->name, m_voicecallcard.c_str()))) {
715 if (m_audiomode == AudioModeWiredHeadset) {
716 /* If previous mode is wired, it means it got unplugged */
717 m_handleevent = true;
718 m_audiomodetoset = AudioModeBtOrWiredOrEarpiece;
719 } else if ((m_audiomode == AudioModeEarpiece) || ((m_audiomode == AudioModeSpeaker))) {
720 /* Now only trigger the event in case wired headset/headphone is now available */
721 pa_card_port_info *port_info = NULL;
722 for (int i = 0; i < info->n_ports; i++) {
723 if (info->ports[i] && (info->ports[i]->available == PA_PORT_AVAILABLE_YES) && (
724 !strcmp(info->ports[i]->name, "output-wired_headset") ||
725 !strcmp(info->ports[i]->name, "output-wired_headphone"))) {
726 m_handleevent = true;
727 m_audiomodetoset = AudioModeWiredOrEarpiece;
728 }
729 }
730 } else if (m_audiomode == AudioModeBluetooth) {
731 /* Handle the event so we can update the audiomodes */
732 m_handleevent = true;
733 m_audiomodetoset = AudioModeBluetooth;
734 }
735 }
736}
737
738void QPulseAudioEngineWorker::unplugCardCallback()
739{
740 if (m_callstatus != CallEnded) {
741 m_handleevent = true;
742 }
743}
744
745void QPulseAudioEngineWorker::handleCardEvent(const int evt, const unsigned int idx)
746{
747 pa_operation *o = NULL;
748
749 /* Internal state var used to know if we need to update our internal state */
750 m_handleevent = false;
751
752 if (evt == PA_SUBSCRIPTION_EVENT_NEW) {
753 o = pa_context_get_card_info_by_index(m_context, idx, plug_card_cb, this);
754 if (!handleOperation(o, "pa_context_get_card_info_by_index"))
755 return;
756
757 if (m_default_bt_card_fallback != "") {
758 o = pa_context_set_card_profile_by_name(m_context,
759 m_default_bt_card_fallback.c_str(), PULSEAUDIO_PROFILE_A2DP, success_cb, this);
760 if (!handleOperation(o, "pa_context_set_card_profile_by_name"))
761 return;
762 m_default_bt_card_fallback = "";
763 }
764
765 if (m_handleevent) {
766 qDebug("Adding new BT-HSP capable device");
767 /* In case A2DP is available, switch to HSP */
768 if (setupVoiceCall() < 0)
769 return;
770 /* Enable the HSP output port */
771 setCallMode(m_callstatus, AudioModeBluetooth);
772 }
773 } else if (evt == PA_SUBSCRIPTION_EVENT_CHANGE) {
774 o = pa_context_get_card_info_by_index(m_context, idx, update_card_cb, this);
775 if (!handleOperation(o, "pa_context_get_card_info_by_index"))
776 return;
777
778 if (m_default_bt_card_fallback != "") {
779 o = pa_context_set_card_profile_by_name(m_context,
780 m_default_bt_card_fallback.c_str(), PULSEAUDIO_PROFILE_A2DP, success_cb, this);
781 if (!handleOperation(o, "pa_context_set_card_profile_by_name"))
782 return;
783 m_default_bt_card_fallback = "";
784 }
785
786 if (m_handleevent) {
787 /* In this case it means the handset state changed */
788 qDebug("Notifying card changes for the voicecall capable card");
789 setCallMode(m_callstatus, m_audiomodetoset);
790 }
791 } else if (evt == PA_SUBSCRIPTION_EVENT_REMOVE) {
792 /* Check if the main HSP card was removed */
793 if (m_bt_hsp != "") {
794 o = pa_context_get_card_info_by_name(m_context, m_bt_hsp.c_str(), unplug_card_cb, this);
795 if (!handleOperation(o, "pa_context_get_sink_info_by_name"))
796 return;
797 }
798 if (m_bt_hsp_a2dp != "") {
799 o = pa_context_get_card_info_by_name(m_context, m_bt_hsp_a2dp.c_str(), unplug_card_cb, this);
800 if (!handleOperation(o, "pa_context_get_sink_info_by_name"))
801 return;
802 }
803 if (m_handleevent) {
804 qDebug("Notifying about BT-HSP card removal");
805 /* Needed in order to save the default sink/source */
806 if (setupVoiceCall() < 0)
807 return;
808 /* Enable the default handset output port */
809 setCallMode(m_callstatus, AudioModeWiredOrEarpiece);
810 }
811 }
812}
813
814Q_GLOBAL_STATIC(QPulseAudioEngine, pulseEngine);
815
816QPulseAudioEngine::QPulseAudioEngine(QObject *parent) :
817 QObject(parent)
818{
819 qRegisterMetaType<CallStatus>();
820 qRegisterMetaType<AudioMode>();
821 qRegisterMetaType<AudioModes>();
822 mWorker = new QPulseAudioEngineWorker();
823 QObject::connect(mWorker, SIGNAL(audioModeChanged(const AudioMode)), this, SIGNAL(audioModeChanged(const AudioMode)), Qt::QueuedConnection);
824 QObject::connect(mWorker, SIGNAL(availableAudioModesChanged(const AudioModes)), this, SIGNAL(availableAudioModesChanged(const AudioModes)), Qt::QueuedConnection);
825 mWorker->createPulseContext();
826 mWorker->moveToThread(&mThread);
827 mThread.start();
828}
829
830QPulseAudioEngine::~QPulseAudioEngine()
831{
832 mThread.quit();
833 mThread.wait();
834}
835
836QPulseAudioEngine *QPulseAudioEngine::instance()
837{
838 QPulseAudioEngine *engine = pulseEngine();
839 return engine;
840}
841
842void QPulseAudioEngine::setCallMode(CallStatus callstatus, AudioMode audiomode)
843{
844 QMetaObject::invokeMethod(mWorker, "setCallMode", Qt::QueuedConnection, Q_ARG(CallStatus, callstatus), Q_ARG(AudioMode, audiomode));
845}
846
847void QPulseAudioEngine::setMicMute(bool muted)
848{
849 QMetaObject::invokeMethod(mWorker, "setMicMute", Qt::QueuedConnection, Q_ARG(bool, muted));
850}
851
852QT_END_NAMESPACE
853
8540
=== added file 'handler/qpulseaudioengine.h'
--- handler/qpulseaudioengine.h 1970-01-01 00:00:00 +0000
+++ handler/qpulseaudioengine.h 2017-02-06 13:26:33 +0000
@@ -0,0 +1,126 @@
1/****************************************************************************
2**
3** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
4** Contact: http://www.qt-project.org/legal
5**
6** This file was taken from qt5 and modified by
7** David Henningsson <david.henningsson@canonical.com> for usage in
8** telepathy-ofono.
9**
10** GNU Lesser General Public License Usage
11** Alternatively, this file may be used under the terms of the GNU Lesser
12** General Public License version 2.1 as published by the Free Software
13** Foundation and appearing in the file LICENSE.LGPL included in the
14** packaging of this file. Please review the following information to
15** ensure the GNU Lesser General Public License version 2.1 requirements
16** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
17**
18****************************************************************************/
19
20#ifndef QPULSEAUDIOENGINE_H
21#define QPULSEAUDIOENGINE_H
22
23#include <QtCore/qmap.h>
24#include <QtCore/qbytearray.h>
25#include <QThread>
26#include <pulse/pulseaudio.h>
27
28enum AudioMode {
29 AudioModeEarpiece = 0x0001,
30 AudioModeWiredHeadset = 0x0002,
31 AudioModeSpeaker = 0x0004,
32 AudioModeBluetooth = 0x0008,
33 AudioModeBtOrWiredOrEarpiece = AudioModeBluetooth | AudioModeWiredHeadset | AudioModeEarpiece,
34 AudioModeWiredOrEarpiece = AudioModeWiredHeadset | AudioModeEarpiece,
35 AudioModeWiredOrSpeaker = AudioModeWiredHeadset | AudioModeSpeaker,
36 AudioModeBtOrWiredOrSpeaker = AudioModeBluetooth | AudioModeWiredOrSpeaker
37};
38
39Q_DECLARE_METATYPE(AudioMode)
40
41typedef QList<AudioMode> AudioModes;
42Q_DECLARE_METATYPE(AudioModes)
43
44enum CallStatus {
45 CallRinging,
46 CallActive,
47 CallEnded
48};
49
50Q_DECLARE_METATYPE(CallStatus)
51
52QT_BEGIN_NAMESPACE
53
54class QPulseAudioEngineWorker : public QObject
55{
56 Q_OBJECT
57
58public:
59 QPulseAudioEngineWorker(QObject *parent = 0);
60 ~QPulseAudioEngineWorker();
61
62 pa_threaded_mainloop *mainloop() { return m_mainLoop; }
63 pa_context *context() { return m_context; }
64 bool createPulseContext(void);
65 int setupVoiceCall(void);
66 void restoreVoiceCall(void);
67 /* Callbacks to be used internally */
68 void cardInfoCallback(const pa_card_info *card);
69 void sinkInfoCallback(const pa_sink_info *sink);
70 void sourceInfoCallback(const pa_source_info *source);
71 void serverInfoCallback(const pa_server_info *server);
72 void plugCardCallback(const pa_card_info *card);
73 void updateCardCallback(const pa_card_info *card);
74 void unplugCardCallback();
75
76Q_SIGNALS:
77 void audioModeChanged(const AudioMode mode);
78 void availableAudioModesChanged(const AudioModes modes);
79
80public Q_SLOTS:
81 void handleCardEvent(const int evt, const unsigned int idx);
82 void setCallMode(CallStatus callstatus, AudioMode audiomode);
83 void setMicMute(bool muted); /* True if muted, false if unmuted */
84
85private:
86 pa_mainloop_api *m_mainLoopApi;
87 pa_threaded_mainloop *m_mainLoop;
88 pa_context *m_context;
89
90 AudioModes m_availableAudioModes;
91 CallStatus m_callstatus;
92 AudioMode m_audiomode;
93 AudioMode m_audiomodetoset;
94 bool m_micmute, m_handleevent;
95 std::string m_nametoset, m_valuetoset;
96 std::string m_defaultsink, m_defaultsource;
97 std::string m_bt_hsp, m_bt_hsp_a2dp;
98 std::string m_default_bt_card_fallback;
99 std::string m_voicecallcard, m_voicecallhighest, m_voicecallprofile;
100
101 bool handleOperation(pa_operation *operation, const char *func_name);
102 void releasePulseContext(void);
103};
104
105class QPulseAudioEngine : public QObject
106{
107 Q_OBJECT
108public:
109 explicit QPulseAudioEngine(QObject *parent = 0);
110 ~QPulseAudioEngine();
111 static QPulseAudioEngine *instance();
112
113 void setCallMode(CallStatus callstatus, AudioMode audiomode);
114 void setMicMute(bool muted); /* True if muted, false if unmuted */
115
116Q_SIGNALS:
117 void audioModeChanged(const AudioMode mode);
118 void availableAudioModesChanged(const AudioModes modes);
119private:
120 QPulseAudioEngineWorker *mWorker;
121 QThread mThread;
122};
123
124QT_END_NAMESPACE
125
126#endif
0127
=== removed file 'handler/qpulseaudioengine.h'
--- handler/qpulseaudioengine.h 2017-02-06 13:26:33 +0000
+++ handler/qpulseaudioengine.h 1970-01-01 00:00:00 +0000
@@ -1,126 +0,0 @@
1/****************************************************************************
2**
3** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
4** Contact: http://www.qt-project.org/legal
5**
6** This file was taken from qt5 and modified by
7** David Henningsson <david.henningsson@canonical.com> for usage in
8** telepathy-ofono.
9**
10** GNU Lesser General Public License Usage
11** Alternatively, this file may be used under the terms of the GNU Lesser
12** General Public License version 2.1 as published by the Free Software
13** Foundation and appearing in the file LICENSE.LGPL included in the
14** packaging of this file. Please review the following information to
15** ensure the GNU Lesser General Public License version 2.1 requirements
16** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
17**
18****************************************************************************/
19
20#ifndef QPULSEAUDIOENGINE_H
21#define QPULSEAUDIOENGINE_H
22
23#include <QtCore/qmap.h>
24#include <QtCore/qbytearray.h>
25#include <QThread>
26#include <pulse/pulseaudio.h>
27
28enum AudioMode {
29 AudioModeEarpiece = 0x0001,
30 AudioModeWiredHeadset = 0x0002,
31 AudioModeSpeaker = 0x0004,
32 AudioModeBluetooth = 0x0008,
33 AudioModeBtOrWiredOrEarpiece = AudioModeBluetooth | AudioModeWiredHeadset | AudioModeEarpiece,
34 AudioModeWiredOrEarpiece = AudioModeWiredHeadset | AudioModeEarpiece,
35 AudioModeWiredOrSpeaker = AudioModeWiredHeadset | AudioModeSpeaker,
36 AudioModeBtOrWiredOrSpeaker = AudioModeBluetooth | AudioModeWiredOrSpeaker
37};
38
39Q_DECLARE_METATYPE(AudioMode)
40
41typedef QList<AudioMode> AudioModes;
42Q_DECLARE_METATYPE(AudioModes)
43
44enum CallStatus {
45 CallRinging,
46 CallActive,
47 CallEnded
48};
49
50Q_DECLARE_METATYPE(CallStatus)
51
52QT_BEGIN_NAMESPACE
53
54class QPulseAudioEngineWorker : public QObject
55{
56 Q_OBJECT
57
58public:
59 QPulseAudioEngineWorker(QObject *parent = 0);
60 ~QPulseAudioEngineWorker();
61
62 pa_threaded_mainloop *mainloop() { return m_mainLoop; }
63 pa_context *context() { return m_context; }
64 bool createPulseContext(void);
65 int setupVoiceCall(void);
66 void restoreVoiceCall(void);
67 /* Callbacks to be used internally */
68 void cardInfoCallback(const pa_card_info *card);
69 void sinkInfoCallback(const pa_sink_info *sink);
70 void sourceInfoCallback(const pa_source_info *source);
71 void serverInfoCallback(const pa_server_info *server);
72 void plugCardCallback(const pa_card_info *card);
73 void updateCardCallback(const pa_card_info *card);
74 void unplugCardCallback();
75
76Q_SIGNALS:
77 void audioModeChanged(const AudioMode mode);
78 void availableAudioModesChanged(const AudioModes modes);
79
80public Q_SLOTS:
81 void handleCardEvent(const int evt, const unsigned int idx);
82 void setCallMode(CallStatus callstatus, AudioMode audiomode);
83 void setMicMute(bool muted); /* True if muted, false if unmuted */
84
85private:
86 pa_mainloop_api *m_mainLoopApi;
87 pa_threaded_mainloop *m_mainLoop;
88 pa_context *m_context;
89
90 AudioModes m_availableAudioModes;
91 CallStatus m_callstatus;
92 AudioMode m_audiomode;
93 AudioMode m_audiomodetoset;
94 bool m_micmute, m_handleevent;
95 std::string m_nametoset, m_valuetoset;
96 std::string m_defaultsink, m_defaultsource;
97 std::string m_bt_hsp, m_bt_hsp_a2dp;
98 std::string m_default_bt_card_fallback;
99 std::string m_voicecallcard, m_voicecallhighest, m_voicecallprofile;
100
101 bool handleOperation(pa_operation *operation, const char *func_name);
102 void releasePulseContext(void);
103};
104
105class QPulseAudioEngine : public QObject
106{
107 Q_OBJECT
108public:
109 explicit QPulseAudioEngine(QObject *parent = 0);
110 ~QPulseAudioEngine();
111 static QPulseAudioEngine *instance();
112
113 void setCallMode(CallStatus callstatus, AudioMode audiomode);
114 void setMicMute(bool muted); /* True if muted, false if unmuted */
115
116Q_SIGNALS:
117 void audioModeChanged(const AudioMode mode);
118 void availableAudioModesChanged(const AudioModes modes);
119private:
120 QPulseAudioEngineWorker *mWorker;
121 QThread mThread;
122};
123
124QT_END_NAMESPACE
125
126#endif
1270
=== modified file 'libtelephonyservice/chatentry.cpp'
--- libtelephonyservice/chatentry.cpp 2016-11-23 19:37:30 +0000
+++ libtelephonyservice/chatentry.cpp 2017-02-06 13:26:33 +0000
@@ -165,31 +165,42 @@
165165
166void ChatEntry::onRolesChanged(const HandleRolesMap &added, const HandleRolesMap &removed)166void ChatEntry::onRolesChanged(const HandleRolesMap &added, const HandleRolesMap &removed)
167{167{
168 Q_UNUSED(added);
169 Q_UNUSED(removed);
170
171 RolesMap rolesMap;
172 Tp::TextChannel* channel = 0;168 Tp::TextChannel* channel = 0;
173 if (rolesInterface) {169 if (rolesInterface) {
174 rolesMap = rolesInterface->getRoles();170 if (mRolesMap.isEmpty()) {
171 mRolesMap = rolesInterface->getRoles();
172 }
175 channel = qvariant_cast<Tp::TextChannel*>(rolesInterface->property("channel"));173 channel = qvariant_cast<Tp::TextChannel*>(rolesInterface->property("channel"));
176 }174 }
177175
176 QMapIterator<uint, uint> it(removed);
177 while (it.hasNext()) {
178 it.next();
179 mRolesMap.remove(it.key());
180 }
181
182 QMapIterator<uint, uint> it2(added);
183 while (it2.hasNext()) {
184 it2.next();
185 mRolesMap[it2.key()] = it2.value();
186 }
187
188 // TODO avoid iterating over all participants when not needed
178 Q_FOREACH(Participant* participant, mParticipants) {189 Q_FOREACH(Participant* participant, mParticipants) {
179 if (rolesMap.contains(participant->handle())) {190 if (mRolesMap.contains(participant->handle())) {
180 participant->setRoles(rolesMap[participant->handle()]);191 participant->setRoles(mRolesMap[participant->handle()]);
181 }192 }
182 }193 }
183194
184 Q_FOREACH(Participant* participant, mLocalPendingParticipants) {195 Q_FOREACH(Participant* participant, mLocalPendingParticipants) {
185 if (rolesMap.contains(participant->handle())) {196 if (mRolesMap.contains(participant->handle())) {
186 participant->setRoles(rolesMap[participant->handle()]);197 participant->setRoles(mRolesMap[participant->handle()]);
187 }198 }
188 }199 }
189200
190 Q_FOREACH(Participant* participant, mRemotePendingParticipants) {201 Q_FOREACH(Participant* participant, mRemotePendingParticipants) {
191 if (rolesMap.contains(participant->handle())) {202 if (mRolesMap.contains(participant->handle())) {
192 participant->setRoles(rolesMap[participant->handle()]);203 participant->setRoles(mRolesMap[participant->handle()]);
193 }204 }
194 }205 }
195206
@@ -202,7 +213,7 @@
202 return;213 return;
203 }214 }
204215
205 mSelfContactRoles = rolesMap[selfContact->handle().at(0)];216 mSelfContactRoles = mRolesMap[selfContact->handle().at(0)];
206 Q_EMIT selfContactRolesChanged();217 Q_EMIT selfContactRolesChanged();
207}218}
208219
@@ -685,15 +696,14 @@
685 }696 }
686 }697 }
687698
688 RolesMap rolesMap;699 if (rolesInterface && mRolesMap.isEmpty()) {
689 if (rolesInterface) {700 mRolesMap = rolesInterface->getRoles();
690 rolesMap = rolesInterface->getRoles();
691 }701 }
692 // now add the new participants702 // now add the new participants
693 // FIXME: check for duplicates?703 // FIXME: check for duplicates?
694 Q_FOREACH(Tp::ContactPtr contact, added) {704 Q_FOREACH(Tp::ContactPtr contact, added) {
695 uint handle = contact->handle().at(0);705 uint handle = contact->handle().at(0);
696 list << new Participant(contact->id(), rolesMap[handle], handle, this);706 list << new Participant(contact->id(), mRolesMap[handle], handle, this);
697 }707 }
698}708}
699709
700710
=== modified file 'libtelephonyservice/chatentry.h'
--- libtelephonyservice/chatentry.h 2017-02-06 13:26:33 +0000
+++ libtelephonyservice/chatentry.h 2017-02-06 13:26:33 +0000
@@ -226,6 +226,7 @@
226 Tp::Client::ChannelInterfaceRoomConfigInterface *roomConfigInterface;226 Tp::Client::ChannelInterfaceRoomConfigInterface *roomConfigInterface;
227 Tp::Client::ChannelInterfaceSubjectInterface *subjectInterface;227 Tp::Client::ChannelInterfaceSubjectInterface *subjectInterface;
228 ChannelInterfaceRolesInterface *rolesInterface;228 ChannelInterfaceRolesInterface *rolesInterface;
229 RolesMap mRolesMap;
229};230};
230231
231#endif // CHATENTRY_H232#endif // CHATENTRY_H
232233
=== modified file 'libtelephonyservice/contactwatcher.cpp'
--- libtelephonyservice/contactwatcher.cpp 2017-02-06 13:26:33 +0000
+++ libtelephonyservice/contactwatcher.cpp 2017-02-06 13:26:33 +0000
@@ -42,8 +42,6 @@
42ContactWatcher::ContactWatcher(QObject *parent) :42ContactWatcher::ContactWatcher(QObject *parent) :
43 QObject(parent), mRequest(0), mInteractive(false), mCompleted(false)43 QObject(parent), mRequest(0), mInteractive(false), mCompleted(false)
44{44{
45 // addressable VCard fields defaults to "tel" only
46 mAddressableFields << "tel";
47 connect(ContactUtils::sharedManager(),45 connect(ContactUtils::sharedManager(),
48 SIGNAL(contactsAdded(QList<QContactId>)),46 SIGNAL(contactsAdded(QList<QContactId>)),
49 SLOT(onContactsAdded(QList<QContactId>)));47 SLOT(onContactsAdded(QList<QContactId>)));
@@ -67,7 +65,7 @@
6765
68void ContactWatcher::startSearching()66void ContactWatcher::startSearching()
69{67{
70 if (!mCompleted || mIdentifier.isEmpty() || !mInteractive) {68 if (!mCompleted || mIdentifier.isEmpty() || !mInteractive || mAddressableFields.isEmpty()) {
71 // component is not ready yet or no identifier given,69 // component is not ready yet or no identifier given,
72 // or the number is not interactive and thus doesn't need contact info at all70 // or the number is not interactive and thus doesn't need contact info at all
73 return;71 return;
@@ -271,10 +269,6 @@
271void ContactWatcher::setAddressableFields(const QStringList &fields)269void ContactWatcher::setAddressableFields(const QStringList &fields)
272{270{
273 mAddressableFields = fields;271 mAddressableFields = fields;
274 // if the addressable fields is empty, fall back to matching phone numbers
275 if (mAddressableFields.isEmpty()) {
276 mAddressableFields << "tel";
277 }
278 Q_EMIT addressableFieldsChanged();272 Q_EMIT addressableFieldsChanged();
279273
280 startSearching();274 startSearching();
281275
=== modified file 'tests/Ubuntu.Telephony/ContactWatcherTest.cpp'
--- tests/Ubuntu.Telephony/ContactWatcherTest.cpp 2015-10-09 19:48:07 +0000
+++ tests/Ubuntu.Telephony/ContactWatcherTest.cpp 2017-02-06 13:26:33 +0000
@@ -95,7 +95,7 @@
9595
96 // set the phone number and wait for the match to happen96 // set the phone number and wait for the match to happen
97 watcher.setIdentifier(identifier);97 watcher.setIdentifier(identifier);
9898 watcher.setAddressableFields(QStringList() << "tel");
9999
100 // contact fetching is asynchronous so use QTRY_COMPARE for the first signal spy100 // contact fetching is asynchronous so use QTRY_COMPARE for the first signal spy
101 // for the subsequent ones it is fine to use just QCOMPARE101 // for the subsequent ones it is fine to use just QCOMPARE
@@ -128,6 +128,7 @@
128 QSignalSpy unknownSpy(&watcher, SIGNAL(isUnknownChanged()));128 QSignalSpy unknownSpy(&watcher, SIGNAL(isUnknownChanged()));
129129
130 watcher.setIdentifier(identifier);130 watcher.setIdentifier(identifier);
131 watcher.setAddressableFields(QStringList() << "tel");
131132
132 // now create the contact and wait to see if it gets matched133 // now create the contact and wait to see if it gets matched
133 QContact contact = createContact("FirstName",134 QContact contact = createContact("FirstName",
@@ -169,6 +170,7 @@
169 ContactWatcher watcher;170 ContactWatcher watcher;
170 watcher.componentComplete();171 watcher.componentComplete();
171 watcher.setIdentifier(identifier);172 watcher.setIdentifier(identifier);
173 watcher.setAddressableFields(QStringList() << "tel");
172174
173 QSignalSpy contactIdSpy(&watcher, SIGNAL(contactIdChanged()));175 QSignalSpy contactIdSpy(&watcher, SIGNAL(contactIdChanged()));
174 QSignalSpy aliasSpy(&watcher, SIGNAL(aliasChanged()));176 QSignalSpy aliasSpy(&watcher, SIGNAL(aliasChanged()));
@@ -218,6 +220,7 @@
218220
219 // set the phone number and wait for the match to happen221 // set the phone number and wait for the match to happen
220 watcher.setIdentifier(identifier);222 watcher.setIdentifier(identifier);
223 watcher.setAddressableFields(QStringList() << "tel");
221224
222 // at this point we just need to make sure the contactId is correct, the other fields225 // at this point we just need to make sure the contactId is correct, the other fields
223 // are tested in a separate test226 // are tested in a separate test
@@ -269,6 +272,7 @@
269272
270 // set the phone number and wait for the match to happen273 // set the phone number and wait for the match to happen
271 watcher.setIdentifier(identifier);274 watcher.setIdentifier(identifier);
275 watcher.setAddressableFields(QStringList() << "tel");
272276
273 // at this point we just need to make sure the contactId is correct, the other fields277 // at this point we just need to make sure the contactId is correct, the other fields
274 // are tested in a separate test278 // are tested in a separate test
@@ -318,6 +322,7 @@
318322
319 // set the phone number and wait for the match to happen323 // set the phone number and wait for the match to happen
320 watcher.setIdentifier(identifier);324 watcher.setIdentifier(identifier);
325 watcher.setAddressableFields(QStringList() << "tel");
321326
322 // at this point we just need to make sure the contactId is correct, the other fields327 // at this point we just need to make sure the contactId is correct, the other fields
323 // are tested in a separate test328 // are tested in a separate test
@@ -396,6 +401,7 @@
396401
397 // set the phone number and wait for the match to happen402 // set the phone number and wait for the match to happen
398 watcher.setIdentifier(identifier);403 watcher.setIdentifier(identifier);
404 watcher.setAddressableFields(QStringList() << "tel");
399405
400 // component not complete yet406 // component not complete yet
401 QTRY_COMPARE(contactIdSpy.count(), 0);407 QTRY_COMPARE(contactIdSpy.count(), 0);
@@ -430,8 +436,8 @@
430 ContactWatcher watcher;436 ContactWatcher watcher;
431437
432 // check that addressable fields contains "tel" by default438 // check that addressable fields contains "tel" by default
433 QCOMPARE(watcher.addressableFields().count(), 1);439 QCOMPARE(watcher.addressableFields().count(), 0);
434 QCOMPARE(watcher.addressableFields()[0], QString("tel"));440 QCOMPARE(watcher.addressableFields(), QStringList());
435441
436 QSignalSpy addressableFieldsSpy(&watcher, SIGNAL(addressableFieldsChanged()));442 QSignalSpy addressableFieldsSpy(&watcher, SIGNAL(addressableFieldsChanged()));
437 QStringList addressableFields;443 QStringList addressableFields;
@@ -442,8 +448,8 @@
442448
443 // set the addressable fields to an empty value and make sure it falls back to "tel"449 // set the addressable fields to an empty value and make sure it falls back to "tel"
444 watcher.setAddressableFields(QStringList());450 watcher.setAddressableFields(QStringList());
445 QCOMPARE(watcher.addressableFields().count(), 1);451 QCOMPARE(watcher.addressableFields().count(), 0);
446 QCOMPARE(watcher.addressableFields()[0], QString("tel"));452 QCOMPARE(watcher.addressableFields(), QStringList());
447}453}
448454
449void ContactWatcherTest::testExtendedFieldMatch()455void ContactWatcherTest::testExtendedFieldMatch()
@@ -496,6 +502,7 @@
496502
497 // try to match contact A503 // try to match contact A
498 watcherA.setIdentifier(contactIdentifierA);504 watcherA.setIdentifier(contactIdentifierA);
505 watcherA.setAddressableFields(QStringList() << "tel");
499506
500 // mark as complete507 // mark as complete
501 watcherA.componentComplete();508 watcherA.componentComplete();
@@ -513,6 +520,7 @@
513520
514 // try to match contact B521 // try to match contact B
515 watcherB.setIdentifier(contactIdentifierB);522 watcherB.setIdentifier(contactIdentifierB);
523 watcherB.setAddressableFields(QStringList() << "tel");
516524
517 // signal will be fired now525 // signal will be fired now
518 QTRY_COMPARE(contactIdSpyB.count(), 1);526 QTRY_COMPARE(contactIdSpyB.count(), 1);

Subscribers

People subscribed via source and target branches

to all changes: