Merge lp:telephony-service/staging into lp:telephony-service
- staging
- Merge into trunk
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 |
Related bugs: |
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_
- Add method to leave channel by properties.
- Implement AudioRouteManager in telephony-
- Multiple performance improvements on Roles Interfaces and ContactWatcher
- Changed upstart job to also launch telephony-
- 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_
- Add method to leave channel by properties.
- Implement AudioRouteManager in telephony-
- Multiple performance improvements on Roles Interfaces and ContactWatcher
- Changed upstart job to also launch telephony-
- 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.
system-apps-ci-bot (system-apps-ci-bot) wrote : | # |
- 1252. By Gustavo Pichorim Boiko
-
Disable PA on tests.
system-apps-ci-bot (system-apps-ci-bot) wrote : | # |
FAILED: Continuous integration, rev:1252
https:/
Executed test runs:
FAILURE: https:/
SUCCESS: https:/
FAILURE: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
Click here to trigger a rebuild:
https:/
Gustavo Pichorim Boiko (boiko) wrote : | # |
The changes were reviewed individually and tested from this branch, so approving.
- 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
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 ×tamp) |
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__ |
FAILED: Continuous integration, rev:1251 /jenkins. canonical. com/system- apps/job/ lp-telephony- service- ci/1/ /jenkins. canonical. com/system- apps/job/ build/2343/ console /jenkins. canonical. com/system- apps/job/ build-0- fetch/2343 /jenkins. canonical. com/system- apps/job/ build-2- binpkg/ arch=amd64, release= xenial+ overlay/ 2161 /jenkins. canonical. com/system- apps/job/ build-2- binpkg/ arch=amd64, release= xenial+ overlay/ 2161/artifact/ output/ *zip*/output. zip /jenkins. canonical. com/system- apps/job/ build-2- binpkg/ arch=amd64, release= zesty/2161/ console /jenkins. canonical. com/system- apps/job/ build-2- binpkg/ arch=armhf, release= xenial+ overlay/ 2161/console /jenkins. canonical. com/system- apps/job/ build-2- binpkg/ arch=armhf, release= zesty/2161 /jenkins. canonical. com/system- apps/job/ build-2- binpkg/ arch=armhf, release= zesty/2161/ artifact/ output/ *zip*/output. zip /jenkins. canonical. com/system- apps/job/ build-2- binpkg/ arch=i386, release= xenial+ overlay/ 2161 /jenkins. canonical. com/system- apps/job/ build-2- binpkg/ arch=i386, release= xenial+ overlay/ 2161/artifact/ output/ *zip*/output. zip /jenkins. canonical. com/system- apps/job/ build-2- binpkg/ arch=i386, release= zesty/2161 /jenkins. canonical. com/system- apps/job/ build-2- binpkg/ arch=i386, release= zesty/2161/ artifact/ output/ *zip*/output. zip
https:/
Executed test runs:
FAILURE: https:/
SUCCESS: https:/
SUCCESS: https:/
deb: https:/
FAILURE: https:/
FAILURE: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
Click here to trigger a rebuild: /jenkins. canonical. com/system- apps/job/ lp-telephony- service- ci/1/rebuild
https:/