summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMatt Porter <mporter@konsulko.com>2018-06-19 14:56:26 -0400
committerMatt Porter <mporter@konsulko.com>2018-06-22 16:38:30 -0400
commite8b328e0516d8ead9af13c8379a6cd1dbbb8a9e9 (patch)
treedec002e6adcf197c3ac7913fb211b65b73772bc9
parentfc65716b43af6be84d42599d813d3d40df07d376 (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.txt2
-rw-r--r--pbap.cpp227
-rw-r--r--pbap.h116
-rw-r--r--pbapmessage.h5
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
diff --git a/pbap.cpp b/pbap.cpp
index a833b9f..43db8bd 100644
--- a/pbap.cpp
+++ b/pbap.cpp
@@ -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());
}
}
diff --git a/pbap.h b/pbap.h
index f5e675b..2a5fb61 100644
--- a/pbap.h
+++ b/pbap.h
@@ -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",
};
};