~phablet-team/telephony-service/trunk

« back to all changes in this revision

Viewing changes to libtelephonyservice/chatmanager.cpp

  • Committer: Bileto Bot
  • Author(s): Gustavo Pichorim Boiko
  • Date: 2016-11-29 01:08:23 UTC
  • mfrom: (1211.1.25 staging)
  • Revision ID: ci-train-bot@canonical.com-20161129010823-u97pajkho1jfm44s
Group chat support.

Approved by: Gustavo Pichorim Boiko, Tiago Salem Herrmann

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
/*
2
 
 * Copyright (C) 2012-2013 Canonical, Ltd.
 
2
 * Copyright (C) 2012-2016 Canonical, Ltd.
3
3
 *
4
4
 * Authors:
5
5
 *  Gustavo Pichorim Boiko <gustavo.boiko@canonical.com>
 
6
 *  Tiago Salem Herrmann <tiago.herrmann@canonical.com>
6
7
 *
7
8
 * This file is part of telephony-service.
8
9
 *
20
21
 */
21
22
 
22
23
#include "chatmanager.h"
 
24
#include "chatentry.h"
23
25
#include "telepathyhelper.h"
24
26
#include "phoneutils.h"
25
27
#include "config.h"
47
49
    return argument;
48
50
}
49
51
 
 
52
QVariantMap convertPropertiesForDBus(const QVariantMap &properties)
 
53
{
 
54
    QVariantMap propMap = properties;
 
55
    // participants coming from qml are variants
 
56
    if (properties.contains("participantIds")) {
 
57
        QStringList participants = properties["participantIds"].toStringList();
 
58
        if (!participants.isEmpty()) {
 
59
            propMap["participantIds"] = participants;
 
60
        }
 
61
    }
 
62
    return propMap;
 
63
}
 
64
 
50
65
ChatManager::ChatManager(QObject *parent)
51
 
: QObject(parent),
52
 
  mReady(TelepathyHelper::instance()->ready())
 
66
: QObject(parent)
53
67
{
54
68
    qDBusRegisterMetaType<AttachmentList>();
55
69
    qDBusRegisterMetaType<AttachmentStruct>();
57
71
    mMessagesAckTimer.setInterval(25);
58
72
    mMessagesAckTimer.setSingleShot(true);
59
73
    connect(TelepathyHelper::instance(), SIGNAL(channelObserverUnregistered()), SLOT(onChannelObserverUnregistered()));
60
 
    connect(TelepathyHelper::instance(), SIGNAL(setupReady()), SLOT(onTelepathyReady()));
61
74
    connect(&mMessagesAckTimer, SIGNAL(timeout()), SLOT(onAckTimerTriggered()));
62
75
    connect(TelepathyHelper::instance(), SIGNAL(setupReady()), SLOT(onConnectedChanged()));
63
76
}
64
77
 
65
 
void ChatManager::onTelepathyReady()
66
 
{
67
 
    mReady = true;
68
 
    Q_FOREACH(const Tp::TextChannelPtr &channel, mPendingChannels) {
69
 
        onTextChannelAvailable(channel);
70
 
    }
71
 
    mPendingChannels.clear();
72
 
}
73
 
 
74
78
void ChatManager::onChannelObserverUnregistered()
75
79
{
76
 
    QList<ChatEntry*> tmp = mChatEntries;
77
 
    mChatEntries.clear();
78
 
    Q_EMIT chatsChanged();
79
 
    Q_FOREACH(ChatEntry *entry, tmp) {
80
 
        // for some reason deleteLater is not working
81
 
        delete entry;
82
 
    }
 
80
    mTextChannels.clear();
83
81
}
84
82
 
85
83
void ChatManager::onConnectedChanged()
95
93
    return manager;
96
94
}
97
95
 
98
 
QString ChatManager::sendMessage(const QString &accountId, const QStringList &recipients, const QString &message, const QVariant &attachments, const QVariantMap &properties)
 
96
QString ChatManager::startChat(const QString &accountId, const QVariantMap &properties)
 
97
{
 
98
    QVariantMap propMap = convertPropertiesForDBus(properties);
 
99
    QDBusInterface *phoneAppHandler = TelepathyHelper::instance()->handlerInterface();
 
100
    QDBusReply<QString> reply = phoneAppHandler->call("StartChat", accountId, propMap);
 
101
    return reply.value();
 
102
}
 
103
 
 
104
QString ChatManager::sendMessage(const QString &accountId, const QString &message, const QVariant &attachments, const QVariantMap &properties)
99
105
{
100
106
    AccountEntry *account = TelepathyHelper::instance()->accountForId(accountId);
101
107
 
103
109
        return QString();
104
110
    }
105
111
 
 
112
    QVariantMap propMap = convertPropertiesForDBus(properties);
 
113
 
106
114
    // check if files should be copied to a temporary location before passing them to handler
107
115
    bool tmpFiles = (properties.contains("x-canonical-tmp-files") && properties["x-canonical-tmp-files"].toBool());
108
116
 
142
150
    }
143
151
 
144
152
    QDBusInterface *phoneAppHandler = TelepathyHelper::instance()->handlerInterface();
145
 
    QDBusReply<QString> reply = phoneAppHandler->call("SendMessage", account->accountId(), recipients, message, QVariant::fromValue(newAttachments), properties);
 
153
    QDBusReply<QString> reply = phoneAppHandler->call("SendMessage", account->accountId(), message, QVariant::fromValue(newAttachments), propMap);
146
154
    if (reply.isValid()) {
147
155
        return reply.value();
148
156
    }
149
157
    return QString();
150
158
}
151
159
 
 
160
QList<Tp::TextChannelPtr> ChatManager::channelForProperties(const QVariantMap &properties)
 
161
{
 
162
    QList<Tp::TextChannelPtr> channels;
 
163
 
 
164
 
 
165
    Q_FOREACH (Tp::TextChannelPtr channel, mTextChannels) {
 
166
        if (channelMatchProperties(channel, properties)) {
 
167
            channels << channel;
 
168
        }
 
169
    }
 
170
 
 
171
    return channels;
 
172
}
 
173
 
 
174
Tp::TextChannelPtr ChatManager::channelForObjectPath(const QString &objectPath)
 
175
{
 
176
    Q_FOREACH(Tp::TextChannelPtr channel, mTextChannels) {
 
177
        if (channel->objectPath() == objectPath) {
 
178
            return channel;
 
179
        }
 
180
    }
 
181
    return Tp::TextChannelPtr();
 
182
}
 
183
 
 
184
bool ChatManager::channelMatchProperties(const Tp::TextChannelPtr &channel, const QVariantMap &properties)
 
185
{
 
186
    QVariantMap propMap = properties;
 
187
    ChatEntry::ChatType chatType = ChatEntry::ChatTypeNone;
 
188
 
 
189
    QStringList participants;
 
190
    // participants coming from qml are variants
 
191
    if (properties.contains("participantIds")) {
 
192
        participants = properties["participantIds"].toStringList();
 
193
        if (!participants.isEmpty()) {
 
194
            propMap["participantIds"] = participants;
 
195
        }
 
196
    }
 
197
 
 
198
    if (participants.isEmpty() && propMap.contains("participants")) {
 
199
        // try to generate list of participants from "participants"
 
200
        Q_FOREACH(const QVariant &participantMap, propMap["participants"].toList()) {
 
201
            if (participantMap.toMap().contains("identifier")) {
 
202
                participants << participantMap.toMap()["identifier"].toString();
 
203
            }
 
204
        }
 
205
        if (!participants.isEmpty()) {
 
206
            propMap["participantIds"] = participants;
 
207
        }
 
208
    }
 
209
 
 
210
    if (properties.contains("chatType")) {
 
211
        chatType = (ChatEntry::ChatType)properties["chatType"].toInt();
 
212
    } else {
 
213
        if (participants.length() == 1) {
 
214
            chatType = ChatEntry::ChatTypeContact;
 
215
        }
 
216
    }
 
217
 
 
218
    QString accountId;
 
219
    if (propMap.contains("accountId")) {
 
220
        accountId = propMap["accountId"].toString();
 
221
    }
 
222
 
 
223
    if (participants.count() == 0 && chatType == ChatEntry::ChatTypeContact) {
 
224
        return false;
 
225
    }
 
226
 
 
227
    AccountEntry *account = TelepathyHelper::instance()->accountForConnection(channel->connection());
 
228
    if (!account) {
 
229
        return false;
 
230
    }
 
231
 
 
232
    // only channels of the correct type should be returned
 
233
    if ((ChatEntry::ChatType)channel->targetHandleType() != chatType) {
 
234
        return false;
 
235
    }
 
236
 
 
237
    if (chatType == ChatEntry::ChatTypeRoom) {
 
238
        QString chatId = propMap["threadId"].toString();
 
239
        if (!chatId.isEmpty() && channel->targetId() == chatId) {
 
240
            // if we are filtering by one specific accountId, make sure it matches
 
241
            if (!accountId.isEmpty() && accountId != account->accountId()) {
 
242
                return false;
 
243
            }
 
244
 
 
245
            return true;
 
246
        }
 
247
        return false;
 
248
    }
 
249
 
 
250
    Tp::Contacts contacts = channel->groupContacts(false);
 
251
    if (participants.count() != contacts.count()) {
 
252
        return false;
 
253
    }
 
254
    int participantCount = 0;
 
255
    // iterate over participants
 
256
    Q_FOREACH (const Tp::ContactPtr &contact, contacts) {
 
257
        // try the easiest first
 
258
        if (participants.contains(contact->id())) {
 
259
            participantCount++;
 
260
            continue;
 
261
        }
 
262
 
 
263
        // if no exact match, try to use the account's compare function
 
264
        Q_FOREACH(const QString &participant, participants) {
 
265
            if (account->compareIds(participant, contact->id())) {
 
266
                participantCount++;
 
267
                break;
 
268
            }
 
269
        }
 
270
    }
 
271
    return (participantCount == participants.count());
 
272
}
 
273
 
152
274
void ChatManager::onTextChannelAvailable(Tp::TextChannelPtr channel)
153
275
{
154
 
    if (!mReady) {
155
 
        mPendingChannels.append(channel);
156
 
        return;
157
 
    }
158
 
    ChatEntry *chatEntry = new ChatEntry(channel, this);
159
 
    mChatEntries.append(chatEntry);
160
 
 
161
 
    connect(channel.data(),
162
 
            SIGNAL(messageReceived(Tp::ReceivedMessage)),
163
 
            SLOT(onMessageReceived(Tp::ReceivedMessage)));
164
 
    connect(channel.data(),
165
 
            SIGNAL(messageSent(Tp::Message,Tp::MessageSendingFlags,QString)),
166
 
            SLOT(onMessageSent(Tp::Message,Tp::MessageSendingFlags,QString)));
167
 
     connect(channel.data(),
 
276
    mTextChannels << channel;
 
277
    connect(channel.data(),
168
278
            SIGNAL(invalidated(Tp::DBusProxy*,const QString&, const QString&)),
169
279
            SLOT(onChannelInvalidated()));
170
280
 
171
 
    Q_FOREACH(const Tp::ReceivedMessage &message, channel->messageQueue()) {
172
 
        onMessageReceived(message);
173
 
    }
174
 
 
175
 
    Q_EMIT chatsChanged();
176
 
    Q_EMIT chatEntryCreated(chatEntry->account()->accountId(), chatEntry->participants(), chatEntry);
 
281
    Q_EMIT textChannelAvailable(channel);
177
282
}
178
283
 
179
284
void ChatManager::onChannelInvalidated()
180
285
{
181
286
    Tp::TextChannelPtr channel(qobject_cast<Tp::TextChannel*>(sender()));
182
 
    ChatEntry *chatEntry = chatEntryForChannel(channel);
183
 
    if (chatEntry) {
184
 
        mChatEntries.removeAll(chatEntry);
185
 
        // for some reason deleteLater is not working
186
 
        delete chatEntry;
187
 
        Q_EMIT chatsChanged();
188
 
    }
189
 
}
190
 
 
191
 
ChatEntry *ChatManager::chatEntryForChannel(const Tp::TextChannelPtr &channel)
192
 
{
193
 
    Q_FOREACH (ChatEntry *chatEntry, mChatEntries) {
194
 
        if (channel == chatEntry->channel()) {
195
 
            return chatEntry;
196
 
        }
197
 
    }
198
 
    return NULL;
199
 
}
200
 
 
201
 
void ChatManager::onMessageReceived(const Tp::ReceivedMessage &message)
202
 
{
203
 
    // ignore delivery reports for now
204
 
    // FIXME: we need to handle errors on sending messages at some point
205
 
    if (message.isDeliveryReport()) {
206
 
        return;
207
 
    }
208
 
 
209
 
    Q_EMIT messageReceived(message.sender()->id(), message.text(), message.received(), message.messageToken(), true);
210
 
}
211
 
 
212
 
void ChatManager::onMessageSent(const Tp::Message &sentMessage, const Tp::MessageSendingFlags flags, const QString &message)
213
 
{
214
 
    Q_UNUSED(message)
215
 
    Q_UNUSED(flags)
216
 
 
217
 
    Tp::TextChannel *channel = qobject_cast<Tp::TextChannel*>(sender());
218
 
    if (!channel) {
219
 
        return;
220
 
    }
221
 
 
222
 
    QStringList recipients;
223
 
    Q_FOREACH(const Tp::ContactPtr &contact, channel->groupContacts(false)) {
224
 
        recipients << contact->id();
225
 
    }
226
 
 
227
 
    Q_EMIT messageSent(recipients, sentMessage.text());
228
 
}
229
 
 
230
 
void ChatManager::acknowledgeMessage(const QStringList &recipients, const QString &messageId, const QString &accountId)
231
 
{
232
 
    AccountEntry *account = NULL;
233
 
    if (accountId.isNull() || accountId.isEmpty()) {
234
 
        account = TelepathyHelper::instance()->defaultMessagingAccount();
235
 
        if (!account && !TelepathyHelper::instance()->activeAccounts().isEmpty()) {
236
 
            account = TelepathyHelper::instance()->activeAccounts()[0];
237
 
        }
238
 
    } else {
239
 
        account = TelepathyHelper::instance()->accountForId(accountId);
240
 
    }
241
 
 
242
 
    if (!account) {
243
 
        mMessagesToAck[accountId][recipients].append(messageId);
244
 
        return;
245
 
    }
246
 
 
 
287
    mTextChannels.removeAll(channel);
 
288
    Q_EMIT textChannelInvalidated(channel);
 
289
}
 
290
 
 
291
void ChatManager::acknowledgeMessage(const QVariantMap &properties)
 
292
{
 
293
    mMessagesToAck << QVariant::fromValue(convertPropertiesForDBus(properties));
247
294
    mMessagesAckTimer.start();
248
 
    mMessagesToAck[account->accountId()][recipients].append(messageId);
249
295
}
250
296
 
251
 
void ChatManager::acknowledgeAllMessages(const QStringList &recipients, const QString &accountId)
 
297
void ChatManager::acknowledgeAllMessages(const QVariantMap &properties)
252
298
{
253
299
    QDBusInterface *phoneAppHandler = TelepathyHelper::instance()->handlerInterface();
254
 
    phoneAppHandler->asyncCall("AcknowledgeAllMessages", recipients, accountId);
 
300
    phoneAppHandler->asyncCall("AcknowledgeAllMessages", convertPropertiesForDBus(properties));
255
301
}
256
302
 
257
303
void ChatManager::onAckTimerTriggered()
259
305
    // ack all pending messages
260
306
    QDBusInterface *phoneAppHandler = TelepathyHelper::instance()->handlerInterface();
261
307
 
262
 
    QMap<QString, QMap<QStringList,QStringList> >::const_iterator it = mMessagesToAck.constBegin();
263
 
    while (it != mMessagesToAck.constEnd()) {
264
 
        QString accountId = it.key();
265
 
        QMap<QStringList, QStringList>::const_iterator it2 = it.value().constBegin();
266
 
        while (it2 != it.value().constEnd()) {
267
 
            phoneAppHandler->asyncCall("AcknowledgeMessages", it2.key(), it2.value(), accountId);
268
 
            ++it2;
269
 
        }
270
 
        ++it;
271
 
    }
 
308
    phoneAppHandler->asyncCall("AcknowledgeMessages", mMessagesToAck);
272
309
 
273
310
    mMessagesToAck.clear();
274
311
}
275
 
 
276
 
