summaryrefslogtreecommitdiff
path: root/daemon/manager.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'daemon/manager.cpp')
-rw-r--r--daemon/manager.cpp1185
1 files changed, 607 insertions, 578 deletions
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 <QDebug>
-#include <QtContacts/QContact>
-#include <QtContacts/QContactPhoneNumber>
-
-#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<QString, QString> 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<QContact> &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<bool> 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<QString&>(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<QIODevice> 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<QIODevice> 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<QUuid> 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 <QDebug>
+#include <QtContacts/QContact>
+#include <QtContacts/QContactPhoneNumber>
+
+#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<QString, QString> 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<QContact> &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<bool> 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<QString&>(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<QIODevice> 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<QIODevice> 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<QUuid> 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");
+ }
+}