Merge lp:telephony-service/staging into lp:telephony-service

Proposed by Gustavo Pichorim Boiko
Status: Approved
Approved by: Gustavo Pichorim Boiko
Approved revision: 1252
Proposed branch: lp:telephony-service/staging
Merge into: lp:telephony-service
Diff against target: 7142 lines (+4636/-297)
100 files modified
CMakeLists.txt (+10/-10)
TODO (+6/-0)
Ubuntu/Telephony/CMakeLists.txt (+1/-0)
Ubuntu/Telephony/components.cpp (+2/-0)
Ubuntu/Telephony/participantsmodel.cpp (+235/-0)
Ubuntu/Telephony/participantsmodel.h (+92/-0)
accounts/CMakeLists.txt (+2/-0)
accounts/common/DynamicField.qml (+172/-0)
accounts/common/Main.qml (+106/-0)
accounts/common/NewAccountInterface.qml (+314/-0)
accounts/irc/CMakeLists.txt (+2/-0)
accounts/irc/data/CMakeLists.txt (+5/-0)
accounts/irc/data/telephony-irc-im.service (+22/-0)
accounts/irc/data/telephony-irc.provider (+5/-0)
accounts/irc/qml/CMakeLists.txt (+13/-0)
accounts/irc/qml/NewAccount.qml (+63/-0)
accounts/sip/CMakeLists.txt (+2/-0)
accounts/sip/data/CMakeLists.txt (+5/-0)
accounts/sip/data/telephony-sip-im.service (+22/-0)
accounts/sip/data/telephony-sip.provider (+5/-0)
accounts/sip/qml/CMakeLists.txt (+13/-0)
accounts/sip/qml/NewAccount.qml (+48/-0)
approver/approver.cpp (+23/-20)
cmake/modules/GenerateTest.cmake (+1/-0)
debian/account-plugin-irc-unity8.install (+3/-0)
debian/account-plugin-sip-unity8.install (+3/-0)
debian/control (+24/-1)
handler/CMakeLists.txt (+21/-0)
handler/Handler.xml (+64/-9)
handler/TelephonyServiceHandler.client (+9/-0)
handler/accountproperties.cpp (+71/-0)
handler/accountproperties.h (+47/-0)
handler/audioroutemanager.cpp (+231/-0)
handler/audioroutemanager.h (+75/-0)
handler/callagent.cpp (+118/-0)
handler/callagent.h (+52/-0)
handler/callhandler.cpp (+202/-39)
handler/callhandler.h (+10/-2)
handler/farstreamchannel.cpp (+326/-0)
handler/farstreamchannel.h (+80/-0)
handler/handler.cpp (+20/-5)
handler/handler.h (+2/-1)
handler/handlerdbus.cpp (+45/-6)
handler/handlerdbus.h (+21/-2)
handler/main.cpp (+16/-3)
handler/powerd.h (+34/-0)
handler/powerdaudiomodemediator.cpp (+63/-0)
handler/powerdaudiomodemediator.h (+46/-0)
handler/powerddbus.cpp (+43/-0)
handler/powerddbus.h (+38/-0)
handler/qpulseaudioengine.cpp (+853/-0)
handler/qpulseaudioengine.h (+126/-0)
handler/texthandler.cpp (+47/-0)
handler/texthandler.h (+6/-0)
indicator/callchannelobserver.cpp (+19/-3)
indicator/callchannelobserver.h (+2/-1)
indicator/displaynamesettings.cpp (+2/-2)
indicator/messagingmenu.cpp (+14/-14)
indicator/messagingmenu.h (+2/-2)
indicator/textchannelobserver.cpp (+6/-0)
libtelephonyservice/CMakeLists.txt (+4/-8)
libtelephonyservice/accountentry.cpp (+79/-6)
libtelephonyservice/accountentry.h (+24/-1)
libtelephonyservice/accountlist.cpp (+11/-2)
libtelephonyservice/accountlist.h (+2/-0)
libtelephonyservice/applicationutils.cpp (+11/-13)
libtelephonyservice/audiooutput.cpp (+17/-0)
libtelephonyservice/audiooutput.h (+4/-0)
libtelephonyservice/callentry.cpp (+54/-54)
libtelephonyservice/callentry.h (+3/-3)
libtelephonyservice/chatentry.cpp (+46/-23)
libtelephonyservice/chatentry.h (+9/-1)
libtelephonyservice/chatmanager.cpp (+16/-0)
libtelephonyservice/chatmanager.h (+3/-1)
libtelephonyservice/contactwatcher.cpp (+67/-24)
libtelephonyservice/contactwatcher.h (+5/-1)
libtelephonyservice/dbustypes.h (+7/-0)
libtelephonyservice/greetercontacts.cpp (+17/-1)
libtelephonyservice/greetercontacts.h (+3/-1)
libtelephonyservice/ofonoaccountentry.cpp (+7/-1)
libtelephonyservice/ofonoaccountentry.h (+3/-1)
libtelephonyservice/participant.cpp (+13/-2)
libtelephonyservice/participant.h (+11/-1)
libtelephonyservice/phoneutils.cpp (+43/-1)
libtelephonyservice/phoneutils.h (+4/-1)
libtelephonyservice/protocol.cpp (+83/-3)
libtelephonyservice/protocol.h (+42/-0)
libtelephonyservice/protocolmanager.cpp (+2/-2)
libtelephonyservice/telepathyhelper.cpp (+37/-8)
libtelephonyservice/telepathyhelper.h (+5/-2)
libtelephonyservice/tonegenerator.cpp (+12/-1)
libtelephonyservice/tonegenerator.h (+6/-2)
protocols/README.protocols (+8/-0)
protocols/ofono.protocol (+5/-0)
protocols/sip.protocol (+7/-0)
tests/Ubuntu.Telephony/ContactWatcherTest.cpp (+13/-5)
tests/libtelephonyservice/OfonoAccountEntryTest.cpp (+5/-5)
tests/libtelephonyservice/ProtocolTest.cpp (+30/-2)
tests/libtelephonyservice/testProtocols/foo.protocol (+7/-0)
upstart/telephony-service-indicator.conf (+1/-1)
To merge this branch: bzr merge lp:telephony-service/staging
Reviewer Review Type Date Requested Status
Gustavo Pichorim Boiko (community) Approve
system-apps-ci-bot continuous-integration Needs Fixing
Review via email: mp+320715@code.launchpad.net

Commit message

- Add new property to .protocol files to allow join existing channels.
- Add VOIP support to telephony-service.
- Create unity8 sip and irc account packages.
- Add more properties to the .protocol files
- Expose method on dbus to leave all rooms from a certain account.
- Fix protocol filters
- Uses "ua_url_dispatcher_session_open" to launch external apps. That fixes some problems with "ua_url_dispatcher_session_open" not working on Desktop.
- Add method to leave channel by properties.
- Implement AudioRouteManager in telephony-service-handler
- Multiple performance improvements on Roles Interfaces and ContactWatcher
- Changed upstart job to also launch telephony-service-indicator on desktop
- Add ParticipantsModel
- Expose the account parameters to QML
- Export startChat to QML.
- Implemented contact match by online accounts (IRC).
- Monitor app and disconnect/connect accounts when appropriate.

Description of the change

- Add new property to .protocol files to allow join existing channels.
- Add VOIP support to telephony-service.
- Create unity8 sip and irc account packages.
- Add more properties to the .protocol files
- Expose method on dbus to leave all rooms from a certain account.
- Fix protocol filters
- Uses "ua_url_dispatcher_session_open" to launch external apps. That fixes some problems with "ua_url_dispatcher_session_open" not working on Desktop.
- Add method to leave channel by properties.
- Implement AudioRouteManager in telephony-service-handler
- Multiple performance improvements on Roles Interfaces and ContactWatcher
- Changed upstart job to also launch telephony-service-indicator on desktop
- Add ParticipantsModel
- Expose the account parameters to QML
- Export startChat to QML.
- Implemented contact match by online accounts (IRC).
- Monitor app and disconnect/connect accounts when appropriate.

To post a comment you must log in.
Revision history for this message
system-apps-ci-bot (system-apps-ci-bot) wrote :

FAILED: Continuous integration, rev:1251
https://jenkins.canonical.com/system-apps/job/lp-telephony-service-ci/1/
Executed test runs:
    FAILURE: https://jenkins.canonical.com/system-apps/job/build/2343/console
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-0-fetch/2343
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=xenial+overlay/2161
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=xenial+overlay/2161/artifact/output/*zip*/output.zip
    FAILURE: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=zesty/2161/console
    FAILURE: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=xenial+overlay/2161/console
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=zesty/2161
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=zesty/2161/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=xenial+overlay/2161
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=xenial+overlay/2161/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=zesty/2161
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=zesty/2161/artifact/output/*zip*/output.zip

Click here to trigger a rebuild:
https://jenkins.canonical.com/system-apps/job/lp-telephony-service-ci/1/rebuild

review: Needs Fixing (continuous-integration)
lp:telephony-service/staging updated
1252. By Gustavo Pichorim Boiko

Disable PA on tests.

Revision history for this message
system-apps-ci-bot (system-apps-ci-bot) wrote :

FAILED: Continuous integration, rev:1252
https://jenkins.canonical.com/system-apps/job/lp-telephony-service-ci/2/
Executed test runs:
    FAILURE: https://jenkins.canonical.com/system-apps/job/build/2350/console
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-0-fetch/2350
    FAILURE: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=xenial+overlay/2168/console
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=zesty/2168
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=zesty/2168/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=xenial+overlay/2168
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=xenial+overlay/2168/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=zesty/2168
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=zesty/2168/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=xenial+overlay/2168
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=xenial+overlay/2168/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=zesty/2168
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=zesty/2168/artifact/output/*zip*/output.zip

Click here to trigger a rebuild:
https://jenkins.canonical.com/system-apps/job/lp-telephony-service-ci/2/rebuild

review: Needs Fixing (continuous-integration)
Revision history for this message
Gustavo Pichorim Boiko (boiko) wrote :

The changes were reviewed individually and tested from this branch, so approving.

review: Approve
lp:telephony-service/staging updated
1253. By Tiago Salem Herrmann

- Initialize pointer to avoid crashes.
- Connect new accounts in case messaging-app is already active

1254. By Tiago Salem Herrmann

- Use alsasrc as a workaround to be able to record from the microphone.
pulsesrc does not work on some environments.

1255. By Tiago Salem Herrmann

Add requires to telepathy-rakia and mfw-plugin-irc

1256. By Gustavo Pichorim Boiko

Fix watching the connection status changes and properly notify the changes to displayed accounts

Unmerged revisions

1256. By Gustavo Pichorim Boiko

Fix watching the connection status changes and properly notify the changes to displayed accounts

1255. By Tiago Salem Herrmann

Add requires to telepathy-rakia and mfw-plugin-irc

1254. By Tiago Salem Herrmann

- Use alsasrc as a workaround to be able to record from the microphone.
pulsesrc does not work on some environments.

1253. By Tiago Salem Herrmann

- Initialize pointer to avoid crashes.
- Connect new accounts in case messaging-app is already active

1252. By Gustavo Pichorim Boiko

Disable PA on tests.

1251. By Gustavo Pichorim Boiko

Monitor app and disconnect/connect accounts when appropriate.

1250. By Gustavo Pichorim Boiko

Implemented contact match by online accounts (IRC).

1249. By Gustavo Pichorim Boiko

Export startChat to QML.

1248. By Gustavo Pichorim Boiko

Expose the account parameters to QML

1247. By Gustavo Pichorim Boiko

