From b3f9fcecdcf5f73ac902d76b95739b76e6bfcba1 Mon Sep 17 00:00:00 2001 From: Andrew Branson Date: Wed, 10 Feb 2016 00:15:20 +0100 Subject: V3 firmware support improvements Proper timeline notifications for the v3 firmware. Added telegram, whatapp and hangouts notification types. Removed mitakuuluu. --- app/pebble.desktop | 2 +- app/pebblefirmware.cpp | 18 +- app/qml/pages/ManagerPage.qml | 622 +++++++++++---------- daemon/daemon.pro | 6 +- daemon/manager.cpp | 1185 ++++++++++++++++++++-------------------- daemon/manager.h | 317 +++++------ daemon/notificationmanager.cpp | 120 ++-- daemon/notificationmanager.h | 3 + daemon/settings.h | 170 +++--- daemon/timelineitem.cpp | 144 +++++ daemon/timelineitem.h | 194 +++++++ daemon/watchconnector.cpp | 136 ++--- daemon/watchconnector.h | 610 +++++++++++---------- 13 files changed, 1985 insertions(+), 1542 deletions(-) create mode 100644 daemon/timelineitem.cpp create mode 100644 daemon/timelineitem.h diff --git a/app/pebble.desktop b/app/pebble.desktop index c7dd6c3..881941b 100644 --- a/app/pebble.desktop +++ b/app/pebble.desktop @@ -4,4 +4,4 @@ X-Nemo-Application-Type=silica-qt5 Name=Pebble Icon=pebble Exec=pebble %U -MimeType=application/zip; +MimeType=application/x-pebble-pkg; diff --git a/app/pebblefirmware.cpp b/app/pebblefirmware.cpp index 33460e5..e3bbc5a 100644 --- a/app/pebblefirmware.cpp +++ b/app/pebblefirmware.cpp @@ -16,16 +16,28 @@ PebbleFirmware::PebbleFirmware(QObject *parent) : void PebbleFirmware::updateLatest(QString hw) { QNetworkRequest req; - req.setUrl(firmwareURL.arg(hw).arg(hw.startsWith("snowy_") ? "release-v3" : "release-v2")); + req.setUrl(firmwareURL.arg(hw).arg("release-v3.8")); req.setRawHeader("Cache-Control", "no-cache"); qDebug() << "Getting latest firmware" << req.url(); nm->get(req); } -void PebbleFirmware::fetchFirmware(QString type) +void PebbleFirmware::fetchFirmware(QString currVer) { + QJsonObject targetFirmware; + if (_latest.contains("3.x-migration") && currVer < "v3.0.0") { + targetFirmware = _latest.value("3.x-migration").toObject(); + } else if (currVer >= "v3.0.0" && + _latest.value("normal").toObject().value("friendlyVersion").toString() > currVer){ + targetFirmware = _latest.value("normal").toObject(); + } + + if (targetFirmware.isEmpty()) { + qDebug() << "Watch firmware is up to date"; + return; + } QNetworkRequest req; - req.setUrl(_latest.value(type).toObject().value("url").toString()); + req.setUrl(targetFirmware.value("url").toString()); req.setRawHeader("Cache-Control", "no-cache"); qDebug() << "Fetching firmware" << req.url(); nm->get(req); diff --git a/app/qml/pages/ManagerPage.qml b/app/qml/pages/ManagerPage.qml index 8ff0db2..af5a6b2 100644 --- a/app/qml/pages/ManagerPage.qml +++ b/app/qml/pages/ManagerPage.qml @@ -1,301 +1,321 @@ -import QtQuick 2.0 -import QtQml 2.1 -import Sailfish.Silica 1.0 -import org.nemomobile.configuration 1.0 -import org.nemomobile.dbus 2.0 - -Page { - id: page - - ConfigurationGroup { - id: settings - path: "/org/pebbled/settings" - property string profileWhenConnected: "" - property string profileWhenDisconnected: "" - property bool transliterateMessage - property bool useSystemVolume - property bool incomingCallNotification - property bool notificationsCommhistoryd - property bool notificationsMissedCall - property bool notificationsEmails - property bool notificationsMitakuuluu - property bool notificationsTwitter - property bool notificationsFacebook - property bool notificationsOther - property bool notificationsAll - } - - DBusInterface { - id: profiled - - service: 'com.nokia.profiled' - iface: 'com.nokia.profiled' - path: '/com/nokia/profiled' - - property var profiles - } - - Component.onCompleted: { - profiled.typedCall('get_profiles', [], function (result) { - console.log('Got profiles: ' + result); - profiled.profiles = result; - }); - } - - - - SilicaFlickable { - id: flickable - anchors.fill: parent - contentHeight: column.height - - VerticalScrollDecorator { flickable: flickable } - - PullDownMenu { - MenuItem { - text: qsTr("Pebble Appstore") - onClicked: pageStack.push(Qt.resolvedUrl("AppStorePage.qml")) - } - MenuItem { - text: qsTr("About") - onClicked: pageStack.push(Qt.resolvedUrl("AboutPage.qml")) - } - } - - Column { - id: column - - width: page.width - spacing: Theme.paddingLarge - PageHeader { - title: qsTr("Pebble Manager") - } - - Label { - color: Theme.highlightColor - font.pixelSize: Theme.fontSizeSmall - visible: pebbled.active && !pebbled.connected - text: qsTr("Waiting for watch...\nIf it can't be found please check it's available and paired in Bluetooth settings.") - wrapMode: Text.Wrap - anchors { - left: parent.left - right: parent.right - margins: Theme.paddingLarge - } - - } - Button { - visible: pebbled.connected - text: pebbled.name - anchors { - left: parent.left - right: parent.right - margins: Theme.paddingLarge - } - onClicked: pageStack.push(Qt.resolvedUrl("WatchPage.qml")) - } - - Label { - text: qsTr("Service") - font.family: Theme.fontFamilyHeading - color: Theme.highlightColor - anchors.right: parent.right - anchors.rightMargin: Theme.paddingMedium - } - TextSwitch { - text: qsTr("Enabled") - description: pebbled.enabled ? qsTr("Automatic startup") : qsTr("Manual startup") - checked: pebbled.enabled - automaticCheck: false - onClicked: pebbled.setEnabled(!checked) - } - TextSwitch { - text: qsTr("Active") - description: pebbled.active ? qsTr("Running") : qsTr("Dead") - checked: pebbled.active - automaticCheck: false - onClicked: pebbled.setActive(!checked) - } - TextSwitch { - text: qsTr("Connection") - description: pebbled.connected ? qsTr("Connected"): qsTr("Disconnected") - checked: pebbled.connected - automaticCheck: false - onClicked: { - if (pebbled.connected) { - pebbled.disconnect(); - } else { - pebbled.reconnect(); - } - } - } - - Label { - text: qsTr("Settings") - font.family: Theme.fontFamilyHeading - color: Theme.highlightColor - anchors.right: parent.right - anchors.rightMargin: Theme.paddingMedium - } - TextSwitch { - text: qsTr("Forward phone calls") - checked: settings.incomingCallNotification - automaticCheck: false - onClicked: { - settings.incomingCallNotification = !settings.incomingCallNotification; - } - } - TextSwitch { - text: qsTr("Control main volume") - description: qsTr("Pebble music volume buttons change the main phone volume directly instead of through the music player") - checked: settings.useSystemVolume - automaticCheck: true - onClicked: { - settings.useSystemVolume = !settings.useSystemVolume; - } - } - TextSwitch { - text: qsTr("Transliterate messages") - description: qsTr("Messages are transliterated to ASCII before sending to Pebble") - checked: settings.transliterateMessage - automaticCheck: false - onClicked: { - settings.transliterateMessage = !settings.transliterateMessage; - } - } - - Label { - text: qsTr("Notifications") - font.family: Theme.fontFamilyHeading - color: Theme.highlightColor - anchors.right: parent.right - anchors.rightMargin: Theme.paddingMedium - } - - TextSwitch { - text: qsTr("Messaging") - description: qsTr("SMS and IM") - checked: settings.notificationsCommhistoryd - automaticCheck: false - onClicked: { - settings.notificationsCommhistoryd = !settings.notificationsCommhistoryd; - } - } - - TextSwitch { - text: qsTr("Missed call") - checked: settings.notificationsMissedCall - automaticCheck: false - onClicked: { - settings.notificationsMissedCall = !settings.notificationsMissedCall; - } - } - - TextSwitch { - text: qsTr("Emails") - checked: settings.notificationsEmails - automaticCheck: false - onClicked: { - settings.notificationsEmails = !settings.notificationsEmails; - } - } - - TextSwitch { - text: qsTr("Mitakuuluu") - checked: settings.notificationsMitakuuluu - automaticCheck: false - onClicked: { - settings.notificationsMitakuuluu = !settings.notificationsMitakuuluu; - } - } - - TextSwitch { - text: qsTr("Twitter") - checked: settings.notificationsTwitter - automaticCheck: false - onClicked: { - settings.notificationsTwitter = !settings.notificationsTwitter; - } - } - - TextSwitch { - visible: false //not yet supported - text: qsTr("Facebook") - checked: settings.notificationsFacebook - automaticCheck: false - onClicked: { - settings.notificationsFacebook = !settings.notificationsFacebook; - } - } - - TextSwitch { - text: qsTr("Other notifications") - checked: settings.notificationsOther - automaticCheck: false - onClicked: { - settings.notificationsOther = !settings.notificationsOther; - } - } - - TextSwitch { - text: qsTr("All notifications") - checked: settings.notificationsAll - automaticCheck: false - enabled: settings.notificationsOther - onClicked: { - settings.notificationsAll = !settings.notificationsAll; - } - } - - Label { - text: qsTr("Profiles") - font.family: Theme.fontFamilyHeading - color: Theme.highlightColor - anchors.right: parent.right - anchors.rightMargin: Theme.paddingMedium - } - - ComboBox { - label: qsTr("Connected") - menu: ContextMenu { - MenuItem { - text: qsTr("no change") - font.capitalization: Font.SmallCaps - } - Repeater { - model: profiled.profiles - delegate: MenuItem { - text: modelData - down: modelData === settings.profileWhenConnected - } - } - } - value: settings.profileWhenConnected || qsTr("no change") - onCurrentIndexChanged: { - settings.profileWhenConnected = currentIndex ? currentItem.text : "" - } - } - - ComboBox { - label: qsTr("Disconnected") - menu: ContextMenu { - MenuItem { - text: qsTr("no change") - font.capitalization: Font.SmallCaps - } - Repeater { - model: profiled.profiles - delegate: MenuItem { - text: modelData - down: modelData === settings.profileWhenDisconnected - } - } - } - value: settings.profileWhenDisconnected || qsTr("no change") - onCurrentIndexChanged: { - settings.profileWhenDisconnected = currentIndex ? currentItem.text : "" - } - } - } - } -} +import QtQuick 2.0 +import QtQml 2.1 +import Sailfish.Silica 1.0 +import org.nemomobile.configuration 1.0 +import org.nemomobile.dbus 2.0 + +Page { + id: page + + ConfigurationGroup { + id: settings + path: "/org/pebbled/settings" + property string profileWhenConnected: "" + property string profileWhenDisconnected: "" + property bool transliterateMessage + property bool useSystemVolume + property bool incomingCallNotification + property bool notificationsCommhistoryd + property bool notificationsMissedCall + property bool notificationsEmails + property bool notificationsTwitter + property bool notificationsFacebook + property bool notificationsTelegram + property bool notificationsHangouts + property bool notificationsWhatsapp + property bool notificationsOther + property bool notificationsAll + } + + DBusInterface { + id: profiled + + service: 'com.nokia.profiled' + iface: 'com.nokia.profiled' + path: '/com/nokia/profiled' + + property var profiles + } + + Component.onCompleted: { + profiled.typedCall('get_profiles', [], function (result) { + console.log('Got profiles: ' + result); + profiled.profiles = result; + }); + } + + + + SilicaFlickable { + id: flickable + anchors.fill: parent + contentHeight: column.height + + VerticalScrollDecorator { flickable: flickable } + + PullDownMenu { + MenuItem { + text: qsTr("Pebble Appstore") + onClicked: pageStack.push(Qt.resolvedUrl("AppStorePage.qml")) + } + MenuItem { + text: qsTr("About") + onClicked: pageStack.push(Qt.resolvedUrl("AboutPage.qml")) + } + } + + Column { + id: column + + width: page.width + spacing: Theme.paddingLarge + PageHeader { + title: qsTr("Pebble Manager") + } + + Label { + color: Theme.highlightColor + font.pixelSize: Theme.fontSizeSmall + visible: pebbled.active && !pebbled.connected + text: qsTr("Waiting for watch...\nIf it can't be found please check it's available and paired in Bluetooth settings.") + wrapMode: Text.Wrap + anchors { + left: parent.left + right: parent.right + margins: Theme.paddingLarge + } + + } + Button { + visible: pebbled.connected + text: pebbled.name + anchors { + left: parent.left + right: parent.right + margins: Theme.paddingLarge + } + onClicked: pageStack.push(Qt.resolvedUrl("WatchPage.qml")) + } + + Label { + text: qsTr("Service") + font.family: Theme.fontFamilyHeading + color: Theme.highlightColor + anchors.right: parent.right + anchors.rightMargin: Theme.paddingMedium + } + TextSwitch { + text: qsTr("Enabled") + description: pebbled.enabled ? qsTr("Automatic startup") : qsTr("Manual startup") + checked: pebbled.enabled + automaticCheck: false + onClicked: pebbled.setEnabled(!checked) + } + TextSwitch { + text: qsTr("Active") + description: pebbled.active ? qsTr("Running") : qsTr("Dead") + checked: pebbled.active + automaticCheck: false + onClicked: pebbled.setActive(!checked) + } + TextSwitch { + text: qsTr("Connection") + description: pebbled.connected ? qsTr("Connected"): qsTr("Disconnected") + checked: pebbled.connected + automaticCheck: false + onClicked: { + if (pebbled.connected) { + pebbled.disconnect(); + } else { + pebbled.reconnect(); + } + } + } + + Label { + text: qsTr("Settings") + font.family: Theme.fontFamilyHeading + color: Theme.highlightColor + anchors.right: parent.right + anchors.rightMargin: Theme.paddingMedium + } + TextSwitch { + text: qsTr("Forward phone calls") + checked: settings.incomingCallNotification + automaticCheck: false + onClicked: { + settings.incomingCallNotification = !settings.incomingCallNotification; + } + } + TextSwitch { + text: qsTr("Control main volume") + description: qsTr("Pebble music volume buttons change the main phone volume directly instead of through the music player") + checked: settings.useSystemVolume + automaticCheck: true + onClicked: { + settings.useSystemVolume = !settings.useSystemVolume; + } + } + TextSwitch { + text: qsTr("Transliterate messages") + description: qsTr("Messages are transliterated to ASCII before sending to Pebble") + checked: settings.transliterateMessage + automaticCheck: false + onClicked: { + settings.transliterateMessage = !settings.transliterateMessage; + } + } + + Label { + text: qsTr("Notifications") + font.family: Theme.fontFamilyHeading + color: Theme.highlightColor + anchors.right: parent.right + anchors.rightMargin: Theme.paddingMedium + } + + TextSwitch { + text: qsTr("Messaging") + description: qsTr("SMS and IM") + checked: settings.notificationsCommhistoryd + automaticCheck: false + onClicked: { + settings.notificationsCommhistoryd = !settings.notificationsCommhistoryd; + } + } + + TextSwitch { + text: qsTr("Missed call") + checked: settings.notificationsMissedCall + automaticCheck: false + onClicked: { + settings.notificationsMissedCall = !settings.notificationsMissedCall; + } + } + + TextSwitch { + text: qsTr("Emails") + checked: settings.notificationsEmails + automaticCheck: false + onClicked: { + settings.notificationsEmails = !settings.notificationsEmails; + } + } + + TextSwitch { + text: qsTr("Telegram") + checked: settings.notificationsTelegram + automaticCheck: false + onClicked: { + settings.notificationsTelegram = !settings.notificationsTelegram; + } + } + + TextSwitch { + text: qsTr("Whatsapp") + checked: settings.notificationsWhatsapp + automaticCheck: false + onClicked: { + settings.notificationsWhatsapp = !settings.notificationsWhatsapp; + } + } + + TextSwitch { + text: qsTr("Hangouts") + checked: settings.notificationsHangouts + automaticCheck: false + onClicked: { + settings.notificationsHangouts = !settings.notificationsHangouts; + } + } + + TextSwitch { + text: qsTr("Twitter") + checked: settings.notificationsTwitter + automaticCheck: false + onClicked: { + settings.notificationsTwitter = !settings.notificationsTwitter; + } + } + + TextSwitch { + visible: false //not yet supported + text: qsTr("Facebook") + checked: settings.notificationsFacebook + automaticCheck: false + onClicked: { + settings.notificationsFacebook = !settings.notificationsFacebook; + } + } + + TextSwitch { + text: qsTr("Other notifications") + checked: settings.notificationsOther + automaticCheck: false + onClicked: { + settings.notificationsOther = !settings.notificationsOther; + } + } + + TextSwitch { + text: qsTr("All notifications") + checked: settings.notificationsAll + automaticCheck: false + enabled: settings.notificationsOther + onClicked: { + settings.notificationsAll = !settings.notificationsAll; + } + } + + Label { + text: qsTr("Profiles") + font.family: Theme.fontFamilyHeading + color: Theme.highlightColor + anchors.right: parent.right + anchors.rightMargin: Theme.paddingMedium + } + + ComboBox { + label: qsTr("Connected") + menu: ContextMenu { + MenuItem { + text: qsTr("no change") + font.capitalization: Font.SmallCaps + } + Repeater { + model: profiled.profiles + delegate: MenuItem { + text: modelData + down: modelData === settings.profileWhenConnected + } + } + } + value: settings.profileWhenConnected || qsTr("no change") + onCurrentIndexChanged: { + settings.profileWhenConnected = currentIndex ? currentItem.text : "" + } + } + + ComboBox { + label: qsTr("Disconnected") + menu: ContextMenu { + MenuItem { + text: qsTr("no change") + font.capitalization: Font.SmallCaps + } + Repeater { + model: profiled.profiles + delegate: MenuItem { + text: modelData + down: modelData === settings.profileWhenDisconnected + } + } + } + value: settings.profileWhenDisconnected || qsTr("no change") + onCurrentIndexChanged: { + settings.profileWhenDisconnected = currentIndex ? currentItem.text : "" + } + } + } + } +} diff --git a/daemon/daemon.pro b/daemon/daemon.pro index 4520b57..9bdc643 100644 --- a/daemon/daemon.pro +++ b/daemon/daemon.pro @@ -27,7 +27,8 @@ SOURCES += \ packer.cpp \ bankmanager.cpp \ uploadmanager.cpp \ - bundle.cpp + bundle.cpp \ + timelineitem.cpp HEADERS += \ manager.h \ @@ -47,7 +48,8 @@ HEADERS += \ packer.h \ bankmanager.h \ uploadmanager.h \ - bundle.h + bundle.h \ + timelineitem.h DBUS_ADAPTORS += ../org.pebbled.Watch.xml diff --git a/daemon/manager.cpp b/daemon/manager.cpp index 37625e3..4d6a525 100644 --- a/daemon/manager.cpp +++ b/daemon/manager.cpp @@ -1,578 +1,607 @@ -#include -#include -#include - -#include "manager.h" -#include "watch_adaptor.h" -#include "bundle.h" - -Manager::Manager(Settings *settings, QObject *parent) : - QObject(parent), l(metaObject()->className()), settings(settings), - proxy(new PebbledProxy(this)), - watch(new WatchConnector(this)), - upload(new UploadManager(watch, this)), - apps(new AppManager(this)), - bank(new BankManager(watch, upload, apps, this)), - voice(new VoiceCallManager(settings, this)), - notifications(new NotificationManager(settings, this)), - music(new MusicManager(watch, settings, this)), - datalog(new DataLogManager(watch, this)), - appmsg(new AppMsgManager(apps, watch, this)), - js(new JSKitManager(watch, apps, appmsg, settings, this)), - notification(MNotification::DeviceEvent) -{ - connect(settings, SIGNAL(valueChanged(QString)), SLOT(onSettingChanged(const QString&))); - connect(settings, SIGNAL(valuesChanged()), SLOT(onSettingsChanged())); - - // We don't need to handle presence changes, so report them separately and ignore them - QMap parameters; - parameters.insert(QString::fromLatin1("mergePresenceChanges"), QString::fromLatin1("false")); - contacts = new QContactManager("", parameters, this); - - numberFilter.setDetailType(QContactDetail::TypePhoneNumber, QContactPhoneNumber::FieldNumber); - numberFilter.setMatchFlags(QContactFilter::MatchPhoneNumber); - - connect(watch, SIGNAL(connectedChanged()), SLOT(onConnectedChanged())); - watch->setEndpointHandler(WatchConnector::watchPHONE_VERSION, - [this](const QByteArray& data) { - Q_UNUSED(data); - watch->sendPhoneVersion(); - return true; - }); - watch->setEndpointHandler(WatchConnector::watchPHONE_CONTROL, - [this](const QByteArray &data) { - if (data.at(0) == WatchConnector::callHANGUP) { - voice->hangupAll(); - } - return true; - }); - - connect(voice, SIGNAL(activeVoiceCallChanged()), SLOT(onActiveVoiceCallChanged())); - connect(voice, SIGNAL(error(const QString &)), SLOT(onVoiceError(const QString &))); - - connect(notifications, SIGNAL(error(const QString &)), SLOT(onNotifyError(const QString &))); - connect(notifications, SIGNAL(emailNotify(const QString &,const QString &,const QString &)), SLOT(onEmailNotify(const QString &,const QString &,const QString &))); - connect(notifications, SIGNAL(smsNotify(const QString &,const QString &)), SLOT(onSmsNotify(const QString &,const QString &))); - connect(notifications, SIGNAL(twitterNotify(const QString &,const QString &)), SLOT(onTwitterNotify(const QString &,const QString &))); - connect(notifications, SIGNAL(facebookNotify(const QString &,const QString &)), SLOT(onFacebookNotify(const QString &,const QString &))); - - connect(appmsg, &AppMsgManager::appStarted, this, &Manager::onAppOpened); - connect(appmsg, &AppMsgManager::appStopped, this, &Manager::onAppClosed); - - connect(js, &JSKitManager::appNotification, this, &Manager::onAppNotification); - - QDBusConnection session = QDBusConnection::sessionBus(); - new WatchAdaptor(proxy); - session.registerObject("/org/pebbled/Watch", proxy); - session.registerService("org.pebbled"); - - connect(watch, &WatchConnector::pebbleChanged, proxy, &PebbledProxy::NameChanged); - connect(watch, &WatchConnector::pebbleChanged, proxy, &PebbledProxy::AddressChanged); - connect(watch, &WatchConnector::connectedChanged, proxy, &PebbledProxy::ConnectedChanged); - connect(watch, &WatchConnector::versionsChanged, proxy, &PebbledProxy::InfoChanged); - connect(bank, &BankManager::slotsChanged, proxy, &PebbledProxy::AppSlotsChanged); - connect(apps, &AppManager::appsChanged, proxy, &PebbledProxy::AllAppsChanged); - - connect(watch, SIGNAL(connectedChanged()), SLOT(applyProfile())); - - // Set BT icon for notification - notification.setImage("icon-system-bluetooth-device"); - - watch->findPebbles(); -} - -Manager::~Manager() -{ -} - -void Manager::onSettingChanged(const QString &key) -{ - qCDebug(l) << __FUNCTION__ << key << ":" << settings->property(qPrintable(key)); -} - -void Manager::onSettingsChanged() -{ - qCWarning(l) << __FUNCTION__ << "Not implemented!"; -} - -void Manager::onConnectedChanged() -{ - QString message = QString("%1 %2") - .arg(watch->name().isEmpty() ? "Pebble" : watch->name()) - .arg(watch->isConnected() ? "connected" : "disconnected"); - qCDebug(l) << message; - - if (notification.isPublished()) notification.remove(); - - notification.setBody(message); - if (!notification.publish()) { - qCDebug(l) << "Failed publishing notification"; - } -} - -void Manager::onActiveVoiceCallChanged() -{ - qCDebug(l) << "Manager::onActiveVoiceCallChanged()"; - - if (!settings->property("incomingCallNotification").toBool()) { - qCDebug(l) << "Ignoring ActiveVoiceCallChanged because of setting!"; - return; - } - - VoiceCallHandler* handler = voice->activeVoiceCall(); - if (handler) { - connect(handler, SIGNAL(statusChanged()), SLOT(onActiveVoiceCallStatusChanged())); - connect(handler, SIGNAL(destroyed()), SLOT(onActiveVoiceCallStatusChanged())); - if (handler->status()) onActiveVoiceCallStatusChanged(); - } -} - -void Manager::onActiveVoiceCallStatusChanged() -{ - VoiceCallHandler* handler = voice->activeVoiceCall(); - if (!handler) { - qCDebug(l) << "ActiveVoiceCall destroyed"; - watch->endPhoneCall(); - return; - } - - qCDebug(l) << "handlerId:" << handler->handlerId() - << "providerId:" << handler->providerId() - << "status:" << handler->status() - << "statusText:" << handler->statusText() - << "lineId:" << handler->lineId() - << "incoming:" << handler->isIncoming(); - - if (!watch->isConnected()) { - qCDebug(l) << "Watch is not connected"; - return; - } - - switch ((VoiceCallHandler::VoiceCallStatus)handler->status()) { - case VoiceCallHandler::STATUS_ALERTING: - case VoiceCallHandler::STATUS_DIALING: - qCDebug(l) << "Tell outgoing:" << handler->lineId(); - watch->ring(handler->lineId(), findPersonByNumber(handler->lineId()), false); - break; - case VoiceCallHandler::STATUS_INCOMING: - case VoiceCallHandler::STATUS_WAITING: - qCDebug(l) << "Tell incoming:" << handler->lineId(); - watch->ring(handler->lineId(), findPersonByNumber(handler->lineId())); - break; - case VoiceCallHandler::STATUS_NULL: - case VoiceCallHandler::STATUS_DISCONNECTED: - qCDebug(l) << "Endphone"; - watch->endPhoneCall(); - break; - case VoiceCallHandler::STATUS_ACTIVE: - qCDebug(l) << "Startphone"; - watch->startPhoneCall(); - break; - case VoiceCallHandler::STATUS_HELD: - break; - } -} - -QString Manager::findPersonByNumber(QString number) -{ - QString person; - numberFilter.setValue(number); - - const QList &found = contacts->contacts(numberFilter); - if (found.size() == 1) { - person = found[0].detail(QContactDetail::TypeDisplayLabel).value(0).toString(); - } - - if (settings->property("transliterateMessage").toBool()) { - transliterateMessage(person); - } - return person; -} - -void Manager::onVoiceError(const QString &message) -{ - qCCritical(l) << "Error:" << message; -} - - -void Manager::onNotifyError(const QString &message) -{ - qWarning() << "Error:" << message; -} - -void Manager::onSmsNotify(const QString &sender, const QString &data) -{ - if (settings->property("transliterateMessage").toBool()) { - transliterateMessage(sender); - transliterateMessage(data); - } - watch->sendSMSNotification(sender, data); -} - -void Manager::onTwitterNotify(const QString &sender, const QString &data) -{ - if (settings->property("transliterateMessage").toBool()) { - transliterateMessage(sender); - transliterateMessage(data); - } - watch->sendTwitterNotification(sender, data); -} - - -void Manager::onFacebookNotify(const QString &sender, const QString &data) -{ - if (settings->property("transliterateMessage").toBool()) { - transliterateMessage(sender); - transliterateMessage(data); - } - watch->sendFacebookNotification(sender, data); -} - - -void Manager::onEmailNotify(const QString &sender, const QString &data,const QString &subject) -{ - if (settings->property("transliterateMessage").toBool()) { - transliterateMessage(sender); - transliterateMessage(data); - transliterateMessage(subject); - } - watch->sendEmailNotification(sender, data, subject); -} - -void Manager::applyProfile() -{ - QString newProfile = settings->property( - watch->isConnected() ? "profileWhenConnected" - : "profileWhenDisconnected").toString(); - if (!newProfile.isEmpty()) { - QDBusReply res = QDBusConnection::sessionBus().call( - QDBusMessage::createMethodCall("com.nokia.profiled", "/com/nokia/profiled", "com.nokia.profiled", "set_profile") - << newProfile); - if (res.isValid()) { - if (!res.value()) { - qCCritical(l) << "Unable to set profile" << newProfile; - } - } - else { - qCCritical(l) << res.error().message(); - } - } -} - -void Manager::ping(uint val) -{ - qCDebug(l) << "ping" << val; - - if (settings->property("debug").toBool()) { - // magic here! - // I do not want to add specific debugging methods to pebbled - // so just provide some magic Ping() method handling here :-) - switch (val) { - case 128: - watch->sendSMSNotification("SMS", "lorem ipsum"); - return; - case 129: - watch->sendEmailNotification("e-mail", "lorem ipsum", "subject"); - return; - case 130: - watch->sendFacebookNotification("Facebook", "lorem ipsum"); - return; - case 131: - watch->sendTwitterNotification("Twitter", "lorem ipsum"); - return; - case 132: - watch->sendMusicNowPlaying("artist", "album", "track name"); - return; - } - } - - watch->ping(val); -} - -void Manager::transliterateMessage(const QString &text) -{ - if (transliterator.isNull()) { - UErrorCode status = U_ZERO_ERROR; - transliterator.reset(icu::Transliterator::createInstance(icu::UnicodeString::fromUTF8("Any-Latin; Latin-ASCII"),UTRANS_FORWARD, status)); - if (U_FAILURE(status)) { - qCWarning(l) << "Error creaing ICU Transliterator \"Any-Latin; Latin-ASCII\":" << u_errorName(status); - } - } - if (!transliterator.isNull()) { - qCDebug(l) << "String before transliteration:" << text; - - icu::UnicodeString uword = icu::UnicodeString::fromUTF8(text.toStdString()); - transliterator->transliterate(uword); - - std::string translited; - uword.toUTF8String(translited); - - const_cast(text) = QString::fromStdString(translited); - qCDebug(l) << "String after transliteration:" << text; - } -} - -void Manager::onAppNotification(const QUuid &uuid, const QString &title, const QString &body) -{ - Q_UNUSED(uuid); - watch->sendSMSNotification(title, body); -} - -void Manager::onAppOpened(const QUuid &uuid) -{ - currentAppUuid = uuid; - emit proxy->AppUuidChanged(); - emit proxy->AppOpened(uuid.toString()); -} - -void Manager::onAppClosed(const QUuid &uuid) -{ - currentAppUuid = QUuid(); - emit proxy->AppClosed(uuid.toString()); - emit proxy->AppUuidChanged(); -} - -bool Manager::uploadFirmware(bool recovery, const QString &file) -{ - qCDebug(l) << "about to upload" << (recovery ? "recovery" : "normal") - << "firmware:" << file; - - Bundle bundle(Bundle::fromPath(file)); - if (!bundle.isValid()) { - qCWarning(l) << file << "is invalid bundle"; - return false; - } - - if (!bundle.fileExists(Bundle::FIRMWARE) || !bundle.fileExists(Bundle::RESOURCES)) { - qCWarning(l) << file << "is missing binary or resource"; - return false; - } - - watch->systemMessage(WatchConnector::systemFIRMWARE_START, - [this, recovery, bundle](const QByteArray &data) { - qCDebug(l) << "got response to firmware start" << data.toHex(); - - QSharedPointer resourceFile(bundle.openFile(Bundle::RESOURCES)); - if (!resourceFile) { - qCWarning(l) << "failed to open" << bundle.path() << "resource"; - watch->systemMessage(WatchConnector::systemFIRMWARE_FAIL); - return true; - } - - upload->uploadFirmwareResources(resourceFile.data(), bundle.crcFile(Bundle::RESOURCES), - [this, recovery, bundle, resourceFile]() { - qCDebug(l) << "firmware resource upload succesful"; - resourceFile->close(); - // Proceed to upload the resource file - QSharedPointer binaryFile(bundle.openFile(Bundle::FIRMWARE)); - if (binaryFile) { - upload->uploadFirmwareBinary(recovery, binaryFile.data(), bundle.crcFile(Bundle::FIRMWARE), - [this, binaryFile]() { - binaryFile->close(); - qCDebug(l) << "firmware binary upload succesful"; - // Upload succesful - watch->systemMessage(WatchConnector::systemFIRMWARE_COMPLETE); - }, [this, binaryFile](int code) { - binaryFile->close(); - qCWarning(l) << "firmware binary upload failed" << code; - watch->systemMessage(WatchConnector::systemFIRMWARE_FAIL); - }); - } else { - qCWarning(l) << "failed to open" << bundle.path() << "binary"; - watch->systemMessage(WatchConnector::systemFIRMWARE_FAIL); - } - }, [this, resourceFile](int code) { - resourceFile->close(); - qCWarning(l) << "firmware resource upload failed" << code; - watch->systemMessage(WatchConnector::systemFIRMWARE_FAIL); - }); - - return true; - }); - - return true; -} - -QStringList PebbledProxy::AppSlots() const -{ - const int num_slots = manager()->bank->numSlots(); - QStringList l; - l.reserve(num_slots); - - for (int i = 0; i < num_slots; ++i) { - if (manager()->bank->isUsed(i)) { - QUuid uuid = manager()->bank->appAt(i); - l.append(uuid.toString()); - } else { - l.append(QString()); - } - } - - Q_ASSERT(l.size() == num_slots); - - return l; -} - -QVariantList PebbledProxy::AllApps() const -{ - QList uuids = manager()->apps->appUuids(); - QVariantList l; - - foreach (const QUuid &uuid, uuids) { - AppInfo info = manager()->apps->info(uuid); - QVariantMap m; - m.insert("local", QVariant::fromValue(info.isLocal())); - m.insert("uuid", QVariant::fromValue(info.uuid().toString())); - m.insert("short-name", QVariant::fromValue(info.shortName())); - m.insert("long-name", QVariant::fromValue(info.longName())); - m.insert("company-name", QVariant::fromValue(info.companyName())); - m.insert("version-label", QVariant::fromValue(info.versionLabel())); - m.insert("is-watchface", QVariant::fromValue(info.isWatchface())); - m.insert("configurable", QVariant::fromValue(info.capabilities().testFlag(AppInfo::Capability::Configurable))); - m.insert("path", QVariant::fromValue(info.path())); - - if (!info.getMenuIconImage().isNull()) { - m.insert("menu-icon", QVariant::fromValue(info.getMenuIconPng())); - } - - l.append(QVariant::fromValue(m)); - } - - return l; -} - -bool PebbledProxy::SendAppMessage(const QString &uuid, const QVariantMap &data) -{ - Q_ASSERT(calledFromDBus()); - const QDBusMessage msg = message(); - setDelayedReply(true); - manager()->appmsg->send(uuid, data, [this, msg]() { - QDBusMessage reply = msg.createReply(QVariant::fromValue(true)); - this->connection().send(reply); - }, [this, msg]() { - QDBusMessage reply = msg.createReply(QVariant::fromValue(false)); - this->connection().send(reply); - }); - return false; // D-Bus clients should never see this reply. -} - -QString PebbledProxy::StartAppConfiguration(const QString &uuid) -{ - Q_ASSERT(calledFromDBus()); - const QDBusMessage msg = message(); - QDBusConnection conn = connection(); - - if (manager()->currentAppUuid != uuid) { - qCWarning(l) << "Called StartAppConfiguration but the uuid" << uuid << "is not running"; - sendErrorReply(msg.interface() + ".Error.AppNotRunning", - "The requested app is not currently opened in the watch"); - return QString(); - } - - if (!manager()->js->isJSKitAppRunning()) { - qCWarning(l) << "Called StartAppConfiguration but the uuid" << uuid << "is not a JS app"; - sendErrorReply(msg.interface() + ".Error.JSNotActive", - "The requested app is not a PebbleKit JS application"); - return QString(); - } - - // After calling showConfiguration() on the script, - // it will (eventually!) return a URL to us via the appOpenUrl signal. - - // So we can't send the D-Bus reply right now. - setDelayedReply(true); - - // Set up a signal handler to catch the appOpenUrl signal. - QMetaObject::Connection *c = new QMetaObject::Connection; - *c = connect(manager()->js, &JSKitManager::appOpenUrl, - this, [this,conn,msg,c](const QUrl &url) { - // Workaround: due to a GCC crash we can't capture the uuid parameter, but we can extract - // it again from the original message arguments. - // Suspect GCC bug# is 59195, 61233, or 61321. - // TODO Possibly fixed in 4.9.0 - const QString uuid = msg.arguments().at(0).toString(); - - if (manager()->currentAppUuid != uuid) { - // App was changed while we were waiting for the script.. - QDBusMessage reply = msg.createErrorReply(msg.interface() + ".Error.AppNotRunning", - "The requested app is not currently opened in the watch"); - conn.send(reply); - } else { - QDBusMessage reply = msg.createReply(QVariant::fromValue(url.toString())); - conn.send(reply); - } - - disconnect(*c); - delete c; - }); - - // TODO: JS script may fail, never call OpenURL, or something like that - // In those cases we WILL leak the above connection. - // (at least until the next appOpenURL event comes in) - // So we need to also set a timeout or similar. - - manager()->js->showConfiguration(); - - // Note that the above signal handler _might_ have been already called by this point. - - return QString(); // This return value should never be used. -} - -void PebbledProxy::SendAppConfigurationData(const QString &uuid, const QString &data) -{ - Q_ASSERT(calledFromDBus()); - const QDBusMessage msg = message(); - - if (manager()->currentAppUuid != uuid) { - sendErrorReply(msg.interface() + ".Error.AppNotRunning", - "The requested app is not currently opened in the watch"); - return; - } - - if (!manager()->js->isJSKitAppRunning()) { - sendErrorReply(msg.interface() + ".Error.JSNotActive", - "The requested app is not a PebbleKit JS application"); - return; - } - - manager()->js->handleWebviewClosed(data); -} - -void PebbledProxy::UnloadApp(int slot) -{ - Q_ASSERT(calledFromDBus()); - const QDBusMessage msg = message(); - - if (!manager()->bank->unloadApp(slot)) { - sendErrorReply(msg.interface() + ".Error.CannotUnload", - "Cannot unload application"); - } -} - -void PebbledProxy::UploadApp(const QString &uuid, int slot) -{ - Q_ASSERT(calledFromDBus()); - const QDBusMessage msg = message(); - - if (!manager()->bank->uploadApp(QUuid(uuid), slot)) { - sendErrorReply(msg.interface() + ".Error.CannotUpload", - "Cannot upload application"); - } -} - -void PebbledProxy::NotifyFirmware(bool ok) -{ - Q_ASSERT(calledFromDBus()); - manager()->watch->sendFirmwareState(ok); -} - -void PebbledProxy::UploadFirmware(bool recovery, const QString &file) -{ - Q_ASSERT(calledFromDBus()); - const QDBusMessage msg = message(); - - if (!manager()->uploadFirmware(recovery, file)) { - sendErrorReply(msg.interface() + ".Error.CannotUpload", - "Cannot upload firmware"); - } -} +#include +#include +#include + +#include "manager.h" +#include "watch_adaptor.h" +#include "bundle.h" + +Manager::Manager(Settings *settings, QObject *parent) : + QObject(parent), l(metaObject()->className()), settings(settings), + proxy(new PebbledProxy(this)), + watch(new WatchConnector(this)), + upload(new UploadManager(watch, this)), + apps(new AppManager(this)), + bank(new BankManager(watch, upload, apps, this)), + voice(new VoiceCallManager(settings, this)), + notifications(new NotificationManager(settings, this)), + music(new MusicManager(watch, settings, this)), + datalog(new DataLogManager(watch, this)), + appmsg(new AppMsgManager(apps, watch, this)), + js(new JSKitManager(watch, apps, appmsg, settings, this)), + notification(MNotification::DeviceEvent) +{ + connect(settings, SIGNAL(valueChanged(QString)), SLOT(onSettingChanged(const QString&))); + connect(settings, SIGNAL(valuesChanged()), SLOT(onSettingsChanged())); + + // We don't need to handle presence changes, so report them separately and ignore them + QMap parameters; + parameters.insert(QString::fromLatin1("mergePresenceChanges"), QString::fromLatin1("false")); + contacts = new QContactManager("", parameters, this); + + numberFilter.setDetailType(QContactDetail::TypePhoneNumber, QContactPhoneNumber::FieldNumber); + numberFilter.setMatchFlags(QContactFilter::MatchPhoneNumber); + + connect(watch, SIGNAL(connectedChanged()), SLOT(onConnectedChanged())); + watch->setEndpointHandler(WatchConnector::watchPHONE_VERSION, + [this](const QByteArray& data) { + Q_UNUSED(data); + watch->sendPhoneVersion(); + return true; + }); + watch->setEndpointHandler(WatchConnector::watchPHONE_CONTROL, + [this](const QByteArray &data) { + if (data.at(0) == WatchConnector::callHANGUP) { + voice->hangupAll(); + } + return true; + }); + + connect(voice, SIGNAL(activeVoiceCallChanged()), SLOT(onActiveVoiceCallChanged())); + connect(voice, SIGNAL(error(const QString &)), SLOT(onVoiceError(const QString &))); + + connect(notifications, SIGNAL(error(const QString &)), SLOT(onNotifyError(const QString &))); + connect(notifications, SIGNAL(emailNotify(const QString &,const QString &,const QString &)), SLOT(onEmailNotify(const QString &,const QString &,const QString &))); + connect(notifications, SIGNAL(smsNotify(const QString &,const QString &)), SLOT(onSmsNotify(const QString &,const QString &))); + connect(notifications, SIGNAL(twitterNotify(const QString &,const QString &)), SLOT(onTwitterNotify(const QString &,const QString &))); + connect(notifications, SIGNAL(facebookNotify(const QString &,const QString &)), SLOT(onFacebookNotify(const QString &,const QString &))); + connect(notifications, SIGNAL(telegramNotify(const QString &,const QString &)), SLOT(onTelegramNotify(const QString &,const QString &))); + connect(notifications, SIGNAL(hangoutsNotify(const QString &,const QString &)), SLOT(onHangoutsNotify(const QString &,const QString &))); + connect(notifications, SIGNAL(whatappNotify(const QString &,const QString &)), SLOT(onWhatsappNotify(const QString &,const QString &))); + + connect(appmsg, &AppMsgManager::appStarted, this, &Manager::onAppOpened); + connect(appmsg, &AppMsgManager::appStopped, this, &Manager::onAppClosed); + + connect(js, &JSKitManager::appNotification, this, &Manager::onAppNotification); + + QDBusConnection session = QDBusConnection::sessionBus(); + new WatchAdaptor(proxy); + session.registerObject("/org/pebbled/Watch", proxy); + session.registerService("org.pebbled"); + + connect(watch, &WatchConnector::pebbleChanged, proxy, &PebbledProxy::NameChanged); + connect(watch, &WatchConnector::pebbleChanged, proxy, &PebbledProxy::AddressChanged); + connect(watch, &WatchConnector::connectedChanged, proxy, &PebbledProxy::ConnectedChanged); + connect(watch, &WatchConnector::versionsChanged, proxy, &PebbledProxy::InfoChanged); + connect(bank, &BankManager::slotsChanged, proxy, &PebbledProxy::AppSlotsChanged); + connect(apps, &AppManager::appsChanged, proxy, &PebbledProxy::AllAppsChanged); + + connect(watch, SIGNAL(connectedChanged()), SLOT(applyProfile())); + + // Set BT icon for notification + notification.setImage("icon-system-bluetooth-device"); + + watch->findPebbles(); +} + +Manager::~Manager() +{ +} + +void Manager::onSettingChanged(const QString &key) +{ + qCDebug(l) << __FUNCTION__ << key << ":" << settings->property(qPrintable(key)); +} + +void Manager::onSettingsChanged() +{ + qCWarning(l) << __FUNCTION__ << "Not implemented!"; +} + +void Manager::onConnectedChanged() +{ + QString message = QString("%1 %2") + .arg(watch->name().isEmpty() ? "Pebble" : watch->name()) + .arg(watch->isConnected() ? "connected" : "disconnected"); + qCDebug(l) << message; + + if (notification.isPublished()) notification.remove(); + + notification.setBody(message); + if (!notification.publish()) { + qCDebug(l) << "Failed publishing notification"; + } +} + +void Manager::onActiveVoiceCallChanged() +{ + qCDebug(l) << "Manager::onActiveVoiceCallChanged()"; + + if (!settings->property("incomingCallNotification").toBool()) { + qCDebug(l) << "Ignoring ActiveVoiceCallChanged because of setting!"; + return; + } + + VoiceCallHandler* handler = voice->activeVoiceCall(); + if (handler) { + connect(handler, SIGNAL(statusChanged()), SLOT(onActiveVoiceCallStatusChanged())); + connect(handler, SIGNAL(destroyed()), SLOT(onActiveVoiceCallStatusChanged())); + if (handler->status()) onActiveVoiceCallStatusChanged(); + } +} + +void Manager::onActiveVoiceCallStatusChanged() +{ + VoiceCallHandler* handler = voice->activeVoiceCall(); + if (!handler) { + qCDebug(l) << "ActiveVoiceCall destroyed"; + watch->endPhoneCall(); + return; + } + + qCDebug(l) << "handlerId:" << handler->handlerId() + << "providerId:" << handler->providerId() + << "status:" << handler->status() + << "statusText:" << handler->statusText() + << "lineId:" << handler->lineId() + << "incoming:" << handler->isIncoming(); + + if (!watch->isConnected()) { + qCDebug(l) << "Watch is not connected"; + return; + } + + switch ((VoiceCallHandler::VoiceCallStatus)handler->status()) { + case VoiceCallHandler::STATUS_ALERTING: + case VoiceCallHandler::STATUS_DIALING: + qCDebug(l) << "Tell outgoing:" << handler->lineId(); + watch->ring(handler->lineId(), findPersonByNumber(handler->lineId()), false); + break; + case VoiceCallHandler::STATUS_INCOMING: + case VoiceCallHandler::STATUS_WAITING: + qCDebug(l) << "Tell incoming:" << handler->lineId(); + watch->ring(handler->lineId(), findPersonByNumber(handler->lineId())); + break; + case VoiceCallHandler::STATUS_NULL: + case VoiceCallHandler::STATUS_DISCONNECTED: + qCDebug(l) << "Endphone"; + watch->endPhoneCall(); + break; + case VoiceCallHandler::STATUS_ACTIVE: + qCDebug(l) << "Startphone"; + watch->startPhoneCall(); + break; + case VoiceCallHandler::STATUS_HELD: + break; + } +} + +QString Manager::findPersonByNumber(QString number) +{ + QString person; + numberFilter.setValue(number); + + const QList &found = contacts->contacts(numberFilter); + if (found.size() == 1) { + person = found[0].detail(QContactDetail::TypeDisplayLabel).value(0).toString(); + } + + if (settings->property("transliterateMessage").toBool()) { + transliterateMessage(person); + } + return person; +} + +void Manager::onVoiceError(const QString &message) +{ + qCCritical(l) << "Error:" << message; +} + + +void Manager::onNotifyError(const QString &message) +{ + qWarning() << "Error:" << message; +} + +void Manager::onSmsNotify(const QString &sender, const QString &data) +{ + if (settings->property("transliterateMessage").toBool()) { + transliterateMessage(sender); + transliterateMessage(data); + } + watch->sendSMSNotification(sender, data); +} + +void Manager::onTwitterNotify(const QString &sender, const QString &data) +{ + if (settings->property("transliterateMessage").toBool()) { + transliterateMessage(sender); + transliterateMessage(data); + } + watch->sendTwitterNotification(sender, data); +} + + +void Manager::onFacebookNotify(const QString &sender, const QString &data) +{ + if (settings->property("transliterateMessage").toBool()) { + transliterateMessage(sender); + transliterateMessage(data); + } + watch->sendFacebookNotification(sender, data); +} + +void Manager::onTelegramNotify(const QString &sender, const QString &data) +{ + if (settings->property("transliterateMessage").toBool()) { + transliterateMessage(sender); + transliterateMessage(data); + } + watch->sendTelegramNotification(sender, data); +} + +void Manager::onHangoutsNotify(const QString &sender, const QString &data) +{ + if (settings->property("transliterateMessage").toBool()) { + transliterateMessage(sender); + transliterateMessage(data); + } + watch->sendHangoutsNotification(sender, data); +} + +void Manager::onWhatsappNotify(const QString &sender, const QString &data) +{ + if (settings->property("transliterateMessage").toBool()) { + transliterateMessage(sender); + transliterateMessage(data); + } + watch->sendWhatsappNotification(sender, data); +} + +void Manager::onEmailNotify(const QString &sender, const QString &data,const QString &subject) +{ + if (settings->property("transliterateMessage").toBool()) { + transliterateMessage(sender); + transliterateMessage(data); + transliterateMessage(subject); + } + watch->sendEmailNotification(sender, data, subject); +} + +void Manager::applyProfile() +{ + QString newProfile = settings->property( + watch->isConnected() ? "profileWhenConnected" + : "profileWhenDisconnected").toString(); + if (!newProfile.isEmpty()) { + QDBusReply res = QDBusConnection::sessionBus().call( + QDBusMessage::createMethodCall("com.nokia.profiled", "/com/nokia/profiled", "com.nokia.profiled", "set_profile") + << newProfile); + if (res.isValid()) { + if (!res.value()) { + qCCritical(l) << "Unable to set profile" << newProfile; + } + } + else { + qCCritical(l) << res.error().message(); + } + } +} + +void Manager::ping(uint val) +{ + qCDebug(l) << "ping" << val; + + if (settings->property("debug").toBool()) { + // magic here! + // I do not want to add specific debugging methods to pebbled + // so just provide some magic Ping() method handling here :-) + switch (val) { + case 128: + watch->sendSMSNotification("SMS", "lorem ipsum"); + return; + case 129: + watch->sendEmailNotification("e-mail", "lorem ipsum", "subject"); + return; + case 130: + watch->sendFacebookNotification("Facebook", "lorem ipsum"); + return; + case 131: + watch->sendTwitterNotification("Twitter", "lorem ipsum"); + return; + case 132: + watch->sendMusicNowPlaying("artist", "album", "track name"); + return; + } + } + + watch->ping(val); +} + +void Manager::transliterateMessage(const QString &text) +{ + if (transliterator.isNull()) { + UErrorCode status = U_ZERO_ERROR; + transliterator.reset(icu::Transliterator::createInstance(icu::UnicodeString::fromUTF8("Any-Latin; Latin-ASCII"),UTRANS_FORWARD, status)); + if (U_FAILURE(status)) { + qCWarning(l) << "Error creaing ICU Transliterator \"Any-Latin; Latin-ASCII\":" << u_errorName(status); + } + } + if (!transliterator.isNull()) { + qCDebug(l) << "String before transliteration:" << text; + + icu::UnicodeString uword = icu::UnicodeString::fromUTF8(text.toStdString()); + transliterator->transliterate(uword); + + std::string translited; + uword.toUTF8String(translited); + + const_cast(text) = QString::fromStdString(translited); + qCDebug(l) << "String after transliteration:" << text; + } +} + +void Manager::onAppNotification(const QUuid &uuid, const QString &title, const QString &body) +{ + Q_UNUSED(uuid); + watch->sendSMSNotification(title, body); +} + +void Manager::onAppOpened(const QUuid &uuid) +{ + currentAppUuid = uuid; + emit proxy->AppUuidChanged(); + emit proxy->AppOpened(uuid.toString()); +} + +void Manager::onAppClosed(const QUuid &uuid) +{ + currentAppUuid = QUuid(); + emit proxy->AppClosed(uuid.toString()); + emit proxy->AppUuidChanged(); +} + +bool Manager::uploadFirmware(bool recovery, const QString &file) +{ + qCDebug(l) << "about to upload" << (recovery ? "recovery" : "normal") + << "firmware:" << file; + + Bundle bundle(Bundle::fromPath(file)); + if (!bundle.isValid()) { + qCWarning(l) << file << "is invalid bundle"; + return false; + } + + if (!bundle.fileExists(Bundle::FIRMWARE) || !bundle.fileExists(Bundle::RESOURCES)) { + qCWarning(l) << file << "is missing binary or resource"; + return false; + } + + watch->systemMessage(WatchConnector::systemFIRMWARE_START, + [this, recovery, bundle](const QByteArray &data) { + qCDebug(l) << "got response to firmware start" << data.toHex(); + + QSharedPointer resourceFile(bundle.openFile(Bundle::RESOURCES)); + if (!resourceFile) { + qCWarning(l) << "failed to open" << bundle.path() << "resource"; + watch->systemMessage(WatchConnector::systemFIRMWARE_FAIL); + return true; + } + + upload->uploadFirmwareResources(resourceFile.data(), bundle.crcFile(Bundle::RESOURCES), + [this, recovery, bundle, resourceFile]() { + qCDebug(l) << "firmware resource upload succesful"; + resourceFile->close(); + // Proceed to upload the resource file + QSharedPointer binaryFile(bundle.openFile(Bundle::FIRMWARE)); + if (binaryFile) { + upload->uploadFirmwareBinary(recovery, binaryFile.data(), bundle.crcFile(Bundle::FIRMWARE), + [this, binaryFile]() { + binaryFile->close(); + qCDebug(l) << "firmware binary upload succesful"; + // Upload succesful + watch->systemMessage(WatchConnector::systemFIRMWARE_COMPLETE); + }, [this, binaryFile](int code) { + binaryFile->close(); + qCWarning(l) << "firmware binary upload failed" << code; + watch->systemMessage(WatchConnector::systemFIRMWARE_FAIL); + }); + } else { + qCWarning(l) << "failed to open" << bundle.path() << "binary"; + watch->systemMessage(WatchConnector::systemFIRMWARE_FAIL); + } + }, [this, resourceFile](int code) { + resourceFile->close(); + qCWarning(l) << "firmware resource upload failed" << code; + watch->systemMessage(WatchConnector::systemFIRMWARE_FAIL); + }); + + return true; + }); + + return true; +} + +QStringList PebbledProxy::AppSlots() const +{ + const int num_slots = manager()->bank->numSlots(); + QStringList l; + l.reserve(num_slots); + + for (int i = 0; i < num_slots; ++i) { + if (manager()->bank->isUsed(i)) { + QUuid uuid = manager()->bank->appAt(i); + l.append(uuid.toString()); + } else { + l.append(QString()); + } + } + + Q_ASSERT(l.size() == num_slots); + + return l; +} + +QVariantList PebbledProxy::AllApps() const +{ + QList uuids = manager()->apps->appUuids(); + QVariantList l; + + foreach (const QUuid &uuid, uuids) { + AppInfo info = manager()->apps->info(uuid); + QVariantMap m; + m.insert("local", QVariant::fromValue(info.isLocal())); + m.insert("uuid", QVariant::fromValue(info.uuid().toString())); + m.insert("short-name", QVariant::fromValue(info.shortName())); + m.insert("long-name", QVariant::fromValue(info.longName())); + m.insert("company-name", QVariant::fromValue(info.companyName())); + m.insert("version-label", QVariant::fromValue(info.versionLabel())); + m.insert("is-watchface", QVariant::fromValue(info.isWatchface())); + m.insert("configurable", QVariant::fromValue(info.capabilities().testFlag(AppInfo::Capability::Configurable))); + m.insert("path", QVariant::fromValue(info.path())); + + if (!info.getMenuIconImage().isNull()) { + m.insert("menu-icon", QVariant::fromValue(info.getMenuIconPng())); + } + + l.append(QVariant::fromValue(m)); + } + + return l; +} + +bool PebbledProxy::SendAppMessage(const QString &uuid, const QVariantMap &data) +{ + Q_ASSERT(calledFromDBus()); + const QDBusMessage msg = message(); + setDelayedReply(true); + manager()->appmsg->send(uuid, data, [this, msg]() { + QDBusMessage reply = msg.createReply(QVariant::fromValue(true)); + this->connection().send(reply); + }, [this, msg]() { + QDBusMessage reply = msg.createReply(QVariant::fromValue(false)); + this->connection().send(reply); + }); + return false; // D-Bus clients should never see this reply. +} + +QString PebbledProxy::StartAppConfiguration(const QString &uuid) +{ + Q_ASSERT(calledFromDBus()); + const QDBusMessage msg = message(); + QDBusConnection conn = connection(); + + if (manager()->currentAppUuid != uuid) { + qCWarning(l) << "Called StartAppConfiguration but the uuid" << uuid << "is not running"; + sendErrorReply(msg.interface() + ".Error.AppNotRunning", + "The requested app is not currently opened in the watch"); + return QString(); + } + + if (!manager()->js->isJSKitAppRunning()) { + qCWarning(l) << "Called StartAppConfiguration but the uuid" << uuid << "is not a JS app"; + sendErrorReply(msg.interface() + ".Error.JSNotActive", + "The requested app is not a PebbleKit JS application"); + return QString(); + } + + // After calling showConfiguration() on the script, + // it will (eventually!) return a URL to us via the appOpenUrl signal. + + // So we can't send the D-Bus reply right now. + setDelayedReply(true); + + // Set up a signal handler to catch the appOpenUrl signal. + QMetaObject::Connection *c = new QMetaObject::Connection; + *c = connect(manager()->js, &JSKitManager::appOpenUrl, + this, [this,conn,msg,c](const QUrl &url) { + // Workaround: due to a GCC crash we can't capture the uuid parameter, but we can extract + // it again from the original message arguments. + // Suspect GCC bug# is 59195, 61233, or 61321. + // TODO Possibly fixed in 4.9.0 + const QString uuid = msg.arguments().at(0).toString(); + + if (manager()->currentAppUuid != uuid) { + // App was changed while we were waiting for the script.. + QDBusMessage reply = msg.createErrorReply(msg.interface() + ".Error.AppNotRunning", + "The requested app is not currently opened in the watch"); + conn.send(reply); + } else { + QDBusMessage reply = msg.createReply(QVariant::fromValue(url.toString())); + conn.send(reply); + } + + disconnect(*c); + delete c; + }); + + // TODO: JS script may fail, never call OpenURL, or something like that + // In those cases we WILL leak the above connection. + // (at least until the next appOpenURL event comes in) + // So we need to also set a timeout or similar. + + manager()->js->showConfiguration(); + + // Note that the above signal handler _might_ have been already called by this point. + + return QString(); // This return value should never be used. +} + +void PebbledProxy::SendAppConfigurationData(const QString &uuid, const QString &data) +{ + Q_ASSERT(calledFromDBus()); + const QDBusMessage msg = message(); + + if (manager()->currentAppUuid != uuid) { + sendErrorReply(msg.interface() + ".Error.AppNotRunning", + "The requested app is not currently opened in the watch"); + return; + } + + if (!manager()->js->isJSKitAppRunning()) { + sendErrorReply(msg.interface() + ".Error.JSNotActive", + "The requested app is not a PebbleKit JS application"); + return; + } + + manager()->js->handleWebviewClosed(data); +} + +void PebbledProxy::UnloadApp(int slot) +{ + Q_ASSERT(calledFromDBus()); + const QDBusMessage msg = message(); + + if (!manager()->bank->unloadApp(slot)) { + sendErrorReply(msg.interface() + ".Error.CannotUnload", + "Cannot unload application"); + } +} + +void PebbledProxy::UploadApp(const QString &uuid, int slot) +{ + Q_ASSERT(calledFromDBus()); + const QDBusMessage msg = message(); + + if (!manager()->bank->uploadApp(QUuid(uuid), slot)) { + sendErrorReply(msg.interface() + ".Error.CannotUpload", + "Cannot upload application"); + } +} + +void PebbledProxy::NotifyFirmware(bool ok) +{ + Q_ASSERT(calledFromDBus()); + manager()->watch->sendFirmwareState(ok); +} + +void PebbledProxy::UploadFirmware(bool recovery, const QString &file) +{ + Q_ASSERT(calledFromDBus()); + const QDBusMessage msg = message(); + + if (!manager()->uploadFirmware(recovery, file)) { + sendErrorReply(msg.interface() + ".Error.CannotUpload", + "Cannot upload firmware"); + } +} diff --git a/daemon/manager.h b/daemon/manager.h index e3143b1..0c6032b 100644 --- a/daemon/manager.h +++ b/daemon/manager.h @@ -1,157 +1,160 @@ -#ifndef MANAGER_H -#define MANAGER_H - -#include "watchconnector.h" -#include "uploadmanager.h" -#include "voicecallmanager.h" -#include "notificationmanager.h" -#include "musicmanager.h" -#include "datalogmanager.h" -#include "appmsgmanager.h" -#include "jskitmanager.h" -#include "appmanager.h" -#include "bankmanager.h" -#include "settings.h" - -#include -#include -#include -#include -#include -#include - -#include - -using namespace QtContacts; - -class PebbledProxy; - -class Manager : public QObject, protected QDBusContext -{ - Q_OBJECT - QLoggingCategory l; - - friend class PebbledProxy; - - Settings *settings; - - PebbledProxy *proxy; - - WatchConnector *watch; - UploadManager *upload; - AppManager *apps; - BankManager *bank; - VoiceCallManager *voice; - NotificationManager *notifications; - MusicManager *music; - DataLogManager *datalog; - AppMsgManager *appmsg; - JSKitManager *js; - - MNotification notification; - - QContactManager *contacts; - QContactDetailFilter numberFilter; - - QUuid currentAppUuid; - - QScopedPointer transliterator; - -public: - explicit Manager(Settings *settings, QObject *parent = 0); - ~Manager(); - - QString findPersonByNumber(QString number); - - bool uploadFirmware(bool recovery, const QString &file); - -protected: - void transliterateMessage(const QString &text); - -public slots: - void applyProfile(); - void ping(uint val); - -private slots: - void onSettingChanged(const QString &key); - void onSettingsChanged(); - void onConnectedChanged(); - void onActiveVoiceCallChanged(); - void onVoiceError(const QString &message); - void onActiveVoiceCallStatusChanged(); - void onNotifyError(const QString &message); - void onSmsNotify(const QString &sender, const QString &data); - void onTwitterNotify(const QString &sender, const QString &data); - void onFacebookNotify(const QString &sender, const QString &data); - void onEmailNotify(const QString &sender, const QString &data,const QString &subject); - - void onAppNotification(const QUuid &uuid, const QString &title, const QString &body); - void onAppOpened(const QUuid &uuid); - void onAppClosed(const QUuid &uuid); -}; - -/** This class is what's actually exported over D-Bus, - * so the names of the slots and properties must match with org.pebbled.Watch D-Bus interface. - * Case sensitive. Otherwise, _runtime_ failures will occur. */ -// Some of the methods are marked inline so that they may be inlined inside qt_metacall -class PebbledProxy : public QObject, protected QDBusContext -{ - Q_OBJECT - QLoggingCategory l; - - Q_PROPERTY(QString Name READ Name NOTIFY NameChanged) - Q_PROPERTY(QString Address READ Address NOTIFY AddressChanged) - Q_PROPERTY(QVariantMap Info READ Info NOTIFY InfoChanged) - Q_PROPERTY(bool Connected READ Connected NOTIFY ConnectedChanged) - Q_PROPERTY(QString AppUuid READ AppUuid NOTIFY AppUuidChanged) - Q_PROPERTY(QStringList AppSlots READ AppSlots NOTIFY AppSlotsChanged) - Q_PROPERTY(QVariantList AllApps READ AllApps NOTIFY AllAppsChanged) - - inline Manager* manager() const { return static_cast(parent()); } - -public: - inline explicit PebbledProxy(QObject *parent) - : QObject(parent), l(metaObject()->className()) {} - - inline QString Name() const { qCDebug(l) << manager()->watch->name(); return manager()->watch->name(); } - inline QString Address() const { qCDebug(l) << manager()->watch->address().toString(); return manager()->watch->address().toString(); } - inline QVariantMap Info() const { qCDebug(l) << manager()->watch->versions().toMap(); return manager()->watch->versions().toMap(); } - inline bool Connected() const { qCDebug(l) << manager()->watch->isConnected(); return manager()->watch->isConnected(); } - inline QString AppUuid() const { return manager()->currentAppUuid.toString(); } - - QStringList AppSlots() const; - - QVariantList AllApps() const; - -public slots: - inline void Disconnect() { manager()->watch->disconnect(); } - inline void Reconnect() { manager()->watch->connect(); } - inline void Ping(uint val) { manager()->ping(val); } - inline void SyncTime() { manager()->watch->time(); } - - inline void LaunchApp(const QString &uuid) { manager()->appmsg->launchApp(uuid); } - inline void CloseApp(const QString &uuid) { manager()->appmsg->closeApp(uuid); } - - bool SendAppMessage(const QString &uuid, const QVariantMap &data); - QString StartAppConfiguration(const QString &uuid); - void SendAppConfigurationData(const QString &uuid, const QString &data); - - void UnloadApp(int slot); - void UploadApp(const QString &uuid, int slot); - - void NotifyFirmware(bool ok); - void UploadFirmware(bool recovery, const QString &file); - -signals: - void NameChanged(); - void AddressChanged(); - void InfoChanged(); - void ConnectedChanged(); - void AppUuidChanged(); - void AppSlotsChanged(); - void AllAppsChanged(); - void AppOpened(const QString &uuid); - void AppClosed(const QString &uuid); -}; - -#endif // MANAGER_H +#ifndef MANAGER_H +#define MANAGER_H + +#include "watchconnector.h" +#include "uploadmanager.h" +#include "voicecallmanager.h" +#include "notificationmanager.h" +#include "musicmanager.h" +#include "datalogmanager.h" +#include "appmsgmanager.h" +#include "jskitmanager.h" +#include "appmanager.h" +#include "bankmanager.h" +#include "settings.h" + +#include +#include +#include +#include +#include +#include + +#include + +using namespace QtContacts; + +class PebbledProxy; + +class Manager : public QObject, protected QDBusContext +{ + Q_OBJECT + QLoggingCategory l; + + friend class PebbledProxy; + + Settings *settings; + + PebbledProxy *proxy; + + WatchConnector *watch; + UploadManager *upload; + AppManager *apps; + BankManager *bank; + VoiceCallManager *voice; + NotificationManager *notifications; + MusicManager *music; + DataLogManager *datalog; + AppMsgManager *appmsg; + JSKitManager *js; + + MNotification notification; + + QContactManager *contacts; + QContactDetailFilter numberFilter; + + QUuid currentAppUuid; + + QScopedPointer transliterator; + +public: + explicit Manager(Settings *settings, QObject *parent = 0); + ~Manager(); + + QString findPersonByNumber(QString number); + + bool uploadFirmware(bool recovery, const QString &file); + +protected: + void transliterateMessage(const QString &text); + +public slots: + void applyProfile(); + void ping(uint val); + +private slots: + void onSettingChanged(const QString &key); + void onSettingsChanged(); + void onConnectedChanged(); + void onActiveVoiceCallChanged(); + void onVoiceError(const QString &message); + void onActiveVoiceCallStatusChanged(); + void onNotifyError(const QString &message); + void onSmsNotify(const QString &sender, const QString &data); + void onTwitterNotify(const QString &sender, const QString &data); + void onFacebookNotify(const QString &sender, const QString &data); + void onTelegramNotify(const QString &sender, const QString &data); + void onHangoutsNotify(const QString &sender, const QString &data); + void onWhatsappNotify(const QString &sender, const QString &data); + void onEmailNotify(const QString &sender, const QString &data,const QString &subject); + + void onAppNotification(const QUuid &uuid, const QString &title, const QString &body); + void onAppOpened(const QUuid &uuid); + void onAppClosed(const QUuid &uuid); +}; + +/** This class is what's actually exported over D-Bus, + * so the names of the slots and properties must match with org.pebbled.Watch D-Bus interface. + * Case sensitive. Otherwise, _runtime_ failures will occur. */ +// Some of the methods are marked inline so that they may be inlined inside qt_metacall +class PebbledProxy : public QObject, protected QDBusContext +{ + Q_OBJECT + QLoggingCategory l; + + Q_PROPERTY(QString Name READ Name NOTIFY NameChanged) + Q_PROPERTY(QString Address READ Address NOTIFY AddressChanged) + Q_PROPERTY(QVariantMap Info READ Info NOTIFY InfoChanged) + Q_PROPERTY(bool Connected READ Connected NOTIFY ConnectedChanged) + Q_PROPERTY(QString AppUuid READ AppUuid NOTIFY AppUuidChanged) + Q_PROPERTY(QStringList AppSlots READ AppSlots NOTIFY AppSlotsChanged) + Q_PROPERTY(QVariantList AllApps READ AllApps NOTIFY AllAppsChanged) + + inline Manager* manager() const { return static_cast(parent()); } + +public: + inline explicit PebbledProxy(QObject *parent) + : QObject(parent), l(metaObject()->className()) {} + + inline QString Name() const { qCDebug(l) << manager()->watch->name(); return manager()->watch->name(); } + inline QString Address() const { qCDebug(l) << manager()->watch->address().toString(); return manager()->watch->address().toString(); } + inline QVariantMap Info() const { qCDebug(l) << manager()->watch->versions().toMap(); return manager()->watch->versions().toMap(); } + inline bool Connected() const { qCDebug(l) << manager()->watch->isConnected(); return manager()->watch->isConnected(); } + inline QString AppUuid() const { return manager()->currentAppUuid.toString(); } + + QStringList AppSlots() const; + + QVariantList AllApps() const; + +public slots: + inline void Disconnect() { manager()->watch->disconnect(); } + inline void Reconnect() { manager()->watch->connect(); } + inline void Ping(uint val) { manager()->ping(val); } + inline void SyncTime() { manager()->watch->time(); } + + inline void LaunchApp(const QString &uuid) { manager()->appmsg->launchApp(uuid); } + inline void CloseApp(const QString &uuid) { manager()->appmsg->closeApp(uuid); } + + bool SendAppMessage(const QString &uuid, const QVariantMap &data); + QString StartAppConfiguration(const QString &uuid); + void SendAppConfigurationData(const QString &uuid, const QString &data); + + void UnloadApp(int slot); + void UploadApp(const QString &uuid, int slot); + + void NotifyFirmware(bool ok); + void UploadFirmware(bool recovery, const QString &file); + +signals: + void NameChanged(); + void AddressChanged(); + void InfoChanged(); + void ConnectedChanged(); + void AppUuidChanged(); + void AppSlotsChanged(); + void AllAppsChanged(); + void AppOpened(const QString &uuid); + void AppClosed(const QString &uuid); +}; + +#endif // MANAGER_H diff --git a/daemon/notificationmanager.cpp b/daemon/notificationmanager.cpp index 8e21ad9..0c56bc2 100644 --- a/daemon/notificationmanager.cpp +++ b/daemon/notificationmanager.cpp @@ -114,7 +114,7 @@ uint NotificationManager::Notify(const QString &app_name, uint replaces_id, cons Q_UNUSED(expire_timeout); // new place to check notification owner in Sailfish 1.1.6 - QString owner = hints.value("x-nemo-owner", "none").toString(); + QString owner = hints.value("x-nemo-owner", "Unknown").toString(); qCDebug(l) << Q_FUNC_INFO << "Got notification via dbus from" << this->getCleanAppName(app_name) << " Owner: " << owner; qCDebug(l) << hints; @@ -127,79 +127,81 @@ uint NotificationManager::Notify(const QString &app_name, uint replaces_id, cons QString category = hints.value("category", "").toString(); QStringHash categoryParams = this->getCategoryParams(category); - // Ignore transient notifications (notif hints override category hints) - // Hack this to accept transient -preview and -summary notifications, as we don't know how to decode the actual notifs yet - if (!category.endsWith("preview") && !category.endsWith("summary") && - hints.value("transient", categoryParams.value("transient", "false")).toString() == "true") { - qCDebug(l) << "Ignoring transient notification from " << owner; - return 0; - } - - if (app_name == "messageserver5" || owner == "messageserver5") { + if (category == "x-nemo.email") { if (!settings->property("notificationsEmails").toBool()) { qCDebug(l) << "Ignoring email notification because of setting!"; return 0; } - -// This is how we should obtain messaging notifications, if we manage to deserialize this 'PersonalNotification'. -// QByteArray commdata = hints.value("x-commhistoryd-data", "no data").toByteArray(); -// if (!commdata.contains((char)0)) -// data = QByteArray::fromBase64(commdata); -// QDataStream stream(commdata); -// stream.setVersion(QDataStream::Qt_5_0); -// stream >> - - QString subject = hints.value("x-nemo-preview-summary", "").toString(); - QString data = hints.value("x-nemo-preview-body", "").toString(); - - // Prioritize subject over data - if (subject.isEmpty() && !data.isEmpty()) { - subject = data; - data = ""; - } - - if (!subject.isEmpty()) { - emit this->emailNotify(subject, data, ""); - } - } else if (app_name == "commhistoryd" || owner == "commhistoryd") { - if (summary == "" && body == "") { - if (category == "x-nemo.call.missed") { - if (!settings->property("notificationsMissedCall").toBool()) { - qCDebug(l) << "Ignoring MissedCall notification because of setting!"; - return 0; - } - } else { - if (!settings->property("notificationsCommhistoryd").toBool()) { - qCDebug(l) << "Ignoring commhistoryd notification because of setting!"; - return 0; - } - } - emit this->smsNotify(hints.value("x-nemo-preview-summary", "default").toString(), - hints.value("x-nemo-preview-body", "default").toString() - ); - } - } else if (app_name == "harbour-mitakuuluu2-server" || owner == "harbour-mitakuuluu2-server") { - if (!settings->property("notificationsMitakuuluu").toBool()) { - qCDebug(l) << "Ignoring mitakuuluu notification because of setting!"; + emit this->emailNotify(summary, body, app_name); + } + else if (category == "x-nemo.call.missed") { + if (!settings->property("notificationsMissedCall").toBool()) { + qCDebug(l) << "Ignoring MissedCall notification because of setting!"; return 0; } - - emit this->smsNotify(hints.value("x-nemo-preview-body", "default").toString(), - hints.value("x-nemo-preview-summary", "default").toString() + emit this->smsNotify(hints.value("x-nemo-preview-summary", summary).toString(), + hints.value("x-nemo-preview-body", body).toString() + ); + } + else if (category == "x-nemo.messaging.sms.preview") { + if (!settings->property("notificationsCommhistoryd").toBool()) { + qCDebug(l) << "Ignoring commhistoryd notification because of setting!"; + return 0; + } + emit this->smsNotify(hints.value("x-nemo-preview-summary", summary).toString(), + hints.value("x-nemo-preview-body", body).toString() ); - } else if (app_name == "twitter-notifications-client" || owner == "twitter-notifications-client") { + } + else if (app_name.toLower().contains("whatsapp") || app_name.toLower().contains("whatsup")) { + if (!settings->property("notificationsWhatsapp").toBool()) { + qCDebug(l) << "Ignoring whatsapp notification because of setting!"; + return 0; + } + emit this->whatsappNotify(hints.value("x-nemo-preview-summary", summary).toString(), + hints.value("x-nemo-preview-body", body).toString() + ); + } + else if (hints.value("x-nemo-origin-package").toString() == "org.telegram.messenger" + || category.startsWith("harbour.sailorgram")) { + if (!settings->property("notificationsTelegram").toBool()) { + qCDebug(l) << "Ignoring telegram notification because of setting!"; + return 0; + } + emit this->telegramNotify(hints.value("x-nemo-preview-summary", summary).toString(), + hints.value("x-nemo-preview-body", body).toString() + ); + } + else if (app_name.toLower().contains("hangouts") || app_name.toLower().contains("hangish")) { + if (!settings->property("notificationsHangouts").toBool()) { + qCDebug(l) << "Ignoring telegram notification because of setting!"; + return 0; + } + emit this->hangoutNotify(hints.value("x-nemo-preview-summary", summary).toString(), + hints.value("x-nemo-preview-body", body).toString() + ); + } + else if (app_name == "twitter-notifications-client" || owner == "twitter-notifications-client") { if (!settings->property("notificationsTwitter").toBool()) { qCDebug(l) << "Ignoring twitter notification because of setting!"; return 0; } - emit this->twitterNotify(hints.value("x-nemo-preview-body", body).toString(), - hints.value("x-nemo-preview-summary", summary).toString() + emit this->twitterNotify(hints.value("x-nemo-preview-summary", summary).toString(), + hints.value("x-nemo-preview-body", body).toString() ); } else { + + // Ignore transient notifications (notif hints override category hints) + // Hack this to accept transient -preview and -summary notifications, as we don't know how to decode the actual notifs yet + if (!category.endsWith("preview") && !category.endsWith("summary") && + hints.value("transient", categoryParams.value("transient", "false")).toString() == "true") { + qCDebug(l) << "Ignoring transient notification from " << owner; + return 0; + } + // Prioritize x-nemo-preview* over dbus direct summary and body - QString subject = hints.value("x-nemo-preview-summary", "").toString(); - QString data = hints.value("x-nemo-preview-body", "").toString(); + QString subject = hints.value("x-nemo-preview-summary", summary).toString(); + QString data = hints.value("x-nemo-preview-body", body).toString(); int prio = categoryParams.value("x-nemo-priority", "0").toInt(); qCDebug(l) << "MSG Prio:" << prio; diff --git a/daemon/notificationmanager.h b/daemon/notificationmanager.h index 037ff07..830bfb4 100644 --- a/daemon/notificationmanager.h +++ b/daemon/notificationmanager.h @@ -29,6 +29,9 @@ Q_SIGNALS: void smsNotify(const QString &sender, const QString &data); void twitterNotify(const QString &sender, const QString &data); void facebookNotify(const QString &sender, const QString &data); + void telegramNotify(const QString &sender, const QString &data); + void hangoutNotify(const QString &sender, const QString &data); + void whatsappNotify(const QString &sender, const QString &data); void emailNotify(const QString &sender, const QString &data,const QString &subject); public Q_SLOTS: diff --git a/daemon/settings.h b/daemon/settings.h index 4534a54..826f0ce 100644 --- a/daemon/settings.h +++ b/daemon/settings.h @@ -1,81 +1,89 @@ -#ifndef SETTINGS_H -#define SETTINGS_H - -#include - -class Settings : public MDConfGroup -{ - Q_OBJECT - - Q_PROPERTY(QString profileWhenConnected MEMBER profileWhenConnected NOTIFY profileWhenConnectedChanged) - Q_PROPERTY(QString profileWhenDisconnected MEMBER profileWhenDisconnected NOTIFY profileWhenDisconnectedChanged) - Q_PROPERTY(bool transliterateMessage MEMBER transliterateMessage NOTIFY transliterateMessageChanged) - Q_PROPERTY(bool useSystemVolume MEMBER useSystemVolume NOTIFY useSystemVolumeChanged) - Q_PROPERTY(bool incomingCallNotification MEMBER incomingCallNotification NOTIFY incomingCallNotificationChanged) - Q_PROPERTY(bool notificationsCommhistoryd MEMBER notificationsCommhistoryd NOTIFY notificationsCommhistorydChanged) - Q_PROPERTY(bool notificationsMissedCall MEMBER notificationsMissedCall NOTIFY notificationsMissedCallChanged) - Q_PROPERTY(bool notificationsEmails MEMBER notificationsEmails NOTIFY notificationsEmailsChanged) - Q_PROPERTY(bool notificationsMitakuuluu MEMBER notificationsMitakuuluu NOTIFY notificationsMitakuuluuChanged) - Q_PROPERTY(bool notificationsTwitter MEMBER notificationsTwitter NOTIFY notificationsTwitterChanged) - Q_PROPERTY(bool notificationsFacebook MEMBER notificationsFacebook NOTIFY notificationsFacebookChanged) - Q_PROPERTY(bool notificationsOther MEMBER notificationsOther NOTIFY notificationsOtherChanged) - Q_PROPERTY(bool notificationsAll MEMBER notificationsAll NOTIFY notificationsAllChanged) - Q_PROPERTY(QString accountToken MEMBER accountToken NOTIFY accountTokenChanged) - Q_PROPERTY(bool debug MEMBER debug NOTIFY debugChanged) - - QString profileWhenConnected; - QString profileWhenDisconnected; - bool transliterateMessage; - bool useSystemVolume; - bool incomingCallNotification; - bool notificationsCommhistoryd; - bool notificationsMissedCall; - bool notificationsEmails; - bool notificationsMitakuuluu; - bool notificationsTwitter; - bool notificationsFacebook; - bool notificationsOther; - bool notificationsAll; - QString accountToken; - bool debug; - -public: - explicit Settings(QObject *parent = 0) : - MDConfGroup("/org/pebbled/settings", parent, BindProperties), - transliterateMessage(false), - useSystemVolume(true), - incomingCallNotification(true), - notificationsCommhistoryd(true), - notificationsMissedCall(true), - notificationsEmails(false), - notificationsMitakuuluu(true), - notificationsTwitter(true), - notificationsFacebook(true), - notificationsOther(true), - notificationsAll(false), - debug(false) - { - resolveMetaObject(); - QMetaObject::invokeMethod(this, "propertyChanged", Qt::DirectConnection); - sync(); - } - -signals: - void profileWhenConnectedChanged(); - void profileWhenDisconnectedChanged(); - void transliterateMessageChanged(); - void useSystemVolumeChanged(); - void incomingCallNotificationChanged(); - void notificationsCommhistorydChanged(); - void notificationsMissedCallChanged(); - void notificationsEmailsChanged(); - void notificationsMitakuuluuChanged(); - void notificationsTwitterChanged(); - void notificationsFacebookChanged(); - void notificationsOtherChanged(); - void notificationsAllChanged(); - void accountTokenChanged(); - void debugChanged(); -}; - -#endif // SETTINGS_H +#ifndef SETTINGS_H +#define SETTINGS_H + +#include + +class Settings : public MDConfGroup +{ + Q_OBJECT + + Q_PROPERTY(QString profileWhenConnected MEMBER profileWhenConnected NOTIFY profileWhenConnectedChanged) + Q_PROPERTY(QString profileWhenDisconnected MEMBER profileWhenDisconnected NOTIFY profileWhenDisconnectedChanged) + Q_PROPERTY(bool transliterateMessage MEMBER transliterateMessage NOTIFY transliterateMessageChanged) + Q_PROPERTY(bool useSystemVolume MEMBER useSystemVolume NOTIFY useSystemVolumeChanged) + Q_PROPERTY(bool incomingCallNotification MEMBER incomingCallNotification NOTIFY incomingCallNotificationChanged) + Q_PROPERTY(bool notificationsCommhistoryd MEMBER notificationsCommhistoryd NOTIFY notificationsCommhistorydChanged) + Q_PROPERTY(bool notificationsMissedCall MEMBER notificationsMissedCall NOTIFY notificationsMissedCallChanged) + Q_PROPERTY(bool notificationsEmails MEMBER notificationsEmails NOTIFY notificationsEmailsChanged) + Q_PROPERTY(bool notificationsTwitter MEMBER notificationsTwitter NOTIFY notificationsTwitterChanged) + Q_PROPERTY(bool notificationsFacebook MEMBER notificationsFacebook NOTIFY notificationsFacebookChanged) + Q_PROPERTY(bool notificationsTelegram MEMBER notificationsTelegram NOTIFY notificationsTelegramChanged) + Q_PROPERTY(bool notificationsWhatsapp MEMBER notificationsWhatsapp NOTIFY notificationsWhatsappChanged) + Q_PROPERTY(bool notificationsHangouts MEMBER notificationsHangouts NOTIFY notificationsHangoutsChanged) + Q_PROPERTY(bool notificationsOther MEMBER notificationsOther NOTIFY notificationsOtherChanged) + Q_PROPERTY(bool notificationsAll MEMBER notificationsAll NOTIFY notificationsAllChanged) + Q_PROPERTY(QString accountToken MEMBER accountToken NOTIFY accountTokenChanged) + Q_PROPERTY(bool debug MEMBER debug NOTIFY debugChanged) + + QString profileWhenConnected; + QString profileWhenDisconnected; + bool transliterateMessage; + bool useSystemVolume; + bool incomingCallNotification; + bool notificationsCommhistoryd; + bool notificationsMissedCall; + bool notificationsEmails; + bool notificationsTwitter; + bool notificationsFacebook; + bool notificationsTelegram; + bool notificationsWhatsapp; + bool notificationsHangouts; + bool notificationsOther; + bool notificationsAll; + QString accountToken; + bool debug; + +public: + explicit Settings(QObject *parent = 0) : + MDConfGroup("/org/pebbled/settings", parent, BindProperties), + transliterateMessage(false), + useSystemVolume(true), + incomingCallNotification(true), + notificationsCommhistoryd(true), + notificationsMissedCall(true), + notificationsEmails(false), + notificationsTwitter(true), + notificationsFacebook(true), + notificationsTelegram(true), + notificationsWhatsapp(true), + notificationsHangouts(true), + notificationsOther(true), + notificationsAll(false), + debug(false) + { + resolveMetaObject(); + QMetaObject::invokeMethod(this, "propertyChanged", Qt::DirectConnection); + sync(); + } + +signals: + void profileWhenConnectedChanged(); + void profileWhenDisconnectedChanged(); + void transliterateMessageChanged(); + void useSystemVolumeChanged(); + void incomingCallNotificationChanged(); + void notificationsCommhistorydChanged(); + void notificationsMissedCallChanged(); + void notificationsEmailsChanged(); + void notificationsTwitterChanged(); + void notificationsFacebookChanged(); + void notificationsTelegramChanged(); + void notificationsWhatsappChanged(); + void notificationsHangoutsChanged(); + void notificationsOtherChanged(); + void notificationsAllChanged(); + void accountTokenChanged(); + void debugChanged(); +}; + +#endif // SETTINGS_H diff --git a/daemon/timelineitem.cpp b/daemon/timelineitem.cpp new file mode 100644 index 0000000..4bc699c --- /dev/null +++ b/daemon/timelineitem.cpp @@ -0,0 +1,144 @@ +#include "timelineitem.h" + +TimelineItem::TimelineItem(TimelineItem::Type type, Flags flags, const QDateTime ×tamp, quint16 duration): + TimelineItem(QUuid::createUuid(), type, flags, timestamp, duration) +{ + +} + +TimelineItem::TimelineItem(const QUuid &uuid, TimelineItem::Type type, Flags flags, const QDateTime ×tamp, quint16 duration): + PebblePacket(), + m_itemId(uuid), + m_timestamp(timestamp), + m_duration(duration), + m_type(type), + m_flags(flags) +{ + +} + +QUuid TimelineItem::itemId() const +{ + return m_itemId; +} + +void TimelineItem::setLayout(quint8 layout) +{ + m_layout = layout; +} + +void TimelineItem::setFlags(Flags flags) +{ + m_flags = flags; +} + +void TimelineItem::appendAttribute(const TimelineAttribute &attribute) +{ + m_attributes.append(attribute); +} + +void TimelineItem::appendAction(const TimelineAction &action) +{ + m_actions.append(action); +} + +QList TimelineItem::attributes() const +{ + return m_attributes; +} + +QList TimelineItem::actions() const +{ + return m_actions; +} + +QByteArray TimelineItem::serialize() const +{ + QByteArray ret; + ret.append(m_itemId.toRfc4122()); + ret.append(m_parentId.toRfc4122()); + int ts = m_timestamp.toMSecsSinceEpoch() / 1000; + ret.append(ts & 0xFF); ret.append((ts >> 8) & 0xFF); ret.append((ts >> 16) & 0xFF); ret.append((ts >> 24) & 0xFF); + ret.append(m_duration & 0xFF); ret.append(((m_duration >> 8) & 0xFF)); + ret.append((quint8)m_type); + ret.append(m_flags & 0xFF); ret.append(((m_flags >> 8) & 0xFF)); + ret.append(m_layout); + + QByteArray serializedAttributes; + foreach (const TimelineAttribute &attribute, m_attributes) { + serializedAttributes.append(attribute.serialize()); + } + + QByteArray serializedActions; + foreach (const TimelineAction &action, m_actions) { + serializedActions.append(action.serialize()); + } + quint16 dataLength = serializedAttributes.length() + serializedActions.length(); + ret.append(dataLength & 0xFF); ret.append(((dataLength >> 8) & 0xFF)); + ret.append(m_attributes.count()); + ret.append(m_actions.count()); + ret.append(serializedAttributes); + ret.append(serializedActions); + return ret; +} + +TimelineAction::TimelineAction(quint8 actionId, TimelineAction::Type type, const QList &attributes): + PebblePacket(), + m_actionId(actionId), + m_type(type), + m_attributes(attributes) +{ + +} + +void TimelineAction::appendAttribute(const TimelineAttribute &attribute) +{ + m_attributes.append(attribute); +} + +void TimelineAttribute::setContent(const QString &content) +{ + m_content = content.toUtf8(); +} + +void TimelineAttribute::setContent(TimelineAttribute::IconID iconId) +{ + m_content.clear(); + m_content.append((quint8)iconId); + m_content.append('\0'); + m_content.append('\0'); + m_content.append(0x80); +} + +void TimelineAttribute::setContent(TimelineAttribute::Color color) +{ + m_content.clear(); + m_content.append((quint8)color); +} + +void TimelineAttribute::setContent(const QStringList &values) +{ + m_content.clear(); + foreach (const QString &value, values) { + if (!m_content.isEmpty()) { + m_content.append('\0'); + } + m_content.append(value.toUtf8()); + } +} + +void TimelineAttribute::setContent(quint8 data) +{ + m_content.clear(); + m_content.append(data); +} + +QByteArray TimelineAttribute::serialize() const +{ + QByteArray ret; + ret.append((quint8)m_type); + ret.append(m_content.length() & 0xFF); ret.append(((m_content.length() >> 8) & 0xFF)); // length + ret.append(m_content); + return ret; +} + diff --git a/daemon/timelineitem.h b/daemon/timelineitem.h new file mode 100644 index 0000000..9d7850c --- /dev/null +++ b/daemon/timelineitem.h @@ -0,0 +1,194 @@ +#ifndef TIMELINEITEM_H +#define TIMELINEITEM_H + +#include +#include + +#include "watchconnector.h" + + +class TimelineAttribute +{ +public: + enum Type { + TypeTitle = 0x01, + TypeSubtitle = 0x02, + TypeBody = 0x03, + TypeTinyIcon = 0x04, + TypeLargeIcon = 0x06, + TypeFieldNames = 0x19, + TypeFieldValues = 0x1a, + TypeColor = 0x1c, + TypeRecurring = 0x1f + }; + enum IconID { + IconIDDefaultBell = 0x01, + IconIDDefaultMissedCall = 0x02, + IconIDReminder = 0x03, + IconIDFlag = 0x04, + IconIDWhatsApp = 0x05, + IconIDTwitter = 0x06, + IconIDTelegram = 0x07, + IconIDHangout = 0x08, + IconIDGMail = 0x09, + IconIDFlash = 0x0a, // TODO: what service is this? + IconIDFacebook = 0x0b, + IconIDMusic = 0x0c, + IconIDAlarm = 0x0d, + IconIDWeather = 0x0e, + IconIDGuess = 0x31 + }; + + enum Color { + ColorWhite = 0x00, + ColorBlack = 0x80, + ColorDarkBlue = 0x81, + ColorBlue = 0x82, + ColorLightBlue = 0x83, + ColorDarkGreen = 0x84, + ColorGray = 0x85, + ColorBlue2 = 0x86, + ColorLightBlue2 = 0x87, + ColorGreen = 0x88, + ColorOliveGreen = 0x89, + ColorLightGreen = 0x90, + ColorViolet = 0x91, + ColorViolet2 = 0x91, + ColorBlue3 = 0x92, + ColorBrown = 0x93, + ColorGray2 = 0x94, + ColorBlue4 = 0x95, + ColorBlue5 = 0x96, + ColorRed = 0xA0, + ColorOrange = 0xB8, + ColorYellow = 0xBC + }; + + TimelineAttribute(Type type, const QByteArray &content): + m_type(type), + m_content(content) + {} + + TimelineAttribute(Type type, IconID iconId): + m_type(type) + { + setContent(iconId); + } + TimelineAttribute(Type type, Color color): + m_type(type) + { + setContent(color); + } + TimelineAttribute(Type type, const QStringList &values): + m_type(type) + { + setContent(values); + } + TimelineAttribute(Type type, quint8 data): + m_type(type) + { + setContent(data); + } + + void setContent(const QString &content); + void setContent(IconID iconId); + void setContent(Color color); + void setContent(const QStringList &values); + void setContent(quint8 data); + + QByteArray serialize() const; +private: + Type m_type; + QByteArray m_content; +}; + +class TimelineAction: public PebblePacket +{ +public: + enum Type { + TypeAncsDismiss = 1, + TypeGeneric = 2, + TypeResponse = 3, + TypeDismiss = 4, + TypeHTTP = 5, + TypeSnooze = 6, + TypeOpenWatchApp = 7, + TypeEmpty = 8, + TypeRemove = 9, + TypeOpenPin = 10 + }; + TimelineAction(quint8 actionId, Type type, const QList &attributes = QList()); + void appendAttribute(const TimelineAttribute &attribute); + + QByteArray serialize() const override { + QByteArray ret; + ret.append(m_actionId); + ret.append((quint8)m_type); + ret.append(m_attributes.count()); + foreach (const TimelineAttribute &attr, m_attributes) { + ret.append(attr.serialize()); + } + return ret; + } + +private: + quint8 m_actionId; + Type m_type; + QList m_attributes; +}; + +class TimelineItem: public PebblePacket +{ +public: + enum Type { + TypeNotification = 1, + TypePin = 2, + TypeReminder = 3 + }; + + // TODO: this is probably not complete and maybe even wrong. + enum Flag { + FlagNone = 0x00, + FlagSingleEvent = 0x01, + FlagTimeInUTC = 0x02, + FlagAllDay = 0x04 + }; + Q_DECLARE_FLAGS(Flags, Flag) + + // TODO: This is not complete + enum Layout { + LayoutGenericPin = 0x01, + LayoutCalendar = 0x02 + }; + + TimelineItem(Type type, TimelineItem::Flags flags = FlagNone, const QDateTime ×tamp = QDateTime::currentDateTime(), quint16 duration = 0); + TimelineItem(const QUuid &uuid, Type type, Flags flags = FlagNone, const QDateTime ×tamp = QDateTime::currentDateTime(), quint16 duration = 0); + + QUuid itemId() const; + + void setLayout(quint8 layout); + void setFlags(Flags flags); + + void appendAttribute(const TimelineAttribute &attribute); + void appendAction(const TimelineAction &action); + + QList attributes() const; + QList actions() const; + + QByteArray serialize() const override; + +private: + QUuid m_itemId; + QUuid m_parentId; + QDateTime m_timestamp; + quint16 m_duration = 0; + Type m_type; + Flags m_flags; // quint16 + quint8 m_layout = 0x01; // TODO: find out what this is about + QList m_attributes; + QList m_actions; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(TimelineItem::Flags) + +#endif // TIMELINEITEM_H diff --git a/daemon/watchconnector.cpp b/daemon/watchconnector.cpp index 5f15ee6..4ed8130 100644 --- a/daemon/watchconnector.cpp +++ b/daemon/watchconnector.cpp @@ -10,6 +10,7 @@ #include "unpacker.h" #include "watchconnector.h" +#include "timelineitem.h" static const int RECONNECT_TIMEOUT = 500; //ms static const bool PROTOCOL_DEBUG = false; @@ -600,7 +601,6 @@ QString WatchConnector::timeStamp() void WatchConnector::sendNotification(uint lead, QString sender, QString data, QString subject) { - qCDebug(l) << _versions.main.version; if (_versions.main.version < "v3.0") { QStringList tmp; tmp.append(sender); @@ -613,80 +613,71 @@ void WatchConnector::sendNotification(uint lead, QString sender, QString data, Q sendMessage(watchNOTIFICATION, res); } else { - int source; + TimelineAttribute::IconID iconId = TimelineAttribute::IconIDDefaultBell; + TimelineAttribute::Color color = TimelineAttribute::ColorRed; + QString muteName; switch (lead) { - case leadEMAIL: - source = 19; - break; case leadFACEBOOK: - source = 11; + iconId = TimelineAttribute::IconIDFacebook; + color = TimelineAttribute::ColorBlue; + muteName = "facebook"; break; - case leadSMS: - source = 45; + case leadHANGOUTS: + iconId = TimelineAttribute::IconIDHangout; + color = TimelineAttribute::ColorGreen; + muteName = "Hangout"; + break; + case leadMISSEDCALL: + iconId = TimelineAttribute::IconIDDefaultMissedCall; + muteName = "call notifications"; + break; + case leadTELEGRAM: + iconId = TimelineAttribute::IconIDTelegram; + color = TimelineAttribute::ColorLightBlue; + muteName = "Telegram"; break; case leadTWITTER: - source = 6; + iconId = TimelineAttribute::IconIDTwitter; + color = TimelineAttribute::ColorBlue2; + muteName = "Twitter"; + break; + case leadWHATSAPP: + iconId = TimelineAttribute::IconIDWhatsApp; + color = TimelineAttribute::ColorGreen; + muteName = "WhatsApp"; break; + case leadSMS: + muteName = "SMS"; + iconId = TimelineAttribute::IconIDDefaultBell; + break; + case leadEMAIL: default: - source = 1; + muteName = "e mails"; + iconId = TimelineAttribute::IconIDDefaultBell; + break; } - int attributesCount = 0; - QByteArray attributes; - - attributesCount++; - QByteArray senderBytes = sender.left(64).toUtf8(); - attributes.append(0x01); // id = title - attributes.append(senderBytes.length() & 0xFF); attributes.append(((senderBytes.length() >> 8) & 0xFF)); // length - attributes.append(senderBytes); // content - - attributesCount++; - QByteArray subjectBytes = (subject.isEmpty() ? data : subject).left(64).toUtf8(); - attributes.append(0x02); // id = subtitle - attributes.append(subjectBytes.length() & 0xFF); attributes.append((subjectBytes.length() >> 8) & 0xFF); // length - attributes.append(subjectBytes); //content - - if (!data.isEmpty()) { - attributesCount++; - QByteArray dataBytes = data.left(512).toUtf8(); - attributes.append(0x03); // id = body - attributes.append(dataBytes.length() & 0xFF); attributes.append((dataBytes.length() >> 8) & 0xFF); // length - attributes.append(dataBytes); // content - } + QUuid itemUuid = QUuid::createUuid(); + TimelineItem timelineItem(itemUuid, TimelineItem::TypeNotification); + timelineItem.setFlags(TimelineItem::FlagSingleEvent); + + TimelineAttribute titleAttribute(TimelineAttribute::TypeTitle, sender.left(64).toUtf8()); + timelineItem.appendAttribute(titleAttribute); + + TimelineAttribute subjectAttribute(TimelineAttribute::TypeSubtitle, subject.left(64).toUtf8()); + timelineItem.appendAttribute(subjectAttribute); - attributesCount++; - attributes.append(0x04); // id = tinyicon - attributes.append(0x04); attributes.append('\0'); // length - attributes.append(source); attributes.append('\0'); attributes.append('\0'); attributes.append('\0'); // content - - - QByteArray actions; - actions.append('\0'); // action id - actions.append(0x04); // type = dismiss - actions.append(0x01); // attributes length = 1 - actions.append(0x01); // attribute id = title - actions.append(0x07); actions.append('\0'); // attribute length - actions.append("Dismiss"); // attribute content - - - QByteArray itemId = QUuid::createUuid().toRfc4122(); - int time = QDateTime::currentMSecsSinceEpoch() / 1000; - QByteArray item; - item.append(itemId); // item id - item.append(QUuid().toRfc4122()); // parent id - item.append(time & 0xFF); item.append((time >> 8) & 0xFF); item.append((time >> 16) & 0xFF); item.append((time >> 24) & 0xFF); // timestamp - item.append('\0'); item.append('\0'); // duration - item.append(0x01); // type: notification - item.append('\0'); item.append('\0'); // flags - item.append(0x01); // layout - - int length = attributes.length() + actions.length(); - item.append(length & 0xFF); item.append((length >> 8) & 0xFF); // data length - item.append(attributesCount); // attributes count - item.append(0x01); // actions count - item.append(attributes); - item.append(actions); + TimelineAttribute bodyAttribute(TimelineAttribute::TypeBody, data.toUtf8()); + timelineItem.appendAttribute(bodyAttribute); + TimelineAttribute iconAttribute(TimelineAttribute::TypeTinyIcon, iconId); + timelineItem.appendAttribute(iconAttribute); + + TimelineAttribute colorAttribute(TimelineAttribute::TypeColor, color); + timelineItem.appendAttribute(colorAttribute); + + QByteArray item = timelineItem.serialize(); + QByteArray itemId = itemUuid.toRfc4122(); int token = (qrand() % ((int)pow(2, 16) - 2)) + 1; QByteArray blob; blob.append(0x01); // command = insert @@ -696,10 +687,8 @@ void WatchConnector::sendNotification(uint lead, QString sender, QString data, Q blob.append(itemId); // key blob.append(item.length() & 0xFF); blob.append((item.length() >> 8) & 0xFF); // value length blob.append(item); - sendMessage(watchBLOB_DB, blob); } - } void WatchConnector::sendSMSNotification(QString sender, QString data) @@ -722,6 +711,21 @@ void WatchConnector::sendEmailNotification(QString sender, QString data, QString sendNotification(leadEMAIL, sender, data, subject); } +void WatchConnector::sendWhatsappNotification(QString sender, QString data) +{ + sendNotification(leadWHATSAPP, sender, data, ""); +} + +void WatchConnector::sendTelegramNotification(QString sender, QString data) +{ + sendNotification(leadTELEGRAM, sender, data, ""); +} + +void WatchConnector::sendHangoutsNotification(QString sender, QString data) +{ + sendNotification(leadHANGOUTS, sender, data, ""); +} + void WatchConnector::sendMusicNowPlaying(QString artist, QString album, QString track) { QStringList tmp; diff --git a/daemon/watchconnector.h b/daemon/watchconnector.h index 83e065c..de2ce1a 100644 --- a/daemon/watchconnector.h +++ b/daemon/watchconnector.h @@ -1,294 +1,316 @@ -#ifndef WATCHCONNECTOR_H -#define WATCHCONNECTOR_H - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -class WatchConnector : public QObject -{ - Q_OBJECT - QLoggingCategory l; - - Q_ENUMS(Endpoint) - - Q_PROPERTY(QString name READ name NOTIFY pebbleChanged) - Q_PROPERTY(QString connected READ isConnected NOTIFY connectedChanged) - -public: - enum Endpoint { - watchTIME = 11, - watchVERSION = 16, - watchPHONE_VERSION = 17, - watchSYSTEM_MESSAGE = 18, - watchMUSIC_CONTROL = 32, - watchPHONE_CONTROL = 33, - watchAPPLICATION_MESSAGE = 48, - watchLAUNCHER = 49, - watchLOGS = 2000, - watchPING = 2001, - watchLOG_DUMP = 2002, - watchRESET = 2003, - watchAPP = 2004, - watchAPP_LOGS = 2006, - watchNOTIFICATION = 3000, - watchEXTENSIBLE_NOTIFS = 3010, // Deprecated in 3.x - watchRESOURCE = 4000, - watchFACTORY_SETTINGS = 5001, - watchAPP_MANAGER = 6000, // Deprecated in 3.x - watchAPP_FETCH = 6001, // New in 3.x - watchDATA_LOGGING = 6778, - watchSCREENSHOT = 8000, - watchFILE_MANAGER = 8181, - watchCORE_DUMP = 9000, - watchAUDIO = 10000, // New in 3.x - watchBLOB_DB = 45531, // New in 3.x - watchPUTBYTES = 48879 - }; - enum { - callANSWER = 1, - callHANGUP = 2, - callGET_STATE = 3, - callINCOMING = 4, - callOUTGOING = 5, - callMISSED = 6, - callRING = 7, - callSTART = 8, - callEND = 9 - }; - enum MusicControl { - musicPLAY_PAUSE = 1, - musicPAUSE = 2, - musicPLAY = 3, - musicNEXT = 4, - musicPREVIOUS = 5, - musicVOLUME_UP = 6, - musicVOLUME_DOWN = 7, - musicGET_NOW_PLAYING = 8, - musicSEND_NOW_PLAYING = 9 - }; - enum SystemMessage { - systemFIRMWARE_AVAILABLE = 0, - systemFIRMWARE_START = 1, - systemFIRMWARE_COMPLETE = 2, - systemFIRMWARE_FAIL = 3, - systemFIRMWARE_UP_TO_DATE = 4, - systemFIRMWARE_OUT_OF_DATE = 5, - systemBLUETOOTH_START_DISCOVERABLE = 6, - systemBLUETOOTH_END_DISCOVERABLE = 7 - }; - enum AppManager { - appmgrGET_APPBANK_STATUS = 1, - appmgrREMOVE_APP = 2, - appmgrREFRESH_APP = 3, - appmgrGET_APPBANK_UUIDS = 5 - }; - enum AppMessage { - appmsgPUSH = 1, - appmsgREQUEST = 2, - appmsgACK = 0xFF, - appmsgNACK = 0x7F - }; - enum DataLogMessage { - datalogOPEN = 1, - datalogDATA = 2, - datalogCLOSE = 3, - datalogTIMEOUT = 7 - }; - enum { - launcherSTARTED = 1, - launcherSTOPPED = 0 - }; - enum { - leadEMAIL = 0, - leadSMS = 1, - leadFACEBOOK = 2, - leadTWITTER = 3, - leadNOW_PLAYING_DATA = 16 - }; - enum { - sessionCapGAMMA_RAY = 0x80000000 - }; - enum { - remoteCapTELEPHONY = 16, - remoteCapSMS = 32, - remoteCapGPS = 64, - remoteCapBTLE = 128, - remoteCapCAMERA_REAR = 256, - remoteCapACCEL = 512, - remoteCapGYRO = 1024, - remoteCapCOMPASS = 2048 - }; - enum { - osUNKNOWN = 0, - osIOS = 1, - osANDROID = 2, - osOSX = 3, - osLINUX = 4, - osWINDOWS = 5 - }; - enum UploadType { - uploadFIRMWARE = 1, - uploadRECOVERY = 2, - uploadSYS_RESOURCES = 3, - uploadRESOURCES = 4, - uploadBINARY = 5, - uploadFILE = 6, - uploadWORKER = 7 - }; - enum PutBytesCommand { - putbytesINIT = 1, - putbytesSEND = 2, - putbytesCOMMIT = 3, - putbytesABORT = 4, - putbytesCOMPLETE = 5 - }; - - enum HardwareRevision { - HR_UNKNOWN = 0, - TINTIN_EV1 = 1, - TINTIN_EV2 = 2, - TINTIN_EV2_3 = 3, - TINTIN_EV2_4 = 4, - TINTIN_V1_5 = 5, - BIANCA = 6, - SNOWY_EVT2 = 7, - SNOWY_DVT = 8, - SPALDING_EVT = 9, - BOBBY_SMILES = 10, - SPALDING = 11, - - TINTIN_BB = 0xFF, - TINTIN_BB2 = 0xFE, - SNOWY_BB = 0xFD, - SNOWY_BB2 = 0xFC, - SPALDING_BB2 = 0xFB - }; - enum HardwarePlatform { - HP_UNKNOWN = 0, - APLITE, - BASALT, - CHALK - }; - typedef QPair HWMap; - QMap hardwareMapping; - - struct SoftwareVersion { - QDateTime build; - QString version; - QString commit; - bool is_recovery; - HardwareRevision hw_revision; - QString hw_string; - quint8 metadata_version; - - QVariantMap toMap() const; - }; - - struct WatchVersions { - SoftwareVersion main; - SoftwareVersion safe; - QDateTime bootLoaderBuild; - QString hardwareRevision; - QString hardwarePlatform; - QString serialNumber; - QByteArray address; - - QVariantMap toMap() const; - void clear(); - bool isEmpty() const; - }; - - typedef QMap Dict; - enum DictItemType { - typeBYTES, - typeSTRING, - typeUINT, - typeINT - }; - - typedef std::function EndpointHandlerFunc; - - explicit WatchConnector(QObject *parent = 0); - virtual ~WatchConnector(); - - inline bool isConnected() const { return is_connected; } - inline QString name() const { return pebbles.keys(address()).at(0); } - inline QBluetoothAddress address() const { return socket != nullptr ? socket->peerAddress() : QBluetoothAddress(); } - inline WatchVersions versions() const { return _versions; } - - void setEndpointHandler(uint endpoint, const EndpointHandlerFunc &func); - void clearEndpointHandler(uint endpoint); - - static QString timeStamp(); - static QString decodeEndpoint(uint val); - -signals: - void pebbleChanged(); - void versionsChanged(); - void connectedChanged(); - -public slots: - bool findPebbles(); - void scheduleReconnect(); - void connect(); - void disconnect(); - - void sendMessage(uint endpoint, const QByteArray &data, const EndpointHandlerFunc &callback = EndpointHandlerFunc()); - void ping(uint cookie); - void systemMessage(SystemMessage msg, const EndpointHandlerFunc &callback = EndpointHandlerFunc()); - void time(); - - void sendNotification(uint lead, QString sender, QString data, QString subject); - void sendSMSNotification(QString sender, QString data); - void sendEmailNotification(QString sender, QString data, QString subject); - void sendFacebookNotification(QString sender, QString data); - void sendTwitterNotification(QString sender, QString data); - void sendMusicNowPlaying(QString artist, QString album, QString track); - void sendPhoneVersion(); - void sendFirmwareState(bool ok); - - void buildData(QByteArray &res, QStringList data); - QByteArray buildMessageData(uint lead, QStringList data); - - void phoneControl(char act, uint cookie, QStringList datas); - void ring(QString number, QString name, bool incoming=true, uint cookie=0); - void startPhoneCall(uint cookie=0); - void endPhoneCall(uint cookie=0); - -private slots: - void onReadSocket(); - void onBytesWritten(qint64); - void onConnected(); - void onDisconnected(); - void onError(QBluetoothSocket::SocketError error); - -private: - void sendData(const QByteArray &data); - bool dispatchMessage(uint endpoint, const QByteArray &data); - - QPointer socket; - QHash> tmpHandlers; - QHash handlers; - bool is_connected; - QByteArray writeData; - QTimer reconnectTimer; - QTimer timeSyncTimer; - QMap pebbles; - int currentPebble; - quint64 _last_address; - WatchVersions _versions; - HardwarePlatform platform; -}; - -QDebug operator<< (QDebug d, const WatchConnector::SoftwareVersion &ver); -QDebug operator<< (QDebug d, const WatchConnector::WatchVersions &ver); - -#endif // WATCHCONNECTOR_H +#ifndef WATCHCONNECTOR_H +#define WATCHCONNECTOR_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class PebblePacket { +public: + PebblePacket() {} + virtual ~PebblePacket() = default; + virtual QByteArray serialize() const = 0; + QByteArray packString(const QString &string) const { + QByteArray tmp = string.left(0xEF).toUtf8(); + QByteArray ret; + ret.append((tmp.length() + 1) & 0xFF); + ret.append(tmp); + ret.append('\0'); + return ret; + } +}; + +class WatchConnector : public QObject +{ + Q_OBJECT + QLoggingCategory l; + + Q_ENUMS(Endpoint) + + Q_PROPERTY(QString name READ name NOTIFY pebbleChanged) + Q_PROPERTY(QString connected READ isConnected NOTIFY connectedChanged) + +public: + enum Endpoint { + watchTIME = 11, + watchVERSION = 16, + watchPHONE_VERSION = 17, + watchSYSTEM_MESSAGE = 18, + watchMUSIC_CONTROL = 32, + watchPHONE_CONTROL = 33, + watchAPPLICATION_MESSAGE = 48, + watchLAUNCHER = 49, + watchLOGS = 2000, + watchPING = 2001, + watchLOG_DUMP = 2002, + watchRESET = 2003, + watchAPP = 2004, + watchAPP_LOGS = 2006, + watchNOTIFICATION = 3000, + watchEXTENSIBLE_NOTIFS = 3010, // Deprecated in 3.x + watchRESOURCE = 4000, + watchFACTORY_SETTINGS = 5001, + watchAPP_MANAGER = 6000, // Deprecated in 3.x + watchAPP_FETCH = 6001, // New in 3.x + watchDATA_LOGGING = 6778, + watchSCREENSHOT = 8000, + watchFILE_MANAGER = 8181, + watchCORE_DUMP = 9000, + watchAUDIO = 10000, // New in 3.x + watchBLOB_DB = 45531, // New in 3.x + watchPUTBYTES = 48879 + }; + enum { + callANSWER = 1, + callHANGUP = 2, + callGET_STATE = 3, + callINCOMING = 4, + callOUTGOING = 5, + callMISSED = 6, + callRING = 7, + callSTART = 8, + callEND = 9 + }; + enum MusicControl { + musicPLAY_PAUSE = 1, + musicPAUSE = 2, + musicPLAY = 3, + musicNEXT = 4, + musicPREVIOUS = 5, + musicVOLUME_UP = 6, + musicVOLUME_DOWN = 7, + musicGET_NOW_PLAYING = 8, + musicSEND_NOW_PLAYING = 9 + }; + enum SystemMessage { + systemFIRMWARE_AVAILABLE = 0, + systemFIRMWARE_START = 1, + systemFIRMWARE_COMPLETE = 2, + systemFIRMWARE_FAIL = 3, + systemFIRMWARE_UP_TO_DATE = 4, + systemFIRMWARE_OUT_OF_DATE = 5, + systemBLUETOOTH_START_DISCOVERABLE = 6, + systemBLUETOOTH_END_DISCOVERABLE = 7 + }; + enum AppManager { + appmgrGET_APPBANK_STATUS = 1, + appmgrREMOVE_APP = 2, + appmgrREFRESH_APP = 3, + appmgrGET_APPBANK_UUIDS = 5 + }; + enum AppMessage { + appmsgPUSH = 1, + appmsgREQUEST = 2, + appmsgACK = 0xFF, + appmsgNACK = 0x7F + }; + enum DataLogMessage { + datalogOPEN = 1, + datalogDATA = 2, + datalogCLOSE = 3, + datalogTIMEOUT = 7 + }; + enum { + launcherSTARTED = 1, + launcherSTOPPED = 0 + }; + enum { + leadEMAIL = 0, + leadSMS = 1, + leadFACEBOOK = 2, + leadTWITTER = 3, + leadTELEGRAM = 4, + leadHANGOUTS = 5, + leadWHATSAPP = 6, + leadMISSEDCALL = 7, + leadNOW_PLAYING_DATA = 16 + }; + enum { + sessionCapGAMMA_RAY = 0x80000000 + }; + enum { + remoteCapTELEPHONY = 16, + remoteCapSMS = 32, + remoteCapGPS = 64, + remoteCapBTLE = 128, + remoteCapCAMERA_REAR = 256, + remoteCapACCEL = 512, + remoteCapGYRO = 1024, + remoteCapCOMPASS = 2048 + }; + enum { + osUNKNOWN = 0, + osIOS = 1, + osANDROID = 2, + osOSX = 3, + osLINUX = 4, + osWINDOWS = 5 + }; + enum UploadType { + uploadFIRMWARE = 1, + uploadRECOVERY = 2, + uploadSYS_RESOURCES = 3, + uploadRESOURCES = 4, + uploadBINARY = 5, + uploadFILE = 6, + uploadWORKER = 7 + }; + enum PutBytesCommand { + putbytesINIT = 1, + putbytesSEND = 2, + putbytesCOMMIT = 3, + putbytesABORT = 4, + putbytesCOMPLETE = 5 + }; + + enum HardwareRevision { + HR_UNKNOWN = 0, + TINTIN_EV1 = 1, + TINTIN_EV2 = 2, + TINTIN_EV2_3 = 3, + TINTIN_EV2_4 = 4, + TINTIN_V1_5 = 5, + BIANCA = 6, + SNOWY_EVT2 = 7, + SNOWY_DVT = 8, + SPALDING_EVT = 9, + BOBBY_SMILES = 10, + SPALDING = 11, + + TINTIN_BB = 0xFF, + TINTIN_BB2 = 0xFE, + SNOWY_BB = 0xFD, + SNOWY_BB2 = 0xFC, + SPALDING_BB2 = 0xFB + }; + enum HardwarePlatform { + HP_UNKNOWN = 0, + APLITE, + BASALT, + CHALK + }; + typedef QPair HWMap; + QMap hardwareMapping; + + struct SoftwareVersion { + QDateTime build; + QString version; + QString commit; + bool is_recovery; + HardwareRevision hw_revision; + QString hw_string; + quint8 metadata_version; + + QVariantMap toMap() const; + }; + + struct WatchVersions { + SoftwareVersion main; + SoftwareVersion safe; + QDateTime bootLoaderBuild; + QString hardwareRevision; + QString hardwarePlatform; + QString serialNumber; + QByteArray address; + + QVariantMap toMap() const; + void clear(); + bool isEmpty() const; + }; + + typedef QMap Dict; + enum DictItemType { + typeBYTES, + typeSTRING, + typeUINT, + typeINT + }; + + typedef std::function EndpointHandlerFunc; + + explicit WatchConnector(QObject *parent = 0); + virtual ~WatchConnector(); + + inline bool isConnected() const { return is_connected; } + inline QString name() const { return pebbles.keys(address()).at(0); } + inline QBluetoothAddress address() const { return socket != nullptr ? socket->peerAddress() : QBluetoothAddress(); } + inline WatchVersions versions() const { return _versions; } + + void setEndpointHandler(uint endpoint, const EndpointHandlerFunc &func); + void clearEndpointHandler(uint endpoint); + + static QString timeStamp(); + static QString decodeEndpoint(uint val); + +signals: + void pebbleChanged(); + void versionsChanged(); + void connectedChanged(); + +public slots: + bool findPebbles(); + void scheduleReconnect(); + void connect(); + void disconnect(); + + void sendMessage(uint endpoint, const QByteArray &data, const EndpointHandlerFunc &callback = EndpointHandlerFunc()); + void ping(uint cookie); + void systemMessage(SystemMessage msg, const EndpointHandlerFunc &callback = EndpointHandlerFunc()); + void time(); + + void sendNotification(uint lead, QString sender, QString data, QString subject); + void sendSMSNotification(QString sender, QString data); + void sendEmailNotification(QString sender, QString data, QString subject); + void sendFacebookNotification(QString sender, QString data); + void sendTwitterNotification(QString sender, QString data); + void sendTelegramNotification(QString sender, QString data); + void sendHangoutsNotification(QString sender, QString data); + void sendWhatsappNotification(QString sender, QString data); + void sendMusicNowPlaying(QString artist, QString album, QString track); + void sendPhoneVersion(); + void sendFirmwareState(bool ok); + + void buildData(QByteArray &res, QStringList data); + QByteArray buildMessageData(uint lead, QStringList data); + + void phoneControl(char act, uint cookie, QStringList datas); + void ring(QString number, QString name, bool incoming=true, uint cookie=0); + void startPhoneCall(uint cookie=0); + void endPhoneCall(uint cookie=0); + +private slots: + void onReadSocket(); + void onBytesWritten(qint64); + void onConnected(); + void onDisconnected(); + void onError(QBluetoothSocket::SocketError error); + +private: + void sendData(const QByteArray &data); + bool dispatchMessage(uint endpoint, const QByteArray &data); + + QPointer socket; + QHash> tmpHandlers; + QHash handlers; + bool is_connected; + QByteArray writeData; + QTimer reconnectTimer; + QTimer timeSyncTimer; + QMap pebbles; + int currentPebble; + quint64 _last_address; + WatchVersions _versions; + HardwarePlatform platform; +}; + +QDebug operator<< (QDebug d, const WatchConnector::SoftwareVersion &ver); +QDebug operator<< (QDebug d, const WatchConnector::WatchVersions &ver); + +#endif // WATCHCONNECTOR_H -- cgit v1.2.3