Merge lp:~phablet-team/telepathy-ofono/mms_group_as_room into lp:telepathy-ofono/staging

Proposed by Gustavo Pichorim Boiko
Status: Merged
Approved by: Tiago Salem Herrmann
Approved revision: 180
Merged at revision: 178
Proposed branch: lp:~phablet-team/telepathy-ofono/mms_group_as_room
Merge into: lp:telepathy-ofono/staging
Prerequisite: lp:~phablet-team/telepathy-ofono/add-targetids-initialinviteeids
Diff against target: 933 lines (+471/-82)
9 files modified
CMakeLists.txt (+3/-1)
connection.cpp (+149/-27)
connection.h (+8/-2)
mmsgroupcache.cpp (+146/-0)
mmsgroupcache.h (+44/-0)
ofonotextchannel.cpp (+98/-47)
ofonotextchannel.h (+10/-5)
schema/CMakeLists.txt (+3/-0)
schema/v2.sql (+10/-0)
To merge this branch: bzr merge lp:~phablet-team/telepathy-ofono/mms_group_as_room
Reviewer Review Type Date Requested Status
Tiago Salem Herrmann (community) Approve
Gustavo Pichorim Boiko (community) Approve
Review via email: mp+308993@code.launchpad.net

This proposal supersedes a proposal from 2016-10-08.

Commit message

Implement MMS groups as Room channels.

Description of the change

Implement MMS groups as Room channels.

To post a comment you must log in.
Revision history for this message
Gustavo Pichorim Boiko (boiko) wrote :

Looks good!

review: Approve
Revision history for this message
Tiago Salem Herrmann (tiagosh) wrote :