Add ParticipantsModel

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'CMakeLists.txt'
2--- CMakeLists.txt 2016-10-06 19:41:33 +0000
3+++ CMakeLists.txt 2017-04-05 14:05:16 +0000
4@@ -15,13 +15,6 @@
5 # just to make debug easier, print the system processor
6 message(STATUS "System processor: ${CMAKE_SYSTEM_PROCESSOR}")
7
8-# Check if should build using ubuntu platform api
9-check_include_file_cxx("ubuntu/application/init.h" USE_UBUNTU_PLATFORM_API)
10-
11-if (USE_UBUNTU_PLATFORM_API)
12- add_definitions(-DUSE_UBUNTU_PLATFORM_API)
13-endif (USE_UBUNTU_PLATFORM_API)
14-
15 set(TELEPHONY_SERVICE_DIR ${CMAKE_INSTALL_DATADIR}/telephony-service)
16
17 configure_file(config.h.in ${CMAKE_CURRENT_BINARY_DIR}/config.h @ONLY)
18@@ -35,10 +28,8 @@
19
20 find_package(Qt5Contacts)
21 find_package(Qt5DBus)
22-#find_package(Qt5Gui)
23 find_package(Qt5Multimedia)
24 find_package(Qt5Qml)
25-#find_package(Qt5Quick)
26 find_package(Qt5Test)
27 find_package(Qt5Feedback)
28 find_package(Qt5Network)
29@@ -48,7 +39,7 @@
30
31 option(SKIP_QML_TESTS "Skip QML tests" OFF)
32
33-if(NOT CMAKE_CROSSCOMPILING)
34+if(CMAKE_CROSSCOMPILING)
35 find_program(QMAKE_EXECUTABLE qmake)
36 if(QMAKE_EXECUTABLE STREQUAL "QMAKE_EXECUTABLE-NOTFOUND")
37 message(FATAL_ERROR "qmake not found")
38@@ -71,10 +62,18 @@
39
40 find_package(PkgConfig REQUIRED)
41 pkg_check_modules(TP_QT5 REQUIRED TelepathyQt5)
42+pkg_check_modules(TP_QT5_FS REQUIRED TelepathyQt5Farstream)
43 pkg_check_modules(NOTIFY REQUIRED libnotify)
44 pkg_check_modules(MESSAGING_MENU REQUIRED messaging-menu)
45 pkg_check_modules(UserMetrics REQUIRED libusermetricsinput-1)
46 pkg_check_modules(HISTORY REQUIRED history-service)
47+pkg_check_modules(TPFS REQUIRED telepathy-farstream)
48+pkg_check_modules(GST REQUIRED gstreamer-1.0)
49+pkg_check_modules(FS REQUIRED farstream-0.2)
50+pkg_check_modules(UBUNTU_PLATFORM_API ubuntu-platform-api)
51+pkg_check_modules(GSETTINGS_QT REQUIRED gsettings-qt)
52+pkg_check_modules(PULSEAUDIO libpulse)
53+pkg_check_modules(URL_DISPATCHER REQUIRED url-dispatcher-1)
54
55 add_definitions(-DQT_NO_KEYWORDS)
56
57@@ -110,6 +109,7 @@
58 add_subdirectory(po)
59 add_subdirectory(tests)
60 add_subdirectory(protocols)
61+add_subdirectory(accounts)
62
63 include(EnableCoverageReport)
64 #####################################################################
65
66=== modified file 'TODO'
67--- TODO 2012-11-27 12:21:47 +0000
68+++ TODO 2017-04-05 14:05:16 +0000
69@@ -1,6 +1,9 @@
70 General items
71 - Formatting of phone numbers
72 - Check how to import/export potfiles from the ts one
73+- Add an automated test to check if features are getting correctly exported
74+ by AccountEntry
75+- Implement unit test for launching external app from indicator.
76
77 Contact integration items:
78 - Fix contact search when names contain accentuated characters
79@@ -16,3 +19,6 @@
80 places
81 - Change the ChannelObserver and the ChannelApprover code to allow using from
82 both places.
83+
84+Handler
85+- Move app monitoring to from TextHandler to ApplicationUtils
86
87=== modified file 'Ubuntu/Telephony/CMakeLists.txt'
88--- Ubuntu/Telephony/CMakeLists.txt 2016-07-05 03:34:11 +0000
89+++ Ubuntu/Telephony/CMakeLists.txt 2017-04-05 14:05:16 +0000
90@@ -2,6 +2,7 @@
91
92 set(plugin_SRCS
93 presencerequest.cpp
94+ participantsmodel.cpp
95 components.cpp
96 )
97
98
99=== modified file 'Ubuntu/Telephony/components.cpp'
100--- Ubuntu/Telephony/components.cpp 2016-11-23 19:28:18 +0000
101+++ Ubuntu/Telephony/components.cpp 2017-04-05 14:05:16 +0000
102@@ -39,6 +39,7 @@
103 #include "accountentry.h"
104 #include "accountlist.h"
105 #include "audiooutput.h"
106+#include "participantsmodel.h"
107
108 #include <QQmlEngine>
109 #include <qqml.h>
110@@ -87,5 +88,6 @@
111 qmlRegisterType<ContactWatcher>(uri, 0, 1, "ContactWatcher");
112 qmlRegisterType<Participant>(uri, 0, 1, "Participant");
113 qmlRegisterType<PresenceRequest>(uri, 0, 1, "PresenceRequest");
114+ qmlRegisterType<ParticipantsModel>(uri, 0, 1, "ParticipantsModel");
115 qmlRegisterType<PhoneUtils>(uri, 0, 1, "PhoneUtils");
116 }
117
118=== added file 'Ubuntu/Telephony/participantsmodel.cpp'
119--- Ubuntu/Telephony/participantsmodel.cpp 1970-01-01 00:00:00 +0000
120+++ Ubuntu/Telephony/participantsmodel.cpp 2017-04-05 14:05:16 +0000
121@@ -0,0 +1,235 @@
122+/*
123+ * Copyright (C) 2013-2017 Canonical, Ltd.
124+ *
125+ * Authors:
126+ * Gustavo Pichorim Boiko <gustavo.boiko@canonical.com>
127+ * Tiago Salem Herrmann <tiago.herrmann@canonical.com>
128+ *
129+ * This file is part of telephony-service.
130+ *
131+ * telephony-service is free software; you can redistribute it and/or modify
132+ * it under the terms of the GNU General Public License as published by
133+ * the Free Software Foundation; version 3.
134+ *
135+ * telephony-service is distributed in the hope that it will be useful,
136+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
137+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
138+ * GNU General Public License for more details.
139+ *
140+ * You should have received a copy of the GNU General Public License
141+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
142+ */
143+
144+#include "participantsmodel.h"
145+#include <participant.h>
146+#include <QDebug>
147+
148+Q_DECLARE_METATYPE(Participant)
149+
150+ParticipantsModel::ParticipantsModel(QObject *parent) :
151+ QAbstractListModel(parent), mWaitingForQml(false), mCanFetchMore(true), mChatEntry(NULL)
152+{
153+ qRegisterMetaType<Participant>();
154+ mRoles[AliasRole] = "alias";
155+ mRoles[IdentifierRole] = "identifier";
156+ mRoles[RolesRole] = "roles";
157+ mRoles[StateRole] = "state";
158+
159+ connect(this, SIGNAL(rowsInserted(QModelIndex,int,int)), this, SIGNAL(countChanged()));
160+ connect(this, SIGNAL(rowsRemoved(QModelIndex,int,int)), this, SIGNAL(countChanged()));
161+ connect(this, SIGNAL(modelReset()), this, SIGNAL(countChanged()));
162+}
163+
164+bool ParticipantsModel::canFetchMore(const QModelIndex &parent) const
165+{
166+ return !mParticipantsCache.isEmpty();
167+}
168+
169+ParticipantsModel::~ParticipantsModel()
170+{
171+}
172+
173+void ParticipantsModel::fetchMore(const QModelIndex &parent)
174+{
175+ if (parent.isValid() ) {
176+ return;
177+ }
178+
179+ int max = 14;
180+ while (max >= 0 && !mParticipantsCache.isEmpty()) {
181+ addParticipant(mParticipantsCache.takeFirst());
182+ max--;
183+ }
184+
185+ if (mParticipantsCache.isEmpty()) {
186+ mCanFetchMore = false;
187+ Q_EMIT canFetchMoreChanged();
188+ }
189+}
190+
191+int ParticipantsModel::rowCount(const QModelIndex &parent) const
192+{
193+ if (parent.isValid()) {
194+ return 0;
195+ }
196+
197+ return mParticipants.count();
198+}
199+
200+QHash<int, QByteArray> ParticipantsModel::roleNames() const
201+{
202+ return mRoles;
203+}
204+
205+void ParticipantsModel::addParticipantCache(Participant *participant)
206+{
207+ int pos = positionForItem(participant->identifier(), true);
208+ mParticipantsCache.insert(pos, participant);
209+}
210+
211+void ParticipantsModel::addParticipant(Participant *participant)
212+{
213+ int pos = positionForItem(participant->identifier());
214+ beginInsertRows(QModelIndex(), pos, pos);
215+ mParticipants.insert(pos, participant);
216+ endInsertRows();
217+}
218+
219+void ParticipantsModel::removeParticipant(Participant *participant)
220+{
221+ int pos = mParticipants.indexOf(participant);
222+ if (pos >= 0) {
223+ beginRemoveRows(QModelIndex(), pos, pos);
224+ mParticipants.removeAt(pos);
225+ endRemoveRows();
226+ }
227+ pos = mParticipantsCache.indexOf(participant);
228+ if (pos >= 0) {
229+ mParticipantsCache.removeAt(pos);
230+ }
231+}
232+
233+QVariant ParticipantsModel::data(const QModelIndex &index, int role) const
234+{
235+ if (!index.isValid() || index.row() < 0 || index.row() >= rowCount()) {
236+ return QVariant();
237+ }
238+ switch (role) {
239+ case IdentifierRole:
240+ return mParticipants[index.row()]->identifier();
241+ break;
242+ case AliasRole:
243+ return mParticipants[index.row()]->alias();
244+ break;
245+ case StateRole:
246+ return 0;
247+ break;
248+ case RolesRole:
249+ return mParticipants[index.row()]->roles();
250+ break;
251+ }
252+ return QVariant();
253+}
254+
255+bool ParticipantsModel::lessThan(const QString &left, const QString &right) const
256+{
257+ // this method will push participant with names starting with non-letter
258+ // characters to the end of the list
259+ if (left.isEmpty() || right.isEmpty()) {
260+ return false;
261+ }
262+ if (left.at(0).isLetter() && right.at(0).isLetter()) {
263+ return left.localeAwareCompare(right) < 0;
264+ }
265+ if (!left.at(0).isLetter() && right.at(0).isLetter()) {
266+ return false;
267+ }
268+ if (left.at(0).isLetter() && !right.at(0).isLetter()) {
269+ return true;
270+ }
271+
272+ return false;
273+}
274+
275+int ParticipantsModel::positionForItem(const QString &item, bool cache) const
276+{
277+ // do a binary search for the item position on the list
278+ int lowerBound = 0;
279+ int upperBound = cache ? mParticipantsCache.count() - 1 : rowCount() - 1;
280+ if (upperBound < 0) {
281+ return 0;
282+ }
283+
284+ while (true) {
285+ int pos = (upperBound + lowerBound) / 2;
286+ const QString posItem = cache ? mParticipantsCache[pos]->identifier() : index(pos).data(IdentifierRole).toString();
287+ if (lowerBound == pos) {
288+ if (lessThan(item, posItem)) {
289+ return pos;
290+ }
291+ }
292+ if (lessThan(posItem, item)) {
293+ lowerBound = pos + 1; // its in the upper
294+ if (lowerBound > upperBound) {
295+ return pos += 1;
296+ }
297+ } else if (lowerBound > upperBound) {
298+ return pos;
299+ } else {
300+ upperBound = pos - 1; // its in the lower
301+ }
302+ }
303+}
304+
305+void ParticipantsModel::classBegin()
306+{
307+ mWaitingForQml = true;
308+}
309+
310+void ParticipantsModel::componentComplete()
311+{
312+ mWaitingForQml = false;
313+}
314+
315+QVariant ParticipantsModel::get(int row) const
316+{
317+ QVariantMap data;
318+ QModelIndex idx = index(row, 0);
319+ if (idx.isValid()) {
320+ QHash<int, QByteArray> roles = roleNames();
321+ Q_FOREACH(int role, roles.keys()) {
322+ data.insert(roles[role], idx.data(role));
323+ }
324+ }
325+
326+ return data;
327+}
328+
329+ChatEntry* ParticipantsModel::chatEntry() const
330+{
331+ return mChatEntry;
332+}
333+
334+void ParticipantsModel::setChatEntry(ChatEntry *entry)
335+{
336+ if (mChatEntry == entry) {
337+ return;
338+ }
339+ ChatEntry *previousChatEntry = mChatEntry;
340+ mChatEntry = entry;
341+ if (!entry) {
342+ return;
343+ }
344+ if (previousChatEntry) {
345+ previousChatEntry->disconnect(this);
346+ }
347+ connect(mChatEntry, SIGNAL(participantAdded(Participant *)), SLOT(addParticipant(Participant *)));
348+ connect(mChatEntry, SIGNAL(participantRemoved(Participant *)), SLOT(removeParticipant(Participant *)));
349+ Q_FOREACH(Participant *participant, mChatEntry->allParticipants()) {
350+ addParticipantCache(participant);
351+ }
352+ fetchMore();
353+ mCanFetchMore = !mParticipantsCache.isEmpty();
354+ Q_EMIT canFetchMoreChanged();
355+ Q_EMIT chatEntryChanged();
356+}
357
358=== added file 'Ubuntu/Telephony/participantsmodel.h'
359--- Ubuntu/Telephony/participantsmodel.h 1970-01-01 00:00:00 +0000
360+++ Ubuntu/Telephony/participantsmodel.h 2017-04-05 14:05:16 +0000
361@@ -0,0 +1,92 @@
362+/*
363+ * Copyright (C) 2013-2017 Canonical, Ltd.
364+ *
365+ * Authors:
366+ * Gustavo Pichorim Boiko <gustavo.boiko@canonical.com>
367+ * Tiago Salem Herrmann <tiago.herrmann@canonical.com>
368+ *
369+ * This file is part of telephony-service.
370+ *
371+ * telephony-service is free software; you can redistribute it and/or modify
372+ * it under the terms of the GNU General Public License as published by
373+ * the Free Software Foundation; version 3.
374+ *
375+ * telephony-service is distributed in the hope that it will be useful,
376+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
377+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
378+ * GNU General Public License for more details.
379+ *
380+ * You should have received a copy of the GNU General Public License
381+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
382+ */
383+
384+#ifndef PARTICIPANTSMODEL_H
385+#define PARTICIPANTSMODEL_H
386+
387+#include "chatentry.h"
388+#include <QAbstractListModel>
389+#include <QStringList>
390+#include <QQmlParserStatus>
391+#include <QQmlListProperty>
392+
393+class Participant;
394+
395+class ParticipantsModel : public QAbstractListModel, public QQmlParserStatus
396+{
397+ Q_OBJECT
398+ Q_INTERFACES(QQmlParserStatus)
399+ Q_PROPERTY(int count READ rowCount NOTIFY countChanged)
400+ Q_PROPERTY(bool canFetchMore READ canFetchMore NOTIFY canFetchMoreChanged)
401+ Q_PROPERTY(ChatEntry* chatEntry READ chatEntry WRITE setChatEntry NOTIFY chatEntryChanged)
402+ Q_ENUMS(Role)
403+
404+public:
405+ enum Role {
406+ IdentifierRole = Qt::UserRole,
407+ AliasRole,
408+ RolesRole,
409+ StateRole
410+ };
411+
412+ explicit ParticipantsModel(QObject *parent = 0);
413+ ~ParticipantsModel();
414+
415+ Q_INVOKABLE virtual bool canFetchMore(const QModelIndex &parent = QModelIndex()) const;
416+ Q_INVOKABLE virtual void fetchMore(const QModelIndex &parent = QModelIndex());
417+ virtual QHash<int, QByteArray> roleNames() const;
418+ virtual QVariant data(const QModelIndex &index, int role) const;
419+ int rowCount(const QModelIndex &parent = QModelIndex()) const;
420+
421+ Q_INVOKABLE virtual QVariant get(int row) const;
422+
423+ Q_INVOKABLE void setChatEntry(ChatEntry *entry);
424+ ChatEntry* chatEntry() const;
425+
426+ void addParticipantCache(Participant *participant);
427+
428+ void classBegin();
429+ void componentComplete();
430+
431+private Q_SLOTS:
432+ void addParticipant(Participant *participant);
433+ void removeParticipant(Participant *participant);
434+
435+Q_SIGNALS:
436+ void countChanged();
437+ void canFetchMoreChanged();
438+ void chatEntryChanged();
439+
440+protected:
441+ bool lessThan(const QString &left, const QString &right) const;
442+ int positionForItem(const QString &item, bool cache = false) const;
443+
444+private:
445+ QHash<int, QByteArray> mRoles;
446+ QList<Participant*> mParticipants;
447+ bool mWaitingForQml;
448+ bool mCanFetchMore;
449+ ChatEntry *mChatEntry;
450+ QList<Participant*> mParticipantsCache;
451+};
452+
453+#endif // PARTICIPANTSMODEL_H
454
455=== added directory 'accounts'
456=== added file 'accounts/CMakeLists.txt'
457--- accounts/CMakeLists.txt 1970-01-01 00:00:00 +0000
458+++ accounts/CMakeLists.txt 2017-04-05 14:05:16 +0000
459@@ -0,0 +1,2 @@
460+add_subdirectory(sip)
461+add_subdirectory(irc)
462
463=== added directory 'accounts/common'
464=== added file 'accounts/common/DynamicField.qml'
465--- accounts/common/DynamicField.qml 1970-01-01 00:00:00 +0000
466+++ accounts/common/DynamicField.qml 2017-04-05 14:05:16 +0000
467@@ -0,0 +1,172 @@
468+/*
469+ * Copyright (C) 2017 Canonical, Ltd.
470+ *
471+ * Authors:
472+ * Renato Araujo Oliveira Filho <renato.filho@canonical.com>
473+ *
474+ * This file is part of telephony-service.
475+ *
476+ * telephony-service is free software; you can redistribute it and/or modify
477+ * it under the terms of the GNU General Public License as published by
478+ * the Free Software Foundation; version 3.
479+ *
480+ * telephony-service is distributed in the hope that it will be useful,
481+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
482+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
483+ * GNU General Public License for more details.
484+ *
485+ * You should have received a copy of the GNU General Public License
486+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
487+ */
488+
489+import QtQuick 2.0
490+import Ubuntu.Components 1.3
491+
492+Loader {
493+ id: root
494+
495+ property var model
496+ readonly property string value: status === Loader.Ready ? item.value : ""
497+ readonly property bool isEmpty: status === Loader.Ready ? item.isEmpty : true
498+
499+ Component {
500+ id: stringField
501+
502+ TextField {
503+ id: field
504+
505+ property alias label: field.placeholderText
506+ property string defaultValue
507+
508+ readonly property alias value: field.text
509+ readonly property bool isEmpty: value === "" || (defaultValue && value === model.defaultValue)
510+
511+ text: defaultValue ? defaultValue : ""
512+ }
513+ }
514+
515+ Component {
516+ id: booleanField
517+
518+ Item {
519+ property alias label: fieldLabel.text
520+ property string defaultValue
521+
522+ readonly property string value: fieldValue.checked ? "true" : "false"
523+ readonly property bool isEmpty: (defaultValue && value === model.defaultValue)
524+
525+ height: fieldValue.height
526+
527+ Label {
528+ id: fieldLabel
529+
530+ anchors {
531+ left: parent.left
532+ right: fieldValue.left
533+ verticalCenter: parent.verticalCenter
534+ }
535+ }
536+ Switch {
537+ id: fieldValue
538+
539+ anchors {
540+ right: parent.right
541+ verticalCenter: parent.verticalCenter
542+ }
543+ checked: (defaultValue && (defaultValue === 'true'))
544+ }
545+ }
546+ }
547+
548+ Component {
549+ id: numericField
550+
551+ TextField {
552+ id: field
553+
554+ property alias label: field.placeholderText
555+ property string defaultValue
556+
557+ readonly property alias value: field.text
558+ readonly property bool isEmpty: value === "" || (defaultValue && (value === defaultValue))
559+
560+ inputMethodHints: Qt.ImhDigitsOnly
561+ validator: IntValidator {}
562+ }
563+ }
564+
565+ Component{
566+ id: passwordField
567+
568+ Item {
569+ property alias label: field.placeholderText
570+ readonly property alias value: field.text
571+ readonly property bool isEmpty: value === ""
572+
573+ height: field.height + showPasswordCheck.height
574+ TextField {
575+ id: field
576+
577+ echoMode: showPasswordCheck.checked ? TextInput.Normal : TextInput.Password
578+ anchors {
579+ left: parent.left
580+ right: parent.right
581+ }
582+ onTextChanged: root.changed()
583+ }
584+ CheckBox {
585+ id: showPasswordCheck
586+ anchors {
587+ left: field.left
588+ top: field.bottom
589+ topMargin: units.gu(1)
590+ }
591+ }
592+ Label {
593+ text: i18n.tr("Show Password")
594+ anchors {
595+ top: showPasswordCheck.top
596+ left: showPasswordCheck.right
597+ leftMargin: units.gu(1)
598+ right: field.right
599+ }
600+ }
601+ }
602+ }
603+
604+
605+ sourceComponent: {
606+ if (!model)
607+ return null
608+
609+ if (!model.inputType) {
610+ console.warn("Model does not contain 'inputType'")
611+ return null
612+ }
613+
614+ switch (model.inputType) {
615+ case 'string':
616+ return stringField
617+ case 'boolean':
618+ return booleanField
619+ case 'numeric':
620+ return numericField
621+ case 'password':
622+ return passwordField
623+ }
624+ }
625+
626+ Binding {
627+ target: root.item
628+ property: "label"
629+ value: model.label
630+ when: root.status == Loader.Ready
631+ }
632+
633+ Binding {
634+ target: root.item
635+ property: "defaultValue"
636+ value: model.defaultValue
637+ when: model.hasOwnProperty('defaultValue') && root.status == Loader.Ready
638+ }
639+}
640
641=== added file 'accounts/common/Main.qml'
642--- accounts/common/Main.qml 1970-01-01 00:00:00 +0000
643+++ accounts/common/Main.qml 2017-04-05 14:05:16 +0000
644@@ -0,0 +1,106 @@
645+/*
646+ * Copyright (C) 2017 Canonical, Ltd.
647+ *
648+ * Authors:
649+ * Renato Araujo Oliveira Filho <renato.filho@canonical.com>
650+ *
651+ * This file is part of telephony-service.
652+ *
653+ * telephony-service is free software; you can redistribute it and/or modify
654+ * it under the terms of the GNU General Public License as published by
655+ * the Free Software Foundation; version 3.
656+ *
657+ * telephony-service is distributed in the hope that it will be useful,
658+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
659+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
660+ * GNU General Public License for more details.
661+ *
662+ * You should have received a copy of the GNU General Public License
663+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
664+ */
665+
666+import QtQuick 2.0
667+import Ubuntu.Components 1.3
668+import Ubuntu.OnlineAccounts.Plugin 1.0
669+
670+Item {
671+ id: rootFlickable
672+
673+ property int keyboardSize: Qt.inputMethod.visible ? Qt.inputMethod.keyboardRectangle.height : 0
674+
675+ signal finished
676+
677+ anchors.fill: parent
678+
679+ Flickable {
680+ anchors {
681+ left: parent.left
682+ right: parent.right
683+ top: parent.top
684+ bottom: btnConfirm.top
685+ }
686+ contentWidth: parent.width
687+ contentHeight: editPageLoader.item.height + keyboardSize
688+ clip: true
689+
690+ Loader {
691+ id: editPageLoader
692+ sourceComponent: account.accountId != 0 ? existingAccountComponent : newAccountComponent
693+ anchors {
694+ left: parent.left
695+ right: parent.right
696+ }
697+
698+ Connections {
699+ target: editPageLoader.item
700+ onFinished: rootFlickable.finished()
701+ }
702+ }
703+
704+ Component {
705+ id: newAccountComponent
706+ NewAccount {}
707+ }
708+
709+ Component {
710+ id: existingAccountComponent
711+ Options {}
712+ }
713+ }
714+
715+ Button {
716+ id: btnConfirm
717+ text: i18n.tr("Continue")
718+ color: UbuntuColors.orange
719+ anchors {
720+ left: parent.left
721+ right: parent.right
722+ bottom: btnCancel.top
723+ margins: units.gu(2)
724+ bottomMargin: units.gu(1)
725+ }
726+ enabled: editPageLoader.item && editPageLoader.item.isValid
727+ onClicked: {
728+ if (editPageLoader.item)
729+ editPageLoader.item.confirm()
730+ }
731+
732+ }
733+
734+ Button {
735+ id: btnCancel
736+
737+ text: i18n.tr("Cancel")
738+ anchors {
739+ left: parent.left
740+ right: parent.right
741+ bottom: parent.bottom
742+ margins: units.gu(2)
743+ bottomMargin: units.gu(1)
744+ }
745+ onClicked: {
746+ if (editPageLoader.item)
747+ editPageLoader.item.cancel()
748+ }
749+ }
750+}
751
752=== added file 'accounts/common/NewAccountInterface.qml'
753--- accounts/common/NewAccountInterface.qml 1970-01-01 00:00:00 +0000
754+++ accounts/common/NewAccountInterface.qml 2017-04-05 14:05:16 +0000
755@@ -0,0 +1,314 @@
756+/*
757+ * Copyright (C) 2017 Canonical, Ltd.
758+ *
759+ * Authors:
760+ * Renato Araujo Oliveira Filho <renato.filho@canonical.com>
761+ *
762+ * This file is part of telephony-service.
763+ *
764+ * telephony-service is free software; you can redistribute it and/or modify
765+ * it under the terms of the GNU General Public License as published by
766+ * the Free Software Foundation; version 3.
767+ *
768+ * telephony-service is distributed in the hope that it will be useful,
769+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
770+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
771+ * GNU General Public License for more details.
772+ *
773+ * You should have received a copy of the GNU General Public License
774+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
775+ */
776+
777+import QtQuick 2.0
778+import Ubuntu.Components 1.3
779+import Ubuntu.OnlineAccounts 0.1
780+
781+Item {
782+ id: root
783+
784+ readonly property string keyPrefix: "telepathy/"
785+ readonly property var accountObjectHandle: account ? account.objectHandle : undefined
786+ readonly property alias isValid: paramsRepeater.fieldHasValues
787+
788+ property string manager
789+ property string protocol
790+ property string icon
791+ property var params
792+ property var advancedParams
793+ property bool hasCrendentials: true
794+
795+
796+ signal finished
797+ height: fields.childrenRect.height +
798+ units.gu(10)
799+
800+ function getAccountService() {
801+ var service = serviceModel.get(0, "accountServiceHandle")
802+ if (!service) {
803+ console.warn("No service handle from model")
804+ return null
805+ }
806+
807+ return accountServiceComponent.createObject(null,
808+ {"objectHandle": service})
809+ }
810+
811+ // virual
812+ function extendedSettings(inputFields)
813+ {
814+ return {}
815+ //Helper class to be extended by derived class
816+ }
817+
818+ // virtual
819+ function formatDisplayName(inputFields)
820+ {
821+ return inputFields['account']
822+ // Helper function that allow the derived class to format a different display name
823+ }
824+
825+ function saveServiceSettings(serviceIM, creds) {
826+ var settingsIM = serviceIM.settings
827+ var inputFields = {}
828+
829+ settingsIM[root.keyPrefix + 'manager'] = root.manager
830+ settingsIM[root.keyPrefix + 'protocol'] = root.protocol
831+ settingsIM[root.keyPrefix + 'Icon'] = root.icon
832+
833+ // basic fields
834+ for (var i=0; i < paramsRepeater.count; i++) {
835+ var fieldData = root.params[i]
836+ var field = paramsRepeater.itemAt(i)
837+ var fieldParamName = root.keyPrefix + 'param-' + fieldData.name
838+
839+ if (field.isEmpty) {
840+ delete settingsIM[fieldParamName]
841+ } else {
842+ inputFields[fieldData.name] = field.value
843+ if (fieldData.store) {
844+ settingsIM[fieldParamName] = field.value
845+ }
846+ }
847+ }
848+
849+ // advanced fields
850+ for (var i=0; i < advancedParamsRepeater.count; i++) {
851+ var xFieldData = root.advancedParams[i]
852+ var xField = advancedParamsRepeater.itemAt(i)
853+ var xFieldParamName = root.keyPrefix + 'param-' + xFieldData.name
854+
855+ if (xField.isEmpty) {
856+ delete settingsIM[xFieldParamName]
857+ } else {
858+ inputFields[xFieldData.name] = xField.value
859+
860+ if (xFieldData.store) {
861+ settingsIM[xFieldParamName] = xField.value
862+ }
863+ }
864+ }
865+
866+
867+ var xSettings = extendedSettings(inputFields)
868+ for (var key in xSettings) {
869+ settingsIM[root.keyPrefix + key] = xSettings[key]
870+ }
871+
872+ account.updateDisplayName(formatDisplayName(inputFields))
873+
874+ serviceIM.updateSettings(settingsIM)
875+ //serviceIM.credentials = creds
876+ //serviceIM.updateServiceEnabled(true)
877+ }
878+
879+ function continueAccountSave(creds) {
880+ var imService = root.getAccountService()
881+ if (!imService) {
882+ console.warn("Fail to retrieve account service")
883+ return
884+ }
885+
886+ root.saveServiceSettings(imService, creds)
887+ if (creds)
888+ globalAccountService.credentials = creds
889+ globalAccountService.updateServiceEnabled(true)
890+
891+ account.synced.connect(root.finished)
892+ account.sync()
893+ }
894+
895+ function credentialsStored() {
896+ if (creds.credentialsId === 0) {
897+ console.warn("Credentials not stored correct")
898+ return
899+ }
900+
901+ var imService = root.getAccountService()
902+ if (!imService) {
903+ console.warn("Fail to retrieve account service")
904+ return
905+ }
906+
907+ continueAccountSave(creds)
908+ }
909+
910+ function parseCrendentials() {
911+ var credentials = {'userName': '', 'password': ''}
912+
913+ for (var i=0; i < paramsRepeater.count; i++) {
914+ var fieldData = root.params[i]
915+ var field = paramsRepeater.itemAt(i)
916+
917+ if (fieldData.name === 'account')
918+ credentials['userName'] = field.value
919+
920+ if (fieldData.name === 'password')
921+ credentials['password'] = field.value
922+ }
923+
924+ return credentials
925+ }
926+
927+ function cancel() {
928+ account.removed.connect(root.finished)
929+ account.remove(Account.RemoveCredentials)
930+ }
931+
932+ function confirm() {
933+ var info = root.parseCrendentials()
934+ // save account
935+ account.updateDisplayName(info.userName)
936+ if (root.hasCrendentials) {
937+ creds.userName = info.userName
938+ creds.secret = info.password
939+ creds.sync()
940+ } else {
941+ continueAccountSave(null)
942+ }
943+ }
944+
945+ Column {
946+ id: fields
947+
948+ anchors {
949+ top: parent.top
950+ topMargin: units.gu(5)
951+ left: parent.left
952+ right: parent.right
953+ }
954+ height: childrenRect.height
955+ spacing: units.gu(2)
956+
957+ Icon {
958+ anchors.horizontalCenter: fields.horizontalCenter
959+ name: root.icon
960+ }
961+
962+ Repeater {
963+ id: paramsRepeater
964+
965+ property bool fieldHasValues: false
966+
967+ function checkFieldsHasValues()
968+ {
969+ var hasEmptyField = false
970+ for (var i = 0; i < paramsRepeater.count; i++) {
971+
972+ var child = paramsRepeater.itemAt(i)
973+ if (child && child.isEmpty) {
974+ hasEmptyField = true
975+ break
976+ }
977+ }
978+ fieldHasValues = !hasEmptyField
979+ }
980+
981+ width: parent.width
982+ model: root.params
983+ DynamicField {
984+ model: modelData
985+ anchors{
986+ left: parent.left
987+ right: parent.right
988+ margins: units.gu(4)
989+ }
990+ onValueChanged: paramsRepeater.checkFieldsHasValues()
991+ }
992+ }
993+
994+ Item {
995+ id: div
996+
997+ anchors{
998+ left: parent.left
999+ right: parent.right
1000+ }
1001+ height: units.gu(3)
1002+ visible: root.advancedParams.length > 0
1003+ }
1004+
1005+ Label {
1006+ id: advancedParamsTitle
1007+
1008+ anchors{
1009+ left: parent.left
1010+ right: parent.right
1011+ margins: units.gu(4)
1012+ }
1013+ visible: root.advancedParams.length > 0
1014+ text: i18n.tr("Advanced Options")
1015+ textSize: Label.Medium
1016+ }
1017+
1018+ Repeater {
1019+ id: advancedParamsRepeater
1020+
1021+ width: parent.width
1022+ model: root.advancedParams
1023+ DynamicField {
1024+ model: modelData
1025+ anchors{
1026+ left: parent.left
1027+ right: parent.right
1028+ margins: units.gu(4)
1029+ }
1030+ }
1031+ }
1032+ }
1033+
1034+ AccountService {
1035+ id: globalAccountService
1036+
1037+ objectHandle: account.accountServiceHandle
1038+ autoSync: false
1039+ }
1040+
1041+ Credentials {
1042+ id: creds
1043+
1044+ caption: account.provider.id
1045+ acl: "*" // untill later
1046+ storeSecret: true
1047+ onCredentialsIdChanged: {
1048+ console.debug("Credetials id changed")
1049+ root.credentialsStored()
1050+ }
1051+ }
1052+
1053+ // necessary to store settings on the "IM" service
1054+ AccountServiceModel {
1055+ id: serviceModel
1056+
1057+ includeDisabled: true
1058+ account: root.accountObjectHandle
1059+ serviceType: "IM"
1060+ }
1061+
1062+ Component {
1063+ id: accountServiceComponent
1064+
1065+ AccountService {
1066+ autoSync: false
1067+ }
1068+ }
1069+}
1070
1071=== added directory 'accounts/irc'
1072=== added file 'accounts/irc/CMakeLists.txt'
1073--- accounts/irc/CMakeLists.txt 1970-01-01 00:00:00 +0000
1074+++ accounts/irc/CMakeLists.txt 2017-04-05 14:05:16 +0000
1075@@ -0,0 +1,2 @@
1076+add_subdirectory(qml)
1077+add_subdirectory(data)
1078
1079=== added directory 'accounts/irc/data'
1080=== added file 'accounts/irc/data/CMakeLists.txt'
1081--- accounts/irc/data/CMakeLists.txt 1970-01-01 00:00:00 +0000
1082+++ accounts/irc/data/CMakeLists.txt 2017-04-05 14:05:16 +0000
1083@@ -0,0 +1,5 @@
1084+file(GLOB PROVIDER_FILES *.provider)
1085+install(FILES ${PROVIDER_FILES} DESTINATION share/accounts/providers/)
1086+
1087+file(GLOB SERVICE_FILES *.service)
1088+install(FILES ${SERVICE_FILES} DESTINATION share/accounts/services/)
1089
1090=== added file 'accounts/irc/data/telephony-irc-im.service'
1091--- accounts/irc/data/telephony-irc-im.service 1970-01-01 00:00:00 +0000
1092+++ accounts/irc/data/telephony-irc-im.service 2017-04-05 14:05:16 +0000
1093@@ -0,0 +1,22 @@
1094+<?xml version="1.0" encoding="UTF-8"?>
1095+<service id="telephony-irc-im">
1096+ <type>IM</type>
1097+ <name>IRC</name>
1098+ <icon>irc</icon>
1099+ <provider>telephony-irc</provider>
1100+
1101+ <!-- default settings (account settings have precedence over these) -->
1102+ <template>
1103+ <group name="telepathy">
1104+ <setting name="manager">irc</setting>
1105+ <setting name="protocol">irc</setting>
1106+ <setting name="ConnectAutomatically">false</setting>
1107+ <setting type="s" name="AutomaticPresence">1;offline;;</setting>
1108+ </group>
1109+ <group name="auth">
1110+ <setting name="method">password</setting>
1111+ <setting name="mechanism">password</setting>
1112+ </group>
1113+ </template>
1114+
1115+</service>
1116
1117=== added file 'accounts/irc/data/telephony-irc.provider'
1118--- accounts/irc/data/telephony-irc.provider 1970-01-01 00:00:00 +0000
1119+++ accounts/irc/data/telephony-irc.provider 2017-04-05 14:05:16 +0000
1120@@ -0,0 +1,5 @@
1121+<?xml version="1.0" encoding="UTF-8" ?>
1122+<provider id="telephony-irc">
1123+ <name>IRC</name>
1124+ <icon>irc</icon>
1125+</provider>
1126
1127=== added directory 'accounts/irc/qml'
1128=== added file 'accounts/irc/qml/AccountInfo.qml'
1129=== added file 'accounts/irc/qml/CMakeLists.txt'
1130--- accounts/irc/qml/CMakeLists.txt 1970-01-01 00:00:00 +0000
1131+++ accounts/irc/qml/CMakeLists.txt 2017-04-05 14:05:16 +0000
1132@@ -0,0 +1,13 @@
1133+file(GLOB QML_PLUGIN_FILES *.qml)
1134+
1135+# The path (including plug-in name) where the QML files are installed
1136+set(QML_PLUGIN_INSTALL_DIR share/accounts/qml-plugins/telephony-irc/)
1137+
1138+# Do not install symbolic links
1139+set (QML_PLUGIN_RESOLVED_FILES "")
1140+foreach (QML_PLUGIN_FILE ${QML_PLUGIN_FILES})
1141+ get_filename_component(resolvedFile "${QML_PLUGIN_FILE}" REALPATH)
1142+ list (APPEND QML_PLUGIN_RESOLVED_FILES "${resolvedFile}")
1143+endforeach()
1144+
1145+install(FILES ${QML_PLUGIN_RESOLVED_FILES} DESTINATION ${QML_PLUGIN_INSTALL_DIR})
1146
1147=== added symlink 'accounts/irc/qml/DynamicField.qml'
1148=== target is u'../../common/DynamicField.qml'
1149=== added symlink 'accounts/irc/qml/Main.qml'
1150=== target is u'../../common/Main.qml'
1151=== added file 'accounts/irc/qml/NewAccount.qml'
1152--- accounts/irc/qml/NewAccount.qml 1970-01-01 00:00:00 +0000
1153+++ accounts/irc/qml/NewAccount.qml 2017-04-05 14:05:16 +0000
1154@@ -0,0 +1,63 @@
1155+/*
1156+ * Copyright (C) 2017 Canonical, Ltd.
1157+ *
1158+ * Authors:
1159+ * Renato Araujo Oliveira Filho <renato.filho@canonical.com>
1160+ *
1161+ * This file is part of telephony-service.
1162+ *
1163+ * telephony-service is free software; you can redistribute it and/or modify
1164+ * it under the terms of the GNU General Public License as published by
1165+ * the Free Software Foundation; version 3.
1166+ *
1167+ * telephony-service is distributed in the hope that it will be useful,
1168+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1169+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1170+ * GNU General Public License for more details.
1171+ *
1172+ * You should have received a copy of the GNU General Public License
1173+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1174+ */
1175+
1176+import QtQuick 2.0
1177+
1178+NewAccountInterface {
1179+ id: root
1180+
1181+ anchors {
1182+ left: parent.left
1183+ right: parent.right
1184+ verticalCenter: parent.verticalCenter
1185+ }
1186+
1187+ manager: 'irc'
1188+ protocol: 'irc'
1189+ icon: 'irc'
1190+ hasCrendentials: false
1191+ params: [
1192+ {'name': 'server', 'inputType': 'string', 'label': i18n.tr('Network. (Eg: chat.freenode.net)'), 'store': true},
1193+ {'name': 'nickname', 'inputType': 'string', 'label': i18n.tr('Nickname'), 'store': true},
1194+ ]
1195+ advancedParams: [
1196+ {'name': 'port', 'inputType': 'numeric', 'label': i18n.tr('Port'), 'store': true},
1197+ {'name': 'use-ssl', 'inputType': 'boolean', 'label': i18n.tr('Use ssl'), 'store': true},
1198+ {'name': 'verify-ssl-cert', 'inputType': 'boolean', 'label': i18n.tr('Verify ssl certificate'), 'store': true},
1199+ {'name': 'username', 'inputType': 'string', 'label': i18n.tr('Username'), 'store': true},
1200+ {'name': 'password', 'inputType': 'password', 'label': i18n.tr('Password'), 'store': true},
1201+ {'name': 'fullname', 'inputType': 'string', 'label': i18n.tr('Real name'), 'store': true},
1202+ ]
1203+
1204+ function extendedSettings(inputFields)
1205+ {
1206+ var settings = {}
1207+ settings['param-account'] = inputFields['nickname'] + "@" + inputFields['server']
1208+ if (settings['param-port'] == "")
1209+ settings['param-port'] = "6667"
1210+ return settings
1211+ }
1212+
1213+ function formatDisplayName(inputFields)
1214+ {
1215+ return inputFields['nickname'] + "@" + inputFields['server']
1216+ }
1217+}
1218
1219=== added symlink 'accounts/irc/qml/NewAccountInterface.qml'
1220=== target is u'../../common/NewAccountInterface.qml'
1221=== added directory 'accounts/sip'
1222=== added file 'accounts/sip/CMakeLists.txt'
1223--- accounts/sip/CMakeLists.txt 1970-01-01 00:00:00 +0000
1224+++ accounts/sip/CMakeLists.txt 2017-04-05 14:05:16 +0000
1225@@ -0,0 +1,2 @@
1226+add_subdirectory(qml)
1227+add_subdirectory(data)
1228
1229=== added directory 'accounts/sip/data'
1230=== added file 'accounts/sip/data/CMakeLists.txt'
1231--- accounts/sip/data/CMakeLists.txt 1970-01-01 00:00:00 +0000
1232+++ accounts/sip/data/CMakeLists.txt 2017-04-05 14:05:16 +0000
1233@@ -0,0 +1,5 @@
1234+file(GLOB PROVIDER_FILES *.provider)
1235+install(FILES ${PROVIDER_FILES} DESTINATION share/accounts/providers/)
1236+
1237+file(GLOB SERVICE_FILES *.service)
1238+install(FILES ${SERVICE_FILES} DESTINATION share/accounts/services/)
1239
1240=== added file 'accounts/sip/data/telephony-sip-im.service'
1241--- accounts/sip/data/telephony-sip-im.service 1970-01-01 00:00:00 +0000
1242+++ accounts/sip/data/telephony-sip-im.service 2017-04-05 14:05:16 +0000
1243@@ -0,0 +1,22 @@
1244+<?xml version="1.0" encoding="UTF-8"?>
1245+<service id="telephony-sip-im">
1246+ <type>IM</type>
1247+ <name>SIP</name>
1248+ <icon>sip</icon>
1249+ <provider>telephony-sip</provider>
1250+
1251+ <!-- default settings (account settings have precedence over these) -->
1252+ <template>
1253+ <group name="telepathy">
1254+ <setting name="manager">sofiasip</setting>
1255+ <setting name="protocol">sip</setting>
1256+ <setting name="ConnectAutomatically">true</setting>
1257+ <setting type="s" name="AutomaticPresence">2;available;;</setting>
1258+ </group>
1259+ <group name="auth">
1260+ <setting name="method">password</setting>
1261+ <setting name="mechanism">password</setting>
1262+ </group>
1263+ </template>
1264+
1265+</service>
1266
1267=== added file 'accounts/sip/data/telephony-sip.provider'
1268--- accounts/sip/data/telephony-sip.provider 1970-01-01 00:00:00 +0000
1269+++ accounts/sip/data/telephony-sip.provider 2017-04-05 14:05:16 +0000
1270@@ -0,0 +1,5 @@
1271+<?xml version="1.0" encoding="UTF-8" ?>
1272+<provider id="telephony-sip">
1273+ <name>SIP</name>
1274+ <icon>sip</icon>
1275+</provider>
1276
1277=== added directory 'accounts/sip/qml'
1278=== added file 'accounts/sip/qml/AccountInfo.qml'
1279=== added file 'accounts/sip/qml/CMakeLists.txt'
1280--- accounts/sip/qml/CMakeLists.txt 1970-01-01 00:00:00 +0000
1281+++ accounts/sip/qml/CMakeLists.txt 2017-04-05 14:05:16 +0000
1282@@ -0,0 +1,13 @@
1283+file(GLOB QML_PLUGIN_FILES *.qml)
1284+
1285+# The path (including plug-in name) where the QML files are installed
1286+set(QML_PLUGIN_INSTALL_DIR share/accounts/qml-plugins/telephony-sip/)
1287+
1288+# Do not install symbolic links
1289+set (QML_PLUGIN_RESOLVED_FILES "")
1290+foreach (QML_PLUGIN_FILE ${QML_PLUGIN_FILES})
1291+ get_filename_component(resolvedFile "${QML_PLUGIN_FILE}" REALPATH)
1292+ list (APPEND QML_PLUGIN_RESOLVED_FILES "${resolvedFile}")
1293+endforeach()
1294+
1295+install(FILES ${QML_PLUGIN_RESOLVED_FILES} DESTINATION ${QML_PLUGIN_INSTALL_DIR})
1296
1297=== added symlink 'accounts/sip/qml/DynamicField.qml'
1298=== target is u'../../common/DynamicField.qml'
1299=== added symlink 'accounts/sip/qml/Main.qml'
1300=== target is u'../../common/Main.qml'
1301=== added file 'accounts/sip/qml/NewAccount.qml'
1302--- accounts/sip/qml/NewAccount.qml 1970-01-01 00:00:00 +0000
1303+++ accounts/sip/qml/NewAccount.qml 2017-04-05 14:05:16 +0000
1304@@ -0,0 +1,48 @@
1305+/*
1306+ * Copyright (C) 2017 Canonical, Ltd.
1307+ *
1308+ * Authors:
1309+ * Renato Araujo Oliveira Filho <renato.filho@canonical.com>
1310+ *
1311+ * This file is part of telephony-service.
1312+ *
1313+ * telephony-service is free software; you can redistribute it and/or modify
1314+ * it under the terms of the GNU General Public License as published by
1315+ * the Free Software Foundation; version 3.
1316+ *
1317+ * telephony-service is distributed in the hope that it will be useful,
1318+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1319+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1320+ * GNU General Public License for more details.
1321+ *
1322+ * You should have received a copy of the GNU General Public License
1323+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1324+ */
1325+
1326+import QtQuick 2.0
1327+
1328+NewAccountInterface {
1329+ id: root
1330+
1331+ manager: 'sofiasip'
1332+ protocol: 'sip'
1333+ icon: 'sip'
1334+ hasCrendentials: false
1335+ params: [
1336+ {'name': 'account', 'inputType': 'string', 'label': i18n.tr('Sip Id. (Eg: user@my.sip.net)'), 'store': true},
1337+ {'name': 'password', 'inputType': 'password', 'label': i18n.tr('Password'), 'store': true}
1338+ ]
1339+ advancedParams: [
1340+ {'name': 'discover-stun', 'inputType': 'boolean', 'label': i18n.tr('Discover the STUN server automatically'), 'store': true, 'defaultValue' : 'false' },
1341+ {'name': 'stun-server', 'inputType': 'string', 'label': i18n.tr('STUN server'), 'store': true},
1342+ {'name': 'stun-port', 'inputType': 'numeric', 'label': i18n.tr('STUN port'), 'store': true},
1343+ {'name': 'discover-binding', 'inputType': 'boolean', 'label': i18n.tr('Divscover Binding'), 'store': true, 'defaultValue': 'true'},
1344+ {'name': 'proxy-host', 'inputType': 'string', 'label': i18n.tr('Proxy server'), 'store': true},
1345+ {'name': 'port', 'inputType': 'numeric', 'label': i18n.tr('Proxy port'), 'store': true},
1346+ {'name': 'keepalive-mechanism', 'inputType': 'string', 'label': i18n.tr('Keep alive mechanism'), 'store': true, 'defaultValue': 'auto'},
1347+ {'name': 'keepalive-interval', 'inputType': 'numeric', 'label': i18n.tr('Keep alive interval'), 'store': true},
1348+ {'name': 'auth-user', 'inputType': 'string', 'label': i18n.tr('Authentication username'), 'store': true},
1349+ {'name': 'transport', 'inputType': 'string', 'label': i18n.tr('Transport'), 'store': true, 'defaultValue': 'auto'},
1350+ {'name': 'loose-routing', 'inputType': 'boolean', 'label': i18n.tr('Loose Routing'), 'store': true, 'defaultValue': 'false'}
1351+ ]
1352+}
1353
1354=== added symlink 'accounts/sip/qml/NewAccountInterface.qml'
1355=== target is u'../../common/NewAccountInterface.qml'
1356=== modified file 'approver/approver.cpp'
1357--- approver/approver.cpp 2016-11-23 19:28:18 +0000
1358+++ approver/approver.cpp 2017-04-05 14:05:16 +0000
1359@@ -1,5 +1,5 @@
1360 /*
1361- * Copyright (C) 2012-2016 Canonical, Ltd.
1362+ * Copyright (C) 2012-2017 Canonical, Ltd.
1363 *
1364 * Authors:
1365 * Tiago Salem Herrmann <tiago.herrmann@canonical.com>
1366@@ -27,10 +27,12 @@
1367 #include "chatmanager.h"
1368 #include "config.h"
1369 #include "contactutils.h"
1370+#include "contactwatcher.h"
1371 #include "greetercontacts.h"
1372 #include "ringtone.h"
1373 #include "callmanager.h"
1374 #include "callentry.h"
1375+#include "protocol.h"
1376 #include "protocolmanager.h"
1377 #include "tonegenerator.h"
1378 #include "telepathyhelper.h"
1379@@ -315,11 +317,13 @@
1380
1381 mChannels.remove(pr);
1382
1383+ QString id = ContactWatcher::normalizeIdentifier(contact->id(), true);
1384+
1385 // and now set up the contact matching for either greeter mode or regular mode
1386 if (GreeterContacts::isGreeterMode()) {
1387 // show the snap decision right away because contact info might never arrive
1388 showSnapDecision(dispatchOp, channel);
1389- GreeterContacts::instance()->setContactFilter(QContactPhoneNumber::match(contact->id()));
1390+ GreeterContacts::instance()->setContactFilter(QContactPhoneNumber::match(id));
1391 } else {
1392 AccountEntry *account = TelepathyHelper::instance()->accountForConnection(callChannel->connection());
1393 if (!account) {
1394@@ -329,7 +333,7 @@
1395
1396 // try to match the contact info
1397 QContactFetchRequest *request = new QContactFetchRequest(this);
1398- request->setFilter(QContactPhoneNumber::match(contact->id()));
1399+ request->setFilter(QContactPhoneNumber::match(id));
1400
1401 // lambda function to update the notification
1402 QObject::connect(request, &QContactAbstractRequest::stateChanged, [this, request, dispatchOp, channel](QContactAbstractRequest::State state) {
1403@@ -350,14 +354,9 @@
1404 showSnapDecision(dispatchOp, channel, contact);
1405 });
1406
1407- // FIXME: For accounts not based on phone numbers, don't try to match contacts for now
1408- if (account->type() == AccountEntry::PhoneAccount) {
1409- request->setManager(ContactUtils::sharedManager());
1410- request->start();
1411- } else {
1412- // just emit the signal to pretend we did a contact search
1413- Q_EMIT request->stateChanged(QContactAbstractRequest::FinishedState);
1414- }
1415+ // FIXME: For accounts not based on phone numbers, check what to do
1416+ request->setManager(ContactUtils::sharedManager());
1417+ request->start();
1418 }
1419 }
1420
1421@@ -440,42 +439,46 @@
1422 data->channel = channel;
1423 bool unknownNumber = false;
1424
1425+ QString id = ContactWatcher::normalizeIdentifier(telepathyContact->id(), true);
1426+
1427 AccountEntry *account = TelepathyHelper::instance()->accountForConnection(channel->connection());
1428 if (!account) {
1429 qCritical() << "Call exists with no account for connection";
1430 return false;
1431 }
1432
1433+ bool supportsText = (account->protocolInfo()->features() & Protocol::TextChats);
1434+
1435 mCachedBody = QString();
1436
1437 if (account->type() == AccountEntry::PhoneAccount &&
1438 TelepathyHelper::instance()->multiplePhoneAccounts()) {
1439 mCachedBody = QString::fromUtf8(C::gettext("On [%1]")).arg(account->displayName());
1440 mCachedBody += "\n";
1441- if (!telepathyContact->id().isEmpty()) {
1442- if (telepathyContact->id().startsWith(OFONO_PRIVATE_NUMBER)) {
1443+ if (!id.isEmpty()) {
1444+ if (id.startsWith(OFONO_PRIVATE_NUMBER)) {
1445 mCachedBody += QString::fromUtf8(C::gettext("Private number"));
1446 unknownNumber = true;
1447- } else if (telepathyContact->id().startsWith(OFONO_UNKNOWN_NUMBER)) {
1448+ } else if (id.startsWith(OFONO_UNKNOWN_NUMBER)) {
1449 mCachedBody += QString::fromUtf8(C::gettext("Unknown number"));
1450 unknownNumber = true;
1451 } else {
1452- mCachedBody += telepathyContact->id();
1453+ mCachedBody += id;
1454 }
1455 } else {
1456 mCachedBody += C::gettext("Caller number is not available");
1457 unknownNumber = true;
1458 }
1459 } else {
1460- if (!telepathyContact->id().isEmpty()) {
1461- if (telepathyContact->id().startsWith(OFONO_PRIVATE_NUMBER)) {
1462+ if (!id.isEmpty()) {
1463+ if (id.startsWith(OFONO_PRIVATE_NUMBER)) {
1464 mCachedBody = QString::fromUtf8(C::gettext("Calling from private number"));
1465 unknownNumber = true;
1466- } else if (telepathyContact->id().startsWith(OFONO_UNKNOWN_NUMBER)) {
1467+ } else if (id.startsWith(OFONO_UNKNOWN_NUMBER)) {
1468 mCachedBody = QString::fromUtf8(C::gettext("Calling from unknown number"));
1469 unknownNumber = true;
1470 } else {
1471- mCachedBody = QString::fromUtf8(C::gettext("Calling from %1")).arg(telepathyContact->id());
1472+ mCachedBody = QString::fromUtf8(C::gettext("Calling from %1")).arg(id);
1473 }
1474 } else {
1475 mCachedBody = C::gettext("Caller number is not available");
1476@@ -544,7 +547,7 @@
1477 data,
1478 delete_event_data);
1479
1480- if (!unknownNumber) {
1481+ if (!unknownNumber && supportsText) {
1482 notify_notification_add_action(notification,
1483 "action_decline_expansion",
1484 C::gettext("Message & decline"),
1485
1486=== modified file 'cmake/modules/GenerateTest.cmake'
1487--- cmake/modules/GenerateTest.cmake 2016-12-13 01:51:47 +0000
1488+++ cmake/modules/GenerateTest.cmake 2017-04-05 14:05:16 +0000
1489@@ -78,6 +78,7 @@
1490 MC_ACCOUNT_DIR=${TMPDIR}
1491 MC_MANAGER_DIR=${TMPDIR}
1492 MC_CLIENTS_DIR=${TMPDIR}
1493+ PA_DISABLED=1
1494 TELEPHONY_SERVICE_TEST=1
1495 TELEPHONY_SERVICE_PROTOCOLS_DIR=${CMAKE_SOURCE_DIR}/tests/common/protocols
1496 TEST_DATA_DIR=${CMAKE_SOURCE_DIR}/tests/common/data)
1497
1498=== added file 'debian/account-plugin-irc-unity8.install'
1499--- debian/account-plugin-irc-unity8.install 1970-01-01 00:00:00 +0000
1500+++ debian/account-plugin-irc-unity8.install 2017-04-05 14:05:16 +0000
1501@@ -0,0 +1,3 @@
1502+usr/share/accounts/qml-plugins/telephony-irc/*
1503+usr/share/accounts/providers/telephony-irc.provider
1504+usr/share/accounts/services/telephony-irc-im.service
1505
1506=== added file 'debian/account-plugin-sip-unity8.install'
1507--- debian/account-plugin-sip-unity8.install 1970-01-01 00:00:00 +0000
1508+++ debian/account-plugin-sip-unity8.install 2017-04-05 14:05:16 +0000
1509@@ -0,0 +1,3 @@
1510+usr/share/accounts/qml-plugins/telephony-sip/*
1511+usr/share/accounts/providers/telephony-sip.provider
1512+usr/share/accounts/services/telephony-sip-im.service
1513
1514=== modified file 'debian/control'
1515--- debian/control 2016-12-05 15:14:33 +0000
1516+++ debian/control 2017-04-05 14:05:16 +0000
1517@@ -13,10 +13,12 @@
1518 libicu-dev,
1519 libmessaging-menu-dev,
1520 libnotify-dev,
1521+ libgsettings-qt-dev,
1522 libphonenumber-dev,
1523 libtelepathy-qt5-dev,
1524- libubuntu-application-api-dev [armhf],
1525 libprotobuf-dev,
1526+ libpulse-dev,
1527+ liburl-dispatcher1-dev,
1528 pkg-config,
1529 python:any,
1530 qml-module-qttest,
1531@@ -92,3 +94,24 @@
1532 This package contains the QML plugin providing the features from the telephony
1533 PhoneNumber to applications.
1534
1535+Package: account-plugin-sip-unity8
1536+Architecture: all
1537+Pre-Depends: dpkg (>= 1.15.6~)
1538+Depends: mcp-account-manager-uoa-common,
1539+ telepathy-accounts-signon,
1540+ telepathy-rakia
1541+Description: Online account plugin for unity8
1542+ Online account plugin for unity8.
1543+ .
1544+ This package contains the online account plugin providing sip account.
1545+
1546+Package: account-plugin-irc-unity8
1547+Architecture: all
1548+Pre-Depends: dpkg (>= 1.15.6~)
1549+Depends: mcp-account-manager-uoa-common,
1550+ mfw-plugin-irc
1551+Description: Online account plugin for unity8
1552+ Online account plugin for unity8.
1553+ .
1554+ This package contains the online account plugin providing irc account.
1555+
1556
1557=== modified file 'handler/CMakeLists.txt'
1558--- handler/CMakeLists.txt 2016-12-06 16:45:01 +0000
1559+++ handler/CMakeLists.txt 2017-04-05 14:05:16 +0000
1560@@ -1,12 +1,24 @@
1561+if (PULSEAUDIO_FOUND)
1562+ add_definitions(-DUSE_PULSEAUDIO)
1563+ set(USE_PULSEAUDIO ON)
1564+ set(QPULSEAUDIOENGINE_CPP qpulseaudioengine.cpp)
1565+endif (PULSEAUDIO_FOUND)
1566
1567 set(qt_SRCS
1568+ accountproperties.cpp
1569+ callagent.cpp
1570+ audioroutemanager.cpp
1571 callhandler.cpp
1572 chatstartingjob.cpp
1573+ farstreamchannel.cpp
1574 handler.cpp
1575 handlerdbus.cpp
1576 messagejob.cpp
1577 messagesendingjob.cpp
1578+ powerdaudiomodemediator.cpp
1579+ powerddbus.cpp
1580 texthandler.cpp
1581+ ${QPULSEAUDIOENGINE_CPP}
1582 )
1583
1584 set(handler_SRCS main.cpp ${qt_SRCS})
1585@@ -16,8 +28,13 @@
1586
1587 include_directories(
1588 ${TP_QT5_INCLUDE_DIRS}
1589+ ${TPFS_INCLUDE_DIRS}
1590+ ${FS_INCLUDE_DIRS}
1591+ ${GST_INCLUDE_DIRS}
1592 ${CMAKE_SOURCE_DIR}/libtelephonyservice
1593 ${CMAKE_CURRENT_BINARY_DIR}
1594+ ${GSETTINGS_QT_INCLUDE_DIRS}
1595+ ${PULSEAUDIO_INCLUDE_DIRS}
1596 )
1597
1598 add_executable(telephony-service-handler ${handler_SRCS} ${handler_HDRS})
1599@@ -25,7 +42,11 @@
1600
1601 target_link_libraries(telephony-service-handler
1602 ${TP_QT5_LIBRARIES}
1603+ ${TP_QT5_FS_LIBRARIES}
1604+ ${TPFS_LIBRARIES}
1605+ ${FS_LIBRARIES}
1606 telephonyservice
1607+ ${PULSEAUDIO_LIBRARIES}
1608 )
1609
1610 enable_coverage(telephony-service-handler)
1611
1612=== modified file 'handler/Handler.xml'
1613--- handler/Handler.xml 2016-12-06 16:45:01 +0000
1614+++ handler/Handler.xml 2017-04-05 14:05:16 +0000
1615@@ -114,13 +114,6 @@
1616 <arg name="objectPath" type="s" direction="in"/>
1617 <arg name="muted" type="b" direction="in"/>
1618 </method>
1619- <method name="SetActiveAudioOutput">
1620- <dox:d><![CDATA[
1621- Change the current active audio output
1622- ]]></dox:d>
1623- <arg name="objectPath" type="s" direction="in"/>
1624- <arg name="id" type="s" direction="in"/>
1625- </method>
1626 <method name="SendDTMF">
1627 <dox:d><![CDATA[
1628 Send a DTMF to the given channel
1629@@ -177,9 +170,48 @@
1630 <dox:d><![CDATA[
1631 Get the list of current available protocols
1632 ]]></dox:d>
1633- <arg name="result" type="a(sussss)" direction="out"/>
1634+ <arg name="result" type="a(susussbbssssbbbbbbb)" direction="out"/>
1635 <annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="ProtocolList"/>
1636 </method>
1637+ <method name="LeaveRooms">
1638+ <dox:d><![CDATA[
1639+ Close all rooms of a given account
1640+ ]]></dox:d>
1641+ <arg name="accountId" type="s" direction="in"/>
1642+ <arg name="message" type="s" direction="in"/>
1643+ </method>
1644+ <method name="GetAllAccountsProperties">
1645+ <dox:d><![CDATA[
1646+ Get the properties of all available accounts
1647+ ]]></dox:d>
1648+ <arg name="result" type="a{sa{sv}}" direction="out"/>
1649+ <annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="AllAccountsProperties"/>
1650+ </method>
1651+ <method name="GetAccountProperties">
1652+ <dox:d><![CDATA[
1653+ Get the properties of the given accountId
1654+ ]]></dox:d>
1655+ <arg name="accoundId" type="s" direction="in"/>
1656+ <arg name="result" type="a{sv}" direction="out"/>
1657+ <annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="QVariantMap"/>
1658+ </method>
1659+ <method name="SetAccountProperties">
1660+ <dox:d><![CDATA[
1661+ Set the properties of the given accountId
1662+ ]]></dox:d>
1663+ <arg name="accoundId" type="s" direction="in"/>
1664+ <arg name="properties" type="a{sv}" direction="in"/>
1665+ <annotation name="org.qtproject.QtDBus.QtTypeName.In1" value="QVariantMap"/>
1666+ </method>
1667+ <signal name="AccountPropertiesChanged">
1668+ <dox:d><![CDATA[
1669+ The properties of a given account changed.
1670+ ]]></dox:d>
1671+ <arg name="accountId" type="s"/>
1672+ <arg name="properties" type="a{sv}"/>
1673+ <annotation name="org.qtproject.QtDBus.QtTypeName.Out1" value="QVariantMap"/>
1674+ <annotation name="org.qtproject.QtDBus.QtTypeName.In1" value="QVariantMap"/>
1675+ </signal>
1676 <signal name="CallPropertiesChanged">
1677 <dox:d><![CDATA[
1678 The properties of a given call changed.
1679@@ -213,9 +245,32 @@
1680 <dox:d><![CDATA[
1681 The protocols files in protocols dir have changed
1682 ]]></dox:d>
1683- <arg name="protocols" type="a(sussss)"/>
1684+ <arg name="protocols" type="a(susussbbssssbbbbbbb)"/>
1685 <annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="ProtocolList"/>
1686 <annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="ProtocolList"/>
1687 </signal>
1688+ <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="true"/>
1689+ <property name="ActiveAudioOutput" type="s" access="readwrite"/>
1690+ <signal name="ActiveAudioOutputChanged">
1691+ <dox:d><![CDATA[
1692+ The active audio output has changed
1693+ ]]></dox:d>
1694+ <arg name="id" type="s"/>
1695+ </signal>
1696+ <method name="AudioOutputs">
1697+ <dox:d><![CDATA[
1698+ ]]></dox:d>
1699+ <arg name="outputs" type="a(sss)" direction="out"/>
1700+ <annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="AudioOutputDBusList"/>
1701+ <annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="AudioOutputDBusList"/>
1702+ </method>
1703+ <signal name="AudioOutputsChanged">
1704+ <dox:d><![CDATA[
1705+ The available audio outputs have changed
1706+ ]]></dox:d>
1707+ <arg name="outputs" type="a(sss)"/>
1708+ <annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="AudioOutputDBusList"/>
1709+ <annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="AudioOutputDBusList"/>
1710+ </signal>
1711 </interface>
1712 </node>
1713
1714=== modified file 'handler/TelephonyServiceHandler.client'
1715--- handler/TelephonyServiceHandler.client 2016-04-15 22:25:51 +0000
1716+++ handler/TelephonyServiceHandler.client 2017-04-05 14:05:16 +0000
1717@@ -6,3 +6,12 @@
1718
1719 [org.freedesktop.Telepathy.Client.Handler.HandlerChannelFilter 3]
1720 org.freedesktop.Telepathy.Channel.ChannelType s=org.freedesktop.Telepathy.Channel.Type.Call1
1721+
1722+[org.freedesktop.Telepathy.Client.Handler.HandlerChannelFilter 1]
1723+org.freedesktop.Telepathy.Channel.ChannelType s=org.freedesktop.Telepathy.Channel.Type.Call1
1724+org.freedesktop.Telepathy.Channel.Type.Call1.InitialAudio b=true
1725+
1726+[org.freedesktop.Telepathy.Client.Handler.Capabilities]
1727+org.freedesktop.Telepathy.Channel.Type.Call1/audio=true
1728+org.freedesktop.Telepathy.Channel.Type.Call1/ice=true
1729+org.freedesktop.Telepathy.Channel.Type.Call1/gtalk-p2p=true
1730
1731=== added file 'handler/accountproperties.cpp'
1732--- handler/accountproperties.cpp 1970-01-01 00:00:00 +0000
1733+++ handler/accountproperties.cpp 2017-04-05 14:05:16 +0000
1734@@ -0,0 +1,71 @@
1735+/*
1736+ * Copyright (C) 2017 Canonical, Ltd.
1737+ *
1738+ * Authors:
1739+ * Gustavo Pichorim Boiko <gustavo.boiko@canonical.com>
1740+ *
1741+ * This file is part of telephony-service.
1742+ *
1743+ * telephony-service is free software; you can redistribute it and/or modify
1744+ * it under the terms of the GNU General Public License as published by
1745+ * the Free Software Foundation; version 3.
1746+ *
1747+ * telephony-service is distributed in the hope that it will be useful,
1748+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1749+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1750+ * GNU General Public License for more details.
1751+ *
1752+ * You should have received a copy of the GNU General Public License
1753+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1754+ */
1755+
1756+#include "accountproperties.h"
1757+#include "telepathyhelper.h"
1758+#include <QSettings>
1759+
1760+#define SETTINGS_DOMAIN "com.canonical.TelephonyServiceHandler"
1761+
1762+AccountProperties *AccountProperties::instance()
1763+{
1764+ static AccountProperties *self = new AccountProperties();
1765+ return self;
1766+}
1767+
1768+QMap<QString, QVariantMap> AccountProperties::allProperties()
1769+{
1770+ QMap<QString,QVariantMap> props;
1771+ for (auto accountId : TelepathyHelper::instance()->accountIds()) {
1772+ props[accountId] = accountProperties(accountId);
1773+ }
1774+}
1775+
1776+QVariantMap AccountProperties::accountProperties(const QString &accountId)
1777+{
1778+ QVariantMap props;
1779+ mSettings->beginGroup(formatAccountId(accountId));
1780+ for (auto key : mSettings->allKeys()) {
1781+ props[key] = mSettings->value(key);
1782+ }
1783+ mSettings->endGroup();
1784+ return props;
1785+}
1786+
1787+void AccountProperties::setAccountProperties(const QString &accountId, const QVariantMap &properties)
1788+{
1789+ mSettings->beginGroup(formatAccountId(accountId));
1790+ for (auto key : properties.keys()) {
1791+ mSettings->setValue(key, properties[key]);
1792+ }
1793+ mSettings->endGroup();
1794+}
1795+
1796+QString AccountProperties::formatAccountId(const QString &accountId)
1797+{
1798+ return QUrl::toPercentEncoding(accountId);
1799+}
1800+
1801+AccountProperties::AccountProperties(QObject *parent)
1802+: QObject(parent),
1803+ mSettings(new QSettings(SETTINGS_DOMAIN, QString(), parent))
1804+{
1805+}
1806
1807=== added file 'handler/accountproperties.h'
1808--- handler/accountproperties.h 1970-01-01 00:00:00 +0000
1809+++ handler/accountproperties.h 2017-04-05 14:05:16 +0000
1810@@ -0,0 +1,47 @@
1811+/*
1812+ * Copyright (C) 2017 Canonical, Ltd.
1813+ *
1814+ * Authors:
1815+ * Gustavo Pichorim Boiko <gustavo.boiko@canonical.com>
1816+ *
1817+ * This file is part of telephony-service.
1818+ *
1819+ * telephony-service is free software; you can redistribute it and/or modify
1820+ * it under the terms of the GNU General Public License as published by
1821+ * the Free Software Foundation; version 3.
1822+ *
1823+ * telephony-service is distributed in the hope that it will be useful,
1824+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1825+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1826+ * GNU General Public License for more details.
1827+ *
1828+ * You should have received a copy of the GNU General Public License
1829+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1830+ */
1831+
1832+#ifndef ACCOUNTPROPERTIES_H
1833+#define ACCOUNTPROPERTIES_H
1834+
1835+#include <QObject>
1836+
1837+class QSettings;
1838+
1839+class AccountProperties : public QObject
1840+{
1841+ Q_OBJECT
1842+public:
1843+ static AccountProperties *instance();
1844+
1845+ QMap<QString,QVariantMap> allProperties();
1846+ QVariantMap accountProperties(const QString &accountId);
1847+ void setAccountProperties(const QString &accountId, const QVariantMap &properties);
1848+ QString formatAccountId(const QString &accountId);
1849+
1850+protected:
1851+ explicit AccountProperties(QObject *parent = 0);
1852+
1853+private:
1854+ QSettings *mSettings;
1855+};
1856+
1857+#endif // ACCOUNTPROPERTIES_H
1858
1859=== added file 'handler/audioroutemanager.cpp'
1860--- handler/audioroutemanager.cpp 1970-01-01 00:00:00 +0000
1861+++ handler/audioroutemanager.cpp 2017-04-05 14:05:16 +0000
1862@@ -0,0 +1,231 @@
1863+/*
1864+ * Copyright (C) 2016 Canonical, Ltd.
1865+ *
1866+ * Authors:
1867+ * Tiago Salem Herrmann <tiago.herrmann@canonical.com>
1868+ *
1869+ * This file is part of telephony-service.
1870+ *
1871+ * telephony-service is free software; you can redistribute it and/or modify
1872+ * it under the terms of the GNU General Public License as published by
1873+ * the Free Software Foundation; version 3.
1874+ *
1875+ * telephony-service is distributed in the hope that it will be useful,
1876+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1877+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1878+ * GNU General Public License for more details.
1879+ *
1880+ * You should have received a copy of the GNU General Public License
1881+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1882+ */
1883+
1884+#include "audioroutemanager.h"
1885+#include "telepathyhelper.h"
1886+#include "accountentry.h"
1887+#include <TelepathyQt/Contact>
1888+#include <TelepathyQt/Functors>
1889+
1890+
1891+static void enable_earpiece()
1892+{
1893+#ifdef USE_PULSEAUDIO
1894+ QPulseAudioEngine::instance()->setCallMode(CallActive, AudioModeBtOrWiredOrEarpiece);
1895+#endif
1896+}
1897+
1898+static void enable_normal()
1899+{
1900+#ifdef USE_PULSEAUDIO
1901+ QTimer* timer = new QTimer();
1902+ timer->setSingleShot(true);
1903+ QObject::connect(timer, &QTimer::timeout, [=](){
1904+ QPulseAudioEngine::instance()->setMicMute(false);
1905+ QPulseAudioEngine::instance()->setCallMode(CallEnded, AudioModeWiredOrSpeaker);
1906+ timer->deleteLater();
1907+ });
1908+ timer->start(2000);
1909+#endif
1910+}
1911+
1912+static void enable_speaker()
1913+{
1914+#ifdef USE_PULSEAUDIO
1915+ QPulseAudioEngine::instance()->setCallMode(CallActive, AudioModeSpeaker);
1916+#endif
1917+}
1918+
1919+static void enable_ringtone()
1920+{
1921+#ifdef USE_PULSEAUDIO
1922+ QPulseAudioEngine::instance()->setCallMode(CallRinging, AudioModeBtOrWiredOrSpeaker);
1923+#endif
1924+}
1925+
1926+AudioRouteManager *AudioRouteManager::instance()
1927+{
1928+ static AudioRouteManager *self = new AudioRouteManager();
1929+ return self;
1930+}
1931+
1932+AudioRouteManager::AudioRouteManager(QObject *parent) :
1933+ QObject(parent), mAudioModeMediator(mPowerDDBus)
1934+{
1935+ TelepathyHelper::instance()->registerChannelObserver("TelephonyServiceHandlerAudioRouteManager");
1936+
1937+ QObject::connect(TelepathyHelper::instance()->channelObserver(), SIGNAL(callChannelAvailable(Tp::CallChannelPtr)),
1938+ this, SLOT(onCallChannelAvailable(Tp::CallChannelPtr)));
1939+
1940+#ifdef USE_PULSEAUDIO
1941+ // update audio modes
1942+ QObject::connect(QPulseAudioEngine::instance(), SIGNAL(audioModeChanged(AudioMode)), SLOT(onAudioModeChanged(AudioMode)));
1943+ QObject::connect(QPulseAudioEngine::instance(), SIGNAL(availableAudioModesChanged(AudioModes)), SLOT(onAvailableAudioModesChanged(AudioModes)));
1944+
1945+ // check if we should indeed use pulseaudio
1946+ QByteArray pulseAudioDisabled = qgetenv("PA_DISABLED");
1947+ mHasPulseAudio = true;
1948+ if (!pulseAudioDisabled.isEmpty())
1949+ mHasPulseAudio = false;
1950+#endif
1951+
1952+ connect(this, &AudioRouteManager::activeAudioOutputChanged, Tp::memFun(&mAudioModeMediator, &PowerDAudioModeMediator::audioModeChanged));
1953+ connect(this, &AudioRouteManager::lastChannelClosed, Tp::memFun(&mAudioModeMediator, &PowerDAudioModeMediator::audioOutputClosed));
1954+}
1955+
1956+void AudioRouteManager::onCallChannelAvailable(Tp::CallChannelPtr callChannel)
1957+{
1958+ connect(callChannel.data(),
1959+ SIGNAL(callStateChanged(Tp::CallState)),
1960+ SLOT(onCallStateChanged(Tp::CallState)));
1961+
1962+ mChannels.append(callChannel);
1963+ updateAudioRoute(true);
1964+}
1965+
1966+void AudioRouteManager::onCallStateChanged(Tp::CallState state)
1967+{
1968+ Tp::CallChannelPtr channel(qobject_cast<Tp::CallChannel*>(sender()));
1969+ if (!channel) {
1970+ return;
1971+ }
1972+
1973+ if (channel->callState() == Tp::CallStateEnded) {
1974+ mChannels.removeOne(channel);
1975+ }
1976+ updateAudioRoute(false);
1977+}
1978+
1979+void AudioRouteManager::setActiveAudioOutput(const QString &id)
1980+{
1981+#ifdef USE_PULSEAUDIO
1982+ // fallback to earpiece/headset
1983+ AudioMode mode = AudioModeWiredOrEarpiece;
1984+ if (id == "bluetooth") {
1985+ mode = AudioModeBluetooth;
1986+ } else if (id == "speaker") {
1987+ mode = AudioModeSpeaker;
1988+ }
1989+ if (mHasPulseAudio)
1990+ QPulseAudioEngine::instance()->setCallMode(CallActive, mode);
1991+#endif
1992+}
1993+
1994+QString AudioRouteManager::activeAudioOutput()
1995+{
1996+ return mActiveAudioOutput;
1997+}
1998+
1999+AudioOutputDBusList AudioRouteManager::audioOutputs() const
2000+{
2001+ return mAudioOutputs;
2002+}
2003+
2004+void AudioRouteManager::updateAudioRoute(bool newCall)
2005+{
2006+#ifdef USE_PULSEAUDIO
2007+ if (!mHasPulseAudio)
2008+ return;
2009+#endif
2010+
2011+ int currentCalls = mChannels.size();
2012+ if (currentCalls != 0) {
2013+ if (currentCalls == 1) {
2014+ // if we have only one call, check if it's incoming and
2015+ // enable speaker mode so the ringtone is audible
2016+ Tp::CallChannelPtr callChannel = mChannels.first();
2017+ AccountEntry *accountEntry = TelepathyHelper::instance()->accountForConnection(callChannel->connection());
2018+ if (!accountEntry || !callChannel) {
2019+ return;
2020+ }
2021+
2022+ bool incoming = callChannel->initiatorContact() != accountEntry->account()->connection()->selfContact();
2023+ Tp::CallState state = callChannel->callState();
2024+ if (incoming && newCall) {
2025+ enable_ringtone();
2026+ return;
2027+ }
2028+ if (state == Tp::CallStateEnded) {
2029+ enable_normal();
2030+ return;
2031+ }
2032+ // if only one call and dialing, or incoming call just accepted, then default to earpiece
2033+ if (newCall || (state == Tp::CallStateAccepted && incoming)) {
2034+ enable_earpiece();
2035+ return;
2036+ }
2037+ }
2038+ } else {
2039+ enable_normal();
2040+ Q_EMIT lastChannelClosed();
2041+ }
2042+}
2043+
2044+#ifdef USE_PULSEAUDIO
2045+void AudioRouteManager::onAudioModeChanged(AudioMode mode)
2046+{
2047+ qDebug("PulseAudio audio mode changed: 0x%x", mode);
2048+
2049+ if (mode == AudioModeEarpiece && mActiveAudioOutput != "earpiece") {
2050+ mActiveAudioOutput = "earpiece";
2051+ } else if (mode == AudioModeWiredHeadset && mActiveAudioOutput != "wired_headset") {
2052+ mActiveAudioOutput = "wired_headset";
2053+ } else if (mode == AudioModeSpeaker && mActiveAudioOutput != "speaker") {
2054+ mActiveAudioOutput = "speaker";
2055+ } else if (mode == AudioModeBluetooth && mActiveAudioOutput != "bluetooth") {
2056+ mActiveAudioOutput = "bluetooth";
2057+ }
2058+ Q_EMIT activeAudioOutputChanged(mActiveAudioOutput);
2059+}
2060+
2061+void AudioRouteManager::onAvailableAudioModesChanged(AudioModes modes)
2062+{
2063+ qDebug("PulseAudio available audio modes changed");
2064+ bool defaultFound = false;
2065+ mAudioOutputs.clear();
2066+ Q_FOREACH(const AudioMode &mode, modes) {
2067+ AudioOutputDBus output;
2068+ if (mode == AudioModeBluetooth) {
2069+ // there can be only one bluetooth
2070+ output.id = "bluetooth";
2071+ output.type = "bluetooth";
2072+ // we dont support names for now, so we set a default value
2073+ output.name = "bluetooth";
2074+ } else if (mode == AudioModeEarpiece || mode == AudioModeWiredHeadset) {
2075+ if (!defaultFound) {
2076+ defaultFound = true;
2077+ output.id = "default";
2078+ output.type = "default";
2079+ output.name = "default";
2080+ } else {
2081+ continue;
2082+ }
2083+ } else if (mode == AudioModeSpeaker) {
2084+ output.id = "speaker";
2085+ output.type = "speaker";
2086+ output.name = "speaker";
2087+ }
2088+ mAudioOutputs << output;
2089+ }
2090+ Q_EMIT audioOutputsChanged(mAudioOutputs);
2091+}
2092+#endif
2093+
2094
2095=== added file 'handler/audioroutemanager.h'
2096--- handler/audioroutemanager.h 1970-01-01 00:00:00 +0000
2097+++ handler/audioroutemanager.h 2017-04-05 14:05:16 +0000
2098@@ -0,0 +1,75 @@
2099+/*
2100+ * Copyright (C) 2016 Canonical, Ltd.
2101+ *
2102+ * Authors:
2103+ * Tiago Salem Herrmann <tiago.herrmann@canonical.com>
2104+ *
2105+ * This file is part of telephony-service.
2106+ *
2107+ * telephony-service is free software; you can redistribute it and/or modify
2108+ * it under the terms of the GNU General Public License as published by
2109+ * the Free Software Foundation; version 3.
2110+ *
2111+ * telephony-service is distributed in the hope that it will be useful,
2112+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
2113+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2114+ * GNU General Public License for more details.
2115+ *
2116+ * You should have received a copy of the GNU General Public License
2117+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
2118+ */
2119+
2120+#ifndef AUDIOROUTEMANAGER_H
2121+#define AUDIOROUTEMANAGER_H
2122+
2123+#ifdef USE_PULSEAUDIO
2124+#include "qpulseaudioengine.h"
2125+#endif
2126+#include "audiooutput.h"
2127+#include "powerdaudiomodemediator.h"
2128+#include "powerddbus.h"
2129+#include <QObject>
2130+#include <TelepathyQt/CallChannel>
2131+
2132+
2133+class AudioRouteManager : public QObject
2134+{
2135+ Q_OBJECT
2136+
2137+public:
2138+ static AudioRouteManager *instance();
2139+ void setActiveAudioOutput(const QString &id);
2140+ QString activeAudioOutput();
2141+ AudioOutputDBusList audioOutputs() const;
2142+ void updateAudioRoute(bool newCall = false);
2143+
2144+public Q_SLOTS:
2145+ void onCallChannelAvailable(Tp::CallChannelPtr callChannel);
2146+
2147+Q_SIGNALS:
2148+ void audioOutputsChanged(const AudioOutputDBusList &outputs);
2149+ void activeAudioOutputChanged(const QString &id);
2150+ void lastChannelClosed();
2151+
2152+protected Q_SLOTS:
2153+ void onCallStateChanged(Tp::CallState state);
2154+
2155+private Q_SLOTS:
2156+#ifdef USE_PULSEAUDIO
2157+ void onAudioModeChanged(AudioMode mode);
2158+ void onAvailableAudioModesChanged(AudioModes modes);
2159+#endif
2160+
2161+private:
2162+ explicit AudioRouteManager(QObject *parent = 0);
2163+ QList<Tp::CallChannelPtr> mChannels;
2164+ AudioOutputDBusList mAudioOutputs;
2165+ QString mActiveAudioOutput;
2166+ PowerDDBus mPowerDDBus;
2167+ PowerDAudioModeMediator mAudioModeMediator;
2168+#ifdef USE_PULSEAUDIO
2169+ bool mHasPulseAudio;
2170+#endif
2171+};
2172+
2173+#endif // AUDIOROUTEMANAGER_H
2174
2175=== added file 'handler/callagent.cpp'
2176--- handler/callagent.cpp 1970-01-01 00:00:00 +0000
2177+++ handler/callagent.cpp 2017-04-05 14:05:16 +0000
2178@@ -0,0 +1,118 @@
2179+/*
2180+ * Copyright (C) 2017 Canonical, Ltd.
2181+ *
2182+ * Authors:
2183+ * Gustavo Pichorim Boiko <gustavo.boiko@canonical.com>
2184+ *
2185+ * This file is part of telephony-service.
2186+ *
2187+ * telephony-service is free software; you can redistribute it and/or modify
2188+ * it under the terms of the GNU General Public License as published by
2189+ * the Free Software Foundation; version 3.
2190+ *
2191+ * telephony-service is distributed in the hope that it will be useful,
2192+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
2193+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2194+ * GNU General Public License for more details.
2195+ *
2196+ * You should have received a copy of the GNU General Public License
2197+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
2198+ */
2199+
2200+#include "callagent.h"
2201+#include <TelepathyQt/CallContent>
2202+#include <TelepathyQt/Contact>
2203+#include <TelepathyQt/Farstream/Channel>
2204+#include <QDebug>
2205+
2206+CallAgent::CallAgent(const Tp::CallChannelPtr &channel, QObject *parent) :
2207+ QObject(parent), mChannel(channel), mFarstreamChannel(0)
2208+{
2209+ connect(mChannel.data(),
2210+ SIGNAL(invalidated(Tp::DBusProxy*,QString,QString)),
2211+ SLOT(onCallChannelInvalidated()));
2212+ connect(mChannel.data(),
2213+ SIGNAL(callStateChanged(Tp::CallState)),
2214+ SLOT(onCallStateChanged(Tp::CallState)));
2215+ connect(mChannel.data(),
2216+ SIGNAL(contentAdded(Tp::CallContentPtr)),
2217+ SLOT(onContentAdded(Tp::CallContentPtr)));
2218+
2219+ Q_FOREACH(const Tp::CallContentPtr &content, mChannel->contents()) {
2220+ onContentAdded(content);
2221+ }
2222+
2223+ if (!mChannel->handlerStreamingRequired()) {
2224+ return;
2225+ }
2226+
2227+ Tp::Farstream::PendingChannel *pendingChannel = Tp::Farstream::createChannel(mChannel);
2228+ connect(pendingChannel,
2229+ SIGNAL(finished(Tp::PendingOperation*)),
2230+ SLOT(onFarstreamChannelCreated(Tp::PendingOperation*)));
2231+}
2232+
2233+CallAgent::~CallAgent()
2234+{
2235+ if (mFarstreamChannel) {
2236+ mFarstreamChannel->deleteLater();
2237+ }
2238+}
2239+
2240+void CallAgent::setMute(bool mute)
2241+{
2242+ if (!mFarstreamChannel) {
2243+ return;
2244+ }
2245+
2246+ mFarstreamChannel->setMute(mute);
2247+}
2248+
2249+void CallAgent::onCallChannelInvalidated()
2250+{
2251+ deleteLater();
2252+}
2253+
2254+void CallAgent::onCallStateChanged(Tp::CallState state)
2255+{
2256+ if (state == Tp::CallStatePendingInitiator) {
2257+ mChannel->accept();
2258+ }
2259+}
2260+
2261+void CallAgent::onContentAdded(const Tp::CallContentPtr &content)
2262+{
2263+ if (!mChannel->handlerStreamingRequired()) {
2264+ return;
2265+ }
2266+
2267+ qDebug() << "Content Added, name: " << content->name() << " type: " << content->type();
2268+
2269+ connect(content.data(),
2270+ SIGNAL(streamAdded(Tp::CallStreamPtr)),
2271+ SLOT(onStreamAdded(Tp::CallStreamPtr)));
2272+
2273+ Q_FOREACH(const Tp::CallStreamPtr &stream, content->streams()) {
2274+ onStreamAdded(stream);
2275+ }
2276+}
2277+
2278+void CallAgent::onStreamAdded(const Tp::CallStreamPtr &stream)
2279+{
2280+ qDebug() << "Stream present: " << stream->localSendingState();
2281+
2282+ qDebug() << " members " << stream->remoteMembers().size();
2283+ Q_FOREACH(const Tp::ContactPtr contact, stream->remoteMembers()) {
2284+ qDebug() << " member " << contact->id() << " remoteSendingState=" << stream->remoteSendingState(contact);
2285+ }
2286+}
2287+
2288+void CallAgent::onFarstreamChannelCreated(Tp::PendingOperation *op)
2289+{
2290+ Tp::Farstream::PendingChannel *pendingChannel = qobject_cast<Tp::Farstream::PendingChannel*>(op);
2291+ if (!pendingChannel) {
2292+ return;
2293+ }
2294+
2295+ mFarstreamChannel = new FarstreamChannel(pendingChannel->tfChannel(), this);
2296+}
2297
2298=== added file 'handler/callagent.h'
2299--- handler/callagent.h 1970-01-01 00:00:00 +0000
2300+++ handler/callagent.h 2017-04-05 14:05:16 +0000
2301@@ -0,0 +1,52 @@
2302+/*
2303+ * Copyright (C) 2017 Canonical, Ltd.
2304+ *
2305+ * Authors:
2306+ * Gustavo Pichorim Boiko <gustavo.boiko@canonical.com>
2307+ *
2308+ * This file is part of telephony-service.
2309+ *
2310+ * telephony-service is free software; you can redistribute it and/or modify
2311+ * it under the terms of the GNU General Public License as published by
2312+ * the Free Software Foundation; version 3.
2313+ *
2314+ * telephony-service is distributed in the hope that it will be useful,
2315+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
2316+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2317+ * GNU General Public License for more details.
2318+ *
2319+ * You should have received a copy of the GNU General Public License
2320+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
2321+ */
2322+
2323+#ifndef CALLAGENT_H
2324+#define CALLAGENT_H
2325+
2326+#include <QObject>
2327+#include <TelepathyQt/CallChannel>
2328+#include <TelepathyQt/Farstream/Channel>
2329+#include "farstreamchannel.h"
2330+
2331+class CallAgent : public QObject
2332+{
2333+ Q_OBJECT
2334+public:
2335+ explicit CallAgent(const Tp::CallChannelPtr &channel, QObject *parent = 0);
2336+ ~CallAgent();
2337+
2338+ void setMute(bool mute);
2339+
2340+protected Q_SLOTS:
2341+ void onCallChannelInvalidated();
2342+ void onCallStateChanged(Tp::CallState state);
2343+ void onContentAdded(const Tp::CallContentPtr &content);
2344+ void onStreamAdded(const Tp::CallStreamPtr &stream);
2345+
2346+ void onFarstreamChannelCreated(Tp::PendingOperation *op);
2347+
2348+private:
2349+ Tp::CallChannelPtr mChannel;
2350+ FarstreamChannel *mFarstreamChannel;
2351+};
2352+
2353+#endif // CALLAGENT_H
2354
2355=== modified file 'handler/callhandler.cpp'
2356--- handler/callhandler.cpp 2015-07-06 23:32:13 +0000
2357+++ handler/callhandler.cpp 2017-04-05 14:05:16 +0000
2358@@ -1,5 +1,5 @@
2359 /*
2360- * Copyright (C) 2012-2014 Canonical, Ltd.
2361+ * Copyright (C) 2012-2017 Canonical, Ltd.
2362 *
2363 * Authors:
2364 * Gustavo Pichorim Boiko <gustavo.boiko@canonical.com>
2365@@ -20,15 +20,19 @@
2366 * along with this program. If not, see <http://www.gnu.org/licenses/>.
2367 */
2368
2369+#include "accountproperties.h"
2370+#include "callagent.h"
2371 #include "callhandler.h"
2372-#include "phoneutils.h"
2373 #include "telepathyhelper.h"
2374 #include "accountentry.h"
2375 #include "tonegenerator.h"
2376 #include "greetercontacts.h"
2377+#include "phoneutils.h"
2378 #include <TelepathyQt/ContactManager>
2379 #include <TelepathyQt/PendingContacts>
2380 #include <TelepathyQt/PendingChannelRequest>
2381+#include <TelepathyQt/PendingVariant>
2382+#include <memory>
2383
2384 #define TELEPATHY_MUTE_IFACE "org.freedesktop.Telepathy.Call1.Interface.Mute"
2385 #define DBUS_PROPERTIES_IFACE "org.freedesktop.DBus.Properties"
2386@@ -72,8 +76,7 @@
2387 bool hasActiveCalls = false;
2388
2389 Q_FOREACH(const Tp::CallChannelPtr channel, mCallChannels) {
2390- AccountEntry *accountEntry = TelepathyHelper::instance()->accountForConnection(channel->connection());
2391- bool incoming = channel->initiatorContact() != accountEntry->account()->connection()->selfContact();
2392+ bool incoming = isIncoming(channel);
2393 bool dialing = !incoming && (channel->callState() == Tp::CallStateInitialised);
2394 bool active = channel->callState() == Tp::CallStateActive;
2395
2396@@ -94,6 +97,7 @@
2397
2398 void CallHandler::startCall(const QString &targetId, const QString &accountId)
2399 {
2400+ QString finalId = targetId;
2401 // Request the contact to start audio call
2402 AccountEntry *accountEntry = TelepathyHelper::instance()->accountForId(accountId);
2403 if (!accountEntry) {
2404@@ -105,7 +109,24 @@
2405 return;
2406 }
2407
2408- connect(connection->contactManager()->contactsForIdentifiers(QStringList() << targetId),
2409+ // FIXME: this is a workaround, there might be a better way of handling this.
2410+ // One idea is to implement the Addressing interface on the SIP connection manager such that
2411+ // we can request a handle based on the vCard field "tel"
2412+ if (accountEntry->protocolInfo()->name() == "sip") {
2413+ // check if the phone number needs rewriting
2414+ QVariantMap accountProperties = AccountProperties::instance()->accountProperties(accountId);
2415+ finalId = applyNumberRewritingRules(finalId, accountProperties);
2416+
2417+ // replace the numbers by a SIP URI
2418+ QString domain = accountEntry->account()->parameters()["account"].toString();
2419+ if (domain.contains("@")) {
2420+ domain = domain.split("@")[1];
2421+
2422+ finalId = QString("sip:%1@%2").arg(PhoneUtils::normalizePhoneNumber(finalId)).arg(domain);
2423+ }
2424+ }
2425+
2426+ connect(connection->contactManager()->contactsForIdentifiers(QStringList() << finalId),
2427 SIGNAL(finished(Tp::PendingOperation*)),
2428 SLOT(onContactsAvailable(Tp::PendingOperation*)));
2429 }
2430@@ -146,41 +167,30 @@
2431 return;
2432 }
2433
2434- // FIXME: replace by a proper TpQt implementation of mute
2435- QDBusInterface muteInterface(channel->busName(), channel->objectPath(), TELEPATHY_MUTE_IFACE);
2436- muteInterface.call("RequestMuted", muted);
2437-}
2438-
2439-void CallHandler::setActiveAudioOutput(const QString &objectPath, const QString &id)
2440-{
2441- Tp::CallChannelPtr channel = callFromObjectPath(objectPath);
2442-
2443- QDBusInterface audioOutputsInterface(channel->busName(), channel->objectPath(), CANONICAL_TELEPHONY_AUDIOOUTPUTS_IFACE);
2444- audioOutputsInterface.call("SetActiveAudioOutput", id);
2445+ if (channel->handlerStreamingRequired()) {
2446+ CallAgent *agent = mCallAgents[channel.data()];
2447+ if (!agent) {
2448+ return;
2449+ }
2450+ agent->setMute(muted);
2451+ } else {
2452+ // FIXME: replace by a proper TpQt implementation of mute
2453+ QDBusInterface muteInterface(channel->busName(), channel->objectPath(), TELEPATHY_MUTE_IFACE);
2454+ muteInterface.call("RequestMuted", muted);
2455+ }
2456 }
2457
2458 void CallHandler::sendDTMF(const QString &objectPath, const QString &key)
2459 {
2460- bool ok;
2461- Tp::DTMFEvent event = (Tp::DTMFEvent)key.toInt(&ok);
2462- if (!ok) {
2463- if (!key.compare("*")) {
2464- event = Tp::DTMFEventAsterisk;
2465- } else if (!key.compare("#")) {
2466- event = Tp::DTMFEventHash;
2467- } else {
2468- qWarning() << "Tone not recognized. DTMF failed";
2469- return;
2470- }
2471- }
2472 /*
2473- * play locally (via tone generator) only if we are on a call, or if this is
2474+ * play locally (via tone generator) only if we are on a call, or if this is
2475 * dialpad sounds
2476 */
2477- if (GreeterContacts::instance()->dialpadSoundsEnabled() &&
2478+ int event = toDTMFEvent(key);
2479+ if (GreeterContacts::instance()->dialpadSoundsEnabled() &&
2480 !GreeterContacts::instance()->silentMode() && objectPath.isEmpty()
2481 || !objectPath.isEmpty()) {
2482- ToneGenerator::instance()->playDTMFTone((uint)event);
2483+ ToneGenerator::instance()->playDTMFTone(event);
2484 }
2485
2486 Tp::CallChannelPtr channel = callFromObjectPath(objectPath);
2487@@ -190,14 +200,15 @@
2488
2489 // save the dtmfString to send to clients that request it
2490 QString dtmfString = channel->property("dtmfString").toString();
2491+ QString pendingDTMF = channel->property("pendingDTMF").toString();
2492+ pendingDTMF += key;
2493 dtmfString += key;
2494 channel->setProperty("dtmfString", dtmfString);
2495+ channel->setProperty("pendingDTMF", pendingDTMF);
2496
2497- Q_FOREACH(const Tp::CallContentPtr &content, channel->contents()) {
2498- if (content->supportsDTMF()) {
2499- /* send DTMF to network (via telepathy and oFono) */
2500- content->startDTMFTone(event);
2501- }
2502+ // if there is only one pending DTMF event, start playing it
2503+ if (pendingDTMF.length() == 1) {
2504+ playNextDTMFTone(channel);
2505 }
2506
2507 Q_EMIT callPropertiesChanged(channel->objectPath(), getCallProperties(channel->objectPath()));
2508@@ -266,13 +277,12 @@
2509 void CallHandler::onCallChannelAvailable(Tp::CallChannelPtr channel)
2510 {
2511 QDBusInterface callChannelIface(channel->busName(), channel->objectPath(), DBUS_PROPERTIES_IFACE);
2512- QDBusMessage reply = callChannelIface.call("GetAll", CANONICAL_TELEPHONY_AUDIOOUTPUTS_IFACE);
2513- QVariantList args = reply.arguments();
2514- QMap<QString, QVariant> map = qdbus_cast<QMap<QString, QVariant> >(args[0]);
2515 channel->setProperty("timestamp", QDateTime::currentDateTimeUtc());
2516
2517 if (channel->callState() == Tp::CallStateActive) {
2518 channel->setProperty("activeTimestamp", QDateTime::currentDateTimeUtc());
2519+ } else if (channel->callState() == Tp::CallStatePendingInitiator) {
2520+ channel->accept();
2521 }
2522
2523 connect(channel.data(),
2524@@ -282,6 +292,10 @@
2525 SIGNAL(callStateChanged(Tp::CallState)),
2526 SLOT(onCallStateChanged(Tp::CallState)));
2527
2528+ // FIXME: save this to a list
2529+ CallAgent *agent = new CallAgent(channel, this);
2530+ mCallAgents[channel.data()] = agent;
2531+
2532 mCallChannels.append(channel);
2533 Q_EMIT callPropertiesChanged(channel->objectPath(), getCallProperties(channel->objectPath()));
2534 }
2535@@ -331,7 +345,12 @@
2536 }
2537
2538 mCallChannels.removeAll(channel);
2539+ if (mCallAgents.contains(channel.data())) {
2540+ CallAgent *agent = mCallAgents.take(channel.data());
2541+ agent->deleteLater();
2542+ }
2543
2544+ ToneGenerator::instance()->stopTone();
2545 if (mCallChannels.isEmpty() && !mHangupRequested) {
2546 ToneGenerator::instance()->playCallEndedTone();
2547 }
2548@@ -346,10 +365,29 @@
2549 }
2550
2551 switch (state) {
2552+ case Tp::CallStatePendingInitiator:
2553+ case Tp::CallStateInitialising:
2554+ if (!isIncoming(channel) && channel->handlerStreamingRequired()) {
2555+ ToneGenerator::instance()->playDialingTone();
2556+ }
2557+ break;
2558+ case Tp::CallStateInitialised:
2559+ if (!isIncoming(channel) && channel->handlerStreamingRequired()) {
2560+ ToneGenerator::instance()->stopTone();
2561+ ToneGenerator::instance()->playRingingTone();
2562+ }
2563+ break;
2564 case Tp::CallStateActive:
2565+ if (channel->handlerStreamingRequired()) {
2566+ ToneGenerator::instance()->stopTone();
2567+ }
2568 channel->setProperty("activeTimestamp", QDateTime::currentDateTimeUtc());
2569 Q_EMIT callPropertiesChanged(channel->objectPath(), getCallProperties(channel->objectPath()));
2570 break;
2571+ case Tp::CallStateEnded:
2572+ ToneGenerator::instance()->stopTone();
2573+ channel->requestClose();
2574+ break;
2575 }
2576 }
2577
2578@@ -387,3 +425,128 @@
2579
2580 return channel;
2581 }
2582+
2583+void CallHandler::playNextDTMFTone(Tp::CallChannelPtr channel)
2584+{
2585+ // the channel might have been closed already
2586+ if (!channel) {
2587+ return;
2588+ }
2589+
2590+ QString pendingDTMF = channel->property("pendingDTMF").toString();
2591+ QString key = "";
2592+ if (!pendingDTMF.isEmpty()) {
2593+ key = pendingDTMF[0];
2594+ }
2595+
2596+ int event = toDTMFEvent(key);
2597+
2598+ Q_FOREACH(const Tp::CallContentPtr &content, channel->contents()) {
2599+ if (content->supportsDTMF()) {
2600+
2601+ /* stop any previous DTMF tone before sending the new one*/
2602+ connect(content->stopDTMFTone(), &Tp::PendingOperation::finished, [=](Tp::PendingOperation *op){
2603+ // in case stopDTMFTone, it might mean the service automatically stops the tone,
2604+ // so try playing the next one
2605+ if (op->isError()) {
2606+ /* send DTMF to network (via telepathy) */
2607+ if (event >= 0) {
2608+ content->startDTMFTone((Tp::DTMFEvent)event);
2609+ }
2610+ triggerNextDTMFTone(channel);
2611+ return;
2612+ }
2613+
2614+ Tp::Client::CallContentInterfaceDTMFInterface *dtmfInterface = content->interface<Tp::Client::CallContentInterfaceDTMFInterface>();
2615+ Tp::PendingVariant *pv = dtmfInterface->requestPropertyCurrentlySendingTones();
2616+ connect(pv, &Tp::PendingOperation::finished, [=](){
2617+ bool sendingTones = pv->result().toBool();
2618+ // if we already stopped sending tones, we can send the next one
2619+ if (!sendingTones) {
2620+ /* send DTMF to network (via telepathy) */
2621+ if (event >= 0) {
2622+ content->startDTMFTone((Tp::DTMFEvent)event);
2623+ }
2624+ triggerNextDTMFTone(channel);
2625+ return;
2626+ }
2627+
2628+ // in case the previous tone is not finished, we need to wait for it
2629+ auto conn = std::make_shared<QMetaObject::Connection>();
2630+ *conn = connect(dtmfInterface, &Tp::Client::CallContentInterfaceDTMFInterface::StoppedTones, [=](){
2631+ QObject::disconnect(*conn);
2632+
2633+ /* send DTMF to network (via telepathy) */
2634+ if (event >= 0) {
2635+ content->startDTMFTone((Tp::DTMFEvent)event);
2636+ }
2637+ triggerNextDTMFTone(channel);
2638+ });
2639+ });
2640+
2641+ });
2642+ }
2643+ }
2644+}
2645+
2646+void CallHandler::triggerNextDTMFTone(Tp::CallChannelPtr channel)
2647+{
2648+ QTimer::singleShot(250, [=](){
2649+ QString pendingDTMF = channel->property("pendingDTMF").toString();
2650+ if (pendingDTMF.isEmpty()) {
2651+ return;
2652+ }
2653+ pendingDTMF.remove(0, 1);
2654+ channel->setProperty("pendingDTMF", pendingDTMF);
2655+ playNextDTMFTone(channel);
2656+ });
2657+}
2658+
2659+int CallHandler::toDTMFEvent(const QString &key)
2660+{
2661+ bool ok;
2662+ int ev = key.toInt(&ok);
2663+ if (!ok) {
2664+ if (key == "*") {
2665+ ev = Tp::DTMFEventAsterisk;
2666+ } else if (key == "#") {
2667+ ev = Tp::DTMFEventHash;
2668+ } else {
2669+ ev = -1;
2670+ }
2671+ }
2672+ return ev;
2673+}
2674+
2675+bool CallHandler::isIncoming(const Tp::CallChannelPtr &channel) const
2676+{
2677+ AccountEntry *accountEntry = TelepathyHelper::instance()->accountForConnection(channel->connection());
2678+ return channel->initiatorContact() != accountEntry->account()->connection()->selfContact();
2679+}
2680+
2681+QString CallHandler::applyNumberRewritingRules(const QString &originalNumber, const QVariantMap &properties)
2682+{
2683+ QString finalNumber = originalNumber;
2684+ if (properties.contains("numberRewrite") && properties["numberRewrite"].toBool()) {
2685+ // FIXME: do a proper phone number identification implementation
2686+ // for now consider anything bigger than 6 digits to be a phone number
2687+ if (finalNumber.length() <= 6) {
2688+ return finalNumber;
2689+ }
2690+
2691+ QString defaultCountryCode = properties["defaultCountryCode"].toString();
2692+ QString defaultAreaCode = properties["defaultAreaCode"].toString();
2693+ QString removeCharacters = properties["removeCharacters"].toString();
2694+ QString prefix = properties["prefix"].toString();
2695+
2696+ if (!defaultCountryCode.startsWith("+")) {
2697+ defaultCountryCode.prepend("+");
2698+ }
2699+
2700+ finalNumber = PhoneUtils::getFullNumber(finalNumber, defaultCountryCode, defaultAreaCode);
2701+ finalNumber.remove(removeCharacters);
2702+ finalNumber.prepend(prefix);
2703+ }
2704+
2705+ return finalNumber;
2706+}
2707
2708=== modified file 'handler/callhandler.h'
2709--- handler/callhandler.h 2015-03-04 18:02:13 +0000
2710+++ handler/callhandler.h 2017-04-05 14:05:16 +0000
2711@@ -1,5 +1,5 @@
2712 /*
2713- * Copyright (C) 2012-2013 Canonical, Ltd.
2714+ * Copyright (C) 2012-2017 Canonical, Ltd.
2715 *
2716 * Authors:
2717 * Gustavo Pichorim Boiko <gustavo.boiko@canonical.com>
2718@@ -28,6 +28,7 @@
2719 #include <TelepathyQt/CallChannel>
2720
2721 class TelepathyHelper;
2722+class CallAgent;
2723
2724 class CallHandler : public QObject
2725 {
2726@@ -44,7 +45,6 @@
2727 void hangUpCall(const QString &objectPath);
2728 void setHold(const QString &objectPath, bool hold);
2729 void setMuted(const QString &objectPath, bool muted);
2730- void setActiveAudioOutput(const QString &objectPath, const QString &id);
2731 void sendDTMF(const QString &objectPath, const QString &key);
2732
2733 // conference call related
2734@@ -61,6 +61,13 @@
2735 Tp::CallChannelPtr existingCall(const QString &targetId);
2736 Tp::CallChannelPtr callFromObjectPath(const QString &objectPath);
2737
2738+ void playNextDTMFTone(Tp::CallChannelPtr channel);
2739+ void triggerNextDTMFTone(Tp::CallChannelPtr channel);
2740+ static int toDTMFEvent(const QString &key);
2741+ bool isIncoming(const Tp::CallChannelPtr &channel) const;
2742+
2743+ QString applyNumberRewritingRules(const QString &originalNumber, const QVariantMap &properties);
2744+
2745 protected Q_SLOTS:
2746 void onContactsAvailable(Tp::PendingOperation *op);
2747 void onCallHangupFinished(Tp::PendingOperation *op);
2748@@ -72,6 +79,7 @@
2749
2750 QMap<QString, Tp::ContactPtr> mContacts;
2751 QList<Tp::CallChannelPtr> mCallChannels;
2752+ QMap<Tp::CallChannel*,CallAgent*> mCallAgents;
2753 QMap<Tp::PendingOperation*,Tp::CallChannelPtr> mClosingChannels;
2754 bool mHangupRequested;
2755 };
2756
2757=== added file 'handler/farstreamchannel.cpp'
2758--- handler/farstreamchannel.cpp 1970-01-01 00:00:00 +0000
2759+++ handler/farstreamchannel.cpp 2017-04-05 14:05:16 +0000
2760@@ -0,0 +1,326 @@
2761+/*
2762+ * Copyright (C) 2017 Canonical, Ltd.
2763+ *
2764+ * Authors:
2765+ * Gustavo Pichorim Boiko <gustavo.boiko@canonical.com>
2766+ *
2767+ * This file is part of telephony-service.
2768+ *
2769+ * telephony-service is free software; you can redistribute it and/or modify
2770+ * it under the terms of the GNU General Public License as published by
2771+ * the Free Software Foundation; version 3.
2772+ *
2773+ * telephony-service is distributed in the hope that it will be useful,
2774+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
2775+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2776+ * GNU General Public License for more details.
2777+ *
2778+ * You should have received a copy of the GNU General Public License
2779+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
2780+ */
2781+
2782+#include "farstreamchannel.h"
2783+#include <farstream/fs-utils.h>
2784+#include <QDebug>
2785+
2786+FarstreamChannel::FarstreamChannel(TfChannel *channel, QObject *parent) :
2787+ mChannel(channel), QObject(parent), mPipeline(0), mBus(0), mBusSource(0),
2788+ mConferenceAddedSignal(0), mConferenceRemovedSignal(0), mContentAddedSignal(0),
2789+ mContentRemovedSignal(0), mAudioInput(0), mAudioOutput(0)
2790+{
2791+ qDebug() << __PRETTY_FUNCTION__;
2792+ initialize();
2793+}
2794+
2795+FarstreamChannel::~FarstreamChannel()
2796+{
2797+ // stop audio input and output
2798+ if (mAudioInput) {
2799+ setState(mAudioInput, GST_STATE_NULL);
2800+ gst_object_unref(mAudioInput);
2801+ }
2802+
2803+ if (mAudioOutput) {
2804+ setState(mAudioOutput, GST_STATE_NULL);
2805+ gst_object_unref(mAudioOutput);
2806+ }
2807+
2808+ // now clear the notifiers
2809+ Q_FOREACH(FsElementAddedNotifier *notifier, mNotifiers) {
2810+ fs_element_added_notifier_remove(notifier, GST_BIN(mPipeline));
2811+ g_object_unref(notifier);
2812+ }
2813+ mNotifiers.clear();
2814+
2815+ // clear the bus stuff
2816+ if (mBusSource) {
2817+ g_source_remove(mBusSource);
2818+ }
2819+
2820+ if (mBus) {
2821+ gst_object_unref(mBus);
2822+ }
2823+
2824+ // and finally the pipeline
2825+ if (mPipeline) {
2826+ setState(mPipeline, GST_STATE_NULL);
2827+ gst_object_unref(mPipeline);
2828+ }
2829+}
2830+
2831+void FarstreamChannel::setMute(bool mute)
2832+{
2833+ GstElement *input_volume = gst_bin_get_by_name(GST_BIN(mPipeline), "input_volume");
2834+ g_object_set(input_volume, "mute", mute, NULL);
2835+ g_object_unref(input_volume);
2836+}
2837+
2838+void FarstreamChannel::initialize()
2839+{
2840+ qDebug() << __PRETTY_FUNCTION__;
2841+ // connect all the signals
2842+ mConferenceAddedSignal = g_signal_connect(mChannel, "fs-conference-added",
2843+ G_CALLBACK(&FarstreamChannel::onConferenceAdded),
2844+ this);
2845+ mConferenceRemovedSignal = g_signal_connect(mChannel, "fs-conference-removed",
2846+ G_CALLBACK(&FarstreamChannel::onConferenceRemoved),
2847+ this);
2848+ mContentAddedSignal = g_signal_connect(mChannel, "content-added",
2849+ G_CALLBACK(&FarstreamChannel::onContentAdded),
2850+ this);
2851+ mContentRemovedSignal = g_signal_connect(mChannel, "content-removed",
2852+ G_CALLBACK(&FarstreamChannel::onContentRemoved),
2853+ this);
2854+
2855+ // and initialize the gstreamer pipeline
2856+ mPipeline = gst_pipeline_new(NULL);
2857+ if (!mPipeline) {
2858+ qCritical() << "Failed to create GStreamer pipeline.";
2859+ return;
2860+ }
2861+
2862+ mBus = gst_pipeline_get_bus(GST_PIPELINE(mPipeline));
2863+ if (!mBus) {
2864+ qCritical() << "Failed to get GStreamer pipeline bus.";
2865+ return;
2866+ }
2867+
2868+ mBusSource = gst_bus_add_watch(mBus, (GstBusFunc) &FarstreamChannel::onBusWatch, this);
2869+
2870+ if (!setState(mPipeline, GST_STATE_PLAYING)) {
2871+ return;
2872+ }
2873+}
2874+
2875+GstElement *FarstreamChannel::initializeAudioSource(TfContent *content)
2876+{
2877+ qDebug() << __PRETTY_FUNCTION__;
2878+ GstElement *element = gst_parse_bin_from_description ("alsasrc ! audio/x-raw, rate=8000 ! queue"
2879+ " ! audioconvert ! audioresample"
2880+ " ! volume name=input_volume ! audioconvert ",
2881+ TRUE, NULL);
2882+ gint input_volume = 0;
2883+ g_object_get (content, "requested-input-volume", &input_volume, NULL);
2884+
2885+ if (input_volume >= 0) {
2886+ GstElement *volume = gst_bin_get_by_name (GST_BIN (element), "input_volume");
2887+ g_object_set (volume, "volume", (double)input_volume / 255.0, NULL);
2888+ gst_object_unref (volume);
2889+ }
2890+
2891+ // FIXME: we probably need to handle input volume request changes
2892+ mAudioOutput = element;
2893+ return element;
2894+}
2895+
2896+bool FarstreamChannel::addToPipeline(GstElement *element)
2897+{
2898+ qDebug() << __PRETTY_FUNCTION__ << GST_ELEMENT_NAME(element);
2899+ if (!mPipeline) {
2900+ qWarning() << "No gstreamer pipeline found.";
2901+ return false;
2902+ }
2903+
2904+ if (!gst_bin_add(GST_BIN(mPipeline), element)) {
2905+ qCritical() << "Failed to add bin" << GST_ELEMENT_NAME(element) << "to pipeline.";
2906+ return false;
2907+ }
2908+ qDebug() << "Succeeded adding to pipeline!";
2909+ return true;
2910+}
2911+
2912+void FarstreamChannel::removeFromPipeline(GstElement *element)
2913+{
2914+ qDebug() << __PRETTY_FUNCTION__ << GST_ELEMENT_NAME(element);
2915+ gst_element_set_locked_state(element, TRUE);
2916+ setState(element, GST_STATE_NULL);
2917+ gst_bin_remove (GST_BIN (mPipeline), element);
2918+}
2919+
2920+bool FarstreamChannel::setState(GstElement *element, GstState state)
2921+{
2922+ qDebug() << __PRETTY_FUNCTION__ << GST_ELEMENT_NAME(element) << gst_element_state_get_name(state);
2923+ GstStateChangeReturn result = gst_element_set_state(element, state);
2924+ if (result == GST_STATE_CHANGE_FAILURE) {
2925+ qCritical() << "Failed to set GStreamer element" << GST_ELEMENT_NAME(element) << "state to" << gst_element_state_get_name(state);
2926+ return false;
2927+ }
2928+ qDebug() << "Succeeded playing!";
2929+ return true;
2930+}
2931+
2932+gboolean FarstreamChannel::onBusWatch(GstBus *bus, GstMessage *message, FarstreamChannel *self)
2933+{
2934+ Q_UNUSED(bus)
2935+ if (!self->mChannel) {
2936+ return TRUE;
2937+ }
2938+
2939+ // FIXME: maybe we need to do some error handling here?
2940+ tf_channel_bus_message(self->mChannel, message);
2941+ return TRUE;
2942+}
2943+
2944+void FarstreamChannel::onConferenceAdded(TfChannel *channel, FsConference *conference, FarstreamChannel *self)
2945+{
2946+ qDebug() << __PRETTY_FUNCTION__;
2947+ Q_UNUSED(channel)
2948+
2949+ /* Add notifier to set the various element properties as needed */
2950+ GKeyFile *keyfile = fs_utils_get_default_element_properties (GST_ELEMENT(conference));
2951+ if (keyfile != NULL) {
2952+ qDebug() << "Loaded default properties for" << GST_ELEMENT_NAME(conference);
2953+ FsElementAddedNotifier *notifier = fs_element_added_notifier_new();
2954+ fs_element_added_notifier_set_properties_from_keyfile(notifier, keyfile);
2955+ fs_element_added_notifier_add(notifier, GST_BIN(self->mPipeline));
2956+
2957+ // FIXME: right now we are leaking the notifiers, check when to remove them
2958+ self->mNotifiers.append(notifier);
2959+ }
2960+
2961+ if (!self->addToPipeline(GST_ELEMENT(conference))) {
2962+ return;
2963+ }
2964+
2965+ self->setState(GST_ELEMENT(conference), GST_STATE_PLAYING);
2966+}
2967+
2968+void FarstreamChannel::onConferenceRemoved(TfChannel *channel, FsConference *conference, FarstreamChannel *self)
2969+{
2970+ qDebug() << __PRETTY_FUNCTION__;
2971+ Q_UNUSED(channel);
2972+
2973+ // just remove the conference from the pipeline
2974+ self->removeFromPipeline(GST_ELEMENT(conference));
2975+}
2976+
2977+void FarstreamChannel::onContentAdded(TfChannel *channel, TfContent *content, FarstreamChannel *self)
2978+{
2979+ qDebug() << __PRETTY_FUNCTION__;
2980+ Q_UNUSED(channel)
2981+
2982+ g_signal_connect(content, "src-pad-added",
2983+ G_CALLBACK(&FarstreamChannel::onSrcPadAdded), self);
2984+ g_signal_connect(content, "start-sending",
2985+ G_CALLBACK(&FarstreamChannel::onStartSending), self);
2986+ g_signal_connect(content, "stop-sending",
2987+ G_CALLBACK(&FarstreamChannel::onStopSending), self);
2988+}
2989+
2990+void FarstreamChannel::onContentRemoved(TfChannel *channel, TfContent *content, FarstreamChannel *self)
2991+{
2992+ qDebug() << __PRETTY_FUNCTION__;
2993+ // FIXME: implement
2994+}
2995+
2996+bool FarstreamChannel::onStartSending(TfContent *content, FarstreamChannel *self)
2997+{
2998+ qDebug() << __PRETTY_FUNCTION__;
2999+ GstPad *sinkPad;
3000+ FsMediaType mediaType;
3001+ GstElement *element;
3002+
3003+ g_object_get (content, "sink-pad", &sinkPad, "media-type", &mediaType, NULL);
3004+
3005+ switch (mediaType) {
3006+ case FS_MEDIA_TYPE_AUDIO:
3007+ element = self->initializeAudioSource(content);
3008+ break;
3009+ // FIXME: add video support
3010+ default:
3011+ qWarning() << "Unsupported media type:" << mediaType;
3012+ g_object_unref(sinkPad);
3013+ return false;
3014+ }
3015+
3016+ if (!self->addToPipeline(element)) {
3017+ g_object_unref(sinkPad);
3018+ return false;
3019+ }
3020+
3021+ GstPad *sourcePad = gst_element_get_static_pad (element, "src");
3022+ if (GST_PAD_LINK_FAILED (gst_pad_link (sourcePad, sinkPad))) {
3023+ qCritical() << "Failed to link source pad to content's sink pad";
3024+ g_object_unref(sinkPad);
3025+ g_object_unref(sourcePad);
3026+ return false;
3027+ }
3028+
3029+ self->setState(element, GST_STATE_PLAYING);
3030+ self->mAudioInput = element;
3031+
3032+ g_object_unref(sinkPad);
3033+ g_object_unref(sourcePad);
3034+
3035+ return true;
3036+}
3037+
3038+void FarstreamChannel::onStopSending(TfContent *content, FarstreamChannel *self)
3039+{
3040+ qDebug() << __PRETTY_FUNCTION__;
3041+ // FIXME: implement
3042+}
3043+
3044+void FarstreamChannel::onSrcPadAdded(TfContent *content, uint handle, FsStream *stream, GstPad *pad, FsCodec *codec, FarstreamChannel *self)
3045+{
3046+ qDebug() << __PRETTY_FUNCTION__;
3047+ gchar *codecString = fs_codec_to_string (codec);
3048+ qDebug() << __PRETTY_FUNCTION__ << "Codec:" << codecString;
3049+
3050+ FsMediaType mediaType;
3051+ GstElement *element;
3052+
3053+ g_object_get (content, "media-type", &mediaType, NULL);
3054+
3055+ switch (mediaType) {
3056+ case FS_MEDIA_TYPE_AUDIO: {
3057+ QString outputVolume = QString("output_volume%1").arg(codecString);
3058+ QString description = QString("audioconvert ! audioresample "
3059+ "! volume name=\"%1\" "
3060+ "! audioconvert ! autoaudiosink").arg(outputVolume);
3061+ element = gst_parse_bin_from_description (description.toUtf8().data(), TRUE, NULL);
3062+ GstElement *volume = gst_bin_get_by_name (GST_BIN (element), outputVolume.toUtf8().data());
3063+
3064+ // FIXME: we need to handle volume request changes in the volume element
3065+ gst_object_unref (volume);
3066+ break;
3067+ }
3068+ // FIXME: handle video
3069+ default:
3070+ qWarning() << "Unsupported media type:" << mediaType;
3071+ return;
3072+ }
3073+
3074+ if (!self->addToPipeline(element)) {
3075+ return;
3076+ }
3077+
3078+ GstPad *sinkPad = gst_element_get_static_pad (element, "sink");
3079+ if (GST_PAD_LINK_FAILED (gst_pad_link (pad, sinkPad))) {
3080+ qCritical() << "Failed to link content's source pad to local sink pad";
3081+ }
3082+
3083+ self->setState(element, GST_STATE_PLAYING);
3084+
3085+ g_object_unref (sinkPad);
3086+}
3087
3088=== added file 'handler/farstreamchannel.h'
3089--- handler/farstreamchannel.h 1970-01-01 00:00:00 +0000
3090+++ handler/farstreamchannel.h 2017-04-05 14:05:16 +0000
3091@@ -0,0 +1,80 @@
3092+/*
3093+ * Copyright (C) 2017 Canonical, Ltd.
3094+ *
3095+ * Authors:
3096+ * Gustavo Pichorim Boiko <gustavo.boiko@canonical.com>
3097+ *
3098+ * This file is part of telephony-service.
3099+ *
3100+ * telephony-service is free software; you can redistribute it and/or modify
3101+ * it under the terms of the GNU General Public License as published by
3102+ * the Free Software Foundation; version 3.
3103+ *
3104+ * telephony-service is distributed in the hope that it will be useful,
3105+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
3106+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3107+ * GNU General Public License for more details.
3108+ *
3109+ * You should have received a copy of the GNU General Public License
3110+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
3111+ */
3112+
3113+#ifndef FARSTREAMCHANNEL_H
3114+#define FARSTREAMCHANNEL_H
3115+
3116+#include <QObject>
3117+#include <QList>
3118+#include <telepathy-farstream/telepathy-farstream.h>
3119+#include <farstream/fs-conference.h>
3120+#include <farstream/fs-stream.h>
3121+#include <farstream/fs-element-added-notifier.h>
3122+
3123+class FarstreamChannel : public QObject
3124+{
3125+ Q_OBJECT
3126+public:
3127+ explicit FarstreamChannel(TfChannel *channel, QObject *parent = 0);
3128+ ~FarstreamChannel();
3129+
3130+ void setMute(bool mute);
3131+
3132+protected:
3133+ void initialize();
3134+ GstElement *initializeAudioSource(TfContent *content);
3135+
3136+ // gstreamer helpers
3137+ bool addToPipeline(GstElement *element);
3138+ void removeFromPipeline(GstElement *bin);
3139+ bool setState(GstElement *element, GstState state);
3140+
3141+ // glib signal handlers
3142+ static gboolean onBusWatch(GstBus *bus, GstMessage *message, FarstreamChannel *self);
3143+ static void onConferenceAdded(TfChannel *channel, FsConference *conference, FarstreamChannel *self);
3144+ static void onConferenceRemoved(TfChannel *channel, FsConference *conference, FarstreamChannel *self);
3145+ static void onContentAdded(TfChannel *channel, TfContent * content, FarstreamChannel *self);
3146+ static void onContentRemoved(TfChannel *channel, TfContent * content, FarstreamChannel *self);
3147+ static bool onStartSending(TfContent *content, FarstreamChannel *self);
3148+ static void onStopSending(TfContent *content, FarstreamChannel *self);
3149+ static void onSrcPadAdded(TfContent *content, uint handle, FsStream *stream, GstPad *pad, FsCodec *codec, FarstreamChannel *self);
3150+
3151+private:
3152+ TfChannel *mChannel;
3153+
3154+ // gstreamer stuff
3155+ GstElement *mPipeline;
3156+ GstBus *mBus;
3157+ uint mBusSource;
3158+ GstElement *mAudioInput;
3159+ GstElement *mAudioOutput;
3160+
3161+ // signal IDs
3162+ gulong mConferenceAddedSignal;
3163+ gulong mConferenceRemovedSignal;
3164+ gulong mContentAddedSignal;
3165+ gulong mContentRemovedSignal;
3166+
3167+ // farstream stuff
3168+ QList<FsElementAddedNotifier*> mNotifiers;
3169+};
3170+
3171+#endif // FARSTREAMCHANNEL_H
3172
3173=== modified file 'handler/handler.cpp'
3174--- handler/handler.cpp 2016-11-23 19:28:18 +0000
3175+++ handler/handler.cpp 2017-04-05 14:05:16 +0000
3176@@ -1,5 +1,5 @@
3177 /*
3178- * Copyright (C) 2012-2016 Canonical, Ltd.
3179+ * Copyright (C) 2012-2017 Canonical, Ltd.
3180 *
3181 * Authors:
3182 * Tiago Salem Herrmann <tiago.herrmann@canonical.com>
3183@@ -32,7 +32,7 @@
3184 #include <TelepathyQt/PendingReady>
3185
3186 Handler::Handler(QObject *parent)
3187- : QObject(parent), Tp::AbstractClientHandler(channelFilters())
3188+ : QObject(parent), Tp::AbstractClientHandler(channelFilters(), capabilities())
3189 {
3190 }
3191
3192@@ -102,9 +102,22 @@
3193 specList << Tp::ChannelClassSpec::textChatroom();
3194 specList << Tp::ChannelClassSpec::unnamedTextChat();
3195
3196+ QVariantMap props;
3197+ props[TP_QT_IFACE_CHANNEL_TYPE_CALL + ".InitialAudio"] = true;
3198+ specList << Tp::ChannelClassSpec::audioCall(props);
3199+
3200 return specList;
3201 }
3202
3203+Tp::AbstractClientHandler::Capabilities Handler::capabilities()
3204+{
3205+ QStringList caps;
3206+ caps << TP_QT_IFACE_CHANNEL_TYPE_CALL + "/shm"
3207+ << TP_QT_IFACE_CHANNEL_TYPE_CALL + "/ice"
3208+ << TP_QT_IFACE_CHANNEL_TYPE_CALL + "/gtalk-p2p";
3209+ return Tp::AbstractClientHandler::Capabilities(caps);
3210+}
3211+
3212 void Handler::onTextChannelReady(Tp::PendingOperation *op)
3213 {
3214 Tp::PendingReady *pr = qobject_cast<Tp::PendingReady*>(op);
3215@@ -155,10 +168,12 @@
3216 // if the call is neither Accepted nor Active, it means it got dispatched directly to the handler without passing
3217 // through any approver. For phone calls, this would mean calls getting auto-accepted which is not desirable
3218 // so we return an error here
3219- bool incoming = false;
3220+ bool incoming = !callChannel->isRequested();
3221 AccountEntry *accountEntry = TelepathyHelper::instance()->accountForConnection(callChannel->connection());
3222- if (accountEntry) {
3223- incoming = callChannel->initiatorContact() != accountEntry->account()->connection()->selfContact();
3224+ if (accountEntry &&
3225+ !callChannel->initiatorContact().isNull() &&
3226+ callChannel->initiatorContact() != accountEntry->account()->connection()->selfContact()) {
3227+ incoming = true;
3228 }
3229 if (incoming && callChannel->callState() != Tp::CallStateAccepted && callChannel->callState() != Tp::CallStateActive) {
3230 qWarning() << "Available channel was not approved by telephony-service-approver, ignoring it.";
3231
3232=== modified file 'handler/handler.h'
3233--- handler/handler.h 2015-07-01 22:04:30 +0000
3234+++ handler/handler.h 2017-04-05 14:05:16 +0000
3235@@ -1,5 +1,5 @@
3236 /*
3237- * Copyright (C) 2012-2013 Canonical, Ltd.
3238+ * Copyright (C) 2012-2017 Canonical, Ltd.
3239 *
3240 * Authors:
3241 * Tiago Salem Herrmann <tiago.herrmann@canonical.com>
3242@@ -45,6 +45,7 @@
3243 const QDateTime &userActionTime,
3244 const Tp::AbstractClientHandler::HandlerInfo &handlerInfo);
3245 Tp::ChannelClassSpecList channelFilters();
3246+ Tp::AbstractClientHandler::Capabilities capabilities();
3247
3248 Q_SIGNALS:
3249 void textChannelAvailable(Tp::TextChannelPtr textChannel);
3250
3251=== modified file 'handler/handlerdbus.cpp'
3252--- handler/handlerdbus.cpp 2016-12-06 16:45:01 +0000
3253+++ handler/handlerdbus.cpp 2017-04-05 14:05:16 +0000
3254@@ -1,5 +1,5 @@
3255 /*
3256- * Copyright (C) 2012-2016 Canonical, Ltd.
3257+ * Copyright (C) 2012-2017 Canonical, Ltd.
3258 *
3259 * Authors:
3260 * Ugo Riboni <ugo.riboni@canonical.com>
3261@@ -21,6 +21,9 @@
3262 * along with this program. If not, see <http://www.gnu.org/licenses/>.
3263 */
3264
3265+#include "accountproperties.h"
3266+#include "audiooutput.h"
3267+#include "audioroutemanager.h"
3268 #include "callhandler.h"
3269 #include "handlerdbus.h"
3270 #include "handleradaptor.h"
3271@@ -53,6 +56,12 @@
3272 &ProtocolManager::protocolsChanged, [this]() {
3273 Q_EMIT ProtocolsChanged(ProtocolManager::instance()->protocols().dbusType());
3274 });
3275+ connect(AudioRouteManager::instance(),
3276+ SIGNAL(audioOutputsChanged(AudioOutputDBusList)),
3277+ SIGNAL(AudioOutputsChanged(AudioOutputDBusList)));
3278+ connect(AudioRouteManager::instance(),
3279+ SIGNAL(activeAudioOutputChanged(QString)),
3280+ SIGNAL(ActiveAudioOutputChanged(QString)));
3281 }
3282
3283 HandlerDBus::~HandlerDBus()
3284@@ -95,6 +104,21 @@
3285 return ProtocolManager::instance()->protocols().dbusType();
3286 }
3287
3288+AllAccountsProperties HandlerDBus::GetAllAccountsProperties()
3289+{
3290+ return AccountProperties::instance()->allProperties();
3291+}
3292+
3293+QVariantMap HandlerDBus::GetAccountProperties(const QString &accountId)
3294+{
3295+ return AccountProperties::instance()->accountProperties(accountId);
3296+}
3297+
3298+void HandlerDBus::SetAccountProperties(const QString &accountId, const QVariantMap &properties)
3299+{
3300+ AccountProperties::instance()->setAccountProperties(accountId, properties);
3301+}
3302+
3303 QString HandlerDBus::registerObject(QObject *object, const QString &path)
3304 {
3305 QString fullPath = QString("%1/%2").arg(DBUS_OBJECT_PATH, path);
3306@@ -125,6 +149,11 @@
3307 TextHandler::instance()->removeParticipants(objectPath, participants, message);
3308 }
3309
3310+void HandlerDBus::LeaveRooms(const QString &accountId, const QString &message)
3311+{
3312+ return TextHandler::instance()->leaveRooms(accountId, message);
3313+}
3314+
3315 bool HandlerDBus::LeaveChat(const QString &objectPath, const QString &message)
3316 {
3317 return TextHandler::instance()->leaveChat(objectPath, message);
3318@@ -140,6 +169,21 @@
3319 return TextHandler::instance()->changeRoomTitle(objectPath, title);
3320 }
3321
3322+void HandlerDBus::setActiveAudioOutput(const QString &id)
3323+{
3324+ AudioRouteManager::instance()->setActiveAudioOutput(id);
3325+}
3326+
3327+QString HandlerDBus::activeAudioOutput() const
3328+{
3329+ return AudioRouteManager::instance()->activeAudioOutput();
3330+}
3331+
3332+AudioOutputDBusList HandlerDBus::AudioOutputs() const
3333+{
3334+ return AudioRouteManager::instance()->audioOutputs();
3335+}
3336+
3337 bool HandlerDBus::connectToBus()
3338 {
3339 new TelephonyServiceHandlerAdaptor(this);
3340@@ -187,11 +231,6 @@
3341 CallHandler::instance()->setMuted(objectPath, muted);
3342 }
3343
3344-void HandlerDBus::SetActiveAudioOutput(const QString &objectPath, const QString &id)
3345-{
3346- CallHandler::instance()->setActiveAudioOutput(objectPath, id);
3347-}
3348-
3349 void HandlerDBus::SendDTMF(const QString &objectPath, const QString &key)
3350 {
3351 CallHandler::instance()->sendDTMF(objectPath, key);
3352
3353=== modified file 'handler/handlerdbus.h'
3354--- handler/handlerdbus.h 2016-12-06 16:45:01 +0000
3355+++ handler/handlerdbus.h 2017-04-05 14:05:16 +0000
3356@@ -1,5 +1,5 @@
3357 /*
3358- * Copyright (C) 2012-2016 Canonical, Ltd.
3359+ * Copyright (C) 2012-2017 Canonical, Ltd.
3360 *
3361 * Authors:
3362 * Ugo Riboni <ugo.riboni@canonical.com>
3363@@ -28,6 +28,9 @@
3364 #include <QtDBus/QDBusContext>
3365 #include "chatmanager.h"
3366 #include "dbustypes.h"
3367+#include "audiooutput.h"
3368+
3369+typedef QMap<QString,QVariantMap> AllAccountsProperties;
3370
3371 /**
3372 * DBus interface for the phone handler
3373@@ -40,6 +43,11 @@
3374 WRITE setCallIndicatorVisible
3375 NOTIFY CallIndicatorVisibleChanged)
3376
3377+ Q_PROPERTY(QString ActiveAudioOutput
3378+ READ activeAudioOutput
3379+ WRITE setActiveAudioOutput
3380+ NOTIFY ActiveAudioOutputChanged)
3381+
3382 public:
3383 HandlerDBus(QObject* parent=0);
3384 ~HandlerDBus();
3385@@ -52,11 +60,16 @@
3386 void setCallIndicatorVisible(bool visible);
3387 // configuration related
3388 ProtocolList GetProtocols();
3389+ AllAccountsProperties GetAllAccountsProperties();
3390+ QVariantMap GetAccountProperties(const QString &accountId);
3391+ void SetAccountProperties(const QString &accountId, const QVariantMap &properties);
3392
3393 QString registerObject(QObject *object, const QString &path);
3394 void unregisterObject(const QString &path);
3395
3396 static HandlerDBus *instance();
3397+ QString activeAudioOutput() const;
3398+ void setActiveAudioOutput(const QString &id);
3399
3400 public Q_SLOTS:
3401 bool connectToBus();
3402@@ -71,27 +84,33 @@
3403 Q_NOREPLY void InviteParticipants(const QString &objectPath, const QStringList &participants, const QString &message);
3404 Q_NOREPLY void RemoveParticipants(const QString &objectPath, const QStringList &participants, const QString &message);
3405 bool LeaveChat(const QString &objectPath, const QString &message);
3406+ Q_NOREPLY void LeaveRooms(const QString &accountId, const QString &message);
3407
3408 // call related
3409 Q_NOREPLY void StartCall(const QString &number, const QString &accountId);
3410 Q_NOREPLY void HangUpCall(const QString &objectPath);
3411 Q_NOREPLY void SetHold(const QString &objectPath, bool hold);
3412 Q_NOREPLY void SetMuted(const QString &objectPath, bool muted);
3413- Q_NOREPLY void SetActiveAudioOutput(const QString &objectPath, const QString &id);
3414 Q_NOREPLY void SendDTMF(const QString &objectPath, const QString &key);
3415
3416 // conference call related
3417 Q_NOREPLY void CreateConferenceCall(const QStringList &objectPaths);
3418 Q_NOREPLY void MergeCall(const QString &conferenceObjectPath, const QString &callObjectPath);
3419 Q_NOREPLY void SplitCall(const QString &objectPath);
3420+ AudioOutputDBusList AudioOutputs() const;
3421+
3422+
3423
3424 Q_SIGNALS:
3425 void onMessageSent(const QString &number, const QString &message);
3426 void CallPropertiesChanged(const QString &objectPath, const QVariantMap &properties);
3427+ void AccountPropertiesChanged(const QString &accountId, const QVariantMap &properties);
3428 void CallIndicatorVisibleChanged(bool visible);
3429 void ConferenceCallRequestFinished(bool succeeded);
3430 void CallHoldingFailed(const QString &objectPath);
3431 void ProtocolsChanged(const ProtocolList &protocols);
3432+ void ActiveAudioOutputChanged(const QString &id);
3433+ void AudioOutputsChanged(const AudioOutputDBusList &audioOutputs);
3434
3435 private:
3436 bool mCallIndicatorVisible;
3437
3438=== modified file 'handler/main.cpp'
3439--- handler/main.cpp 2016-11-23 19:28:18 +0000
3440+++ handler/main.cpp 2017-04-05 14:05:16 +0000
3441@@ -1,5 +1,5 @@
3442 /*
3443- * Copyright (C) 2013-2016 Canonical, Ltd.
3444+ * Copyright (C) 2013-2017 Canonical, Ltd.
3445 *
3446 * Authors:
3447 * Gustavo Pichorim Boiko <gustavo.boiko@canonical.com>
3448@@ -30,6 +30,10 @@
3449 #include <TelepathyQt/AbstractClient>
3450 #include <TelepathyQt/AccountManager>
3451 #include <TelepathyQt/Contact>
3452+#include <telepathy-farstream/telepathy-farstream.h>
3453+#include <TelepathyQt/CallChannel>
3454+
3455+Q_DECLARE_METATYPE(Tp::CallChannelPtr)
3456
3457 int main(int argc, char **argv)
3458 {
3459@@ -37,6 +41,13 @@
3460 QCoreApplication::setApplicationName("telephony-service-handler");
3461
3462 Tp::registerTypes();
3463+ gst_init(&argc, &argv);
3464+ qRegisterMetaType<Tp::CallChannelPtr>();
3465+ qRegisterMetaType<AudioOutputDBus>();
3466+ qRegisterMetaType<AudioOutputDBusList>();
3467+
3468+ qDBusRegisterMetaType<AudioOutputDBus>();
3469+ qDBusRegisterMetaType<AudioOutputDBusList>();
3470
3471 // check if there is already an instance of the handler running
3472 if (ApplicationUtils::checkApplicationRunning(TP_QT_IFACE_CLIENT + ".TelephonyServiceHandler")) {
3473@@ -44,11 +55,13 @@
3474 return 1;
3475 }
3476
3477+ HandlerDBus dbus;
3478 Handler *handler = new Handler();
3479- QObject::connect(TelepathyHelper::instance(), &TelepathyHelper::setupReady, [handler]() {
3480+ QObject::connect(TelepathyHelper::instance(), &TelepathyHelper::setupReady, [&]() {
3481 TelepathyHelper::instance()->registerClient(handler, "TelephonyServiceHandler");
3482+ dbus.connectToBus();
3483 });
3484-
3485+
3486 QObject::connect(handler, SIGNAL(callChannelAvailable(Tp::CallChannelPtr)),
3487 CallHandler::instance(), SLOT(onCallChannelAvailable(Tp::CallChannelPtr)));
3488 QObject::connect(handler, SIGNAL(textChannelAvailable(Tp::TextChannelPtr)),
3489
3490=== added file 'handler/powerd.h'
3491--- handler/powerd.h 1970-01-01 00:00:00 +0000
3492+++ handler/powerd.h 2017-04-05 14:05:16 +0000
3493@@ -0,0 +1,34 @@
3494+/**
3495+ * Copyright (C) 2014 Canonical, Ltd.
3496+ *
3497+ * This program is free software: you can redistribute it and/or modify it under
3498+ * the terms of the GNU Lesser General Public License version 3, as published by
3499+ * the Free Software Foundation.
3500+ *
3501+ * This program is distributed in the hope that it will be useful, but WITHOUT
3502+ * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
3503+ * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
3504+ * Lesser General Public License for more details.
3505+ *
3506+ * You should have received a copy of the GNU Lesser General Public License
3507+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
3508+ *
3509+ * Authors: Andreas Pokorny <andreas.pokorny@canonical.com>
3510+ */
3511+
3512+#ifndef POWERD_H
3513+#define POWERD_H
3514+
3515+class PowerD
3516+{
3517+public:
3518+ PowerD() = default;
3519+ virtual ~PowerD() = default;
3520+ PowerD(PowerD const&) = delete;
3521+ PowerD& operator=(PowerD const&) = delete;
3522+
3523+ virtual void enableProximityHandling() = 0;
3524+ virtual void disableProximityHandling() = 0;
3525+};
3526+
3527+#endif // POWERD_H
3528
3529=== added file 'handler/powerdaudiomodemediator.cpp'
3530--- handler/powerdaudiomodemediator.cpp 1970-01-01 00:00:00 +0000
3531+++ handler/powerdaudiomodemediator.cpp 2017-04-05 14:05:16 +0000
3532@@ -0,0 +1,63 @@
3533+/**
3534+ * Copyright (C) 2014 Canonical, Ltd.
3535+ *
3536+ * This program is free software: you can redistribute it and/or modify it under
3537+ * the terms of the GNU Lesser General Public License version 3, as published by
3538+ * the Free Software Foundation.
3539+ *
3540+ * This program is distributed in the hope that it will be useful, but WITHOUT
3541+ * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
3542+ * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
3543+ * Lesser General Public License for more details.
3544+ *
3545+ * You should have received a copy of the GNU Lesser General Public License
3546+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
3547+ *
3548+ * Authors: Andreas Pokorny <andreas.pokorny@canonical.com>
3549+ */
3550+
3551+#include <QDBusInterface>
3552+#include "powerdaudiomodemediator.h"
3553+
3554+PowerDAudioModeMediator::PowerDAudioModeMediator(PowerD &powerd)
3555+ : powerd(powerd)
3556+{
3557+}
3558+
3559+void PowerDAudioModeMediator::audioModeChanged(const QString &mode)
3560+{
3561+ bool enableProximity = !(mode == "speaker" || mode == "bluetooth" || mode == "wired_headset");
3562+
3563+ if (mProximityEnabled != enableProximity)
3564+ {
3565+ mProximityEnabled = enableProximity;
3566+ apply();
3567+ }
3568+}
3569+
3570+void PowerDAudioModeMediator::apply() const
3571+{
3572+ if (mProximityEnabled) {
3573+ powerd.enableProximityHandling();
3574+ } else {
3575+ // we need to power the screen on before disabling the proximity handling
3576+ QDBusInterface unityIface("com.canonical.Unity.Screen",
3577+ "/com/canonical/Unity/Screen",
3578+ "com.canonical.Unity.Screen",
3579+ QDBusConnection::systemBus());
3580+ QList<QVariant> args;
3581+ args.append("on");
3582+ args.append(3);
3583+ unityIface.callWithArgumentList(QDBus::NoBlock, "setScreenPowerMode", args);
3584+ powerd.disableProximityHandling();
3585+ }
3586+}
3587+
3588+void PowerDAudioModeMediator::audioOutputClosed()
3589+{
3590+ if (mProximityEnabled)
3591+ {
3592+ mProximityEnabled = false;
3593+ apply();
3594+ }
3595+}
3596
3597=== added file 'handler/powerdaudiomodemediator.h'
3598--- handler/powerdaudiomodemediator.h 1970-01-01 00:00:00 +0000
3599+++ handler/powerdaudiomodemediator.h 2017-04-05 14:05:16 +0000
3600@@ -0,0 +1,46 @@
3601+/****************************************************************************
3602+**
3603+** Copyright (C) 2014 Canonical, Ltd.
3604+**
3605+** Authors:
3606+** Andreas Pokorny <andreas.pokorny@canonical.com>
3607+**
3608+** GNU Lesser General Public License Usage
3609+** Alternatively, this file may be used under the terms of the GNU Lesser
3610+** General Public License version 2.1 as published by the Free Software
3611+** Foundation and appearing in the file LICENSE.LGPL included in the
3612+** packaging of this file. Please review the following information to
3613+** ensure the GNU Lesser General Public License version 2.1 requirements
3614+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
3615+**
3616+****************************************************************************/
3617+
3618+#ifndef POWERDAUDIOMODEMEDIATOR_H
3619+#define POWERDAUDIOMODEMEDIATOR_H
3620+
3621+#include "powerd.h"
3622+
3623+#include <QString>
3624+#include <fstream>
3625+#include <memory>
3626+
3627+class PowerD;
3628+/*!
3629+ * \brief PowerDAudioModeMediator is responsible for configuring proximity
3630+ * handling of powerd during different call states and used audio outputs.
3631+ * In General that mean enabling sreen blanking on proximity events, when
3632+ * a call is active and neither a bluetooth headset nor the speakers are used.
3633+ */
3634+class PowerDAudioModeMediator
3635+{
3636+public:
3637+ PowerDAudioModeMediator(PowerD &powerd);
3638+ void audioModeChanged(const QString &mode);
3639+ void audioOutputClosed();
3640+private:
3641+ void apply() const;
3642+ PowerD &powerd;
3643+ bool mProximityEnabled{false};
3644+};
3645+
3646+#endif
3647
3648=== added file 'handler/powerddbus.cpp'
3649--- handler/powerddbus.cpp 1970-01-01 00:00:00 +0000
3650+++ handler/powerddbus.cpp 2017-04-05 14:05:16 +0000
3651@@ -0,0 +1,43 @@
3652+/**
3653+ * Copyright (C) 2014 Canonical, Ltd.
3654+ *
3655+ * This program is free software: you can redistribute it and/or modify it under
3656+ * the terms of the GNU Lesser General Public License version 3, as published by
3657+ * the Free Software Foundation.
3658+ *
3659+ * This program is distributed in the hope that it will be useful, but WITHOUT
3660+ * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
3661+ * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
3662+ * Lesser General Public License for more details.
3663+ *
3664+ * You should have received a copy of the GNU Lesser General Public License
3665+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
3666+ *
3667+ * Authors: Andreas Pokorny <andreas.pokorny@canonical.com>
3668+ */
3669+
3670+#include "powerddbus.h"
3671+
3672+#include <QDBusConnection>
3673+#include <QDBusInterface>
3674+#include <QDBusReply>
3675+
3676+PowerDDBus::PowerDDBus()
3677+ : mPowerDIface{
3678+ new QDBusInterface(
3679+ "com.canonical.powerd",
3680+ "/com/canonical/powerd",
3681+ "com.canonical.powerd",
3682+ QDBusConnection::systemBus())}
3683+{
3684+}
3685+
3686+void PowerDDBus::enableProximityHandling()
3687+{
3688+ mPowerDIface->call("enableProximityHandling", "telephony-service-handler");
3689+}
3690+
3691+void PowerDDBus::disableProximityHandling()
3692+{
3693+ mPowerDIface->call("disableProximityHandling", "telephony-service-handler");
3694+}
3695
3696=== added file 'handler/powerddbus.h'
3697--- handler/powerddbus.h 1970-01-01 00:00:00 +0000
3698+++ handler/powerddbus.h 2017-04-05 14:05:16 +0000
3699@@ -0,0 +1,38 @@
3700+/**
3701+ * Copyright (C) 2014 Canonical, Ltd.
3702+ *
3703+ * This program is free software: you can redistribute it and/or modify it under
3704+ * the terms of the GNU Lesser General Public License version 3, as published by
3705+ * the Free Software Foundation.
3706+ *
3707+ * This program is distributed in the hope that it will be useful, but WITHOUT
3708+ * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
3709+ * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
3710+ * Lesser General Public License for more details.
3711+ *
3712+ * You should have received a copy of the GNU Lesser General Public License
3713+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
3714+ *
3715+ * Authors: Andreas Pokorny <andreas.pokorny@canonical.com>
3716+ */
3717+
3718+#ifndef POWERD_DUBS_H
3719+#define POWERD_DBUS_H
3720+
3721+#include "powerd.h"
3722+
3723+#include <memory>
3724+
3725+class QDBusInterface;
3726+
3727+class PowerDDBus : public PowerD
3728+{
3729+public:
3730+ PowerDDBus();
3731+ void enableProximityHandling() override;
3732+ void disableProximityHandling() override;
3733+private:
3734+ std::unique_ptr<QDBusInterface> mPowerDIface;
3735+};
3736+
3737+#endif
3738
3739=== added file 'handler/qpulseaudioengine.cpp'
3740--- handler/qpulseaudioengine.cpp 1970-01-01 00:00:00 +0000
3741+++ handler/qpulseaudioengine.cpp 2017-04-05 14:05:16 +0000
3742@@ -0,0 +1,853 @@
3743+/****************************************************************************
3744+**
3745+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
3746+** Contact: http://www.qt-project.org/legal
3747+**
3748+** This file was taken from qt5 and modified by
3749+** David Henningsson <david.henningsson@canonical.com> for usage in
3750+** telepathy-ofono.
3751+**
3752+** GNU Lesser General Public License Usage
3753+** Alternatively, this file may be used under the terms of the GNU Lesser
3754+** General Public License version 2.1 as published by the Free Software
3755+** Foundation and appearing in the file LICENSE.LGPL included in the
3756+** packaging of this file. Please review the following information to
3757+** ensure the GNU Lesser General Public License version 2.1 requirements
3758+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
3759+**
3760+****************************************************************************/
3761+
3762+#include <QtCore/qdebug.h>
3763+
3764+#include "qpulseaudioengine.h"
3765+#include <sys/types.h>
3766+#include <unistd.h>
3767+
3768+#define PULSEAUDIO_PROFILE_HSP "headset_head_unit"
3769+#define PULSEAUDIO_PROFILE_A2DP "a2dp_sink"
3770+
3771+QT_BEGIN_NAMESPACE
3772+
3773+static void contextStateCallbackInit(pa_context *context, void *userdata)
3774+{
3775+ Q_UNUSED(context);
3776+ QPulseAudioEngineWorker *pulseEngine = reinterpret_cast<QPulseAudioEngineWorker*>(userdata);
3777+ pa_threaded_mainloop_signal(pulseEngine->mainloop(), 0);
3778+}
3779+
3780+static void contextStateCallback(pa_context *context, void *userdata)
3781+{
3782+ Q_UNUSED(userdata);
3783+ Q_UNUSED(context);
3784+}
3785+
3786+static void success_cb(pa_context *context, int success, void *userdata)
3787+{
3788+ QPulseAudioEngineWorker *pulseEngine = reinterpret_cast<QPulseAudioEngineWorker*>(userdata);
3789+ pa_threaded_mainloop_signal(pulseEngine->mainloop(), 0);
3790+}
3791+
3792+/* Callbacks used when handling events from PulseAudio */
3793+static void plug_card_cb(pa_context *c, const pa_card_info *info, int isLast, void *userdata)
3794+{
3795+ QPulseAudioEngineWorker *pulseEngine = static_cast<QPulseAudioEngineWorker*>(userdata);
3796+ if (isLast != 0 || !pulseEngine || !info) {
3797+ pa_threaded_mainloop_signal(pulseEngine->mainloop(), 0);
3798+ return;
3799+ }
3800+ pulseEngine->plugCardCallback(info);
3801+}
3802+
3803+static void update_card_cb(pa_context *c, const pa_card_info *info, int isLast, void *userdata)
3804+{
3805+ QPulseAudioEngineWorker *pulseEngine = static_cast<QPulseAudioEngineWorker*>(userdata);
3806+ if (isLast != 0 || !pulseEngine || !info) {
3807+ pa_threaded_mainloop_signal(pulseEngine->mainloop(), 0);
3808+ return;
3809+ }
3810+ pulseEngine->updateCardCallback(info);
3811+}
3812+
3813+static void unplug_card_cb(pa_context *c, const pa_card_info *info, int isLast, void *userdata)
3814+{
3815+ QPulseAudioEngineWorker *pulseEngine = static_cast<QPulseAudioEngineWorker*>(userdata);
3816+ if (!pulseEngine) {
3817+ pa_threaded_mainloop_signal(pulseEngine->mainloop(), 0);
3818+ return;
3819+ }
3820+
3821+ if (info == NULL) {
3822+ /* That means that the card used to query card_info was removed */
3823+ pulseEngine->unplugCardCallback();
3824+ }
3825+}
3826+
3827+static void subscribeCallback(pa_context *context, pa_subscription_event_type_t t, uint32_t idx, void *userdata)
3828+{
3829+ /* For card change events (slot plug/unplug and add/remove card) */
3830+ if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_CARD) {
3831+ if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_CHANGE) {
3832+ QMetaObject::invokeMethod((QPulseAudioEngineWorker *) userdata, "handleCardEvent",
3833+ Qt::QueuedConnection, Q_ARG(int, PA_SUBSCRIPTION_EVENT_CHANGE), Q_ARG(unsigned int, idx));
3834+ } else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) {
3835+ QMetaObject::invokeMethod((QPulseAudioEngineWorker *) userdata, "handleCardEvent",
3836+ Qt::QueuedConnection, Q_ARG(int, PA_SUBSCRIPTION_EVENT_NEW), Q_ARG(unsigned int, idx));
3837+ } else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
3838+ QMetaObject::invokeMethod((QPulseAudioEngineWorker *) userdata, "handleCardEvent",
3839+ Qt::QueuedConnection, Q_ARG(int, PA_SUBSCRIPTION_EVENT_REMOVE), Q_ARG(unsigned int, idx));
3840+ }
3841+ }
3842+}
3843+
3844+QPulseAudioEngineWorker::QPulseAudioEngineWorker(QObject *parent)
3845+ : QObject(parent)
3846+ , m_mainLoopApi(0)
3847+ , m_context(0)
3848+ , m_callstatus(CallEnded)
3849+ , m_audiomode(AudioModeSpeaker)
3850+ , m_micmute(false)
3851+ , m_defaultsink("sink.primary")
3852+ , m_defaultsource("source.primary")
3853+ , m_voicecallcard("")
3854+ , m_voicecallhighest("")
3855+ , m_voicecallprofile("")
3856+ , m_bt_hsp("")
3857+ , m_bt_hsp_a2dp("")
3858+ , m_default_bt_card_fallback("")
3859+
3860+{
3861+ m_mainLoop = pa_threaded_mainloop_new();
3862+ if (m_mainLoop == 0) {
3863+ qWarning("Unable to create pulseaudio mainloop");
3864+ return;
3865+ }
3866+
3867+ if (pa_threaded_mainloop_start(m_mainLoop) != 0) {
3868+ qWarning("Unable to start pulseaudio mainloop");
3869+ pa_threaded_mainloop_free(m_mainLoop);
3870+ m_mainLoop = 0;
3871+ return;
3872+ }
3873+
3874+ createPulseContext();
3875+}
3876+
3877+bool QPulseAudioEngineWorker::createPulseContext()
3878+{
3879+ bool keepGoing = true;
3880+ bool ok = true;
3881+
3882+ if (m_context)
3883+ return true;
3884+
3885+ m_mainLoopApi = pa_threaded_mainloop_get_api(m_mainLoop);
3886+
3887+ pa_threaded_mainloop_lock(m_mainLoop);
3888+
3889+ m_context = pa_context_new(m_mainLoopApi, QString(QLatin1String("QtmPulseContext:%1")).arg(::getpid()).toLatin1().constData());
3890+ pa_context_set_state_callback(m_context, contextStateCallbackInit, this);
3891+
3892+ if (!m_context) {
3893+ qWarning("Unable to create new pulseaudio context");
3894+ pa_threaded_mainloop_unlock(m_mainLoop);
3895+ return false;
3896+ }
3897+
3898+ if (pa_context_connect(m_context, NULL, PA_CONTEXT_NOAUTOSPAWN, NULL) < 0) {
3899+ qWarning("Unable to create a connection to the pulseaudio context");
3900+ pa_threaded_mainloop_unlock(m_mainLoop);
3901+ releasePulseContext();
3902+ return false;
3903+ }
3904+
3905+ pa_threaded_mainloop_wait(m_mainLoop);
3906+
3907+ while (keepGoing) {
3908+ switch (pa_context_get_state(m_context)) {
3909+ case PA_CONTEXT_CONNECTING:
3910+ case PA_CONTEXT_AUTHORIZING:
3911+ case PA_CONTEXT_SETTING_NAME:
3912+ break;
3913+
3914+ case PA_CONTEXT_READY:
3915+ qDebug("Pulseaudio connection established.");
3916+ keepGoing = false;
3917+ break;
3918+
3919+ case PA_CONTEXT_TERMINATED:
3920+ qCritical("Pulseaudio context terminated.");
3921+ keepGoing = false;
3922+ ok = false;
3923+ break;
3924+
3925+ case PA_CONTEXT_FAILED:
3926+ default:
3927+ qCritical() << QString("Pulseaudio connection failure: %1").arg(pa_strerror(pa_context_errno(m_context)));
3928+ keepGoing = false;
3929+ ok = false;
3930+ }
3931+
3932+ if (keepGoing) {
3933+ pa_threaded_mainloop_wait(m_mainLoop);
3934+ }
3935+ }
3936+
3937+ if (ok) {
3938+ pa_context_set_state_callback(m_context, contextStateCallback, this);
3939+ pa_context_set_subscribe_callback(m_context, subscribeCallback, this);
3940+ pa_context_subscribe(m_context, PA_SUBSCRIPTION_MASK_CARD, NULL, this);
3941+ } else {
3942+ if (m_context) {
3943+ pa_context_unref(m_context);
3944+ m_context = 0;
3945+ }
3946+ }
3947+
3948+ pa_threaded_mainloop_unlock(m_mainLoop);
3949+ return true;
3950+}
3951+
3952+
3953+void QPulseAudioEngineWorker::releasePulseContext()
3954+{
3955+ if (m_context) {
3956+ pa_threaded_mainloop_lock(m_mainLoop);
3957+ pa_context_disconnect(m_context);
3958+ pa_context_unref(m_context);
3959+ pa_threaded_mainloop_unlock(m_mainLoop);
3960+ m_context = 0;
3961+ }
3962+
3963+}
3964+
3965+QPulseAudioEngineWorker::~QPulseAudioEngineWorker()
3966+{
3967+ releasePulseContext();
3968+
3969+ if (m_mainLoop) {
3970+ pa_threaded_mainloop_stop(m_mainLoop);
3971+ pa_threaded_mainloop_free(m_mainLoop);
3972+ m_mainLoop = 0;
3973+ }
3974+}
3975+
3976+void QPulseAudioEngineWorker::cardInfoCallback(const pa_card_info *info)
3977+{
3978+ pa_card_profile_info2 *voice_call = NULL, *highest = NULL;
3979+ pa_card_profile_info2 *hsp = NULL, *a2dp = NULL;
3980+
3981+ /* For now we only support one card with the voicecall feature */
3982+ for (int i = 0; i < info->n_profiles; i++) {
3983+ if (!highest || info->profiles2[i]->priority > highest->priority)
3984+ highest = info->profiles2[i];
3985+ if (!strcmp(info->profiles2[i]->name, "voicecall"))
3986+ voice_call = info->profiles2[i];
3987+ else if (!strcmp(info->profiles2[i]->name, PULSEAUDIO_PROFILE_HSP) &&
3988+ info->profiles2[i]->available != 0)
3989+ hsp = info->profiles2[i];
3990+ else if (!strcmp(info->profiles2[i]->name, PULSEAUDIO_PROFILE_A2DP) &&
3991+ info->profiles2[i]->available != 0)
3992+ a2dp = info->profiles2[i];
3993+ }
3994+
3995+ /* Record the card that supports voicecall (default one to be used) */
3996+ if (voice_call) {
3997+ qDebug("Found card that supports voicecall: '%s'", info->name);
3998+ m_voicecallcard = info->name;
3999+ m_voicecallhighest = highest->name;
4000+ qDebug("1");
4001+ m_voicecallprofile = voice_call->name;
4002+ qDebug("2");
4003+ }
4004+
4005+ /* Handle the use cases needed for bluetooth */
4006+ if (hsp && a2dp) {
4007+ qDebug("Found card that supports hsp and a2dp: '%s'", info->name);
4008+ m_bt_hsp_a2dp = info->name;
4009+ } else if (hsp && (a2dp == NULL)) {
4010+ /* This card only provides the hsp profile */
4011+ qDebug("Found card that supports only hsp: '%s'", info->name);
4012+ m_bt_hsp = info->name;
4013+ }
4014+ qDebug("3");
4015+}
4016+
4017+void QPulseAudioEngineWorker::sinkInfoCallback(const pa_sink_info *info)
4018+{
4019+ pa_sink_port_info *earpiece = NULL, *speaker = NULL;
4020+ pa_sink_port_info *wired_headset = NULL, *wired_headphone = NULL;
4021+ pa_sink_port_info *preferred = NULL;
4022+ pa_sink_port_info *bluetooth_sco = NULL;
4023+ pa_sink_port_info *speaker_and_wired_headphone = NULL;
4024+ AudioMode audiomodetoset;
4025+ AudioModes modes;
4026+
4027+ for (int i = 0; i < info->n_ports; i++) {
4028+ if (!strcmp(info->ports[i]->name, "output-earpiece"))
4029+ earpiece = info->ports[i];
4030+ else if (!strcmp(info->ports[i]->name, "output-wired_headset") &&
4031+ (info->ports[i]->available != PA_PORT_AVAILABLE_NO))
4032+ wired_headset = info->ports[i];
4033+ else if (!strcmp(info->ports[i]->name, "output-wired_headphone") &&
4034+ (info->ports[i]->available != PA_PORT_AVAILABLE_NO))
4035+ wired_headphone = info->ports[i];
4036+ else if (!strcmp(info->ports[i]->name, "output-speaker"))
4037+ speaker = info->ports[i];
4038+ else if (!strcmp(info->ports[i]->name, "output-bluetooth_sco"))
4039+ bluetooth_sco = info->ports[i];
4040+ else if (!strcmp(info->ports[i]->name, "output-speaker+wired_headphone"))
4041+ speaker_and_wired_headphone = info->ports[i];
4042+ }
4043+
4044+ if (!earpiece || !speaker)
4045+ return; /* Not the right sink */
4046+
4047+ /* Refresh list of available audio modes */
4048+ modes.append(AudioModeEarpiece);
4049+ modes.append(AudioModeSpeaker);
4050+ if (wired_headset || wired_headphone)
4051+ modes.append(AudioModeWiredHeadset);
4052+ if (bluetooth_sco && ((m_bt_hsp != "") || (m_bt_hsp_a2dp != "")))
4053+ modes.append(AudioModeBluetooth);
4054+
4055+ /* Check if the requested mode is available (earpiece*/
4056+ if (((m_audiomode == AudioModeWiredHeadset) && !modes.contains(AudioModeWiredHeadset)) ||
4057+ ((m_audiomode == AudioModeBluetooth) && !modes.contains(AudioModeBluetooth)))
4058+ return;
4059+
4060+ /* Now to decide which output to be used, depending on the active mode */
4061+ if (m_audiomode & AudioModeEarpiece) {
4062+ preferred = earpiece;
4063+ audiomodetoset = AudioModeEarpiece;
4064+ }
4065+ if (m_audiomode & AudioModeSpeaker) {
4066+ preferred = speaker;
4067+ audiomodetoset = AudioModeSpeaker;
4068+ }
4069+ if ((m_audiomode & AudioModeWiredHeadset) && (modes.contains(AudioModeWiredHeadset))) {
4070+ preferred = wired_headset ? wired_headset : wired_headphone;
4071+ audiomodetoset = AudioModeWiredHeadset;
4072+ }
4073+ if (m_callstatus == CallRinging && speaker_and_wired_headphone) {
4074+ preferred = speaker_and_wired_headphone;
4075+ }
4076+ if ((m_audiomode & AudioModeBluetooth) && (modes.contains(AudioModeBluetooth))) {
4077+ preferred = bluetooth_sco;
4078+ audiomodetoset = AudioModeBluetooth;
4079+ }
4080+
4081+ m_audiomode = audiomodetoset;
4082+
4083+ m_nametoset = info->name;
4084+ if (info->active_port != preferred)
4085+ m_valuetoset = preferred->name;
4086+
4087+ if (modes != m_availableAudioModes)
4088+ m_availableAudioModes = modes;
4089+}
4090+
4091+void QPulseAudioEngineWorker::sourceInfoCallback(const pa_source_info *info)
4092+{
4093+ pa_source_port_info *builtin_mic = NULL, *preferred = NULL;
4094+ pa_source_port_info *wired_headset = NULL, *bluetooth_sco = NULL;
4095+
4096+ if (info->monitor_of_sink != PA_INVALID_INDEX)
4097+ return; /* Not the right source */
4098+
4099+ for (int i = 0; i < info->n_ports; i++) {
4100+ if (!strcmp(info->ports[i]->name, "input-builtin_mic"))
4101+ builtin_mic = info->ports[i];
4102+ else if (!strcmp(info->ports[i]->name, "input-wired_headset") &&
4103+ (info->ports[i]->available != PA_PORT_AVAILABLE_NO))
4104+ wired_headset = info->ports[i];
4105+ else if (!strcmp(info->ports[i]->name, "input-bluetooth_sco_headset"))
4106+ bluetooth_sco = info->ports[i];
4107+ }
4108+
4109+ if (!builtin_mic)
4110+ return; /* Not the right source */
4111+
4112+ /* Now to decide which output to be used, depending on the active mode */
4113+ if ((m_audiomode & AudioModeEarpiece) || (m_audiomode & AudioModeSpeaker))
4114+ preferred = builtin_mic;
4115+ if ((m_audiomode & AudioModeWiredHeadset) && (m_availableAudioModes.contains(AudioModeWiredHeadset)))
4116+ preferred = wired_headset ? wired_headset : builtin_mic;
4117+ if ((m_audiomode & AudioModeBluetooth) && (m_availableAudioModes.contains(AudioModeBluetooth)))
4118+ preferred = bluetooth_sco;
4119+
4120+ m_nametoset = info->name;
4121+ if (info->active_port != preferred)
4122+ m_valuetoset = preferred->name;
4123+}
4124+
4125+void QPulseAudioEngineWorker::serverInfoCallback(const pa_server_info *info)
4126+{
4127+ /* Saving default sink/source to restore after call hangup */
4128+ m_defaultsink = info->default_sink_name;
4129+ m_defaultsource = info->default_source_name;
4130+
4131+ /* In the case of a server callback we need to signal the mainloop */
4132+ pa_threaded_mainloop_signal(mainloop(), 0);
4133+}
4134+
4135+static void cardinfo_cb(pa_context *context, const pa_card_info *info, int isLast, void *userdata)
4136+{
4137+ QPulseAudioEngineWorker *pulseEngine = static_cast<QPulseAudioEngineWorker*>(userdata);
4138+ if (isLast != 0 || !pulseEngine || !info) {
4139+ pa_threaded_mainloop_signal(pulseEngine->mainloop(), 0);
4140+ return;
4141+ }
4142+ pulseEngine->cardInfoCallback(info);
4143+}
4144+
4145+static void sinkinfo_cb(pa_context *context, const pa_sink_info *info, int isLast, void *userdata)
4146+{
4147+ QPulseAudioEngineWorker *pulseEngine = static_cast<QPulseAudioEngineWorker*>(userdata);
4148+ if (isLast != 0 || !pulseEngine || !info) {
4149+ pa_threaded_mainloop_signal(pulseEngine->mainloop(), 0);
4150+ return;
4151+ }
4152+ pulseEngine->sinkInfoCallback(info);
4153+}
4154+
4155+static void sourceinfo_cb(pa_context *context, const pa_source_info *info, int isLast, void *userdata)
4156+{
4157+ QPulseAudioEngineWorker *pulseEngine = static_cast<QPulseAudioEngineWorker*>(userdata);
4158+ if (isLast != 0 || !pulseEngine || !info) {
4159+ pa_threaded_mainloop_signal(pulseEngine->mainloop(), 0);
4160+ return;
4161+ }
4162+ pulseEngine->sourceInfoCallback(info);
4163+}
4164+
4165+static void serverinfo_cb(pa_context *context, const pa_server_info *info, void *userdata)
4166+{
4167+ QPulseAudioEngineWorker *pulseEngine = static_cast<QPulseAudioEngineWorker*>(userdata);
4168+ if (!pulseEngine || !info) {
4169+ pa_threaded_mainloop_signal(pulseEngine->mainloop(), 0);
4170+ return;
4171+ }
4172+ pulseEngine->serverInfoCallback(info);
4173+}
4174+
4175+bool QPulseAudioEngineWorker::handleOperation(pa_operation *operation, const char *func_name)
4176+{
4177+ if (!operation) {
4178+ qCritical("'%s' failed (lost PulseAudio connection?)", func_name);
4179+ /* Free resources so it can retry a new connection during next operation */
4180+ pa_threaded_mainloop_unlock(m_mainLoop);
4181+ releasePulseContext();
4182+ return false;
4183+ }
4184+
4185+ while (pa_operation_get_state(operation) == PA_OPERATION_RUNNING)
4186+ pa_threaded_mainloop_wait(m_mainLoop);
4187+ pa_operation_unref(operation);
4188+ return true;
4189+}
4190+
4191+int QPulseAudioEngineWorker::setupVoiceCall()
4192+{
4193+ pa_operation *o;
4194+
4195+ qDebug("Setting up pulseaudio for voice call");
4196+
4197+ pa_threaded_mainloop_lock(m_mainLoop);
4198+
4199+ /* Get and set the default sink/source to be restored later */
4200+ o = pa_context_get_server_info(m_context, serverinfo_cb, this);
4201+ if (!handleOperation(o, "pa_context_get_server_info"))
4202+ return -1;
4203+
4204+ qDebug("Recorded default sink: %s default source: %s",
4205+ m_defaultsink.c_str(), m_defaultsource.c_str());
4206+
4207+ /* Walk through the list of devices, find the voice call capable card and
4208+ * identify if we have bluetooth capable devices (hsp and a2dp) */
4209+ m_voicecallcard = m_voicecallhighest = m_voicecallprofile = "";
4210+ m_bt_hsp = m_bt_hsp_a2dp = "";
4211+ o = pa_context_get_card_info_list(m_context, cardinfo_cb, this);
4212+ if (!handleOperation(o, "pa_context_get_card_info_list"))
4213+ return -1;
4214+ /* In case we have only one bt device that provides hsp and a2dp, we need
4215+ * to make sure we switch the default profile for that card (to hsp) */
4216+ if ((m_bt_hsp_a2dp != "") && (m_bt_hsp == "")) {
4217+ qDebug("Setting PulseAudio card '%s' profile '%s'",
4218+ m_bt_hsp_a2dp.c_str(), PULSEAUDIO_PROFILE_HSP);
4219+ o = pa_context_set_card_profile_by_name(m_context,
4220+ m_bt_hsp_a2dp.c_str(), PULSEAUDIO_PROFILE_HSP, success_cb, this);
4221+ if (!handleOperation(o, "pa_context_set_card_profile_by_name"))
4222+ return -1;
4223+ }
4224+
4225+ pa_threaded_mainloop_unlock(m_mainLoop);
4226+
4227+ return 0;
4228+}
4229+
4230+void QPulseAudioEngineWorker::restoreVoiceCall()
4231+{
4232+ pa_operation *o;
4233+
4234+ qDebug("Restoring pulseaudio previous state");
4235+
4236+ /* Then restore previous settings */
4237+ pa_threaded_mainloop_lock(m_mainLoop);
4238+
4239+ /* See if we need to restore any HSP+AD2P device state */
4240+ if ((m_bt_hsp_a2dp != "") && (m_bt_hsp == "")) {
4241+ qDebug("Restoring PulseAudio card '%s' to profile '%s'",
4242+ m_bt_hsp_a2dp.c_str(), PULSEAUDIO_PROFILE_A2DP);
4243+ o = pa_context_set_card_profile_by_name(m_context,
4244+ m_bt_hsp_a2dp.c_str(), PULSEAUDIO_PROFILE_A2DP, success_cb, this);
4245+ if (!handleOperation(o, "pa_context_set_card_profile_by_name"))
4246+ return;
4247+ }
4248+
4249+ /* Restore default sink/source */
4250+ if (m_defaultsink != "") {
4251+ qDebug("Restoring PulseAudio default sink to '%s'", m_defaultsink.c_str());
4252+ o = pa_context_set_default_sink(m_context, m_defaultsink.c_str(), success_cb, this);
4253+ if (!handleOperation(o, "pa_context_set_default_sink"))
4254+ return;
4255+ }
4256+ if (m_defaultsource != "") {
4257+ qDebug("Restoring PulseAudio default source to '%s'", m_defaultsource.c_str());
4258+ o = pa_context_set_default_source(m_context, m_defaultsource.c_str(), success_cb, this);
4259+ if (!handleOperation(o, "pa_context_set_default_source"))
4260+ return;
4261+ }
4262+
4263+ pa_threaded_mainloop_unlock(m_mainLoop);
4264+}
4265+
4266+void QPulseAudioEngineWorker::setCallMode(CallStatus callstatus, AudioMode audiomode)
4267+{
4268+ if (!createPulseContext()) {
4269+ return;
4270+ }
4271+ CallStatus p_callstatus = m_callstatus;
4272+ AudioMode p_audiomode = m_audiomode;
4273+ AudioModes p_availableAudioModes = m_availableAudioModes;
4274+ pa_operation *o;
4275+
4276+ /* Check if we need to save the current pulseaudio state (e.g. when starting a call) */
4277+ if ((callstatus != CallEnded) && (p_callstatus == CallEnded)) {
4278+ if (setupVoiceCall() < 0) {
4279+ qCritical("Failed to setup PulseAudio for Voice Call");
4280+ return;
4281+ }
4282+ }
4283+
4284+ /* If we have an active call, update internal state (used later when updating sink/source ports) */
4285+ m_callstatus = callstatus;
4286+ m_audiomode = audiomode;
4287+
4288+ pa_threaded_mainloop_lock(m_mainLoop);
4289+
4290+ /* Switch the virtual card mode when call is active and not active
4291+ * This needs to be done before sink/source gets updated, because after changing mode
4292+ * it will automatically move to input/output-parking */
4293+ if ((m_callstatus == CallActive) && (p_callstatus != CallActive) &&
4294+ (m_voicecallcard != "") && (m_voicecallprofile != "")) {
4295+ qDebug("Setting PulseAudio card '%s' profile '%s'",
4296+ m_voicecallcard.c_str(), m_voicecallprofile.c_str());
4297+ o = pa_context_set_card_profile_by_name(m_context,
4298+ m_voicecallcard.c_str(), m_voicecallprofile.c_str(), success_cb, this);
4299+ if (!handleOperation(o, "pa_context_set_card_profile_by_name"))
4300+ return;
4301+ } else if ((m_callstatus == CallEnded) && (m_voicecallcard != "") && (m_voicecallhighest != "")) {
4302+ /* If using droid, make sure to restore to the profile that has the highest score */
4303+ qDebug("Restoring PulseAudio card '%s' to profile '%s'",
4304+ m_voicecallcard.c_str(), m_voicecallhighest.c_str());
4305+ o = pa_context_set_card_profile_by_name(m_context,
4306+ m_voicecallcard.c_str(), m_voicecallhighest.c_str(), success_cb, this);
4307+ if (!handleOperation(o, "pa_context_set_card_profile_by_name"))
4308+ return;
4309+ }
4310+
4311+ /* Find highest compatible sink/source elements from the voicecall
4312+ compatible card (on touch this means the pulse droid element) */
4313+ m_nametoset = m_valuetoset = "";
4314+ o = pa_context_get_sink_info_list(m_context, sinkinfo_cb, this);
4315+ if (!handleOperation(o, "pa_context_get_sink_info_list"))
4316+ return;
4317+ if ((m_nametoset != "") && (m_nametoset != m_defaultsink)) {
4318+ qDebug("Setting PulseAudio default sink to '%s'", m_nametoset.c_str());
4319+ o = pa_context_set_default_sink(m_context, m_nametoset.c_str(), success_cb, this);
4320+ if (!handleOperation(o, "pa_context_set_default_sink"))
4321+ return;
4322+ }
4323+ if (m_valuetoset != "") {
4324+ qDebug("Setting PulseAudio sink '%s' port '%s'",
4325+ m_nametoset.c_str(), m_valuetoset.c_str());
4326+ o = pa_context_set_sink_port_by_name(m_context, m_nametoset.c_str(),
4327+ m_valuetoset.c_str(), success_cb, this);
4328+ if (!handleOperation(o, "pa_context_set_sink_port_by_name"))
4329+ return;
4330+ }
4331+
4332+ /* Same for source */
4333+ m_nametoset = m_valuetoset = "";
4334+ o = pa_context_get_source_info_list(m_context, sourceinfo_cb, this);
4335+ if (!handleOperation(o, "pa_context_get_source_info_list"))
4336+ return;
4337+ if ((m_nametoset != "") && (m_nametoset != m_defaultsource)) {
4338+ qDebug("Setting PulseAudio default source to '%s'", m_nametoset.c_str());
4339+ o = pa_context_set_default_source(m_context, m_nametoset.c_str(), success_cb, this);
4340+ if (!handleOperation(o, "pa_context_set_default_source"))
4341+ return;
4342+ }
4343+ if (m_valuetoset != "") {
4344+ qDebug("Setting PulseAudio source '%s' port '%s'",
4345+ m_nametoset.c_str(), m_valuetoset.c_str());
4346+ o = pa_context_set_source_port_by_name(m_context, m_nametoset.c_str(),
4347+ m_valuetoset.c_str(), success_cb, this);
4348+ if (!handleOperation(o, "pa_context_set_source_port_by_name"))
4349+ return;
4350+ }
4351+
4352+ pa_threaded_mainloop_unlock(m_mainLoop);
4353+
4354+ /* Notify if the list of audio modes changed */
4355+ if (p_availableAudioModes != m_availableAudioModes)
4356+ Q_EMIT availableAudioModesChanged(m_availableAudioModes);
4357+
4358+ /* Notify if call mode changed */
4359+ if (p_audiomode != m_audiomode) {
4360+ Q_EMIT audioModeChanged(m_audiomode);
4361+ }
4362+
4363+ /* If no more active voicecall, restore previous saved pulseaudio state */
4364+ if (callstatus == CallEnded) {
4365+ restoreVoiceCall();
4366+ }
4367+
4368+ /* In case the app had set mute when the call wasn't active, make sure we reflect it here */
4369+ if (m_callstatus != CallEnded)
4370+ setMicMute(m_micmute);
4371+}
4372+
4373+void QPulseAudioEngineWorker::setMicMute(bool muted)
4374+{
4375+ if (!createPulseContext()) {
4376+ return;
4377+ }
4378+
4379+ m_micmute = muted;
4380+
4381+ if (m_callstatus == CallEnded)
4382+ return;
4383+
4384+ pa_threaded_mainloop_lock(m_mainLoop);
4385+
4386+ m_nametoset = "";
4387+ pa_operation *o = pa_context_get_source_info_list(m_context, sourceinfo_cb, this);
4388+ if (!handleOperation(o, "pa_context_get_source_info_list"))
4389+ return;
4390+
4391+ if (m_nametoset != "") {
4392+ int m = m_micmute ? 1 : 0;
4393+ qDebug("Setting PulseAudio source '%s' muted '%d'", m_nametoset.c_str(), m);
4394+ o = pa_context_set_source_mute_by_name(m_context,
4395+ m_nametoset.c_str(), m, success_cb, this);
4396+ if (!handleOperation(o, "pa_context_set_source_mute_by_name"))
4397+ return;
4398+ }
4399+
4400+ pa_threaded_mainloop_unlock(m_mainLoop);
4401+}
4402+
4403+void QPulseAudioEngineWorker::plugCardCallback(const pa_card_info *info)
4404+{
4405+ qDebug("Notified about card (%s) add event from PulseAudio", info->name);
4406+
4407+ /* Check if it's indeed a BT device (with at least one hsp profile) */
4408+ pa_card_profile_info2 *hsp = NULL, *a2dp = NULL;
4409+ for (int i = 0; i < info->n_profiles; i++) {
4410+ if (!strcmp(info->profiles2[i]->name, PULSEAUDIO_PROFILE_HSP))
4411+ hsp = info->profiles2[i];
4412+ else if (!strcmp(info->profiles2[i]->name, PULSEAUDIO_PROFILE_A2DP) &&
4413+ info->profiles2[i]->available != 0) {
4414+ qDebug("Found a2dp");
4415+ a2dp = info->profiles2[i];
4416+ }
4417+ qDebug("%s", info->profiles2[i]->name);
4418+ }
4419+
4420+ if ((!info->active_profile || !strcmp(info->active_profile->name, "off")) && a2dp) {
4421+ qDebug("No profile set");
4422+ m_default_bt_card_fallback = info->name;
4423+ }
4424+
4425+ /* We only care about BT (HSP) devices, and if one is not already available */
4426+ if ((m_callstatus != CallEnded) && ((m_bt_hsp == "") || (m_bt_hsp_a2dp == ""))) {
4427+ if (hsp)
4428+ m_handleevent = true;
4429+ }
4430+}
4431+
4432+void QPulseAudioEngineWorker::updateCardCallback(const pa_card_info *info)
4433+{
4434+ qDebug("Notified about card (%s) changes event from PulseAudio", info->name);
4435+
4436+ /* Check if it's indeed a BT device (with at least one hsp profile) */
4437+ pa_card_profile_info2 *hsp = NULL, *a2dp = NULL;
4438+ for (int i = 0; i < info->n_profiles; i++) {
4439+ if (!strcmp(info->profiles2[i]->name, PULSEAUDIO_PROFILE_HSP))
4440+ hsp = info->profiles2[i];
4441+ else if (!strcmp(info->profiles2[i]->name, PULSEAUDIO_PROFILE_A2DP) &&
4442+ info->profiles2[i]->available != 0) {
4443+ qDebug("Found a2dp");
4444+ a2dp = info->profiles2[i];
4445+ }
4446+ qDebug("%s", info->profiles2[i]->name);
4447+ }
4448+
4449+ if ((!info->active_profile || !strcmp(info->active_profile->name, "off")) && a2dp) {
4450+ qDebug("No profile set");
4451+ m_default_bt_card_fallback = info->name;
4452+ }
4453+
4454+
4455+ /* We only care if the card event for the voicecall capable card */
4456+ if ((m_callstatus == CallActive) && (!strcmp(info->name, m_voicecallcard.c_str()))) {
4457+ if (m_audiomode == AudioModeWiredHeadset) {
4458+ /* If previous mode is wired, it means it got unplugged */
4459+ m_handleevent = true;
4460+ m_audiomodetoset = AudioModeBtOrWiredOrEarpiece;
4461+ } else if ((m_audiomode == AudioModeEarpiece) || ((m_audiomode == AudioModeSpeaker))) {
4462+ /* Now only trigger the event in case wired headset/headphone is now available */
4463+ pa_card_port_info *port_info = NULL;
4464+ for (int i = 0; i < info->n_ports; i++) {
4465+ if (info->ports[i] && (info->ports[i]->available == PA_PORT_AVAILABLE_YES) && (
4466+ !strcmp(info->ports[i]->name, "output-wired_headset") ||
4467+ !strcmp(info->ports[i]->name, "output-wired_headphone"))) {
4468+ m_handleevent = true;
4469+ m_audiomodetoset = AudioModeWiredOrEarpiece;
4470+ }
4471+ }
4472+ } else if (m_audiomode == AudioModeBluetooth) {
4473+ /* Handle the event so we can update the audiomodes */
4474+ m_handleevent = true;
4475+ m_audiomodetoset = AudioModeBluetooth;
4476+ }
4477+ }
4478+}
4479+
4480+void QPulseAudioEngineWorker::unplugCardCallback()
4481+{
4482+ if (m_callstatus != CallEnded) {
4483+ m_handleevent = true;
4484+ }
4485+}
4486+
4487+void QPulseAudioEngineWorker::handleCardEvent(const int evt, const unsigned int idx)
4488+{
4489+ pa_operation *o = NULL;
4490+
4491+ /* Internal state var used to know if we need to update our internal state */
4492+ m_handleevent = false;
4493+
4494+ if (evt == PA_SUBSCRIPTION_EVENT_NEW) {
4495+ o = pa_context_get_card_info_by_index(m_context, idx, plug_card_cb, this);
4496+ if (!handleOperation(o, "pa_context_get_card_info_by_index"))
4497+ return;
4498+
4499+ if (m_default_bt_card_fallback != "") {
4500+ o = pa_context_set_card_profile_by_name(m_context,
4501+ m_default_bt_card_fallback.c_str(), PULSEAUDIO_PROFILE_A2DP, success_cb, this);
4502+ if (!handleOperation(o, "pa_context_set_card_profile_by_name"))
4503+ return;
4504+ m_default_bt_card_fallback = "";
4505+ }
4506+
4507+ if (m_handleevent) {
4508+ qDebug("Adding new BT-HSP capable device");
4509+ /* In case A2DP is available, switch to HSP */
4510+ if (setupVoiceCall() < 0)
4511+ return;
4512+ /* Enable the HSP output port */
4513+ setCallMode(m_callstatus, AudioModeBluetooth);
4514+ }
4515+ } else if (evt == PA_SUBSCRIPTION_EVENT_CHANGE) {
4516+ o = pa_context_get_card_info_by_index(m_context, idx, update_card_cb, this);
4517+ if (!handleOperation(o, "pa_context_get_card_info_by_index"))
4518+ return;
4519+
4520+ if (m_default_bt_card_fallback != "") {
4521+ o = pa_context_set_card_profile_by_name(m_context,
4522+ m_default_bt_card_fallback.c_str(), PULSEAUDIO_PROFILE_A2DP, success_cb, this);
4523+ if (!handleOperation(o, "pa_context_set_card_profile_by_name"))
4524+ return;
4525+ m_default_bt_card_fallback = "";
4526+ }
4527+
4528+ if (m_handleevent) {
4529+ /* In this case it means the handset state changed */
4530+ qDebug("Notifying card changes for the voicecall capable card");
4531+ setCallMode(m_callstatus, m_audiomodetoset);
4532+ }
4533+ } else if (evt == PA_SUBSCRIPTION_EVENT_REMOVE) {
4534+ /* Check if the main HSP card was removed */
4535+ if (m_bt_hsp != "") {
4536+ o = pa_context_get_card_info_by_name(m_context, m_bt_hsp.c_str(), unplug_card_cb, this);
4537+ if (!handleOperation(o, "pa_context_get_sink_info_by_name"))
4538+ return;
4539+ }
4540+ if (m_bt_hsp_a2dp != "") {
4541+ o = pa_context_get_card_info_by_name(m_context, m_bt_hsp_a2dp.c_str(), unplug_card_cb, this);
4542+ if (!handleOperation(o, "pa_context_get_sink_info_by_name"))
4543+ return;
4544+ }
4545+ if (m_handleevent) {
4546+ qDebug("Notifying about BT-HSP card removal");
4547+ /* Needed in order to save the default sink/source */
4548+ if (setupVoiceCall() < 0)
4549+ return;
4550+ /* Enable the default handset output port */
4551+ setCallMode(m_callstatus, AudioModeWiredOrEarpiece);
4552+ }
4553+ }
4554+}
4555+
4556+Q_GLOBAL_STATIC(QPulseAudioEngine, pulseEngine);
4557+
4558+QPulseAudioEngine::QPulseAudioEngine(QObject *parent) :
4559+ QObject(parent)
4560+{
4561+ qRegisterMetaType<CallStatus>();
4562+ qRegisterMetaType<AudioMode>();
4563+ qRegisterMetaType<AudioModes>();
4564+ mWorker = new QPulseAudioEngineWorker();
4565+ QObject::connect(mWorker, SIGNAL(audioModeChanged(const AudioMode)), this, SIGNAL(audioModeChanged(const AudioMode)), Qt::QueuedConnection);
4566+ QObject::connect(mWorker, SIGNAL(availableAudioModesChanged(const AudioModes)), this, SIGNAL(availableAudioModesChanged(const AudioModes)), Qt::QueuedConnection);
4567+ mWorker->createPulseContext();
4568+ mWorker->moveToThread(&mThread);
4569+ mThread.start();
4570+}
4571+
4572+QPulseAudioEngine::~QPulseAudioEngine()
4573+{
4574+ mThread.quit();
4575+ mThread.wait();
4576+}
4577+
4578+QPulseAudioEngine *QPulseAudioEngine::instance()
4579+{
4580+ QPulseAudioEngine *engine = pulseEngine();
4581+ return engine;
4582+}
4583+
4584+void QPulseAudioEngine::setCallMode(CallStatus callstatus, AudioMode audiomode)
4585+{
4586+ QMetaObject::invokeMethod(mWorker, "setCallMode", Qt::QueuedConnection, Q_ARG(CallStatus, callstatus), Q_ARG(AudioMode, audiomode));
4587+}
4588+
4589+void QPulseAudioEngine::setMicMute(bool muted)
4590+{
4591+ QMetaObject::invokeMethod(mWorker, "setMicMute", Qt::QueuedConnection, Q_ARG(bool, muted));
4592+}
4593+
4594+QT_END_NAMESPACE
4595+
4596
4597=== added file 'handler/qpulseaudioengine.h'
4598--- handler/qpulseaudioengine.h 1970-01-01 00:00:00 +0000
4599+++ handler/qpulseaudioengine.h 2017-04-05 14:05:16 +0000
4600@@ -0,0 +1,126 @@
4601+/****************************************************************************
4602+**
4603+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
4604+** Contact: http://www.qt-project.org/legal
4605+**
4606+** This file was taken from qt5 and modified by
4607+** David Henningsson <david.henningsson@canonical.com> for usage in
4608+** telepathy-ofono.
4609+**
4610+** GNU Lesser General Public License Usage
4611+** Alternatively, this file may be used under the terms of the GNU Lesser
4612+** General Public License version 2.1 as published by the Free Software
4613+** Foundation and appearing in the file LICENSE.LGPL included in the
4614+** packaging of this file. Please review the following information to
4615+** ensure the GNU Lesser General Public License version 2.1 requirements
4616+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
4617+**
4618+****************************************************************************/
4619+
4620+#ifndef QPULSEAUDIOENGINE_H
4621+#define QPULSEAUDIOENGINE_H
4622+
4623+#include <QtCore/qmap.h>
4624+#include <QtCore/qbytearray.h>
4625+#include <QThread>
4626+#include <pulse/pulseaudio.h>
4627+
4628+enum AudioMode {
4629+ AudioModeEarpiece = 0x0001,
4630+ AudioModeWiredHeadset = 0x0002,
4631+ AudioModeSpeaker = 0x0004,
4632+ AudioModeBluetooth = 0x0008,
4633+ AudioModeBtOrWiredOrEarpiece = AudioModeBluetooth | AudioModeWiredHeadset | AudioModeEarpiece,
4634+ AudioModeWiredOrEarpiece = AudioModeWiredHeadset | AudioModeEarpiece,
4635+ AudioModeWiredOrSpeaker = AudioModeWiredHeadset | AudioModeSpeaker,
4636+ AudioModeBtOrWiredOrSpeaker = AudioModeBluetooth | AudioModeWiredOrSpeaker
4637+};
4638+
4639+Q_DECLARE_METATYPE(AudioMode)
4640+
4641+typedef QList<AudioMode> AudioModes;
4642+Q_DECLARE_METATYPE(AudioModes)
4643+
4644+enum CallStatus {
4645+ CallRinging,
4646+ CallActive,
4647+ CallEnded
4648+};
4649+
4650+Q_DECLARE_METATYPE(CallStatus)
4651+
4652+QT_BEGIN_NAMESPACE
4653+
4654+class QPulseAudioEngineWorker : public QObject
4655+{
4656+ Q_OBJECT
4657+
4658+public:
4659+ QPulseAudioEngineWorker(QObject *parent = 0);
4660+ ~QPulseAudioEngineWorker();
4661+
4662+ pa_threaded_mainloop *mainloop() { return m_mainLoop; }
4663+ pa_context *context() { return m_context; }
4664+ bool createPulseContext(void);
4665+ int setupVoiceCall(void);
4666+ void restoreVoiceCall(void);
4667+ /* Callbacks to be used internally */
4668+ void cardInfoCallback(const pa_card_info *card);
4669+ void sinkInfoCallback(const pa_sink_info *sink);
4670+ void sourceInfoCallback(const pa_source_info *source);
4671+ void serverInfoCallback(const pa_server_info *server);
4672+ void plugCardCallback(const pa_card_info *card);
4673+ void updateCardCallback(const pa_card_info *card);
4674+ void unplugCardCallback();
4675+
4676+Q_SIGNALS:
4677+ void audioModeChanged(const AudioMode mode);
4678+ void availableAudioModesChanged(const AudioModes modes);
4679+
4680+public Q_SLOTS:
4681+ void handleCardEvent(const int evt, const unsigned int idx);
4682+ void setCallMode(CallStatus callstatus, AudioMode audiomode);
4683+ void setMicMute(bool muted); /* True if muted, false if unmuted */
4684+
4685+private:
4686+ pa_mainloop_api *m_mainLoopApi;
4687+ pa_threaded_mainloop *m_mainLoop;
4688+ pa_context *m_context;
4689+
4690+ AudioModes m_availableAudioModes;
4691+ CallStatus m_callstatus;
4692+ AudioMode m_audiomode;
4693+ AudioMode m_audiomodetoset;
4694+ bool m_micmute, m_handleevent;
4695+ std::string m_nametoset, m_valuetoset;
4696+ std::string m_defaultsink, m_defaultsource;
4697+ std::string m_bt_hsp, m_bt_hsp_a2dp;
4698+ std::string m_default_bt_card_fallback;
4699+ std::string m_voicecallcard, m_voicecallhighest, m_voicecallprofile;
4700+
4701+ bool handleOperation(pa_operation *operation, const char *func_name);
4702+ void releasePulseContext(void);
4703+};
4704+
4705+class QPulseAudioEngine : public QObject
4706+{
4707+ Q_OBJECT
4708+public:
4709+ explicit QPulseAudioEngine(QObject *parent = 0);
4710+ ~QPulseAudioEngine();
4711+ static QPulseAudioEngine *instance();
4712+
4713+ void setCallMode(CallStatus callstatus, AudioMode audiomode);
4714+ void setMicMute(bool muted); /* True if muted, false if unmuted */
4715+
4716+Q_SIGNALS:
4717+ void audioModeChanged(const AudioMode mode);
4718+ void availableAudioModesChanged(const AudioModes modes);
4719+private:
4720+ QPulseAudioEngineWorker *mWorker;
4721+ QThread mThread;
4722+};
4723+
4724+QT_END_NAMESPACE
4725+
4726+#endif
4727
4728=== modified file 'handler/texthandler.cpp'
4729--- handler/texthandler.cpp 2016-11-23 19:37:30 +0000
4730+++ handler/texthandler.cpp 2017-04-05 14:05:16 +0000
4731@@ -35,10 +35,39 @@
4732
4733 TextHandler::TextHandler(QObject *parent)
4734 : QObject(parent)
4735+ , mMessagingAppMonitor("com.canonical.MessagingApp", QDBusConnection::sessionBus(), QDBusServiceWatcher::WatchForRegistration|QDBusServiceWatcher::WatchForUnregistration), mMessagingAppRegistered(false)
4736 {
4737 qDBusRegisterMetaType<AttachmentStruct>();
4738 qDBusRegisterMetaType<AttachmentList>();
4739 qRegisterMetaType<PendingMessage>();
4740+ connect(&mMessagingAppMonitor, SIGNAL(serviceRegistered(const QString&)), SLOT(onMessagingAppOpen()));
4741+ connect(&mMessagingAppMonitor, SIGNAL(serviceUnregistered(const QString&)), SLOT(onMessagingAppClosed()));
4742+ connect(TelepathyHelper::instance(), &TelepathyHelper::accountAdded, [=](AccountEntry *account) {
4743+ if (mMessagingAppRegistered && !account->active() && account->protocolInfo()->leaveRoomsOnClose()) {
4744+ account->reconnect();
4745+ }
4746+ });
4747+}
4748+
4749+void TextHandler::onMessagingAppOpen()
4750+{
4751+ mMessagingAppRegistered = true;
4752+ Q_FOREACH(AccountEntry *account, TelepathyHelper::instance()->accounts()) {
4753+ if (!account->active() && account->protocolInfo()->leaveRoomsOnClose()) {
4754+ account->reconnect();
4755+ }
4756+ }
4757+}
4758+
4759+void TextHandler::onMessagingAppClosed()
4760+{
4761+ mMessagingAppRegistered = false;
4762+ Q_FOREACH(AccountEntry *account, TelepathyHelper::instance()->accounts()) {
4763+ if (account->protocolInfo()->leaveRoomsOnClose()) {
4764+ account->requestDisconnect();
4765+ }
4766+ }
4767+
4768 }
4769
4770 TextHandler *TextHandler::instance()
4771@@ -168,8 +197,14 @@
4772 if (chatType == Tp::HandleTypeNone && targetIds.size() == 1) {
4773 chatType = Tp::HandleTypeContact;
4774 }
4775+
4776 QString roomId = properties["threadId"].toString();
4777
4778+ // try to use the threadId as participantId if empty
4779+ if (chatType == Tp::HandleTypeContact && targetIds.isEmpty()) {
4780+ targetIds << roomId;
4781+ }
4782+
4783 Q_FOREACH(const Tp::TextChannelPtr &channel, mChannels) {
4784 int count = 0;
4785 AccountEntry *channelAccount = TelepathyHelper::instance()->accountForConnection(channel->connection());
4786@@ -275,3 +310,15 @@
4787 return true;
4788 }
4789
4790+void TextHandler::leaveRooms(const QString &accountId, const QString &message)
4791+{
4792+ Q_FOREACH(const Tp::TextChannelPtr &channel, mChannels) {
4793+ if (channel->targetHandleType() != Tp::HandleTypeRoom) {
4794+ continue;
4795+ }
4796+ AccountEntry *account = TelepathyHelper::instance()->accountForConnection(channel->connection());
4797+ if (account && account->accountId() == accountId) {
4798+ leaveChat(channel->objectPath(), message);
4799+ }
4800+ }
4801+}
4802
4803=== modified file 'handler/texthandler.h'
4804--- handler/texthandler.h 2016-11-23 19:28:18 +0000
4805+++ handler/texthandler.h 2017-04-05 14:05:16 +0000
4806@@ -48,11 +48,15 @@
4807 void inviteParticipants(const QString &objectPath, const QStringList &participants, const QString &message);
4808 void removeParticipants(const QString &objectPath, const QStringList &participants, const QString &message);
4809 bool leaveChat(const QString &objectPath, const QString &message);
4810+ void leaveRooms(const QString &accountId, const QString &message);
4811
4812 protected Q_SLOTS:
4813 void onTextChannelAvailable(Tp::TextChannelPtr channel);
4814 void onTextChannelInvalidated();
4815
4816+ void onMessagingAppOpen();
4817+ void onMessagingAppClosed();
4818+
4819 protected:
4820 QList<Tp::TextChannelPtr> existingChannels(const QString &accountId, const QVariantMap &properties);
4821 Tp::TextChannelPtr existingChannelFromObjectPath(const QString &objectPath);
4822@@ -60,6 +64,8 @@
4823 private:
4824 explicit TextHandler(QObject *parent = 0);
4825 QList<Tp::TextChannelPtr> mChannels;
4826+ QDBusServiceWatcher mMessagingAppMonitor;
4827+ bool mMessagingAppRegistered;
4828 };
4829
4830 #endif // TEXTHANDLER_H
4831
4832=== modified file 'indicator/callchannelobserver.cpp'
4833--- indicator/callchannelobserver.cpp 2014-11-19 21:45:35 +0000
4834+++ indicator/callchannelobserver.cpp 2017-04-05 14:05:16 +0000
4835@@ -1,5 +1,5 @@
4836 /*
4837- * Copyright (C) 2013 Canonical, Ltd.
4838+ * Copyright (C) 2013-2017 Canonical, Ltd.
4839 *
4840 * Authors:
4841 * Gustavo Pichorim Boiko <gustavo.boiko@canonical.com>
4842@@ -21,6 +21,7 @@
4843
4844 #include "callchannelobserver.h"
4845 #include "callnotification.h"
4846+#include "contactwatcher.h"
4847 #include "messagingmenu.h"
4848 #include "metrics.h"
4849 #include "telepathyhelper.h"
4850@@ -49,6 +50,13 @@
4851 SLOT(onHoldChanged()));
4852
4853 mChannels.append(callChannel);
4854+ if (callChannel->isReady(Tp::CallChannel::FeatureCallState)) {
4855+ mCallStates[callChannel.data()] = callChannel->callState();
4856+ } else {
4857+ connect(callChannel->becomeReady(Tp::CallChannel::FeatureCallState), &Tp::PendingReady::finished, [&](){
4858+ mCallStates[callChannel.data()] = callChannel->callState();
4859+ });
4860+ }
4861 }
4862
4863 void CallChannelObserver::onCallStateChanged(Tp::CallState state)
4864@@ -70,16 +78,23 @@
4865 switch (state) {
4866 case Tp::CallStateEnded:
4867 Q_EMIT callEnded(channel);
4868+
4869+ // if the missed flag is false, we still have to check if transitioning directly from Initialized to Ended
4870+ if (!missed && incoming) {
4871+ missed = mCallStates[channel.data()] == Tp::CallStateInitialised;
4872+ }
4873+
4874 // add the missed call to the messaging menu
4875 if (missed) {
4876 // FIXME: handle conf call
4877- MessagingMenu::instance()->addCall(channel->targetContact()->id(), accountEntry->accountId(), QDateTime::currentDateTime());
4878+ MessagingMenu::instance()->addCall(ContactWatcher::normalizeIdentifier(channel->targetContact()->id()), accountEntry->accountId(), QDateTime::currentDateTime());
4879 } else {
4880 // and show a notification
4881 // FIXME: handle conf call
4882- CallNotification::instance()->showNotificationForCall(QStringList() << channel->targetContact()->id(), CallNotification::CallEnded);
4883+ CallNotification::instance()->showNotificationForCall(QStringList() << ContactWatcher::normalizeIdentifier(channel->targetContact()->id()), CallNotification::CallEnded);
4884 }
4885
4886+ mCallStates.remove(channel.data());
4887 mChannels.removeOne(channel);
4888
4889 // update the metrics
4890@@ -93,6 +108,7 @@
4891 channel->setProperty("activeTimestamp", QDateTime::currentDateTime());
4892 break;
4893 }
4894+ mCallStates[channel.data()] = state;
4895 }
4896
4897 void CallChannelObserver::onHoldChanged()
4898
4899=== modified file 'indicator/callchannelobserver.h'
4900--- indicator/callchannelobserver.h 2014-01-20 12:57:43 +0000
4901+++ indicator/callchannelobserver.h 2017-04-05 14:05:16 +0000
4902@@ -1,5 +1,5 @@
4903 /*
4904- * Copyright (C) 2013 Canonical, Ltd.
4905+ * Copyright (C) 2013-2017 Canonical, Ltd.
4906 *
4907 * Authors:
4908 * Gustavo Pichorim Boiko <gustavo.boiko@canonical.com>
4909@@ -43,6 +43,7 @@
4910
4911 private:
4912 QList<Tp::CallChannelPtr> mChannels;
4913+ QMap<Tp::CallChannel*,Tp::CallState> mCallStates;
4914 };
4915
4916 #endif // CALLCHANNELOBSERVER_H
4917
4918=== modified file 'indicator/displaynamesettings.cpp'
4919--- indicator/displaynamesettings.cpp 2016-03-18 19:02:50 +0000
4920+++ indicator/displaynamesettings.cpp 2017-04-05 14:05:16 +0000
4921@@ -1,5 +1,5 @@
4922 /*
4923- * Copyright (C) 2016 Canonical, Ltd.
4924+ * Copyright (C) 2016-2017 Canonical, Ltd.
4925 *
4926 * Authors:
4927 * Gustavo Pichorim Boiko <gustavo.boiko@canonical.com>
4928@@ -86,7 +86,7 @@
4929
4930 void DisplayNameSettings::onAccountsChanged()
4931 {
4932- Q_FOREACH(AccountEntry *account, TelepathyHelper::instance()->accounts()) {
4933+ Q_FOREACH(AccountEntry *account, TelepathyHelper::instance()->phoneAccounts()) {
4934 QString modemObjName = account->account()->parameters().value("modem-objpath").toString();
4935 if (mAccountNames.contains(modemObjName) && account->displayName() != mAccountNames[modemObjName]) {
4936 account->setDisplayName(mAccountNames[modemObjName]);
4937
4938=== modified file 'indicator/messagingmenu.cpp'
4939--- indicator/messagingmenu.cpp 2016-11-23 19:28:18 +0000
4940+++ indicator/messagingmenu.cpp 2017-04-05 14:05:16 +0000
4941@@ -1,5 +1,5 @@
4942 /*
4943- * Copyright (C) 2012-2016 Canonical, Ltd.
4944+ * Copyright (C) 2012-2017 Canonical, Ltd.
4945 *
4946 * Authors:
4947 * Gustavo Pichorim Boiko <gustavo.boiko@canonical.com>
4948@@ -244,6 +244,8 @@
4949 file = g_file_new_for_uri(avatar.toString().toUtf8().data());
4950 icon = g_file_icon_new(file);
4951 }
4952+
4953+ qDebug() << "notify message received:" << notificationData.encodedEventId.toUtf8();
4954 MessagingMenuMessage *message = messaging_menu_message_new(notificationData.encodedEventId.toUtf8().data(),
4955 icon,
4956 displayLabel.toUtf8().data(),
4957@@ -287,8 +289,9 @@
4958 mMessages.remove(messageId);
4959 }
4960
4961-void MessagingMenu::addCallToMessagingMenu(Call call, const QString &text)
4962+void MessagingMenu::addCallToMessagingMenu(Call call, const QString &text, bool supportsTextReply)
4963 {
4964+ qDebug() << __PRETTY_FUNCTION__;
4965 GVariant *messages = NULL;
4966 GFile *file = g_file_new_for_uri(call.contactIcon.toString().toUtf8().data());
4967 GIcon *icon = g_file_icon_new(file);
4968@@ -300,7 +303,7 @@
4969 call.timestamp.toMSecsSinceEpoch() * 1000); // the value is expected to be in microseconds
4970
4971 call.messageId = messaging_menu_message_get_id(message);
4972- if (call.targetId != OFONO_PRIVATE_NUMBER && call.targetId != OFONO_UNKNOWN_NUMBER) {
4973+ if (supportsTextReply && call.targetId != OFONO_PRIVATE_NUMBER && call.targetId != OFONO_UNKNOWN_NUMBER) {
4974 messaging_menu_message_add_action(message,
4975 "callBack",
4976 C::gettext("Call back"), // label
4977@@ -334,6 +337,7 @@
4978
4979 void MessagingMenu::addCall(const QString &targetId, const QString &accountId, const QDateTime &timestamp)
4980 {
4981+ qDebug() << __PRETTY_FUNCTION__;
4982 Call call;
4983 bool found = false;
4984 AccountEntry *account = TelepathyHelper::instance()->accountForId(accountId);
4985@@ -392,7 +396,7 @@
4986 // so we just disable it
4987 #ifndef __aarch64__
4988 // place the messaging-menu item only after the contact fetch request is finished, as we canĀ“t simply update
4989- QObject::connect(request, &QContactAbstractRequest::stateChanged, [request, call, text, this]() {
4990+ QObject::connect(request, &QContactAbstractRequest::stateChanged, [=]() {
4991 // only process the results after the finished state is reached
4992 if (request->state() != QContactAbstractRequest::FinishedState) {
4993 return;
4994@@ -412,19 +416,13 @@
4995 newCall.contactIcon = avatar;
4996 }
4997 }
4998- addCallToMessagingMenu(newCall, text);
4999+ addCallToMessagingMenu(newCall, text, account->protocolInfo()->features() & Protocol::TextChats);
5000 #ifndef __aarch64__
The diff has been truncated for viewing.

Subscribers

People subscribed via source and target branches

to all changes: