diff --git a/interfaces/conversationmessage.cpp b/interfaces/conversationmessage.cpp index 2aaebef1..34e5b25d 100644 --- a/interfaces/conversationmessage.cpp +++ b/interfaces/conversationmessage.cpp @@ -1,145 +1,152 @@ /** * Copyright 2018 Simon Redman * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "conversationmessage.h" #include #include Q_LOGGING_CATEGORY(CONVERSATION_MESSAGE_LOGGING_CATEGORY, "kdeconnect.interfaces.conversationmessage") ConversationMessage::ConversationMessage(const QVariantMap& args) : m_eventField(args[QStringLiteral("event")].toInt()), m_body(args[QStringLiteral("body")].toString()), m_date(args[QStringLiteral("date")].toLongLong()), m_type(args[QStringLiteral("type")].toInt()), m_read(args[QStringLiteral("read")].toInt()), m_threadID(args[QStringLiteral("thread_id")].toLongLong()), m_uID(args[QStringLiteral("_id")].toInt()) { QString test = QLatin1String(args[QStringLiteral("addresses")].typeName()); QVariantList jsonAddresses = args[QStringLiteral("addresses")].toList(); for (const QVariant& addressField : jsonAddresses) { const auto& rawAddress = addressField.toMap(); m_addresses.append(ConversationAddress(rawAddress[QStringLiteral("address")].value())); } + QVariantMap::const_iterator subID_it = args.find(QStringLiteral("sub_id")); + m_subID = subID_it == args.end() ? -1 : subID_it->toLongLong(); } ConversationMessage::ConversationMessage (const qint32& eventField, const QString& body, const QList& addresses, const qint64& date, const qint32& type, const qint32& read, const qint64& threadID, - const qint32& uID) + const qint32& uID, + const qint64& subID) : m_eventField(eventField) , m_body(body) , m_addresses(addresses) , m_date(date) , m_type(type) , m_read(read) , m_threadID(threadID) , m_uID(uID) + , m_subID(subID) { } ConversationMessage::ConversationMessage(const ConversationMessage& other) : m_eventField(other.m_eventField) , m_body(other.m_body) , m_addresses(other.m_addresses) , m_date(other.m_date) , m_type(other.m_type) , m_read(other.m_read) , m_threadID(other.m_threadID) , m_uID(other.m_uID) + , m_subID(other.m_subID) { } ConversationMessage::~ConversationMessage() { } ConversationMessage& ConversationMessage::operator=(const ConversationMessage& other) { this->m_eventField = other.m_eventField; this->m_body = other.m_body; this->m_addresses = other.m_addresses; this->m_date = other.m_date; this->m_type = other.m_type; this->m_read = other.m_read; this->m_threadID = other.m_threadID; this->m_uID = other.m_uID; + this->m_subID = other.m_subID; return *this; } ConversationMessage ConversationMessage::fromDBus(const QDBusVariant& var) { QDBusArgument data = var.variant().value(); ConversationMessage message; data >> message; return message; } QVariantMap ConversationMessage::toVariant() const { QVariantList addresses; for (const ConversationAddress& address : m_addresses) { addresses.push_back(address.toVariant()); } return { {QStringLiteral("event"), m_eventField}, {QStringLiteral("body"), m_body}, {QStringLiteral("addresses"), addresses}, {QStringLiteral("date"), m_date}, {QStringLiteral("type"), m_type}, {QStringLiteral("read"), m_read}, {QStringLiteral("thread_id"), m_threadID}, {QStringLiteral("_id"), m_uID}, + {QStringLiteral("sub_id"), m_subID} }; } ConversationAddress::ConversationAddress(QString address) : m_address(address) {} ConversationAddress::ConversationAddress(const ConversationAddress& other) : m_address(other.address()) {} ConversationAddress::~ConversationAddress() {} ConversationAddress& ConversationAddress::operator=(const ConversationAddress& other) { this->m_address = other.m_address; return *this; } QVariantMap ConversationAddress::toVariant() const { return { {QStringLiteral("address"), address()}, }; } void ConversationMessage::registerDbusType() { qDBusRegisterMetaType(); qRegisterMetaType(); qDBusRegisterMetaType(); qRegisterMetaType(); } diff --git a/interfaces/conversationmessage.h b/interfaces/conversationmessage.h index 80d11742..d0ce7852 100644 --- a/interfaces/conversationmessage.h +++ b/interfaces/conversationmessage.h @@ -1,223 +1,232 @@ /** * Copyright 2018 Simon Redman * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef PLUGINS_TELEPHONY_CONVERSATIONMESSAGE_H_ #define PLUGINS_TELEPHONY_CONVERSATIONMESSAGE_H_ #include #include #include "kdeconnectinterfaces_export.h" Q_DECLARE_LOGGING_CATEGORY(CONVERSATION_MESSAGE_LOGGING_CATEGORY) class ConversationAddress; class KDECONNECTINTERFACES_EXPORT ConversationMessage { public: // TYPE field values from Android enum Types { MessageTypeAll = 0, MessageTypeInbox = 1, MessageTypeSent = 2, MessageTypeDraft = 3, MessageTypeOutbox = 4, MessageTypeFailed = 5, MessageTypeQueued = 6, }; /** * Values describing the possible type of events contained in a message * A message's eventField is constructed as a bitwise-OR of events * Any events which are unsupported should be ignored */ enum Events { EventTextMessage = 0x1, // This message has a body field which contains pure, human-readable text EventMultiTarget = 0x2, // This is a multitarget (group) message which has an "addresses" field which is a list of participants in the group }; /** * Build a new message from a keyword argument dictionary * * @param args mapping of field names to values as might be contained in a network packet containing a message */ ConversationMessage(const QVariantMap& args = QVariantMap()); ConversationMessage(const qint32& eventField, const QString& body, const QList& addresses, const qint64& date, const qint32& type, const qint32& read, - const qint64& threadID, const qint32& uID); + const qint64& threadID, const qint32& uID, const qint64& subID); ConversationMessage(const ConversationMessage& other); ~ConversationMessage(); ConversationMessage& operator=(const ConversationMessage& other); static ConversationMessage fromDBus(const QDBusVariant&); static void registerDbusType(); qint32 eventField() const { return m_eventField; } QString body() const { return m_body; } QList addresses() const { return m_addresses; } qint64 date() const { return m_date; } qint32 type() const { return m_type; } qint32 read() const { return m_read; } qint64 threadID() const { return m_threadID; } qint32 uID() const { return m_uID; } + qint64 subID() const { return m_subID; } QVariantMap toVariant() const; bool containsTextBody() const { return (eventField() & ConversationMessage::EventTextMessage); } bool isMultitarget() const { return (eventField() & ConversationMessage::EventMultiTarget); } bool isIncoming() const { return type() == MessageTypeInbox; } bool isOutgoing() const { return type() == MessageTypeSent; } /** * Return the address of the other party of a single-target conversation * Calling this method with a multi-target conversation is ill-defined */ QString getOtherPartyAddress() const; protected: /** * Bitwise OR of event flags * Unsupported flags shall cause the message to be ignored */ qint32 m_eventField; /** * Body of the message */ QString m_body; /** * List of all addresses involved in this conversation * An address is most likely a phone number, but may be something else like an email address */ QList m_addresses; /** * Date stamp (Unix epoch millis) associated with the message */ qint64 m_date; /** * Type of the message. See the message.type enum */ qint32 m_type; /** * Whether we have a read report for this message */ qint32 m_read; /** * Tag which binds individual messages into a thread */ qint64 m_threadID; /** * Value which uniquely identifies a message */ qint32 m_uID; + + /** + * Value which determines SIM id (optional) + */ + qint64 m_subID; }; class KDECONNECTINTERFACES_EXPORT ConversationAddress { public: ConversationAddress(QString address = QStringLiteral()); ConversationAddress(const ConversationAddress& other); ~ConversationAddress(); ConversationAddress& operator=(const ConversationAddress& other); QString address() const { return m_address; } QVariantMap toVariant() const; private: QString m_address; }; inline QDBusArgument &operator<<(QDBusArgument &argument, const ConversationMessage &message) { argument.beginStructure(); argument << message.eventField() << message.body() << message.addresses() << message.date() << message.type() << message.read() << message.threadID() - << message.uID(); + << message.uID() + << message.subID(); argument.endStructure(); return argument; } inline const QDBusArgument &operator>>(const QDBusArgument &argument, ConversationMessage &message) { qint32 event; QString body; QList addresses; qint64 date; qint32 type; qint32 read; qint64 threadID; qint32 uID; + qint64 m_subID; argument.beginStructure(); argument >> event; argument >> body; argument >> addresses; argument >> date; argument >> type; argument >> read; argument >> threadID; argument >> uID; + argument >> m_subID; argument.endStructure(); - message = ConversationMessage(event, body, addresses, date, type, read, threadID, uID); + message = ConversationMessage(event, body, addresses, date, type, read, threadID, uID, m_subID); return argument; } inline QDBusArgument& operator<<(QDBusArgument& argument, const ConversationAddress& address) { argument.beginStructure(); argument << address.address(); argument.endStructure(); return argument; } inline const QDBusArgument& operator>>(const QDBusArgument& argument, ConversationAddress& address) { QString addressField; argument.beginStructure(); argument >> addressField; argument.endStructure(); address = ConversationAddress(addressField); return argument; } Q_DECLARE_METATYPE(ConversationMessage); Q_DECLARE_METATYPE(ConversationAddress); #endif /* PLUGINS_TELEPHONY_CONVERSATIONMESSAGE_H_ */ diff --git a/plugins/sms/conversationsdbusinterface.cpp b/plugins/sms/conversationsdbusinterface.cpp index 187d43b3..90970304 100644 --- a/plugins/sms/conversationsdbusinterface.cpp +++ b/plugins/sms/conversationsdbusinterface.cpp @@ -1,224 +1,224 @@ /** * Copyright 2018 Simon Redman * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "conversationsdbusinterface.h" #include "interfaces/dbusinterfaces.h" #include "interfaces/conversationmessage.h" #include "requestconversationworker.h" #include #include #include Q_LOGGING_CATEGORY(KDECONNECT_CONVERSATIONS, "kdeconnect.conversations") QMap ConversationsDbusInterface::liveConversationInterfaces; ConversationsDbusInterface::ConversationsDbusInterface(KdeConnectPlugin* plugin) : QDBusAbstractAdaptor(const_cast(plugin->device())) , m_device(plugin->device()->id()) , m_plugin(plugin) , m_lastId(0) , m_smsInterface(m_device) { ConversationMessage::registerDbusType(); // Check for an existing interface for the same device // If there is already an interface for this device, we can safely delete is since we have just replaced it const auto& oldInterfaceItr = ConversationsDbusInterface::liveConversationInterfaces.find(m_device); if (oldInterfaceItr != ConversationsDbusInterface::liveConversationInterfaces.end()) { ConversationsDbusInterface* oldInterface = oldInterfaceItr.value(); oldInterface->deleteLater(); ConversationsDbusInterface::liveConversationInterfaces.erase(oldInterfaceItr); } ConversationsDbusInterface::liveConversationInterfaces[m_device] = this; } ConversationsDbusInterface::~ConversationsDbusInterface() { // Wake all threads which were waiting for a reply from this interface // This might result in some noise on dbus, but it's better than leaking a bunch of resources! waitingForMessagesLock.lock(); conversationsWaitingForMessages.clear(); waitingForMessages.wakeAll(); waitingForMessagesLock.unlock(); // Erase this interface from the list of known interfaces const auto myIterator = ConversationsDbusInterface::liveConversationInterfaces.find(m_device); ConversationsDbusInterface::liveConversationInterfaces.erase(myIterator); } QVariantList ConversationsDbusInterface::activeConversations() { QList toReturn; toReturn.reserve(m_conversations.size()); for (auto it = m_conversations.cbegin(); it != m_conversations.cend(); ++it) { const auto& conversation = it.value().values(); if (conversation.isEmpty()) { // This should really never happen because we create a conversation at the same time // as adding a message, but better safe than sorry qCWarning(KDECONNECT_CONVERSATIONS) << "Conversation with ID" << it.key() << "is unexpectedly empty"; break; } const QVariant& message = QVariant::fromValue(*conversation.crbegin()); toReturn.append(message); } return toReturn; } void ConversationsDbusInterface::requestConversation(const qint64& conversationID, int start, int end) { if (start < 0 || end < 0) { qCWarning(KDECONNECT_CONVERSATIONS) << "requestConversation" << "Start and end must be >= 0"; return; } if (end - start < 0) { qCWarning(KDECONNECT_CONVERSATIONS) << "requestConversation" <<"Start must be before end"; return; } RequestConversationWorker* worker = new RequestConversationWorker(conversationID, start, end, this); connect(worker, &RequestConversationWorker::conversationMessageRead, this, &ConversationsDbusInterface::conversationUpdated, Qt::QueuedConnection); worker->work(); } void ConversationsDbusInterface::addMessages(const QList &messages) { QSet updatedConversationIDs; for (const auto& message : messages) { const qint32& threadId = message.threadID(); // We might discover that there are no new messages in this conversation, thus calling it // "updated" might turn out to be a bit misleading // However, we need to report it as updated regardless, for the case where we have already // cached every message of the conversation but we have received a request for more, otherwise // we will never respond to that request updatedConversationIDs.insert(message.threadID()); if (m_known_messages[threadId].contains(message.uID())) { // This message has already been processed. Don't do anything. continue; } // Store the Message in the list corresponding to its thread bool newConversation = !m_conversations.contains(threadId); const auto& threadPosition = m_conversations[threadId].insert(message.date(), message); m_known_messages[threadId].insert(message.uID()); // If this message was inserted at the end of the list, it is the latest message in the conversation bool latestMessage = threadPosition == m_conversations[threadId].end() - 1; // Tell the world about what just happened if (newConversation) { Q_EMIT conversationCreated(QDBusVariant(QVariant::fromValue(message))); } else if (latestMessage) { Q_EMIT conversationUpdated(QDBusVariant(QVariant::fromValue(message))); } } // It feels bad to go through the set of updated conversations again, // but also there are not many times that updatedConversationIDs will be more than one for (qint64 conversationID : updatedConversationIDs) { quint64 numMessages = m_known_messages[conversationID].size(); Q_EMIT conversationLoaded(conversationID, numMessages); } waitingForMessagesLock.lock(); // Remove the waiting flag for all conversations which we just processed conversationsWaitingForMessages.subtract(updatedConversationIDs); waitingForMessages.wakeAll(); waitingForMessagesLock.unlock(); } void ConversationsDbusInterface::removeMessage(const QString& internalId) { // TODO: Delete the specified message from our internal structures Q_UNUSED(internalId); } QList ConversationsDbusInterface::getConversation(const qint64& conversationID) const { return m_conversations.value(conversationID).values(); } void ConversationsDbusInterface::updateConversation(const qint64& conversationID) { waitingForMessagesLock.lock(); if (conversationsWaitingForMessages.contains(conversationID)) { // This conversation is already being waited on, don't allow more than one thread to wait at a time qCDebug(KDECONNECT_CONVERSATIONS) << "Not allowing two threads to wait for conversationID" << conversationID; waitingForMessagesLock.unlock(); return; } qCDebug(KDECONNECT_CONVERSATIONS) << "Requesting conversation with ID" << conversationID << "from remote"; conversationsWaitingForMessages.insert(conversationID); m_smsInterface.requestConversation(conversationID); while (conversationsWaitingForMessages.contains(conversationID)) { waitingForMessages.wait(&waitingForMessagesLock); } waitingForMessagesLock.unlock(); } void ConversationsDbusInterface::replyToConversation(const qint64& conversationID, const QString& message) { const auto messagesList = m_conversations[conversationID]; if (messagesList.isEmpty()) { qCWarning(KDECONNECT_CONVERSATIONS) << "Got a conversationID for a conversation with no messages!"; return; } if (messagesList.first().isMultitarget()) { qWarning(KDECONNECT_CONVERSATIONS) << "Tried to reply to a group MMS which is not supported in this version of KDE Connect"; return; } const QList& addresses = messagesList.first().addresses(); if (addresses.size() > 1) { // TODO: Upgrade for multitarget replies qCWarning(KDECONNECT_CONVERSATIONS) << "Sending replies to multiple recipients is not supported"; return; } - m_smsInterface.sendSms(addresses[0].address(), message); + m_smsInterface.sendSms(addresses[0].address(), message, messagesList.first().subID()); } void ConversationsDbusInterface::sendWithoutConversation(const QString& address, const QString& message) { m_smsInterface.sendSms(address, message); } void ConversationsDbusInterface::requestAllConversationThreads() { // Prepare the list of conversations by requesting the first in every thread m_smsInterface.requestAllConversations(); } QString ConversationsDbusInterface::newId() { return QString::number(++m_lastId); } diff --git a/plugins/sms/smsplugin.cpp b/plugins/sms/smsplugin.cpp index 52da9c44..5c926de9 100644 --- a/plugins/sms/smsplugin.cpp +++ b/plugins/sms/smsplugin.cpp @@ -1,131 +1,135 @@ /** * Copyright 2013 Albert Vaca * Copyright 2018 Simon Redman * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "smsplugin.h" #include #include #include #include #include #include #include #include #include "sendreplydialog.h" K_PLUGIN_CLASS_WITH_JSON(SmsPlugin, "kdeconnect_sms.json") Q_LOGGING_CATEGORY(KDECONNECT_PLUGIN_SMS, "kdeconnect.plugin.sms") SmsPlugin::SmsPlugin(QObject* parent, const QVariantList& args) : KdeConnectPlugin(parent, args) , m_telepathyInterface(QStringLiteral("org.freedesktop.Telepathy.ConnectionManager.kdeconnect"), QStringLiteral("/kdeconnect")) , m_conversationInterface(new ConversationsDbusInterface(this)) { } SmsPlugin::~SmsPlugin() { // m_conversationInterface is self-deleting, see ~ConversationsDbusInterface for more information } bool SmsPlugin::receivePacket(const NetworkPacket& np) { if (np.type() == PACKET_TYPE_SMS_MESSAGES) { return handleBatchMessages(np); } return true; } -void SmsPlugin::sendSms(const QString& phoneNumber, const QString& messageBody) +void SmsPlugin::sendSms(const QString& phoneNumber, const QString& messageBody, const qint64 subID) { - NetworkPacket np(PACKET_TYPE_SMS_REQUEST, { + QVariantMap packetMap({ {QStringLiteral("sendSms"), true}, {QStringLiteral("phoneNumber"), phoneNumber}, {QStringLiteral("messageBody"), messageBody} }); + if (subID != -1) { + packetMap[QStringLiteral("subID")] = subID; + } + NetworkPacket np(PACKET_TYPE_SMS_REQUEST, packetMap); qCDebug(KDECONNECT_PLUGIN_SMS) << "Dispatching SMS send request to remote"; sendPacket(np); } void SmsPlugin::requestAllConversations() { NetworkPacket np(PACKET_TYPE_SMS_REQUEST_CONVERSATIONS); sendPacket(np); } void SmsPlugin::requestConversation (const qint64& conversationID) const { NetworkPacket np(PACKET_TYPE_SMS_REQUEST_CONVERSATION); np.set(QStringLiteral("threadID"), conversationID); sendPacket(np); } void SmsPlugin::forwardToTelepathy(const ConversationMessage& message) { // If we don't have a valid Telepathy interface, bail out if (!(m_telepathyInterface.isValid())) return; qCDebug(KDECONNECT_PLUGIN_SMS) << "Passing a text message to the telepathy interface"; connect(&m_telepathyInterface, SIGNAL(messageReceived(QString,QString)), SLOT(sendSms(QString,QString)), Qt::UniqueConnection); const QString messageBody = message.body(); const QString contactName; // TODO: When telepathy support is improved, look up the contact with KPeople const QString phoneNumber = message.addresses()[0].address(); m_telepathyInterface.call(QDBus::NoBlock, QStringLiteral("sendMessage"), phoneNumber, contactName, messageBody); } bool SmsPlugin::handleBatchMessages(const NetworkPacket& np) { const auto messages = np.get(QStringLiteral("messages")); QList messagesList; messagesList.reserve(messages.count()); for (const QVariant& body : messages) { ConversationMessage message(body.toMap()); if (message.containsTextBody()) { forwardToTelepathy(message); } messagesList.append(message); } m_conversationInterface->addMessages(messagesList); return true; } QString SmsPlugin::dbusPath() const { return QStringLiteral("/modules/kdeconnect/devices/") + device()->id() + QStringLiteral("/sms"); } void SmsPlugin::launchApp() { QProcess::startDetached(QLatin1String("kdeconnect-sms"), { QStringLiteral("--device"), device()->id() }); } #include "smsplugin.moc" diff --git a/plugins/sms/smsplugin.h b/plugins/sms/smsplugin.h index c89b245c..14804060 100644 --- a/plugins/sms/smsplugin.h +++ b/plugins/sms/smsplugin.h @@ -1,161 +1,162 @@ /** * Copyright 2013 Albert Vaca * Copyright 2018 Simon Redman * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef SMSPLUGIN_H #define SMSPLUGIN_H #include #include #include "conversationsdbusinterface.h" #include "interfaces/conversationmessage.h" #include "sendreplydialog.h" /** * Packet used to indicate a batch of messages has been pushed from the remote device * * The body should contain the key "messages" mapping to an array of messages * * For example: * { * "version": 2 // This is the second version of this packet type and * // version 1 packets (which did not carry this flag) * // are incompatible with the new format * "messages" : [ * { "event" : 1, // 32-bit field containing a bitwise-or of event flags * // See constants declared in SMSHelper.Message for defined * // values and explanations * "body" : "Hello", // Text message body * "addresses": > // List of Address objects, one for each participant of the conversation * // The user's Address is excluded so: * // If this is a single-target message, there will only be one * // Address (the other party) * // If this is an incoming multi-target message, the first Address is the * // sender and all other addresses are other parties to the conversation * // If this is an outgoing multi-target message, the sender is implicit * // (the user's phone number) and all Addresses are recipients * "date" : "1518846484880", // Timestamp of the message * "type" : "2", // Compare with Android's * // Telephony.TextBasedSmsColumns.MESSAGE_TYPE_* * "thread_id" : 132 // Thread to which the message belongs * "read" : true // Boolean representing whether a message is read or unread * }, * { ... }, * ... * ] * * The following optional fields of a message object may be defined * "sub_id": // Android's subscriber ID, which is basically used to determine which SIM card the message * // belongs to. This is mostly useful when attempting to reply to an SMS with the correct * // SIM card using PACKET_TYPE_SMS_REQUEST. * // If this value is not defined or if it does not match a valid subscriber_id known by * // Android, we will use whatever subscriber ID Android gives us as the default * * An Address object looks like: * { * "address": // Address (phone number, email address, etc.) of this object * } */ #define PACKET_TYPE_SMS_MESSAGES QStringLiteral("kdeconnect.sms.messages") /** * Packet sent to request a message be sent * * This will almost certainly need to be replaced or augmented to support MMS, * but be sure the Android side remains compatible with old desktop apps! * * The body should look like so: * { "sendSms": true, * "phoneNumber": "542904563213", - * "messageBody": "Hi mom!" + * "messageBody": "Hi mom!", + * "sub_id": "3859358340534" * } */ #define PACKET_TYPE_SMS_REQUEST QStringLiteral("kdeconnect.sms.request") /** * Packet sent to request the most-recent message in each conversations on the device * * The request packet shall contain no body */ #define PACKET_TYPE_SMS_REQUEST_CONVERSATIONS QStringLiteral("kdeconnect.sms.request_conversations") /** * Packet sent to request all the messages in a particular conversation * * The body should contain the key "threadID" mapping to the threadID being requested * For example: * { "threadID": 203 } */ #define PACKET_TYPE_SMS_REQUEST_CONVERSATION QStringLiteral("kdeconnect.sms.request_conversation") Q_DECLARE_LOGGING_CATEGORY(KDECONNECT_PLUGIN_SMS) class Q_DECL_EXPORT SmsPlugin : public KdeConnectPlugin { Q_OBJECT Q_CLASSINFO("D-Bus Interface", "org.kde.kdeconnect.device.sms") public: explicit SmsPlugin(QObject* parent, const QVariantList& args); ~SmsPlugin() override; bool receivePacket(const NetworkPacket& np) override; void connected() override {} QString dbusPath() const override; public Q_SLOTS: - Q_SCRIPTABLE void sendSms(const QString& phoneNumber, const QString& messageBody); + Q_SCRIPTABLE void sendSms(const QString& phoneNumber, const QString& messageBody, const qint64 subID = -1); /** * Send a request to the remote for all of its conversations */ Q_SCRIPTABLE void requestAllConversations(); /** * Send a request to the remote for a particular conversation * * TODO: Make interface capable of requesting limited window of messages */ Q_SCRIPTABLE void requestConversation(const qint64& conversationID) const; Q_SCRIPTABLE void launchApp(); private: /** * Send to the telepathy plugin if it is available */ void forwardToTelepathy(const ConversationMessage& message); /** * Handle a packet which contains many messages, such as PACKET_TYPE_TELEPHONY_MESSAGE */ bool handleBatchMessages(const NetworkPacket& np); QDBusInterface m_telepathyInterface; ConversationsDbusInterface* m_conversationInterface; }; #endif