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

Proposed by Gustavo Pichorim Boiko
Status: Superseded
Proposed branch: lp:~phablet-team/telepathy-ofono/mms_group_as_room
Merge into: lp:telepathy-ofono
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
Ubuntu Phablet Team Pending
Review via email: mp+307997@code.launchpad.net

This proposal has been superseded by a proposal from 2016-10-21.

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.
175. By Tiago Salem Herrmann

General fixes for mms room and sms broadcast

176. By Tiago Salem Herrmann

fix text channel compiling again tp-qt

177. By Tiago Salem Herrmann

Set target id correctly

178. By Gustavo Pichorim Boiko

Fix detecting messages with attachments as MMSs

179. By Tiago Salem Herrmann

fix build

180. By Tiago Salem Herrmann

Send multiple mms when sending attachments on broadcast channels

Unmerged revisions

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-20 23:12:37 +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-20 23:12:37 +0000
32+++ connection.cpp 2016-10-20 23:12:37 +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-20 23:12:37 +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-20 23:12:37 +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-20 23:12:37 +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-20 23:12:37 +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-20 23:12:37 +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-20 23:12:37 +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-20 23:12:37 +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: