From 195efeebee1c60d8ecb73149f7911ec8214591a9 Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Mon, 16 Dec 2019 14:11:55 +0100 Subject: [PATCH] Improve Ubuntu Touch support (add push notification and clean up) --- QtZeroConf | 2 +- clickable.json | 6 +- libnymea-app-core/connection/awsclient.cpp | 4 +- libnymea-app-core/devicemanager.cpp | 2 +- libnymea-app-core/libnymea-app-core.pro | 5 + nymea-app.pro | 14 +- nymea-app/nymea-app.pro | 11 + .../ubports/pushclient.cpp | 208 ++++++ .../platformintegration/ubports/pushclient.h | 82 +++ nymea-app/pushnotifications.cpp | 12 +- nymea-app/pushnotifications.h | 10 + packaging/ubuntu/click/appicon.svg | 610 ++++++++++++++++++ .../ubuntu/click/manifest.json | 8 +- .../ubuntu/click/nymea-app.apparmor | 0 .../ubuntu/click/nymea-app.desktop | 0 packaging/ubuntu/click/push-apparmor.json | 7 + packaging/ubuntu/click/push.json | 4 + packaging/ubuntu/click/pushexec | 19 + 18 files changed, 992 insertions(+), 12 deletions(-) create mode 100644 nymea-app/platformintegration/ubports/pushclient.cpp create mode 100644 nymea-app/platformintegration/ubports/pushclient.h create mode 100644 packaging/ubuntu/click/appicon.svg rename manifest.json => packaging/ubuntu/click/manifest.json (75%) rename nymea-app.apparmor => packaging/ubuntu/click/nymea-app.apparmor (100%) rename nymea-app.desktop => packaging/ubuntu/click/nymea-app.desktop (100%) create mode 100644 packaging/ubuntu/click/push-apparmor.json create mode 100644 packaging/ubuntu/click/push.json create mode 100755 packaging/ubuntu/click/pushexec diff --git a/QtZeroConf b/QtZeroConf index 1e36e430..d3e03568 160000 --- a/QtZeroConf +++ b/QtZeroConf @@ -1 +1 @@ -Subproject commit 1e36e430e98ecec6e15ab06f56467567f22f9e8a +Subproject commit d3e03568a9f20ccd4f98f6a183d411a441f49482 diff --git a/clickable.json b/clickable.json index 78f3ffc8..882d82db 100644 --- a/clickable.json +++ b/clickable.json @@ -1,11 +1,13 @@ { "template": "qmake", "kill": "nymea-app", - "build_args": "CONFIG+=click CONFIG+=ubuntu", + "build_args": "CONFIG+=ubports", "dependencies_target": [ "libavahi-client-dev", "libavahi-common-dev", "libqt5charts5-dev", - "qml-module-qtcharts" + "qml-module-qtcharts", + "libconnectivity-qt1-dev", + "libunity-api-dev" ] } diff --git a/libnymea-app-core/connection/awsclient.cpp b/libnymea-app-core/connection/awsclient.cpp index 2892f3b1..48eedd15 100644 --- a/libnymea-app-core/connection/awsclient.cpp +++ b/libnymea-app-core/connection/awsclient.cpp @@ -56,6 +56,8 @@ AWSClient::AWSClient(QObject *parent) : QObject(parent), QString pushSystem = "GCM"; #elif defined Q_OS_IOS QString pushSystem = "APNS"; +#elif UBPORTS + QString pushSystem = "UBPORTS"; #else QString pushSystem = ""; #endif @@ -645,7 +647,7 @@ void AWSClient::registerPushNotificationEndpoint(const QString ®istrationId, QJsonDocument jsonDoc = QJsonDocument::fromVariant(payload); - qDebug() << "Registering push notification endpoint"; + qDebug() << "Registering push notification endpoint" << qUtf8Printable(QJsonDocument::fromVariant(payload).toJson()); // qDebug() << "POST" << url.toString(); // qDebug() << "HEADERS:"; // foreach (const QByteArray &hdr, request.rawHeaderList()) { diff --git a/libnymea-app-core/devicemanager.cpp b/libnymea-app-core/devicemanager.cpp index 325b099b..01f67de4 100644 --- a/libnymea-app-core/devicemanager.cpp +++ b/libnymea-app-core/devicemanager.cpp @@ -213,7 +213,7 @@ void DeviceManager::getVendorsResponse(const QVariantMap ¶ms) void DeviceManager::getSupportedDevicesResponse(const QVariantMap ¶ms) { - qDebug() << "DeviceClass received:" << qUtf8Printable(QJsonDocument::fromVariant(params).toJson(QJsonDocument::Indented)); +// qDebug() << "DeviceClass received:" << qUtf8Printable(QJsonDocument::fromVariant(params).toJson(QJsonDocument::Indented)); if (params.value("params").toMap().keys().contains("deviceClasses")) { QVariantList deviceClassList = params.value("params").toMap().value("deviceClasses").toList(); foreach (QVariant deviceClassVariant, deviceClassList) { diff --git a/libnymea-app-core/libnymea-app-core.pro b/libnymea-app-core/libnymea-app-core.pro index 309dcbe8..beccd059 100644 --- a/libnymea-app-core/libnymea-app-core.pro +++ b/libnymea-app-core/libnymea-app-core.pro @@ -146,3 +146,8 @@ HEADERS += \ configuration/mqttpolicies.h \ models/devicemodel.h \ system/systemcontroller.h + + +ubports: { + DEFINES += UBPORTS +} diff --git a/nymea-app.pro b/nymea-app.pro index c9c5675e..1c9a1a0f 100644 --- a/nymea-app.pro +++ b/nymea-app.pro @@ -85,9 +85,17 @@ desktopfile.path = /usr/share/applications/ INSTALLS += desktopfile } -click: { -ubuntu_files.path += / -ubuntu_files.files += manifest.json nymea-app.apparmor nymea-app.desktop packaging/android/appicon.svg +ubports: { +ubuntu_files.path = / +ubuntu_files.files += \ + packaging/ubuntu/click/manifest.json \ + packaging/ubuntu/click/nymea-app.apparmor \ + packaging/ubuntu/click/nymea-app.desktop \ + packaging/ubuntu/click/appicon.svg \ + packaging/ubuntu/click/push.json \ + packaging/ubuntu/click/push-apparmor.json \ + packaging/ubuntu/click/pushexec + INSTALLS += ubuntu_files } diff --git a/nymea-app/nymea-app.pro b/nymea-app/nymea-app.pro index fe9b360f..ed2116d8 100644 --- a/nymea-app/nymea-app.pro +++ b/nymea-app/nymea-app.pro @@ -129,6 +129,17 @@ ios: { QMAKE_MAC_XCODE_SETTINGS += IOS_ENTITLEMENTS } +ubports: { + DEFINES += UBPORTS + + CONFIG += link_pkgconfig + PKGCONFIG += connectivity-qt1 + + HEADERS += platformintegration/ubports/pushclient.h + + SOURCES += platformintegration/ubports/pushclient.cpp +} + BR=$$BRANDING !equals(BR, "") { DEFINES += BRANDING=\\\"$${BR}\\\" diff --git a/nymea-app/platformintegration/ubports/pushclient.cpp b/nymea-app/platformintegration/ubports/pushclient.cpp new file mode 100644 index 00000000..9ea8d44d --- /dev/null +++ b/nymea-app/platformintegration/ubports/pushclient.cpp @@ -0,0 +1,208 @@ +/* +Copyright 2014 Canonical Ltd. + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License, version 3 +as published by the Free Software Foundation. + +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 Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this program. If not, see +. +*/ + +#include "pushclient.h" +#include +#include +#include +#include +#include + +#define PUSH_SERVICE "com.ubuntu.PushNotifications" +#define POSTAL_SERVICE "com.ubuntu.Postal" +#define PUSH_PATH "/com/ubuntu/PushNotifications" +#define POSTAL_PATH "/com/ubuntu/Postal" +#define PUSH_IFACE "com.ubuntu.PushNotifications" +#define POSTAL_IFACE "com.ubuntu.Postal" + +PushClient::PushClient(QObject *parent) : + QObject(parent), + ns(new connectivityqt::Connectivity(QDBusConnection::sessionBus(), this)) +{ +} + +void PushClient::setAppId(const QString &appId) { + if (appId == this->appId || appId.isEmpty()) + return; + + this->appId = appId; + emit appIdChanged(appId); + + if (ns->online()) { + registerApp(); + } else { + disconnect(ns.data(), 0, this, 0); + connect(ns.data(), &connectivityqt::Connectivity::onlineUpdated, this, &PushClient::connectionStatusChanged); + } +} + +void PushClient::connectionStatusChanged(bool status) +{ + if (status) { + disconnect(ns.data(), 0, this, 0); + registerApp(); + } +} + +void PushClient::registerApp() +{ + if (appId.isEmpty()) + return; + + pkgname = appId.split("_").at(0); + pkgname = pkgname.replace(".","_2e").replace("-","_2d"); + + QString register_path(PUSH_PATH); + register_path += "/" + pkgname; + + QDBusConnection bus = QDBusConnection::sessionBus(); + + // Register to the push client + QDBusMessage message = QDBusMessage::createMethodCall(PUSH_SERVICE, register_path , PUSH_IFACE, "Register"); + message << appId; + QDBusPendingCall pcall = bus.asyncCall(message); + QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pcall, this); + connect(watcher, &QDBusPendingCallWatcher::finished, this, &PushClient::registerFinished); + + // Connect to the notification signal + QString postal_path(POSTAL_PATH); + postal_path += "/" + pkgname; + bus.connect(POSTAL_SERVICE, postal_path, POSTAL_IFACE, "Post", "s", this, SLOT(notified(QString))); +} + +void PushClient::registerFinished(QDBusPendingCallWatcher *watcher) { + QDBusPendingReply reply = *watcher; + if (reply.isError()) { + status = reply.error().message(); + emit statusChanged(status); + // This has to be delayed because the error signal is not connected yet + QTimer::singleShot(200, this, &PushClient::emitError); + } + else { + this->token = reply.value(); + // Do an initial fetch + QTimer::singleShot(200, this, &PushClient::getNotifications); + emit tokenChanged(this->token); + } + watcher->deleteLater(); +} + +QString PushClient::getAppId() { + return appId; +} + +QString PushClient::getToken() { + return token; +} + +void PushClient::emitError() +{ + emit error(status); +} + +void PushClient::notified(const QString &) +{ + this->getNotifications(); +} + +void PushClient::getNotifications() { + QDBusConnection bus = QDBusConnection::sessionBus(); + QString path(POSTAL_PATH); + path += "/" + pkgname; + QDBusMessage message = QDBusMessage::createMethodCall(POSTAL_SERVICE, path, POSTAL_IFACE, "PopAll"); + message << this->appId; + QDBusPendingCall pcall = bus.asyncCall(message); + QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pcall, this); + connect(watcher, &QDBusPendingCallWatcher::finished,this, &PushClient::popAllFinished); +} + +void PushClient::popAllFinished(QDBusPendingCallWatcher *watcher) { + QDBusPendingReply reply = *watcher; + if (reply.isError()) { + emit error(reply.error().message()); + } + else { + emit notificationsChanged(reply.value()); + } + watcher->deleteLater(); +} + +QStringList PushClient::getPersistent() { + // FIXME: this is blocking, but making it async would change the API + QDBusConnection bus = QDBusConnection::sessionBus(); + QString path(POSTAL_PATH); + path += "/" + pkgname; + QDBusMessage message = QDBusMessage::createMethodCall(POSTAL_SERVICE, path, POSTAL_IFACE, "ListPersistent"); + message << this->appId; + QDBusMessage reply = bus.call(message); + if (reply.type() == QDBusMessage::ErrorMessage) { + emit error(reply.errorMessage()); + } + return reply.arguments()[0].toStringList(); +} + +void PushClient::clearPersistent(const QStringList &tags) { + QDBusConnection bus = QDBusConnection::sessionBus(); + QString path(POSTAL_PATH); + path += "/" + pkgname; + QDBusMessage message = QDBusMessage::createMethodCall(POSTAL_SERVICE, path, POSTAL_IFACE, "ClearPersistent"); + message << this->appId; + for (int i = 0; i < tags.size(); ++i) { + message << tags.at(i); + } + QDBusPendingCall pcall = bus.asyncCall(message); + QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pcall, this); + connect(watcher, &QDBusPendingCallWatcher::finished, this, &PushClient::clearPersistentFinished); +} + +void PushClient::clearPersistentFinished(QDBusPendingCallWatcher *watcher) { + QDBusPendingReply reply = *watcher; + + if (reply.isError()) { + emit error(reply.error().message()); + } else { + // FIXME: this is blocking + emit persistentChanged(getPersistent()); + } +} + +void PushClient::setCount(int count) { + QDBusConnection bus = QDBusConnection::sessionBus(); + QString path(POSTAL_PATH); + bool visible = count != 0; + counter = count; + path += "/" + pkgname; + QDBusMessage message = QDBusMessage::createMethodCall(POSTAL_SERVICE, path, POSTAL_IFACE, "SetCounter"); + message << this->appId << count << visible; + QDBusPendingCall pcall = bus.asyncCall(message); + QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pcall, this); + connect(watcher, &QDBusPendingCallWatcher::finished, this, &PushClient::setCounterFinished); +} + +void PushClient::setCounterFinished(QDBusPendingCallWatcher *watcher) { + QDBusPendingReply reply = *watcher; + if (reply.isError()) { + emit error(reply.error().message()); + } + else { + emit countChanged(counter); + } +} + +int PushClient::getCount() { + return counter; +} diff --git a/nymea-app/platformintegration/ubports/pushclient.h b/nymea-app/platformintegration/ubports/pushclient.h new file mode 100644 index 00000000..044a6502 --- /dev/null +++ b/nymea-app/platformintegration/ubports/pushclient.h @@ -0,0 +1,82 @@ +/* +Copyright 2014 Canonical Ltd. + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License, version 3 +as published by the Free Software Foundation. + +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 Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this program. If not, see +. +*/ + +#ifndef PUSHCLIENT_H +#define PUSHCLIENT_H + +#include +#include +#include +#include + +class QDBusPendingCallWatcher; + +class PushClient : public QObject +{ + Q_OBJECT +public: + explicit PushClient(QObject *parent = 0); + void setAppId(const QString &appid); + QString getStatus() {return this->status;} + QString getAppId(); + QString getToken(); + QStringList getPersistent(); + void setCount(int count); + int getCount(); + + Q_PROPERTY(QString appId WRITE setAppId READ getAppId NOTIFY appIdChanged) + Q_PROPERTY(QString token READ getToken NOTIFY tokenChanged) + Q_PROPERTY(QStringList notifications MEMBER notifications NOTIFY notificationsChanged) + Q_PROPERTY(QString status READ getStatus NOTIFY statusChanged) + Q_PROPERTY(QStringList persistent READ getPersistent NOTIFY persistentChanged) + Q_PROPERTY(int count READ getCount WRITE setCount NOTIFY countChanged) + +signals: + void countChanged(int count); + void notificationsChanged(const QStringList ¬ifications); + void persistentChanged(const QStringList &tags); + void appIdChanged(const QString &appId); + void error(const QString &error); + void tokenChanged(const QString &token); + void statusChanged(const QString &status); + +public slots: + void getNotifications(); + void notified(const QString &appId); + void emitError(); + void clearPersistent(const QStringList &tags); + +private slots: + void registerFinished(QDBusPendingCallWatcher *watcher); + void popAllFinished(QDBusPendingCallWatcher *watcher); + void setCounterFinished(QDBusPendingCallWatcher *watcher); + void clearPersistentFinished(QDBusPendingCallWatcher *watcher); + void connectionStatusChanged(bool status); + +private: + void registerApp(); + + QScopedPointer ns; + QString appId; + QString pkgname; + QString token; + QString status; + QStringList notifications; + int counter; +}; + +#endif // PUSHCLIENT_H diff --git a/nymea-app/pushnotifications.cpp b/nymea-app/pushnotifications.cpp index c02384c4..6151d69b 100644 --- a/nymea-app/pushnotifications.cpp +++ b/nymea-app/pushnotifications.cpp @@ -6,13 +6,21 @@ #include #include #include -#endif - static PushNotifications *m_client_pointer; +#endif PushNotifications::PushNotifications(QObject *parent) : QObject(parent) { connectClient(); + +#ifdef UBPORTS + m_pushClient = new PushClient(this); + m_pushClient->setAppId("io.guh.nymeaapp_nymea-app"); + connect(m_pushClient, &PushClient::tokenChanged, this, [this](const QString &token) { + m_token = token; + emit tokenChanged(); + }); +#endif } QObject *PushNotifications::pushNotificationsProvider(QQmlEngine *engine, QJSEngine *scriptEngine) diff --git a/nymea-app/pushnotifications.h b/nymea-app/pushnotifications.h index b32996e7..b82a2eab 100644 --- a/nymea-app/pushnotifications.h +++ b/nymea-app/pushnotifications.h @@ -8,6 +8,11 @@ #include "firebase/app.h" #include "firebase/messaging.h" #include "firebase/util.h" + +#elif UBPORTS + +#include "platformintegration/ubports/pushclient.h" + #endif #ifdef Q_OS_ANDROID @@ -44,6 +49,11 @@ protected: private: ::firebase::App *m_firebaseApp = nullptr; ::firebase::ModuleInitializer m_firebase_initializer; + +#elif UBPORTS + + PushClient *m_pushClient = nullptr; + #endif private: diff --git a/packaging/ubuntu/click/appicon.svg b/packaging/ubuntu/click/appicon.svg new file mode 100644 index 00000000..7f1ddd65 --- /dev/null +++ b/packaging/ubuntu/click/appicon.svg @@ -0,0 +1,610 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/manifest.json b/packaging/ubuntu/click/manifest.json similarity index 75% rename from manifest.json rename to packaging/ubuntu/click/manifest.json index ab7716eb..b032d75c 100644 --- a/manifest.json +++ b/packaging/ubuntu/click/manifest.json @@ -7,9 +7,13 @@ "nymea-app": { "apparmor": "nymea-app.apparmor", "desktop": "nymea-app.desktop" + }, + "push": { + "apparmor": "push-apparmor.json", + "push-helper": "push.json" } }, - "version": "developer-build", + "version": "0", "maintainer": "Michael Zanetti ", - "framework" : "ubuntu-sdk-15.04.5" + "framework" : "ubuntu-sdk-16.04" } diff --git a/nymea-app.apparmor b/packaging/ubuntu/click/nymea-app.apparmor similarity index 100% rename from nymea-app.apparmor rename to packaging/ubuntu/click/nymea-app.apparmor diff --git a/nymea-app.desktop b/packaging/ubuntu/click/nymea-app.desktop similarity index 100% rename from nymea-app.desktop rename to packaging/ubuntu/click/nymea-app.desktop diff --git a/packaging/ubuntu/click/push-apparmor.json b/packaging/ubuntu/click/push-apparmor.json new file mode 100644 index 00000000..af147848 --- /dev/null +++ b/packaging/ubuntu/click/push-apparmor.json @@ -0,0 +1,7 @@ +{ + "template": "ubuntu-push-helper", + "policy_groups": [ + "push-notification-client" + ], + "policy_version": 16.04 +} diff --git a/packaging/ubuntu/click/push.json b/packaging/ubuntu/click/push.json new file mode 100644 index 00000000..d9a9e170 --- /dev/null +++ b/packaging/ubuntu/click/push.json @@ -0,0 +1,4 @@ +{ + "exec": "pushexec" +} + diff --git a/packaging/ubuntu/click/pushexec b/packaging/ubuntu/click/pushexec new file mode 100755 index 00000000..5de224cf --- /dev/null +++ b/packaging/ubuntu/click/pushexec @@ -0,0 +1,19 @@ +#!/usr/bin/python3 + +import os +import sys +import json + +f1, f2 = sys.argv[1:3] + +payloadJson = json.load(open(f1)) + +dir_path = os.path.dirname(os.path.realpath(__file__)) +payloadJson["notification"]["card"]["icon"] = dir_path + "/appicon.svg" + +payloadJson["notification"]["card"]["actions"] = ["appid://io.guh.nymeaapp/nymea-app/current-user-version"] +#payloadJson["notification"]["emblem-counter"] = {"count": 1, "visible": True } + +print(payloadJson) +open(f2, "w").write(json.dumps(payloadJson) + "\n") +