#include #include #include #include "manager.h" #include "watch_adaptor.h" Manager::Manager(Settings *settings, QObject *parent) : QObject(parent), settings(settings), proxy(new PebbledProxy(this)), watch(new WatchConnector(this)), dbus(new DBusConnector(this)), apps(new AppManager(this)), voice(new VoiceCallManager(settings, this)), notifications(new NotificationManager(settings, this)), music(new MusicManager(watch, this)), datalog(new DataLogManager(watch, this)), appmsg(new AppMsgManager(apps, watch, this)), js(new JSKitManager(apps, appmsg, this)), notification(MNotification::DeviceEvent) { connect(settings, SIGNAL(valueChanged(QString)), SLOT(onSettingChanged(const QString&))); connect(settings, SIGNAL(valuesChanged()), SLOT(onSettingsChanged())); //connect(settings, SIGNAL(silentWhenConnectedChanged(bool)), SLOT(onSilentWhenConnectedChanged(bool))); // 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::messageReceived, this, &Manager::onAppMessage); 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(dbus, &DBusConnector::pebbleChanged, proxy, &PebbledProxy::NameChanged); connect(dbus, &DBusConnector::pebbleChanged, proxy, &PebbledProxy::AddressChanged); connect(watch, &WatchConnector::connectedChanged, proxy, &PebbledProxy::ConnectedChanged); QString currentProfile = getCurrentProfile(); defaultProfile = currentProfile.isEmpty() ? "ambience" : currentProfile; connect(watch, SIGNAL(connectedChanged()), SLOT(applyProfile())); // Music Control interface session.connect("", "/org/mpris/MediaPlayer2", "org.freedesktop.DBus.Properties", "PropertiesChanged", this, SLOT(onMprisPropertiesChanged(QString,QMap,QStringList))); connect(this, SIGNAL(mprisMetadataChanged(QVariantMap)), music, SLOT(onMprisMetadataChanged(QVariantMap))); // Set BT icon for notification notification.setImage("icon-system-bluetooth-device"); if (btDevice.isValid()) { logger()->debug() << "BT local name:" << btDevice.name(); connect(dbus, SIGNAL(pebbleChanged()), SLOT(onPebbleChanged())); dbus->findPebble(); } } Manager::~Manager() { } void Manager::onSettingChanged(const QString &key) { logger()->debug() << __FUNCTION__ << key << ":" << settings->property(qPrintable(key)); } void Manager::onSettingsChanged() { logger()->warn() << __FUNCTION__ << "Not implemented!"; } void Manager::onPebbleChanged() { const QVariantMap & pebble = dbus->pebble(); QString name = pebble["Name"].toString(); if (name.isEmpty()) { logger()->debug() << "Pebble gone"; } else { watch->deviceConnect(name, pebble["Address"].toString()); } } void Manager::onConnectedChanged() { QString message = QString("%1 %2") .arg(watch->name().isEmpty() ? "Pebble" : watch->name()) .arg(watch->isConnected() ? "connected" : "disconnected"); logger()->debug() << message; if (notification.isPublished()) notification.remove(); notification.setBody(message); if (!notification.publish()) { logger()->debug() << "Failed publishing notification"; } if (watch->isConnected()) { QString mpris = this->mpris(); if (not mpris.isEmpty()) { QDBusReply Metadata = QDBusConnection::sessionBus().call( QDBusMessage::createMethodCall(mpris, "/org/mpris/MediaPlayer2", "org.freedesktop.DBus.Properties", "Get") << "org.mpris.MediaPlayer2.Player" << "Metadata"); if (Metadata.isValid()) { setMprisMetadata(Metadata.value().variant().value()); } else { logger()->error() << Metadata.error().message(); setMprisMetadata(QVariantMap()); } } } } void Manager::onActiveVoiceCallChanged() { logger()->debug() << "Manager::onActiveVoiceCallChanged()"; QVariant incomingCallNotification = settings->property("incomingCallNotification"); if (incomingCallNotification.isValid() && !incomingCallNotification.toBool()) { logger()->debug() << "Ignoring ActiveVoiceCallChanged because of setting!"; return; } VoiceCallHandler* handler = voice->activeVoiceCall(); if (handler) { connect(handler, SIGNAL(statusChanged()), SLOT(onActiveVoiceCallStatusChanged())); connect(handler, SIGNAL(destroyed()), SLOT(onActiveVoiceCallStatusChanged())); return; } } void Manager::onActiveVoiceCallStatusChanged() { VoiceCallHandler* handler = voice->activeVoiceCall(); if (!handler) { logger()->debug() << "ActiveVoiceCall destroyed"; watch->endPhoneCall(); return; } logger()->debug() << "handlerId:" << handler->handlerId() << "providerId:" << handler->providerId() << "status:" << handler->status() << "statusText:" << handler->statusText() << "lineId:" << handler->lineId() << "incoming:" << handler->isIncoming(); if (!watch->isConnected()) { logger()->debug() << "Watch is not connected"; return; } switch ((VoiceCallHandler::VoiceCallStatus)handler->status()) { case VoiceCallHandler::STATUS_ALERTING: case VoiceCallHandler::STATUS_DIALING: logger()->debug() << "Tell outgoing:" << handler->lineId(); watch->ring(handler->lineId(), findPersonByNumber(handler->lineId()), false); break; case VoiceCallHandler::STATUS_INCOMING: case VoiceCallHandler::STATUS_WAITING: logger()->debug() << "Tell incoming:" << handler->lineId(); watch->ring(handler->lineId(), findPersonByNumber(handler->lineId())); break; case VoiceCallHandler::STATUS_NULL: case VoiceCallHandler::STATUS_DISCONNECTED: logger()->debug() << "Endphone"; watch->endPhoneCall(); break; case VoiceCallHandler::STATUS_ACTIVE: logger()->debug() << "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) { logger()->error() << "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::onMprisPropertiesChanged(QString interface, QMap changed, QStringList invalidated) { logger()->debug() << interface << changed << invalidated; if (changed.contains("Metadata")) { setMprisMetadata(changed.value("Metadata").value()); } if (changed.contains("PlaybackStatus")) { QString PlaybackStatus = changed.value("PlaybackStatus").toString(); if (PlaybackStatus == "Stopped") { setMprisMetadata(QVariantMap()); } } lastSeenMpris = message().service(); logger()->debug() << "lastSeenMpris:" << lastSeenMpris; } QString Manager::mpris() const { const QStringList &services = dbus->services(); if (not lastSeenMpris.isEmpty() && services.contains(lastSeenMpris)) return lastSeenMpris; foreach (QString service, services) if (service.startsWith("org.mpris.MediaPlayer2.")) return service; return QString(); } void Manager::setMprisMetadata(QDBusArgument metadata) { if (metadata.currentType() == QDBusArgument::MapType) { metadata >> mprisMetadata; emit mprisMetadataChanged(mprisMetadata); } } void Manager::setMprisMetadata(QVariantMap metadata) { mprisMetadata = metadata; emit mprisMetadataChanged(mprisMetadata); } QString Manager::getCurrentProfile() const { QDBusReply profile = QDBusConnection::sessionBus().call( QDBusMessage::createMethodCall("com.nokia.profiled", "/com/nokia/profiled", "com.nokia.profiled", "get_profile")); if (profile.isValid()) { QString currentProfile = profile.value(); logger()->debug() << "Got profile" << currentProfile; return currentProfile; } logger()->error() << profile.error().message(); return QString(); } void Manager::applyProfile() { QString currentProfile = getCurrentProfile(); QString newProfile; if (settings->property("silentWhenConnected").toBool()) { if (watch->isConnected() && currentProfile != "silent") { newProfile = "silent"; defaultProfile = currentProfile; } if (!watch->isConnected() && currentProfile == "silent" && defaultProfile != "silent") { newProfile = defaultProfile; } } else if (currentProfile != defaultProfile) { newProfile = defaultProfile; } 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()) { logger()->error() << "Unable to set profile" << newProfile; } } else { logger()->error() << res.error().message(); } } } 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)) { logger()->warn() << "Error creaing ICU Transliterator \"Any-Latin; Latin-ASCII\":" << u_errorName(status); } } if (!transliterator.isNull()) { logger()->debug() << "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); logger()->debug() << "String after transliteration:" << text; } } void Manager::onAppNotification(const QUuid &uuid, const QString &title, const QString &body) { watch->sendSMSNotification(title, body); } void Manager::onAppMessage(const QUuid &uuid, const QVariantMap &data) { emit proxy->AppMessage(uuid.toString(), data); } 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 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(); if (manager()->currentAppUuid != uuid) { sendErrorReply(msg.interface() + ".Error.AppNotRunning", "The requested app is not currently opened in the watch"); return QString(); } if (!manager()->js->isJSKitAppRunning()) { 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 = connect(manager()->js, &JSKitManager::appOpenUrl, [this,msg,c](const QUrl &url) { // Workaround: due to a GCC bug we can't capture the uuid parameter, but we can extract // it again from the original message arguments. 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"); connection().send(reply); } else { QDBusMessage reply = msg.createReply(QVariant::fromValue(url.toString())); connection().send(reply); } disconnect(c); }); // TODO: JS script may fail, never call OpenURL, or something like that // In those cases we may leak the above connection // 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); }