From 5244f7909e04b23fbd5706dc6bcadafba21f7600 Mon Sep 17 00:00:00 2001 From: Javier Date: Sun, 16 Nov 2014 16:40:06 +0100 Subject: initial notification support --- endianhelpers.h | 27 ++++++++++ hfpag.cc | 6 +++ hfpag.h | 1 + main.cc | 11 ++++ notificationagent.cc | 57 +++++++++++++++++++++ notificationagent.h | 27 ++++++++++ notificationconn.cc | 139 +++++++++++++++++++++++++++++++++++++++++++++++++++ notificationconn.h | 71 ++++++++++++++++++++++++++ sapbtlistener.cc | 19 +++++-- sapbtlistener.h | 1 + sapd.pro | 9 +++- saprotocol.cc | 23 +-------- 12 files changed, 362 insertions(+), 29 deletions(-) create mode 100644 endianhelpers.h create mode 100644 notificationagent.cc create mode 100644 notificationagent.h create mode 100644 notificationconn.cc create mode 100644 notificationconn.h diff --git a/endianhelpers.h b/endianhelpers.h new file mode 100644 index 0000000..f11c669 --- /dev/null +++ b/endianhelpers.h @@ -0,0 +1,27 @@ +#ifndef ENDIANHELPERS_H +#define ENDIANHELPERS_H + +#include + +namespace +{ + +template +inline T read(const QByteArray &data, int &offset) +{ + T unswapped; + qMemCopy(&unswapped, &data.constData()[offset], sizeof(T)); // Unaligned access warning! + offset += sizeof(T); + return qFromBigEndian(unswapped); +} + +template +inline void append(QByteArray &data, const T &value) +{ + T swapped = qToBigEndian(value); + data.append(reinterpret_cast(&swapped), sizeof(T)); +} + +} + +#endif // ENDIANHELPERS_H diff --git a/hfpag.cc b/hfpag.cc index 1d84c8f..2d0abf4 100644 --- a/hfpag.cc +++ b/hfpag.cc @@ -7,9 +7,15 @@ HfpAg::HfpAg(const QBluetoothAddress &addr, QObject *parent) : connect(_socket, SIGNAL(connected()), SLOT(handleConnected())); connect(_socket, SIGNAL(disconnected()), SLOT(handleDisconnected())); connect(_socket, SIGNAL(readyRead()), SLOT(handleReadyRead())); + qDebug() << "Starting HFP connection to " << addr.toString(); _socket->connectToService(addr, 7); // HFP AG } +HfpAg::~HfpAg() +{ + qDebug() << "Destroying HFP"; +} + void HfpAg::send(const QString &cmd) { _socket->write("\r\n", 2); diff --git a/hfpag.h b/hfpag.h index 46e326c..e005c8a 100644 --- a/hfpag.h +++ b/hfpag.h @@ -10,6 +10,7 @@ class HfpAg : public QObject Q_OBJECT public: explicit HfpAg(const QBluetoothAddress &addr, QObject *parent = 0); + ~HfpAg(); private: void send(const QString& cmd); diff --git a/main.cc b/main.cc index fd042f3..b30e9b5 100644 --- a/main.cc +++ b/main.cc @@ -1,3 +1,4 @@ +#include #include #include #include "sapmanager.h" @@ -5,6 +6,10 @@ #include "capabilityagent.h" #include "hostmanageragent.h" +#include "notificationagent.h" + +using std::cerr; +using std::endl; int main(int argc, char *argv[]) { @@ -13,10 +18,16 @@ int main(int argc, char *argv[]) app.setOrganizationDomain("com.javispedro"); app.setApplicationName("sapd"); + if (app.arguments().size() != 2) { + cerr << "Usage:: sapd " << endl; + return EXIT_FAILURE; + } + SAPManager *manager = SAPManager::instance(); CapabilityAgent::registerServices(manager); HostManagerAgent::registerServices(manager); + NotificationAgent::registerServices(manager); QScopedPointer sap_listener(new SAPBTListener); diff --git a/notificationagent.cc b/notificationagent.cc new file mode 100644 index 0000000..70358d0 --- /dev/null +++ b/notificationagent.cc @@ -0,0 +1,57 @@ +#include "sapsocket.h" +#include "sapconnectionrequest.h" +#include "sapserviceinfo.h" +#include "sapchannelinfo.h" +#include "notificationconn.h" +#include "notificationagent.h" + +static NotificationAgent *agent = 0; +static const QLatin1String notification_profile("/system/NotificationService"); + +NotificationAgent::NotificationAgent(QObject *parent) + : QObject(parent), _peer(0), _socket(0) +{ +} + +NotificationAgent* NotificationAgent::instance() +{ + if (!agent) { + agent = new NotificationAgent; + } + return agent; +} + +void NotificationAgent::peerFound(SAPPeer *peer) +{ + qDebug() << "Notification peer found" << peer->peerName(); +} + +void NotificationAgent::requestConnection(SAPConnectionRequest *request) +{ + qDebug() << "Notification request connection from" << request->peer()->peerName(); + SAPConnection *conn = request->connection(); + new NotificationConn(conn, this); + + request->accept(); +} + +void NotificationAgent::registerServices(SAPManager *manager) +{ + SAPServiceInfo service; + SAPChannelInfo channel; + + service.setProfile(notification_profile); + service.setFriendlyName("Notification"); + service.setRole(SAPServiceInfo::RoleProvider); + service.setVersion(1, 0); + service.setConnectionTimeout(0); + + channel.setChannelId(104); + channel.setPayloadType(SAPChannelInfo::PayloadBinary); + channel.setQoSType(SAPChannelInfo::QoSReliabilityDisable); + channel.setQoSDataRate(SAPChannelInfo::QoSDataRateLow); + channel.setQoSPriority(SAPChannelInfo::QoSPriorityLow); + service.addChannel(channel); + + manager->registerServiceAgent(service, instance()); +} diff --git a/notificationagent.h b/notificationagent.h new file mode 100644 index 0000000..1b9c9cb --- /dev/null +++ b/notificationagent.h @@ -0,0 +1,27 @@ +#ifndef NOTIFICATIONAGENT_H +#define NOTIFICATIONAGENT_H + +#include +#include "sappeer.h" +#include "sapmanager.h" +#include "sapagent.h" + +class NotificationAgent : public QObject, public SAPAgent +{ + Q_OBJECT + + explicit NotificationAgent(QObject *parent = 0); + +public: + static NotificationAgent * instance(); + static void registerServices(SAPManager *manager); + + void peerFound(SAPPeer *peer); + void requestConnection(SAPConnectionRequest *request); + +private: + SAPPeer *_peer; + SAPSocket *_socket; +}; + +#endif // NOTIFICATIONAGENT_H diff --git a/notificationconn.cc b/notificationconn.cc new file mode 100644 index 0000000..fde3303 --- /dev/null +++ b/notificationconn.cc @@ -0,0 +1,139 @@ +#include +#include +#include + +#include "sappeer.h" +#include "endianhelpers.h" +#include "notificationconn.h" + +NotificationConn::Notification::Notification() + : type(NotificationPopup), sequenceNumber(0), urgent(false), + applicationId(0), category(0), + count(0), sender(0), + notificationId(-1) +{ +} + +NotificationConn::NotificationConn(SAPConnection *conn, QObject *parent) + : QObject(parent), _conn(conn), _socket(conn->getSocket(104)) +{ + connect(_conn, SIGNAL(disconnected()), SLOT(deleteLater())); + Q_ASSERT(_socket); + connect(_socket, SIGNAL(connected()), SLOT(handleConnected())); + connect(_socket, SIGNAL(messageReceived()), SLOT(handleMessageReceived())); +} + +QByteArray NotificationConn::packNotification(const Notification &n) +{ + QByteArray data; + + append(data, 0x10 | n.type); + append(data, n.sequenceNumber | (n.urgent ? 0x4000 : 0)); + + append(data, n.applicationId); + append(data, n.category); + + int attributeCount = 0; + + // Let's count attributes first + if (!n.packageName.isEmpty()) attributeCount++; + if (n.count >= 0) attributeCount++; + if (!n.title.isEmpty()) attributeCount++; + if (n.time.isValid()) attributeCount++; + if (n.sender >= 0) attributeCount++; + if (!n.body.isEmpty()) attributeCount++; + if (!n.thumbnail.isEmpty()) attributeCount++; + if (!n.applicationName.isEmpty()) attributeCount++; + //TODO if (n.openInHost) attributeCount++; + //if (n.openInWatch) attributeCount++; + if (n.notificationId != -1) attributeCount++; + + append(data, attributeCount); + + if (n.time.isValid()) { + append(data, (NotificationTime << 24) | sizeof(qint64)); + append(data, n.time.toMSecsSinceEpoch()); + } + if (!n.thumbnail.isEmpty()) { + append(data, (NotificationThumbnail << 24) | n.thumbnail.size()); + data.append(n.thumbnail); + } + if (!n.applicationName.isEmpty()) { + QByteArray str = n.applicationName.toUtf8(); + append(data, (NotificationApplicationName << 24) | str.length()); + data.append(str); + } + if (!n.packageName.isEmpty()) { + QByteArray str = n.packageName.toUtf8(); + append(data, (NotificationPackageName << 24) | str.length()); + data.append(str); + } + if (!n.title.isEmpty()) { + QByteArray str = n.title.toUtf8(); + append(data, (NotificationTitle << 24) | str.length()); + data.append(str); + } + if (!n.body.isEmpty()) { + QByteArray str = n.body.toUtf8(); + append(data, (NotificationBody << 24) | str.length()); + data.append(str); + } + if (n.count >= 0) { + append(data, (NotificationCount << 24) | sizeof(quint16)); + append(data, n.count); + } + if (n.sender >= 0) { + append(data, (NotificationSender << 24) | sizeof(quint16)); + append(data, n.sender); + } + if (n.notificationId != -1) { + append(data, (NotificationId << 24) | sizeof(quint32)); + append(data, n.notificationId); + } + + return data; +} + +void NotificationConn::handleConnected() +{ + qDebug() << "Manager socket now connected!"; + + QTimer::singleShot(2000, this, SLOT(performTest())); + +} + +void NotificationConn::handleMessageReceived() +{ + QByteArray data = _socket->receive(); + data.remove(0, 1); // Remove first null byte + + qDebug() << "Got notif msg" << data.toHex(); + + // TODO Seems that we receive the notification ID that we should act upon. +} + +void NotificationConn::performTest() +{ + qDebug() << "Performing notif test"; + + Notification n; + n.sequenceNumber = 1; + n.type = NotificationPopup; + n.time = QDateTime::currentDateTime(); + n.title = "A title"; + n.packageName = "com.example.package"; + n.applicationName = "Example package"; + n.body = "A long body"; + n.sender = 0; + n.count = 13; + n.category = 0; + n.applicationId = 0xc2d7; + n.notificationId = 1; + + QByteArray packet = packNotification(n); + packet.prepend('\0'); + + qDebug() << packet.toHex(); + + _socket->send(packet); +} diff --git a/notificationconn.h b/notificationconn.h new file mode 100644 index 0000000..9be3e20 --- /dev/null +++ b/notificationconn.h @@ -0,0 +1,71 @@ +#ifndef NOTIFICATIONCONN_H +#define NOTIFICATIONCONN_H + +#include +#include +#include "sapconnection.h" +#include "sapsocket.h" + +class NotificationConn : public QObject +{ + Q_OBJECT + +public: + NotificationConn(SAPConnection *conn, QObject *parent = 0); + +protected: + enum NotificationType { + NotificationPopup = 0 + }; + + enum NotificationAttributeType { + NotificationPackageName = 0, + NotificationCount = 1, + NotificationTitle = 2, + NotificationTime = 3, + NotificationSender = 4, + NotificationBody = 5, + NotificationThumbnail = 6, + NotificationApplicationName = 7, + NotificationOpenInWatch = 9, + NotificationOpenInHost = 10, + NotificationId = 11 + }; + + struct Notification { + Notification(); + + NotificationType type; + int sequenceNumber; + bool urgent; + int applicationId; + int category; + QString packageName; + int count; + QString title; + QDateTime time; + int sender; + QString body; + QByteArray thumbnail; + QString applicationName; + //bool openInWatch; + //bool openInHost; + int notificationId; + }; + + QByteArray packNotification(const Notification &n); + +private: + + +private slots: + void handleConnected(); + void handleMessageReceived(); + void performTest(); + +private: + SAPConnection *_conn; + SAPSocket *_socket; +}; + +#endif // NOTIFICATIONCONN_H diff --git a/sapbtlistener.cc b/sapbtlistener.cc index d5cd071..1b4550a 100644 --- a/sapbtlistener.cc +++ b/sapbtlistener.cc @@ -169,17 +169,18 @@ void SAPBTListener::nudge(const QBluetoothAddress &address) { QBluetoothSocket *socket = new QBluetoothSocket(QBluetoothSocket::RfcommSocket, this); + connect(socket, SIGNAL(connected()), socket, SLOT(deleteLater())); + connect(socket, SIGNAL(error(QBluetoothSocket::SocketError)), + this, SLOT(handleNudgeError(QBluetoothSocket::SocketError))); + qDebug() << "Nudging" << address.toString(); socket->connectToService(address, 2); //SAPProtocol::nudgeServiceUuid); #if 1 - HfpAg *ag = new HfpAg(address, this); - connect(socket, SIGNAL(disconnected()), ag, SLOT(deleteLater())); + // At the same time set up and HFP connection to the watch. + new HfpAg(address, this); #endif - - connect(socket, SIGNAL(connected()), socket, SLOT(deleteLater())); - connect(socket, SIGNAL(error(QBluetoothSocket::SocketError)), socket, SLOT(deleteLater())); } void SAPBTListener::acceptConnection() @@ -196,3 +197,11 @@ void SAPBTListener::acceptConnection() // TODO Why am I hardcoding the role here new SAPBTPeer(SAProtocol::ClientRole, socket, this); } + +void SAPBTListener::handleNudgeError(QBluetoothSocket::SocketError error) +{ + QBluetoothSocket *socket = static_cast(sender()); + qWarning() << "Cannot nudge:" << error << socket->errorString(); + socket->abort(); + socket->deleteLater(); +} diff --git a/sapbtlistener.h b/sapbtlistener.h index ee2fb7e..a59a5e1 100644 --- a/sapbtlistener.h +++ b/sapbtlistener.h @@ -32,6 +32,7 @@ private: private slots: void acceptConnection(); + void handleNudgeError(QBluetoothSocket::SocketError error); private: diff --git a/sapd.pro b/sapd.pro index 7b5a154..ee30305 100644 --- a/sapd.pro +++ b/sapd.pro @@ -48,7 +48,9 @@ SOURCES += main.cc \ sapconnectionrequest.cc \ capabilitypeer.cc \ hostmanageragent.cc \ - hostmanagerconn.cc + hostmanagerconn.cc \ + notificationagent.cc \ + notificationconn.cc HEADERS += \ sapbtlistener.h \ @@ -70,4 +72,7 @@ HEADERS += \ sapconnectionrequest.h \ capabilitypeer.h \ hostmanageragent.h \ - hostmanagerconn.h + hostmanagerconn.h \ + notificationagent.h \ + notificationconn.h \ + endianhelpers.h diff --git a/saprotocol.cc b/saprotocol.cc index 5a21762..541f0af 100644 --- a/saprotocol.cc +++ b/saprotocol.cc @@ -1,5 +1,5 @@ #include -#include +#include "endianhelpers.h" #include "saprotocol.h" const QBluetoothUuid SAProtocol::dataServiceUuid(QLatin1String("a49eb41e-cb06-495c-9f4f-aa80a90cdf4a")); @@ -7,27 +7,6 @@ const QBluetoothUuid SAProtocol::nudgeServiceUuid(QLatin1String("a49eb41e-cb06-4 const QLatin1String SAProtocol::capabilityDiscoveryProfile("/System/Reserved/ServiceCapabilityDiscovery"); -namespace -{ - -template -inline T read(const QByteArray &data, int &offset) -{ - T unswapped; - qMemCopy(&unswapped, &data.constData()[offset], sizeof(T)); // Unaligned access warning! - offset += sizeof(T); - return qFromBigEndian(unswapped); -} - -template -inline void append(QByteArray &data, const T &value) -{ - T swapped = qToBigEndian(value); - data.append(reinterpret_cast(&swapped), sizeof(T)); -} - -} - SAProtocol::SAProtocol() { } -- cgit v1.2.3