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. --- daemon/manager.cpp | 1185 +++++++++++++++++++++++++++------------------------- 1 file changed, 607 insertions(+), 578 deletions(-) (limited to 'daemon/manager.cpp') 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"); + } +} -- cgit v1.2.3