QList<ChatEntry*> ChatManager::chatEntries() const
277
 
{
278
 
    return mChatEntries;
279
 
}
280
 
 
281
 
ChatEntry *ChatManager::chatEntryForParticipants(const QString &accountId, const QStringList &participants, bool create)
282
 
{
283
 
    if (participants.count() == 0 || accountId.isEmpty()) {
284
 
        return NULL;
285
 
    }
286
 
 
287
 
    AccountEntry *account = TelepathyHelper::instance()->accountForId(accountId);
288
 
 
289
 
    if (!account) {
290
 
        return NULL;
291
 
    }
292
 
 
293
 
    Q_FOREACH (ChatEntry *chatEntry, mChatEntries) {
294
 
        int participantCount = 0;
295
 
        Tp::Contacts contacts = chatEntry->channel()->groupContacts(false);
296
 
        if (participants.count() != contacts.count()) {
297
 
            continue;
298
 
        }
299
 
        // iterate over participants
300
 
        Q_FOREACH (const Tp::ContactPtr &contact, contacts) {
301
 
            if (account->type() == AccountEntry::PhoneAccount || account->type() == AccountEntry::MultimediaAccount) {
302
 
                Q_FOREACH(const QString &participant, participants) {
303
 
                    if (PhoneUtils::comparePhoneNumbers(participant, contact->id()) > PhoneUtils::NO_MATCH) {
304
 
                        participantCount++;
305
 
                        break;
306
 
                    }
307
 
                }
308
 
                continue;
309
 
            }
310
 
            if (participants.contains(contact->id())) {
311
 
                participantCount++;
312
 
            } else {
313
 
                break;
314
 
            }
315
 
        }
316
 
        if (participantCount == participants.count()) {
317
 
            return chatEntry;
318
 
        }
319
 
    }
320
 
 
321
 
    if (create) {
322
 
        QDBusInterface *phoneAppHandler = TelepathyHelper::instance()->handlerInterface();
323
 
        phoneAppHandler->call("StartChat", accountId, participants);
324
 
    }
325
 
    return NULL;
326
 
}
327
 
 
328
 
ChatEntry *ChatManager::chatEntryForChatRoom(const QString &accountId, const QVariantMap &properties, bool create)
329
 
{
330
 
    Q_UNUSED(accountId)
331
 
    Q_UNUSED(properties)
332
 
    Q_UNUSED(create)
333
 
    // FIXME: implement
334
 
}
335
 
 
336
 
QQmlListProperty<ChatEntry> ChatManager::chats()
337
 
{
338
 
    return QQmlListProperty<ChatEntry>(this, 0, chatCount, chatAt);
339
 
}
340
 
 
341
 
int ChatManager::chatCount(QQmlListProperty<ChatEntry> *p)
342
 
{
343
 
    return ChatManager::instance()->chatEntries().count();
344
 
}
345
 
 
346
 
ChatEntry *ChatManager::chatAt(QQmlListProperty<ChatEntry> *p, int index)
347
 
{
348
 
    return ChatManager::instance()->chatEntries()[index];
349
 
}
350