diff options
author | Matt Porter <mporter@konsulko.com> | 2018-06-19 14:56:26 -0400 |
---|---|---|
committer | Matt Porter <mporter@konsulko.com> | 2018-06-22 16:38:30 -0400 |
commit | e8b328e0516d8ead9af13c8379a6cd1dbbb8a9e9 (patch) | |
tree | dec002e6adcf197c3ac7913fb211b65b73772bc9 | |
parent | fc65716b43af6be84d42599d813d3d40df07d376 (diff) |
pbap: add contacts support and recent call model
Add support for parsing contacts and recent call data from
the PBAP-provided vCards using libvcard. Expose a recent
call list model and interfaces to refresh the data.
Bug-AGL: SPEC-1436
Change-Id: Ia4a02443e22e4a27dc974ef414b765667b26ff83
Signed-off-by: Matt Porter <mporter@konsulko.com>
-rw-r--r-- | CMakeLists.txt | 2 | ||||
-rw-r--r-- | pbap.cpp | 227 | ||||
-rw-r--r-- | pbap.h | 116 | ||||
-rw-r--r-- | pbapmessage.h | 5 |
4 files changed, 344 insertions, 6 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 249f3ac..9165f49 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -21,7 +21,7 @@ add_library(qtappfw SHARED message.cpp messageengine.cpp pbap.cpp pbapmessage.cpp telephony.cpp telephonymessage.cpp weather.cpp weathermessage.cpp) -target_link_libraries(qtappfw Qt5::WebSockets) +target_link_libraries(qtappfw Qt5::WebSockets vcard) set_target_properties(qtappfw PROPERTIES VERSION ${PROJECT_VERSION} SOVERSION 1 @@ -14,17 +14,80 @@ * limitations under the License. */ +#include <QMetaEnum> +#include <QtQml/QQmlEngine> + +#include <vcard/vcard.h> + #include "message.h" #include "messageengine.h" #include "pbap.h" #include "pbapmessage.h" #include "responsemessage.h" -Pbap::Pbap (QUrl &url, QObject * parent) : +PhoneNumber::PhoneNumber(QString number, QString type) +{ + m_number = number; + m_type = stringToEnum(type); +} + +PhoneNumber::~PhoneNumber() +{ +} + +int PhoneNumber::stringToEnum(QString key) +{ + const QMetaObject* metaObject = PhoneNumber::metaObject(); + int enumIndex = metaObject->indexOfEnumerator("PhoneNumberType"); + QMetaEnum mEnum = metaObject->enumerator(enumIndex); + + int value = mEnum.keyToValue(key.toUtf8().data()); + return (value < 0) ? 0 : value; +} + +Contact::Contact(QString name, QList<QObject *>numbers) +{ + m_name = name; + m_numbers = numbers; +} + +Contact::~Contact() +{ +} + +RecentCall::RecentCall(QString name, QString number, QString datetime, QString type) +{ + m_name = name; + m_number = number; + m_datetime = datetime; + m_type = stringToEnum(type); +} + +RecentCall::~RecentCall() +{ +} + +int RecentCall::stringToEnum(QString key) +{ + const QMetaObject* metaObject = RecentCall::metaObject(); + int enumIndex = metaObject->indexOfEnumerator("RecentCallType"); + QMetaEnum mEnum = metaObject->enumerator(enumIndex); + + int value = mEnum.keyToValue(key.toUtf8().data()); + return (value < 0) ? 0 : value; +} + +Pbap::Pbap (QUrl &url, QQmlContext *context, QObject * parent) : QObject(parent), m_mloop(nullptr) { m_mloop = new MessageEngine(url); + m_context = context; + m_context->setContextProperty("RecentCallModel", QVariant::fromValue(m_calls)); + qmlRegisterUncreatableType<RecentCall>("RecentCall", 1, 0, "RecentCall", "Enum"); + + QObject::connect(m_mloop, &MessageEngine::connected, this, &Pbap::onConnected); + QObject::connect(m_mloop, &MessageEngine::disconnected, this, &Pbap::onDisconnected); QObject::connect(m_mloop, &MessageEngine::messageReceived, this, &Pbap::onMessageReceived); } @@ -33,6 +96,33 @@ Pbap::~Pbap() delete m_mloop; } +void Pbap::refreshContacts(int max_entries) +{ + PbapMessage *tmsg = new PbapMessage(); + QJsonObject parameter; + + if (max_entries >= 0) + parameter.insert("max_entries", max_entries); + + tmsg->createRequest("contacts", parameter); + m_mloop->sendMessage(tmsg); + tmsg->deleteLater(); +} + +void Pbap::refreshCalls(int max_entries) +{ + PbapMessage *tmsg = new PbapMessage(); + QJsonObject parameter; + + parameter.insert("list", "cch"); + if (max_entries >= 0) + parameter.insert("max_entries", max_entries); + + tmsg->createRequest("history", parameter); + m_mloop->sendMessage(tmsg); + tmsg->deleteLater(); +} + void Pbap::search(QString number) { PbapMessage *tmsg = new PbapMessage(); @@ -47,6 +137,94 @@ void Pbap::search(QString number) tmsg->deleteLater(); } +void Pbap::updateContacts(QString vcards) +{ + QString name, number, type; + + QList<vCard> contacts_vcards = vCard::fromByteArray(vcards.toUtf8()); + + for (auto vcard : contacts_vcards) { + vCardProperty name_prop = vcard.property(VC_FORMATTED_NAME); + QStringList values = name_prop.values(); + name = values.at(vCardProperty::DefaultValue); + /* + * libvcard has no member function to return a list of named + * properties, so we iterate over all properties and parse + * each identified VC_TELEPHONE property in the vCard. + */ + QList<QObject *> numbers; + vCardPropertyList properties = vcard.properties(); + for (auto property : properties) { + if (property.isValid() && (property.name() == VC_TELEPHONE)) { + QStringList values = property.values(); + number = values.at(0); + vCardParamList params = property.params(); + // The first parameter is always the phone number type + type = params.at(0).value(); + numbers.append(new PhoneNumber(number, type)); + } + } + m_contacts.append(new Contact(name, numbers)); + } + refreshCalls(100); +} + +#define VC_DATETIME "X-IRMC-CALL-DATETIME" + +void Pbap::updateCalls(QString vcards) +{ + QString name, number, datetime, type; + + QList<vCard> history_vcards = vCard::fromByteArray(vcards.toUtf8()); + + for (auto vcard : history_vcards) { + vCardProperty number_prop = vcard.property(VC_TELEPHONE); + if (number_prop.isValid()) { + QStringList values = number_prop.values(); + number = values.at(0); + } + vCardProperty name_prop = vcard.property(VC_FORMATTED_NAME); + QStringList values = name_prop.values(); + name = values.at(0); + // For calls with an empty name, fetch the name from contacts + if (name.isEmpty()) { + bool found = false; + for (auto contact_obj : m_contacts) { + Contact *contact = qobject_cast<Contact *>(contact_obj); + QList<QObject *>numbers = contact->numbers(); + for (auto number_obj : numbers) { + PhoneNumber *phone_number = qobject_cast<PhoneNumber *>(number_obj); + if (number.endsWith(phone_number->number())) { + name = contact->name(); + found = true; + break; + } + } + if (found == true) + break; + } + if (!found) + name = number; + } + vCardProperty datetime_prop = vcard.property(VC_DATETIME); + if (datetime_prop.isValid()) { + vCardParamList params = datetime_prop.params(); + QStringList values = datetime_prop.values(); + type = params.at(0).value(); + datetime = values.at(0); + // Convert the PBAP date/time to ISO 8601 format + datetime.insert(4, '-'); + datetime.insert(7, '-'); + datetime.insert(13, ':'); + datetime.insert(16, ':'); + } + m_calls.append(new RecentCall(name, number, datetime, type)); + } + + // Refresh model + m_context->setContextProperty("RecentCallModel", QVariant::fromValue(m_calls)); +} + void Pbap::sendSearchResults(QJsonArray results) { QString name; @@ -59,12 +237,55 @@ void Pbap::sendSearchResults(QJsonArray results) emit searchResults(name); } +void Pbap::onConnected() +{ + QStringListIterator eventIterator(events); + PbapMessage *tmsg; + + while (eventIterator.hasNext()) { + tmsg = new PbapMessage(); + QJsonObject parameter; + parameter.insert("value", eventIterator.next()); + tmsg->createRequest("subscribe", parameter); + m_mloop->sendMessage(tmsg); + tmsg->deleteLater(); + } +} + +void Pbap::onDisconnected() +{ + QStringListIterator eventIterator(events); + PbapMessage *tmsg; + + while (eventIterator.hasNext()) { + tmsg = new PbapMessage(); + QJsonObject parameter; + parameter.insert("value", eventIterator.next()); + tmsg->createRequest("unsubscribe", parameter); + m_mloop->sendMessage(tmsg); + tmsg->deleteLater(); + } +} + void Pbap::onMessageReceived(MessageType type, Message *msg) { - if (msg->isReply() && type == ResponseRequestMessage) { + if (msg->isEvent() && type == PbapEventMessage) { + PbapMessage *tmsg = qobject_cast<PbapMessage*>(msg); + + if (tmsg->isStatusEvent()) { + emit statusChanged(tmsg->connected()); + if (tmsg->connected() == true) { + refreshContacts(-1); + } + } + } else if (msg->isReply() && type == ResponseRequestMessage) { ResponseMessage *tmsg = qobject_cast<ResponseMessage*>(msg); - if (tmsg->requestVerb() == "search") { + if (tmsg->requestVerb() == "contacts") { + updateContacts(tmsg->replyData().value("vcards").toString()); + } else if (tmsg->requestVerb() == "history") { + updateCalls(tmsg->replyData().value("vcards").toString()); + } else if (tmsg->requestVerb() == "search") { sendSearchResults(tmsg->replyData().value("results").toArray()); } } @@ -20,28 +20,140 @@ #include <QDebug> #include <QObject> #include <QJsonArray> +#include <QtQml/QQmlContext> #include "messageengine.h" +class PhoneNumber : public QObject +{ + Q_OBJECT + + Q_PROPERTY(QString number READ number NOTIFY numberChanged) + Q_PROPERTY(int type READ type NOTIFY typeChanged) + + public: + explicit PhoneNumber(QString number, QString type); + virtual ~PhoneNumber(); + + QString number() {return m_number;}; + int type() {return m_type;}; + + enum PhoneNumberType { + UNKNOWN, + VOICE, + CELL, + HOME, + WORK, + FAX, + }; + Q_ENUM(PhoneNumberType) + + signals: + void numberChanged(); + void typeChanged(); + + private: + QString m_number; + int m_type; + int stringToEnum(QString); +}; + +class Contact : public QObject +{ + Q_OBJECT + + Q_PROPERTY(QString name READ name NOTIFY nameChanged) + Q_PROPERTY(QList<QObject *>numbers READ numbers NOTIFY numbersChanged) + + public: + explicit Contact(QString name, QList<QObject *>numbers); + virtual ~Contact(); + + QString name() {return m_name;}; + QList<QObject *>numbers() {return m_numbers;}; + + signals: + void nameChanged(); + void numbersChanged(); + + private: + QString m_name; + QList<QObject *>m_numbers; +}; + +class RecentCall : public QObject +{ + Q_OBJECT + + Q_PROPERTY(QString name READ name NOTIFY nameChanged) + Q_PROPERTY(QString number READ number NOTIFY numberChanged) + Q_PROPERTY(QString datetime READ datetime NOTIFY datetimeChanged) + Q_PROPERTY(int type READ type NOTIFY typeChanged) + + public: + explicit RecentCall(QString name, QString number, QString datetime, QString type); + virtual ~RecentCall(); + + QString name() {return m_name;}; + QString number() {return m_number;}; + QString datetime() {return m_datetime;}; + int type() {return m_type;}; + + enum RecentCallType { + UNKNOWN, + MISSED, + RECEIVED, + DIALED, + }; + Q_ENUM(RecentCallType) + + signals: + void nameChanged(); + void numberChanged(); + void datetimeChanged(); + void typeChanged(); + + private: + QString m_name; + QString m_number; + QString m_datetime; + int m_type; + int stringToEnum(QString); +}; + class Pbap : public QObject { Q_OBJECT public: - explicit Pbap(QUrl &url, QObject * parent = Q_NULLPTR); + explicit Pbap(QUrl &url, QQmlContext *context, QObject * parent = Q_NULLPTR); virtual ~Pbap(); + Q_INVOKABLE void refreshContacts(int max_entries); + Q_INVOKABLE void refreshCalls(int max_entries); Q_INVOKABLE void search(QString number); signals: - void searchResults(QString name); + void searchResults(QString name); + void statusChanged(bool connected); private: MessageEngine *m_mloop; + QQmlContext *m_context; + QList<QObject *>m_contacts; + QList<QObject *>m_calls; + void updateContacts(QString); + void updateCalls(QString); void sendSearchResults(QJsonArray); // slots + void onConnected(); + void onDisconnected(); void onMessageReceived(MessageType, Message*); + + const QStringList events { + "status", + }; }; #endif // PBAP_H diff --git a/pbapmessage.h b/pbapmessage.h index e3d78c2..65751cb 100644 --- a/pbapmessage.h +++ b/pbapmessage.h @@ -24,6 +24,8 @@ class PbapMessage : public Message Q_OBJECT public: bool createRequest(QString verb, QJsonObject parameter); + bool isStatusEvent() {return (this->eventName() == "status"); }; + bool connected() { return m_event_data.find("connected").value().toBool(); }; private: QStringList verbs { @@ -31,6 +33,9 @@ class PbapMessage : public Message "entry", "history", "search", + "status", + "subscribe", + "unsubscribe", }; }; |