looks good to me too.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'CMakeLists.txt'
2--- CMakeLists.txt 2015-07-20 19:38:54 +0000
3+++ CMakeLists.txt 2016-10-21 08:03:20 +0000
4@@ -33,6 +33,7 @@
5 find_package(LibPhoneNumber REQUIRED)
6 find_package(Qt5Core)
7 find_package(Qt5DBus)
8+find_package(Qt5Network)
9 add_definitions(-DQT_NO_KEYWORDS)
10
11 find_package(PkgConfig REQUIRED)
12@@ -93,6 +94,7 @@
13 mmsdmanager.cpp
14 mmsdservice.cpp
15 mmsdmessage.cpp
16+ mmsgroupcache.cpp
17 pendingmessagesmanager.cpp
18 phoneutils.cpp
19 powerdaudiomodemediator.cpp
20@@ -111,7 +113,7 @@
21
22 enable_testing()
23
24-target_link_libraries(${TELEPATHY_OFONO} ${Qt5Core_LIBRARIES} ${Qt5DBus_LIBRARIES} ${WAUDIO_LIBRARIES} -ltelepathy-qt5 ${TELEPATHY_QT5_SERVICE_LIBRARIES} ${OFONO_QT_LIBRARIES} ${PULSEAUDIO_LIBRARIES} ${SQLITE3_LIBRARIES} ${LibPhoneNumber_LIBRARIES})
25+target_link_libraries(${TELEPATHY_OFONO} ${Qt5Core_LIBRARIES} ${Qt5DBus_LIBRARIES} ${WAUDIO_LIBRARIES} -ltelepathy-qt5 ${TELEPATHY_QT5_SERVICE_LIBRARIES} ${Qt5Network_LIBRARIES} ${OFONO_QT_LIBRARIES} ${PULSEAUDIO_LIBRARIES} ${SQLITE3_LIBRARIES} ${LibPhoneNumber_LIBRARIES})
26 install(TARGETS ${TELEPATHY_OFONO} DESTINATION ${DAEMON_DIR})
27
28 configure_file(ofono.service.in org.freedesktop.Telepathy.ConnectionManager.ofono.service)
29
30=== modified file 'connection.cpp'
31--- connection.cpp 2016-10-21 08:03:20 +0000
32+++ connection.cpp 2016-10-21 08:03:20 +0000
33@@ -1,5 +1,5 @@
34 /**
35- * Copyright (C) 2013 Canonical, Ltd.
36+ * Copyright (C) 2013-2016 Canonical, Ltd.
37 *
38 * This program is free software: you can redistribute it and/or modify it under
39 * the terms of the GNU Lesser General Public License version 3, as published by
40@@ -32,6 +32,7 @@
41
42 #include "mmsdmessage.h"
43 #include "mmsdservice.h"
44+#include "mmsgroupcache.h"
45
46 #ifdef USE_PULSEAUDIO
47 #include "qpulseaudioengine.h"
48@@ -83,6 +84,7 @@
49 Tp::BaseConnection(dbusConnection, cmName, protocolName, parameters),
50 mOfonoModemManager(new OfonoModemManager(this)),
51 mHandleCount(0),
52+ mGroupHandleCount(0),
53 mMmsdManager(new MMSDManager(this)),
54 mConferenceCall(NULL)
55 {
56@@ -125,6 +127,19 @@
57 text.allowedProperties.append(TP_QT_IFACE_CHANNEL_INTERFACE_CONFERENCE + QLatin1String(".InitialInviteeHandles"));
58 text.allowedProperties.append(TP_QT_IFACE_CHANNEL_INTERFACE_CONFERENCE + QLatin1String(".InitialInviteeIDs"));
59
60+ Tp::RequestableChannelClass existingGroupChat;
61+ existingGroupChat.fixedProperties[TP_QT_IFACE_CHANNEL + QLatin1String(".ChannelType")] = TP_QT_IFACE_CHANNEL_TYPE_TEXT;
62+ existingGroupChat.fixedProperties[TP_QT_IFACE_CHANNEL + QLatin1String(".TargetHandleType")] = Tp::HandleTypeRoom;
63+ existingGroupChat.allowedProperties.append(TP_QT_IFACE_CHANNEL + QLatin1String(".TargetHandle"));
64+ existingGroupChat.allowedProperties.append(TP_QT_IFACE_CHANNEL + QLatin1String(".TargetID"));
65+
66+ Tp::RequestableChannelClass newGroupChat;
67+ newGroupChat.fixedProperties[TP_QT_IFACE_CHANNEL + QLatin1String(".ChannelType")] = TP_QT_IFACE_CHANNEL_TYPE_TEXT;
68+ newGroupChat.fixedProperties[TP_QT_IFACE_CHANNEL + QLatin1String(".TargetHandleType")] = Tp::HandleTypeNone;
69+ newGroupChat.allowedProperties.append(TP_QT_IFACE_CHANNEL_INTERFACE_CONFERENCE + QLatin1String(".InitialInviteeHandles"));
70+ newGroupChat.allowedProperties.append(TP_QT_IFACE_CHANNEL_INTERFACE_CONFERENCE + QLatin1String(".InitialInviteeIDs"));
71+ newGroupChat.allowedProperties.append(TP_QT_IFACE_CHANNEL_INTERFACE_ROOM + QLatin1String(".RoomName"));
72+
73 // set requestable call channel properties
74 Tp::RequestableChannelClass call;
75 call.fixedProperties[TP_QT_IFACE_CHANNEL+".ChannelType"] = TP_QT_IFACE_CHANNEL_TYPE_CALL;
76@@ -140,7 +155,7 @@
77 call.allowedProperties.append(TP_QT_IFACE_CHANNEL_TYPE_CALL+".HardwareStreaming");
78 call.allowedProperties.append(TP_QT_IFACE_CHANNEL_INTERFACE_CONFERENCE + QLatin1String(".InitialChannels"));
79
80- requestsIface->requestableChannelClasses << text << call;
81+ requestsIface->requestableChannelClasses << text << call << existingGroupChat << newGroupChat;
82
83 plugInterface(Tp::AbstractConnectionInterfacePtr::dynamicCast(requestsIface));
84
85@@ -368,6 +383,16 @@
86 qDebug() << "oFonoConnection::onMMSServiceRemoved" << path;
87 }
88
89+oFonoTextChannel* oFonoConnection::textChannelForId(const QString &id)
90+{
91+ Q_FOREACH(oFonoTextChannel* channel, mTextChannels) {
92+ if (channel->baseChannel()->targetID() == id) {
93+ return channel;
94+ }
95+ }
96+ return NULL;
97+}
98+
99 oFonoTextChannel* oFonoConnection::textChannelForMembers(const QStringList &members)
100 {
101 Q_FOREACH(oFonoTextChannel* channel, mTextChannels) {
102@@ -398,6 +423,7 @@
103 void oFonoConnection::addMMSToService(const QString &path, const QVariantMap &properties, const QString &servicePath)
104 {
105 qDebug() << "addMMSToService " << path << properties << servicePath;
106+ bool isRoom = false;
107 MMSDMessage *msg = new MMSDMessage(path, properties);
108 mServiceMMSList[servicePath].append(msg);
109 if (properties["Status"] == "received") {
110@@ -409,6 +435,7 @@
111 // remove empty strings if any
112 recipientList.removeAll("");
113 if (recipientList.size() > 1) {
114+ isRoom = true;
115 // remove ourselves from the recipient list
116 Q_FOREACH(const QString &myNumber, mOfonoSimManager->subscriberNumbers()) {
117 Q_FOREACH(const QString &remoteNumber, recipientList) {
118@@ -428,8 +455,19 @@
119 senderNormalizedNumber = "x-ofono-unknown";
120 }
121
122- // check if there is an open channel for this number and use it
123- oFonoTextChannel *channel = textChannelForMembers(QStringList() << senderNormalizedNumber << recipients.toList());
124+ oFonoTextChannel *channel = NULL;
125+ MMSGroup group;
126+ if (isRoom) {
127+ group = MMSGroupCache::existingGroup(QStringList() << senderNormalizedNumber << recipients.toList());
128+ if (!group.groupId.isEmpty()) {
129+ // check if there is an open channel for this group and use it
130+ channel = textChannelForId(group.groupId);
131+ }
132+ } else {
133+ // check if there is an open channel for these numbers and use it (1-1 chat here)
134+ channel = textChannelForMembers(QStringList() << senderNormalizedNumber << recipients.toList());
135+ }
136+
137 if (channel) {
138 channel->mmsReceived(path, ensureHandle(senderNormalizedNumber), properties);
139 return;
140@@ -445,8 +483,13 @@
141 request[TP_QT_IFACE_CHANNEL + QLatin1String(".ChannelType")] = TP_QT_IFACE_CHANNEL_TYPE_TEXT;
142 request[TP_QT_IFACE_CHANNEL + QLatin1String(".InitiatorHandle")] = handle;
143
144- if (initialInviteeHandles.size() > 0) {
145+ if (isRoom) {
146 initialInviteeHandles << handle;
147+ request[TP_QT_IFACE_CHANNEL + QLatin1String(".TargetHandleType")] = Tp::HandleTypeRoom;
148+ // if the group exists, fill the targetId with the existing id
149+ if (!group.groupId.isEmpty()) {
150+ request[TP_QT_IFACE_CHANNEL + QLatin1String(".TargetID")] = group.groupId;
151+ }
152 request[TP_QT_IFACE_CHANNEL_INTERFACE_CONFERENCE + QLatin1String(".InitialInviteeHandles")] = QVariant::fromValue(initialInviteeHandles);
153 ensureChannel(request, yours, false, &error);
154 } else {
155@@ -455,13 +498,19 @@
156 ensureChannel(request, yours, false, &error);
157 }
158
159- if(error.isValid()) {
160+ if (error.isValid()) {
161 qCritical() << "Error creating channel for incoming message " << error.name() << error.message();
162 return;
163 }
164- channel = textChannelForMembers(QStringList() << senderNormalizedNumber << recipients.toList());
165+ if (isRoom) {
166+ channel = textChannelForId(group.groupId);
167+ } else {
168+ channel = textChannelForMembers(QStringList() << senderNormalizedNumber << recipients.toList());
169+ }
170 if (channel) {
171 channel->mmsReceived(path, handle, properties);
172+ } else {
173+ qCritical() << "Failed to create channel for incoming mms" << "isRoom" << isRoom << "groupId" << group.groupId;
174 }
175 }
176 }
177@@ -623,23 +672,42 @@
178 return mHandleCount;
179 }
180
181+uint oFonoConnection::newGroupHandle(const QString &identifier)
182+{
183+ mGroupHandles[++mGroupHandleCount] = identifier;
184+ return mGroupHandleCount;
185+}
186+
187 QStringList oFonoConnection::inspectHandles(uint handleType, const Tp::UIntList& handles, Tp::DBusError *error)
188 {
189 QStringList identifiers;
190
191- if( handleType != Tp::HandleTypeContact ) {
192+ switch (handleType) {
193+ case Tp::HandleTypeContact:
194+ qDebug() << "oFonoConnection::inspectHandles contact" << handles;
195+ Q_FOREACH(uint handle, handles) {
196+ if (mHandles.keys().contains(handle)) {
197+ identifiers.append(mHandles.value(handle));
198+ } else {
199+ error->set(TP_QT_ERROR_INVALID_HANDLE, "Contact handle not found");
200+ return QStringList();
201+ }
202+ }
203+ break;
204+ case Tp::HandleTypeRoom:
205+ qDebug() << "oFonoConnection::inspectHandles group" << handles;
206+ Q_FOREACH(uint handle, handles) {
207+ if (mGroupHandles.keys().contains(handle)) {
208+ identifiers.append(mGroupHandles.value(handle));
209+ } else {
210+ error->set(TP_QT_ERROR_INVALID_HANDLE, "Group handle not found");
211+ return QStringList();
212+ }
213+ }
214+ break;
215+ default:
216 error->set(TP_QT_ERROR_INVALID_ARGUMENT,"Not supported");
217- return QStringList();
218- }
219-
220- qDebug() << "oFonoConnection::inspectHandles " << handles;
221- Q_FOREACH( uint handle, handles) {
222- if (mHandles.keys().contains(handle)) {
223- identifiers.append(mHandles.value(handle));
224- } else {
225- error->set(TP_QT_ERROR_INVALID_HANDLE, "Handle not found");
226- return QStringList();
227- }
228+ break;
229 }
230 qDebug() << "oFonoConnection::inspectHandles " << identifiers;
231 return identifiers;
232@@ -676,18 +744,16 @@
233
234 Tp::BaseChannelPtr oFonoConnection::createTextChannel(const QVariantMap &request, Tp::DBusError *error)
235 {
236+ uint targetHandleType = request.value(TP_QT_IFACE_CHANNEL + QLatin1String(".TargetHandleType")).toUInt();
237 uint targetHandle = request.value(TP_QT_IFACE_CHANNEL + QLatin1String(".TargetHandle")).toUInt();
238- const QString targetId = request.value(TP_QT_IFACE_CHANNEL + QLatin1String(".TargetID")).toString();
239+ QString targetId = request.value(TP_QT_IFACE_CHANNEL + QLatin1String(".TargetID")).toString();
240+ bool isRoom = request.contains(TP_QT_IFACE_CHANNEL_INTERFACE_ROOM + ".RoomName");
241
242 if (mSelfPresence.type != Tp::ConnectionPresenceTypeAvailable) {
243 error->set(TP_QT_ERROR_NETWORK_ERROR, "No network available");
244 return Tp::BaseChannelPtr();
245 }
246
247- if (!targetId.isEmpty()) {
248- targetHandle = ensureHandle(targetId);
249- }
250-
251 QStringList phoneNumbers;
252 bool flash = false;
253 if (request.contains(TP_QT_IFACE_CHANNEL_INTERFACE_CONFERENCE + QLatin1String(".InitialInviteeIDs"))) {
254@@ -699,15 +765,63 @@
255 phoneNumbers << inspectHandles(Tp::HandleTypeContact, handles, error);
256 } else if (request.contains(TP_QT_IFACE_CHANNEL_INTERFACE_CONFERENCE + QLatin1String(".InitialInviteeHandles"))) {
257 phoneNumbers << inspectHandles(Tp::HandleTypeContact, qdbus_cast<Tp::UIntList>(request[TP_QT_IFACE_CHANNEL_INTERFACE_CONFERENCE + QLatin1String(".InitialInviteeHandles")]), error);
258- } else {
259- phoneNumbers << mHandles.value(targetHandle);
260+ };
261+
262+ // if the handle type is none and RoomName is present, we should try to find an existing MMS group
263+ if (targetHandleType == Tp::HandleTypeNone && isRoom) {
264+ MMSGroup group = MMSGroupCache::existingGroup(phoneNumbers);
265+ if (!group.groupId.isEmpty()) {
266+ targetId = group.groupId;
267+ targetHandleType = Tp::HandleTypeRoom;
268+ }
269+ } else if (targetHandleType == Tp::HandleTypeRoom) {
270+ if (targetId.isEmpty()) {
271+ targetId = mGroupHandles.value(targetHandle);
272+ }
273+ // we got the groupId, now lookup the members and subject in the cache
274+ MMSGroup group = MMSGroupCache::existingGroup(targetId);
275+ if (group.groupId.isEmpty()) {
276+ error->set(TP_QT_ERROR_INVALID_HANDLE, "MMS Group not found in cache.");
277+ return Tp::BaseChannelPtr();
278+ }
279+ phoneNumbers = group.members;
280+ isRoom = true;
281+ // FIXME(MMSGroup): add support for MMS group subject
282+ } else if (targetHandleType == Tp::HandleTypeContact && targetHandle != 0) {
283+ targetId = mHandles.value(targetHandle);
284+ }
285+
286+ // now get the appropriate handle
287+ if (!targetId.isEmpty()) {
288+ switch (targetHandleType) {
289+ case Tp::HandleTypeRoom:
290+ targetHandle = ensureGroupHandle(targetId);
291+ break;
292+ case Tp::HandleTypeContact:
293+ default:
294+ targetHandle = ensureHandle(targetId);
295+ phoneNumbers << mHandles.value(targetHandle);
296+ break;
297+ }
298 }
299
300 if (request.contains(TP_QT_IFACE_CHANNEL_INTERFACE_SMS + QLatin1String(".Flash"))) {
301 flash = request[TP_QT_IFACE_CHANNEL_INTERFACE_SMS + QLatin1String(".Flash")].toBool();
302 }
303
304- oFonoTextChannel *channel = new oFonoTextChannel(this, phoneNumbers, flash);
305+ oFonoTextChannel *channel = 0;
306+ if (isRoom) {
307+ // FIXME(MMSGroup): if targetId is empty, this means this is a new group, so we need to
308+ // generate the group ID, probably something like "mms:<hash of member IDs>".
309+ // Also, we need to call MMSGroupCache::saveGroup() to save the group in the cache
310+ MMSGroup group;
311+ group.groupId = MMSGroupCache::generateId(phoneNumbers);
312+ group.members = phoneNumbers;
313+ MMSGroupCache::saveGroup(group);
314+ channel = new oFonoTextChannel(this, targetId, phoneNumbers);
315+ } else {
316+ channel = new oFonoTextChannel(this, QString(), phoneNumbers, flash);
317+ }
318 mTextChannels << channel;
319 QObject::connect(channel, SIGNAL(messageRead(QString)), SLOT(onMessageRead(QString)));
320 QObject::connect(channel, SIGNAL(destroyed()), SLOT(onTextChannelClosed()));
321@@ -1002,6 +1116,14 @@
322 return newHandle(normalizedNumber);
323 }
324
325+uint oFonoConnection::ensureGroupHandle(const QString &groupId)
326+{
327+ if (mGroupHandles.values().contains(groupId)) {
328+ return mGroupHandles.key(groupId);
329+ }
330+ return newGroupHandle(groupId);
331+}
332+
333 bool oFonoConnection::matchChannel(const Tp::BaseChannelPtr &channel, const QVariantMap &request, Tp::DBusError *error)
334 {
335 QString channelType = request[TP_QT_IFACE_CHANNEL + QLatin1String(".ChannelType")].toString();
336
337=== modified file 'connection.h'
338--- connection.h 2015-08-10 21:06:36 +0000
339+++ connection.h 2016-10-21 08:03:20 +0000
340@@ -1,5 +1,5 @@
341 /**
342- * Copyright (C) 2013 Canonical, Ltd.
343+ * Copyright (C) 2013-2016 Canonical, Ltd.
344 *
345 * This program is free software: you can redistribute it and/or modify it under
346 * the terms of the GNU Lesser General Public License version 3, as published by
347@@ -14,6 +14,7 @@
348 * along with this program. If not, see <http://www.gnu.org/licenses/>.
349 *
350 * Authors: Tiago Salem Herrmann <tiago.herrmann@canonical.com>
351+ * Gustavo Pichorim Boiko <gustavo.boiko@canonical.com>
352 */
353
354 #ifndef OFONOCONNECTION_H
355@@ -94,6 +95,7 @@
356 BaseConnectionVoicemailInterfacePtr voicemailIface;
357 BaseConnectionUSSDInterfacePtr supplementaryServicesIface;
358 uint newHandle(const QString &identifier);
359+ uint newGroupHandle(const QString &identifier);
360
361 OfonoMessageManager *messageManager();
362 OfonoVoiceCallManager *voiceCallManager();
363@@ -101,6 +103,8 @@
364 QMap<QString, oFonoCallChannel*> callChannels();
365
366 uint ensureHandle(const QString &phoneNumber);
367+ uint ensureGroupHandle(const QString &groupId);
368+ oFonoTextChannel* textChannelForId(const QString &id);
369 oFonoTextChannel* textChannelForMembers(const QStringList &members);
370 Tp::BaseChannelPtr createTextChannel(const QVariantMap &request, Tp::DBusError *error);
371 Tp::BaseChannelPtr createCallChannel(const QVariantMap &request, Tp::DBusError *error);
372@@ -159,6 +163,9 @@
373 void addMMSToService(const QString &path, const QVariantMap &properties, const QString &servicePath);
374 void ensureTextChannel(const QString &message, const QVariantMap &info, bool flash);
375 QMap<uint, QString> mHandles;
376+ uint mHandleCount;
377+ QMap<uint, QString> mGroupHandles;
378+ uint mGroupHandleCount;
379
380 #ifdef USE_PULSEAUDIO
381 bool mHasPulseAudio;
382@@ -177,7 +184,6 @@
383 OfonoSupplementaryServices *mOfonoSupplementaryServices;
384 OfonoSimManager *mOfonoSimManager;
385 OfonoModem *mOfonoModem;
386- uint mHandleCount;
387 Tp::SimplePresence mSelfPresence;
388 MMSDManager *mMmsdManager;
389 QMap<QString, MMSDService*> mMmsdServices;
390
391=== added file 'mmsgroupcache.cpp'
392--- mmsgroupcache.cpp 1970-01-01 00:00:00 +0000
393+++ mmsgroupcache.cpp 2016-10-21 08:03:20 +0000
394@@ -0,0 +1,146 @@
395+/**
396+ * Copyright (C) 2016 Canonical, Ltd.
397+ *
398+ * This program is free software: you can redistribute it and/or modify it under
399+ * the terms of the GNU Lesser General Public License version 3, as published by
400+ * the Free Software Foundation.
401+ *
402+ * This program is distributed in the hope that it will be useful, but WITHOUT
403+ * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
404+ * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
405+ * Lesser General Public License for more details.
406+ *
407+ * You should have received a copy of the GNU Lesser General Public License
408+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
409+ *
410+ * Authors: Gustavo Pichorim Boiko <gustavo.boiko@canonical.com>
411+ */
412+
413+#include "mmsgroupcache.h"
414+#include "phoneutils_p.h"
415+#include "sqlitedatabase.h"
416+#include <QSqlQuery>
417+#include <QVariant>
418+#include <QCryptographicHash>
419+
420+MMSGroupCache::MMSGroupCache(QObject *parent) : QObject(parent)
421+{
422+}
423+
424+MMSGroup MMSGroupCache::existingGroup(const QStringList &members)
425+{
426+ MMSGroup group;
427+ if (members.isEmpty()) {
428+ return group;
429+ }
430+
431+ // try to find all the threads which at least the first member is part of
432+ QString firstMember = members.first();
433+ QSqlQuery query(SQLiteDatabase::instance()->database());
434+ query.prepare("SELECT groupId FROM mms_group_members WHERE comparePhoneNumbers(memberId, :memberId)");
435+ query.bindValue(":memberId", firstMember);
436+ if (!query.exec()) {
437+ return group;
438+ }
439+
440+ QStringList groupIds;
441+ while (query.next()) {
442+ groupIds << query.value(0).toString();
443+ }
444+
445+ // now get all the groups and see if any matches all the members
446+ for (auto groupId : groupIds) {
447+ query.prepare("SELECT memberId FROM mms_group_members WHERE groupId=:groupId");
448+ query.bindValue(":groupId", groupId);
449+ if (!query.exec()) {
450+ return group;
451+ }
452+ QStringList groupMembers;
453+ while (query.next()) {
454+ groupMembers << query.value(0).toString();
455+ }
456+
457+ // if the groups have a different number of members, they are certainly not the same
458+ if (groupMembers.count() != members.count()) {
459+ continue;
460+ }
461+
462+ // compare the members to see if they match
463+ int match = 0;
464+ for (auto groupMember : groupMembers) {
465+ for (auto member : members) {
466+ if (PhoneUtils::comparePhoneNumbers(groupMember, member)) {
467+ match++;
468+ continue;
469+ }
470+ }
471+ }
472+ if (match == members.count()) {
473+ query.prepare("SELECT subject FROM mms_groups WHERE groupId=:groupId");
474+ query.bindValue(":groupId", groupId);
475+ if (query.exec()) {
476+ query.next();
477+ group.subject = query.value(0).toString();
478+ }
479+ group.groupId = groupId;
480+ group.members = groupMembers;
481+ break;
482+ }
483+ }
484+ return group;
485+}
486+
487+MMSGroup MMSGroupCache::existingGroup(const QString &groupId)
488+{
489+ MMSGroup group;
490+
491+ // select the group to make sure it exists
492+ QSqlQuery query(SQLiteDatabase::instance()->database());
493+ query.prepare("SELECT subject FROM mms_groups WHERE groupId=:groupId");
494+ query.bindValue(":groupId", groupId);
495+ if (query.exec() && query.next()) {
496+ group.groupId = groupId;
497+ group.subject = query.value(0).toString();
498+ query.prepare("SELECT memberId FROM mms_group_members WHERE groupId=:groupId");
499+ query.bindValue(":groupId", groupId);
500+ if (!query.exec()) {
501+ return group;
502+ }
503+
504+ while (query.next()) {
505+ group.members << query.value(0).toString();
506+ }
507+ }
508+
509+ return group;
510+}
511+
512+bool MMSGroupCache::saveGroup(const MMSGroup &group)
513+{
514+ SQLiteDatabase::instance()->beginTransation();
515+
516+ QSqlQuery query(SQLiteDatabase::instance()->database());
517+ query.prepare("INSERT INTO mms_groups(groupId, subject) VALUES (:groupId, :subject)");
518+ query.bindValue(":groupId", group.groupId);
519+ query.bindValue(":subject", group.subject);
520+ if (!query.exec()) {
521+ SQLiteDatabase::instance()->rollbackTransaction();
522+ return false;
523+ }
524+ for (auto member : group.members) {
525+ query.prepare("INSERT INTO mms_group_members(groupId, memberId) VALUES(:groupId, :memberId)");
526+ query.bindValue(":groupId", group.groupId);
527+ query.bindValue(":memberId", member);
528+ if (!query.exec()) {
529+ SQLiteDatabase::instance()->rollbackTransaction();
530+ return false;
531+ }
532+ }
533+ SQLiteDatabase::instance()->finishTransaction();
534+ return true;
535+}
536+
537+QString MMSGroupCache::generateId(const QStringList &phoneNumbers)
538+{
539+ return QString("mms:%1").arg(QString(QCryptographicHash::hash(phoneNumbers.join(";").toLocal8Bit(),QCryptographicHash::Md5).toHex()));
540+}
541
542=== added file 'mmsgroupcache.h'
543--- mmsgroupcache.h 1970-01-01 00:00:00 +0000
544+++ mmsgroupcache.h 2016-10-21 08:03:20 +0000
545@@ -0,0 +1,44 @@
546+/**
547+ * Copyright (C) 2016 Canonical, Ltd.
548+ *
549+ * This program is free software: you can redistribute it and/or modify it under
550+ * the terms of the GNU Lesser General Public License version 3, as published by
551+ * the Free Software Foundation.
552+ *
553+ * This program is distributed in the hope that it will be useful, but WITHOUT
554+ * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
555+ * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
556+ * Lesser General Public License for more details.
557+ *
558+ * You should have received a copy of the GNU Lesser General Public License
559+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
560+ *
561+ * Authors: Gustavo Pichorim Boiko <gustavo.boiko@canonical.com>
562+ */
563+
564+#ifndef MMSGROUPCACHE_H
565+#define MMSGROUPCACHE_H
566+
567+#include <QObject>
568+#include <QStringList>
569+
570+typedef struct {
571+ QString groupId;
572+ QString subject;
573+ QStringList members;
574+} MMSGroup;
575+
576+class MMSGroupCache : public QObject
577+{
578+ Q_OBJECT
579+public:
580+ static MMSGroup existingGroup(const QStringList &members);
581+ static MMSGroup existingGroup(const QString &groupId);
582+ static bool saveGroup(const MMSGroup &group);
583+ static QString generateId(const QStringList &phoneNumbers);
584+
585+private:
586+ explicit MMSGroupCache(QObject *parent = 0);
587+};
588+
589+#endif // MMSGROUPCACHE_H
590
591=== modified file 'ofonotextchannel.cpp'
592--- ofonotextchannel.cpp 2015-08-11 14:01:52 +0000
593+++ ofonotextchannel.cpp 2016-10-21 08:03:20 +0000
594@@ -1,5 +1,5 @@
595 /**
596- * Copyright (C) 2013 Canonical, Ltd.
597+ * Copyright (C) 2013-2016 Canonical, Ltd.
598 *
599 * This program is free software: you can redistribute it and/or modify it under
600 * the terms of the GNU Lesser General Public License version 3, as published by
601@@ -14,6 +14,7 @@
602 * along with this program. If not, see <http://www.gnu.org/licenses/>.
603 *
604 * Authors: Tiago Salem Herrmann <tiago.herrmann@canonical.com>
605+ * Gustavo Pichorim Boiko <gustavo.boiko@canonical.com>
606 */
607
608 // ofono-qt
609@@ -40,7 +41,7 @@
610 }
611
612
613-oFonoTextChannel::oFonoTextChannel(oFonoConnection *conn, QStringList phoneNumbers, bool flash, QObject *parent):
614+oFonoTextChannel::oFonoTextChannel(oFonoConnection *conn, const QString &targetId, QStringList phoneNumbers, bool flash, QObject *parent):
615 QObject(parent),
616 mConnection(conn),
617 mPhoneNumbers(phoneNumbers),
618@@ -49,14 +50,21 @@
619 {
620 qDBusRegisterMetaType<IncomingAttachmentStruct>();
621 qDBusRegisterMetaType<IncomingAttachmentList>();
622+ bool mmsGroupChat = !targetId.isEmpty();
623
624 Tp::BaseChannelPtr baseChannel;
625- if (phoneNumbers.size() == 1) {
626+ if (mmsGroupChat) {
627+ baseChannel = Tp::BaseChannel::create(mConnection,
628+ TP_QT_IFACE_CHANNEL_TYPE_TEXT,
629+ Tp::HandleTypeRoom,
630+ mConnection->ensureGroupHandle(targetId));
631+ } else if (phoneNumbers.size() == 1) {
632 baseChannel = Tp::BaseChannel::create(mConnection,
633 TP_QT_IFACE_CHANNEL_TYPE_TEXT,
634 Tp::HandleTypeContact,
635 mConnection->ensureHandle(mPhoneNumbers[0]));
636 } else {
637+ // sms broadcast
638 baseChannel = Tp::BaseChannel::create(mConnection,
639 TP_QT_IFACE_CHANNEL_TYPE_TEXT);
640 }
641@@ -80,59 +88,54 @@
642
643 baseChannel->plugInterface(Tp::AbstractChannelInterfacePtr::dynamicCast(mMessagesIface));
644
645- mGroupIface = Tp::BaseChannelGroupInterface::create(Tp::ChannelGroupFlagCanAdd, conn->selfHandle());
646- mGroupIface->setAddMembersCallback(Tp::memFun(this,&oFonoTextChannel::onAddMembers));
647- mGroupIface->setRemoveMembersCallback(Tp::memFun(this,&oFonoTextChannel::onRemoveMembers));
648+ Tp::ChannelGroupFlags groupFlags = Tp::ChannelGroupFlagHandleOwnersNotAvailable |
649+ Tp::ChannelGroupFlagMembersChangedDetailed |
650+ Tp::ChannelGroupFlagProperties;
651+
652+ mGroupIface = Tp::BaseChannelGroupInterface::create(groupFlags, mConnection->selfHandle());
653
654 baseChannel->plugInterface(Tp::AbstractChannelInterfacePtr::dynamicCast(mGroupIface));
655- addMembers(phoneNumbers);
656+
657+ Q_FOREACH(const QString &phoneNumber, phoneNumbers) {
658+ uint handle = mConnection->ensureHandle(phoneNumber);
659+ if (!mMembers.contains(handle)) {
660+ mMembers << handle;
661+ }
662+ }
663+
664+ mGroupIface->addMembers(mMembers, phoneNumbers);
665
666 mSMSIface = Tp::BaseChannelSMSInterface::create(flash, true);
667 baseChannel->plugInterface(Tp::AbstractChannelInterfacePtr::dynamicCast(mSMSIface));
668
669+ // FIXME(MMSGroup): create and plug the Room interface, and maybe the subject or the
670+ // roomconfig interface for the subject
671+
672 mBaseChannel = baseChannel;
673 mTextChannel = Tp::BaseChannelTextTypePtr::dynamicCast(mBaseChannel->interface(TP_QT_IFACE_CHANNEL_TYPE_TEXT));
674 mTextChannel->setMessageAcknowledgedCallback(Tp::memFun(this,&oFonoTextChannel::messageAcknowledged));
675 QObject::connect(mBaseChannel.data(), SIGNAL(closed()), this, SLOT(deleteLater()));
676 }
677
678-void oFonoTextChannel::onAddMembers(const Tp::UIntList& handles, const QString& message, Tp::DBusError* error)
679-{
680- addMembers(mConnection->inspectHandles(Tp::HandleTypeContact, handles, error));
681-}
682-
683-void oFonoTextChannel::onRemoveMembers(const Tp::UIntList& handles, const QString& message, Tp::DBusError* error)
684-{
685- Q_FOREACH(uint handle, handles) {
686- Q_FOREACH(const QString &phoneNumber, mConnection->inspectHandles(Tp::HandleTypeContact, Tp::UIntList() << handle, error)) {
687- mPhoneNumbers.removeAll(phoneNumber);
688- }
689- mMembers.removeAll(handle);
690- }
691- mGroupIface->removeMembers(handles);
692-}
693-
694-void oFonoTextChannel::addMembers(QStringList phoneNumbers)
695-{
696- Tp::UIntList handles;
697- Q_FOREACH(const QString &phoneNumber, phoneNumbers) {
698- uint handle = mConnection->ensureHandle(phoneNumber);
699- handles << handle;
700- if (!mPhoneNumbers.contains(phoneNumber)) {
701- mPhoneNumbers << phoneNumber;
702- }
703- if (!mMembers.contains(handle)) {
704- mMembers << handle;
705- }
706- }
707- mGroupIface->addMembers(handles, phoneNumbers);
708-}
709-
710 Tp::UIntList oFonoTextChannel::members()
711 {
712 return mMembers;
713 }
714
715+
716+bool oFonoTextChannel::isMultiPartMessage(const Tp::MessagePartList &message) const
717+{
718+ // multi-part messages happen in two cases:
719+ // - if it has more than two parts
720+ // - if the second part (the first is the header) is not text
721+ if (message.size() > 2 ||
722+ (message.size() == 2 && !message[1]["content-type"].variant().toString().startsWith("text/"))) {
723+ return true;
724+ }
725+
726+ return false;
727+}
728+
729 oFonoTextChannel::~oFonoTextChannel()
730 {
731 Q_FOREACH(const QStringList &fileList, mFilesToRemove) {
732@@ -177,13 +180,14 @@
733 Tp::MessagePart body = message.at(1);
734 QString objpath;
735
736- bool mms = header["x-canonical-mms"].variant().toBool();
737+ bool isRoom = baseChannel()->targetHandleType() == Tp::HandleTypeRoom;
738+ bool isMMS = isRoom || isMultiPartMessage(message);
739
740- if (mms) {
741+ // any mms, either 1-1, group or broadcast
742+ if (isMMS || isRoom) {
743 // pop header out
744 message.removeFirst();
745 OutgoingAttachmentList attachments;
746- // FIXME group chat
747 QString phoneNumber = mPhoneNumbers[0];
748 uint handle = mConnection->ensureHandle(phoneNumber);
749 QStringList temporaryFiles;
750@@ -218,6 +222,23 @@
751 attachment.filePath = file.fileName();
752 attachments << attachment;
753 }
754+ // if this is a broadcast, send multiple mms
755+ if (!isRoom) {
756+ // generate an id to this broadcast operation and its delivery reports
757+ objpath = QDateTime::currentDateTimeUtc().toString(Qt::ISODate) + "-" + QString::number(mMessageCounter++);
758+ Q_FOREACH(const QString &phoneNumber, mPhoneNumbers) {
759+ QString realObjpath = mConnection->sendMMS(QStringList() << phoneNumber, attachments).path();
760+ MMSDMessage *msg = new MMSDMessage(realObjpath, QVariantMap(), this);
761+ QObject::connect(msg, SIGNAL(propertyChanged(QString,QVariant)), SLOT(onMMSPropertyChanged(QString,QVariant)));
762+ mPendingBroadcastMMS[realObjpath] = objpath;
763+ mPendingDeliveryReportUnknown[objpath] = handle;
764+ QTimer::singleShot(0, this, SLOT(onProcessPendingDeliveryReport()));
765+ }
766+ if (temporaryFiles.size() > 0 && !mFilesToRemove.contains(objpath)) {
767+ mFilesToRemove[objpath] = temporaryFiles;
768+ }
769+ return objpath;
770+ }
771 objpath = mConnection->sendMMS(mPhoneNumbers, attachments).path();
772 if (objpath.isEmpty()) {
773 Q_FOREACH(const QString& file, temporaryFiles) {
774@@ -241,6 +262,7 @@
775 return objpath;
776 }
777
778+ // 1-1 sms
779 if (mPhoneNumbers.size() == 1) {
780 QString phoneNumber = mPhoneNumbers[0];
781 uint handle = mConnection->ensureHandle(phoneNumber);
782@@ -270,13 +292,13 @@
783 QObject::connect(msg, SIGNAL(stateChanged(QString)), SLOT(onOfonoMessageStateChanged(QString)));
784 return objpath;
785 } else {
786+ // Broadcast sms
787 bool someMessageSent = false;
788 QString lastPhoneNumber;
789 Q_FOREACH(const QString &phoneNumber, mPhoneNumbers) {
790- uint handle = mConnection->ensureHandle(mPhoneNumbers[0]);
791 objpath = mConnection->messageManager()->sendMessage(phoneNumber, body["content"].variant().toString(), success).path();
792 lastPhoneNumber = phoneNumber;
793- // dont fail if this is a group chat as we cannot track individual messages
794+ // dont fail if this is a broadcast chat as we cannot track individual messages
795 if (objpath.isEmpty() || !success) {
796 if (!success) {
797 qWarning() << mConnection->messageManager()->errorName() << mConnection->messageManager()->errorMessage();
798@@ -313,12 +335,14 @@
799 void oFonoTextChannel::onMMSPropertyChanged(QString property, QVariant value)
800 {
801 qDebug() << "oFonoTextChannel::onMMSPropertyChanged" << property << value;
802+ bool canRemoveFiles = true;
803 MMSDMessage *msg = qobject_cast<MMSDMessage*>(sender());
804 // FIXME - mms groupchat
805 uint handle = mConnection->ensureHandle(mPhoneNumbers[0]);
806 if (!msg) {
807 return;
808 }
809+ QString objectPath = msg->path();
810 if (property == "Status") {
811 Tp::DeliveryStatus status = Tp::DeliveryStatusUnknown;
812 if (value == "Sent") {
813@@ -331,11 +355,38 @@
814 // while it is draft we dont actually send a delivery report
815 return;
816 }
817- Q_FOREACH(const QString& file, mFilesToRemove[msg->path()]) {
818+ if (mPendingBroadcastMMS.contains(objectPath)) {
819+ // if this is the last outstanding mms, we can now remove the files
820+ objectPath = mPendingBroadcastMMS.take(objectPath);
821+ QStringList originalObjPaths = mPendingBroadcastMMS.keys(objectPath);
822+ canRemoveFiles = originalObjPaths.size() == 0;
823+
824+ if (status == Tp::DeliveryStatusAccepted) {
825+ // if we get at least one sucess, we notify sucess no matter if the others fail
826+ mPendingBroadcastFinalResult[objectPath] = true;
827+ }
828+
829+ if (canRemoveFiles) {
830+ if (mPendingBroadcastFinalResult[objectPath]) {
831+ status = Tp::DeliveryStatusAccepted;
832+ } else {
833+ status = Tp::DeliveryStatusPermanentlyFailed;
834+ }
835+ Q_FOREACH(const QString& file, mFilesToRemove[objectPath]) {
836+ QFile::remove(file);
837+ }
838+ mFilesToRemove.remove(objectPath);
839+ sendDeliveryReport(objectPath, handle, status);
840+ mPendingBroadcastFinalResult.remove(objectPath);
841+ }
842+ return;
843+ }
844+
845+ Q_FOREACH(const QString& file, mFilesToRemove[objectPath]) {
846 QFile::remove(file);
847 }
848- mFilesToRemove.remove(msg->path());
849- sendDeliveryReport(msg->path(), handle, status);
850+ mFilesToRemove.remove(objectPath);
851+ sendDeliveryReport(objectPath, handle, status);
852 }
853 }
854
855
856=== modified file 'ofonotextchannel.h'
857--- ofonotextchannel.h 2014-07-24 20:05:08 +0000
858+++ ofonotextchannel.h 2016-10-21 08:03:20 +0000
859@@ -1,5 +1,5 @@
860 /**
861- * Copyright (C) 2013 Canonical, Ltd.
862+ * Copyright (C) 2013-2016 Canonical, Ltd.
863 *
864 * This program is free software: you can redistribute it and/or modify it under
865 * the terms of the GNU Lesser General Public License version 3, as published by
866@@ -14,6 +14,7 @@
867 * along with this program. If not, see <http://www.gnu.org/licenses/>.
868 *
869 * Authors: Tiago Salem Herrmann <tiago.herrmann@canonical.com>
870+ * Gustavo Pichorim Boiko <gustavo.boiko@canonical.com>
871 */
872
873 #ifndef OFONOTEXTCHANNEL_H
874@@ -34,7 +35,9 @@
875 {
876 Q_OBJECT
877 public:
878- oFonoTextChannel(oFonoConnection *conn, QStringList phoneNumbers, bool flash = false, QObject *parent = 0);
879+ /** @brief Constructs a text channel to be used in 1-1 or SMS broadcast conversations */
880+ oFonoTextChannel(oFonoConnection *conn, const QString &targetId, QStringList phoneNumbers, bool flash = false, QObject *parent = 0);
881+
882 QString sendMessage(Tp::MessagePartList message, uint flags, Tp::DBusError* error);
883 void messageReceived(const QString & message, uint handle, const QVariantMap &info);
884 Tp::BaseChannelPtr baseChannel();
885@@ -42,10 +45,10 @@
886 void mmsReceived(const QString &id, uint handle, const QVariantMap &properties);
887 void deliveryReportReceived(const QString& messageId, uint handle, bool success);
888 void sendDeliveryReport(const QString &messageId, uint handle, Tp::DeliveryStatus status);
889- void addMembers(QStringList phoneNumbers);
890 Tp::UIntList members();
891- void onAddMembers(const Tp::UIntList& handles, const QString& message, Tp::DBusError* error);
892- void onRemoveMembers(const Tp::UIntList& handles, const QString& message, Tp::DBusError* error);
893+
894+protected:
895+ bool isMultiPartMessage(const Tp::MessagePartList &message) const;
896
897 private Q_SLOTS:
898 void onMMSPropertyChanged(QString property, QVariant value);
899@@ -70,6 +73,8 @@
900 QMap<QString, uint> mPendingDeliveryReportAccepted;
901 QMap<QString, uint> mPendingDeliveryReportDelivered;
902 QMap<QString, uint> mPendingDeliveryReportUnknown;
903+ QMap<QString, QString> mPendingBroadcastMMS;
904+ QMap<QString, bool> mPendingBroadcastFinalResult;
905 Tp::UIntList mMembers;
906 QMap<QString, QStringList> mFilesToRemove;
907 bool mFlash;
908
909=== modified file 'schema/CMakeLists.txt'
910--- schema/CMakeLists.txt 2013-12-11 14:52:03 +0000
911+++ schema/CMakeLists.txt 2016-10-21 08:03:20 +0000
912@@ -11,3 +11,6 @@
913 )
914
915 add_custom_target(schema_update DEPENDS ${SCHEMA_FILE} ${VERSION_FILE})
916+
917+# just to get the v*sql files to show on QtCreator
918+add_custom_target(schema_files_target ALL SOURCES ${SCHEMA_FILES})
919
920=== added file 'schema/v2.sql'
921--- schema/v2.sql 1970-01-01 00:00:00 +0000
922+++ schema/v2.sql 2016-10-21 08:03:20 +0000
923@@ -0,0 +1,10 @@
924+CREATE TABLE mms_groups (
925+ groupId varchar(255) PRIMARY KEY,
926+ subject varchar(512)
927+);
928+
929+CREATE TABLE mms_group_members (
930+ groupId varchar(255),
931+ memberId varchar(255),
932+ FOREIGN KEY(groupId) REFERENCES mms_groups(groupId)
933+);

Subscribers

People subscribed via source and target branches

to all changes: