summaryrefslogtreecommitdiff
path: root/rockworkd/platformintegration/sailfish
diff options
context:
space:
mode:
Diffstat (limited to 'rockworkd/platformintegration/sailfish')
-rw-r--r--rockworkd/platformintegration/sailfish/callchannelobserver.cpp166
-rw-r--r--rockworkd/platformintegration/sailfish/callchannelobserver.h74
-rw-r--r--rockworkd/platformintegration/sailfish/organizeradapter.cpp97
-rw-r--r--rockworkd/platformintegration/sailfish/organizeradapter.h44
-rw-r--r--rockworkd/platformintegration/sailfish/sailfishplatform.cpp313
-rw-r--r--rockworkd/platformintegration/sailfish/sailfishplatform.h58
-rw-r--r--rockworkd/platformintegration/sailfish/syncmonitorclient.cpp100
-rw-r--r--rockworkd/platformintegration/sailfish/syncmonitorclient.h51
-rw-r--r--rockworkd/platformintegration/sailfish/voicecallhandler.cpp372
-rw-r--r--rockworkd/platformintegration/sailfish/voicecallhandler.h96
-rw-r--r--rockworkd/platformintegration/sailfish/voicecallmanager.cpp315
-rw-r--r--rockworkd/platformintegration/sailfish/voicecallmanager.h111
12 files changed, 1797 insertions, 0 deletions
diff --git a/rockworkd/platformintegration/sailfish/callchannelobserver.cpp b/rockworkd/platformintegration/sailfish/callchannelobserver.cpp
new file mode 100644
index 0000000..a9f41f3
--- /dev/null
+++ b/rockworkd/platformintegration/sailfish/callchannelobserver.cpp
@@ -0,0 +1,166 @@
+#include "callchannelobserver.h"
+
+#include <TelepathyQt/Contact>
+#include <TelepathyQt/PendingContactInfo>
+
+#include <QContactFetchRequest>
+#include <QContactPhoneNumber>
+#include <QContactFilter>
+#include <QContactDetail>
+#include <QContactDisplayLabel>
+
+QTCONTACTS_USE_NAMESPACE
+
+TelepathyMonitor::TelepathyMonitor(QObject *parent):
+ QObject(parent)
+{
+ Tp::registerTypes();
+ QTimer::singleShot(0, this, SLOT(accountManagerSetup()));
+ QMap<QString, QString> parameters;
+ parameters.insert(QString::fromLatin1("mergePresenceChanges"), QString::fromLatin1("false"));
+ m_contactManager = new QContactManager("", parameters, this);
+}
+
+void TelepathyMonitor::hangupCall(uint cookie)
+{
+ if (m_currentCalls.contains(cookie)) {
+ m_currentCalls.value(cookie)->hangup();
+ }
+}
+
+void TelepathyMonitor::accountManagerSetup()
+{
+ m_accountManager = Tp::AccountManager::create(Tp::AccountFactory::create(QDBusConnection::sessionBus(),
+ Tp::Account::FeatureCore),
+ Tp::ConnectionFactory::create(QDBusConnection::sessionBus(),
+ Tp::Connection::FeatureCore));
+ connect(m_accountManager->becomeReady(),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(accountManagerReady(Tp::PendingOperation*)));
+}
+
+void TelepathyMonitor::accountManagerReady(Tp::PendingOperation* operation)
+{
+ if (operation->isError()) {
+ qDebug() << "TelepathyMonitor: accountManager init error.";
+ QTimer::singleShot(1000, this, SLOT(accountManagerSetup())); // again
+ return;
+ }
+ qDebug() << "Telepathy account manager ready";
+
+ foreach (const Tp::AccountPtr& account, m_accountManager->allAccounts()) {
+ connect(account->becomeReady(Tp::Account::FeatureCapabilities),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(accountReady(Tp::PendingOperation*)));
+ }
+
+ connect(m_accountManager.data(), SIGNAL(newAccount(Tp::AccountPtr)), SLOT(newAccount(Tp::AccountPtr)));
+}
+
+void TelepathyMonitor::newAccount(const Tp::AccountPtr& account)
+{
+ connect(account->becomeReady(Tp::Account::FeatureCapabilities),
+ SIGNAL(finished(Tp::PendingOperation*)),
+ SLOT(accountReady(Tp::PendingOperation*)));
+}
+
+void TelepathyMonitor::accountReady(Tp::PendingOperation* operation)
+{
+ if (operation->isError()) {
+ qDebug() << "TelepathyAccount: Operation failed (accountReady)";
+ return;
+ }
+
+ Tp::PendingReady* pendingReady = qobject_cast<Tp::PendingReady*>(operation);
+ if (pendingReady == 0) {
+ qDebug() << "Rejecting account because could not understand ready status";
+ return;
+ }
+ checkAndAddAccount(Tp::AccountPtr::qObjectCast(pendingReady->proxy()));
+}
+
+void TelepathyMonitor::onCallStarted(Tp::CallChannelPtr callChannel)
+{
+ // Haven't figured how to send outgoing calls to pebble yet... discard it
+ if (callChannel->initiatorContact()->id().isEmpty()) {
+ qWarning() << "ignoring phone call. looks like it's an outgoing one";
+ return;
+ }
+
+ m_cookie++;
+ m_currentCalls.insert(m_cookie, callChannel.data());
+ m_currentCallStates.insert(m_cookie, Tp::CallStateInitialising);
+
+ callChannel->becomeReady(Tp::CallChannel::FeatureCallState);
+
+ connect(callChannel.data(), &Tp::CallChannel::callStateChanged, this, &TelepathyMonitor::callStateChanged);
+
+ QString number = callChannel->initiatorContact()->id();
+ qDebug() << "call started" << number;
+
+ // try to match the contact info
+ QContactFetchRequest *request = new QContactFetchRequest(this);
+ request->setFilter(QContactPhoneNumber::match(number));
+
+ // lambda function to update the notification
+ QObject::connect(request, &QContactAbstractRequest::stateChanged, [this, request, number](QContactAbstractRequest::State state) {
+ qDebug() << "request returned";
+ if (!request || state != QContactAbstractRequest::FinishedState) {
+ qDebug() << "error fetching contact" << state;
+ return;
+ }
+
+ QContact contact;
+
+ // create the snap decision only after the contact match finishes
+ if (request->contacts().size() > 0) {
+ // use the first match
+ contact = request->contacts().at(0);
+
+ qDebug() << "have contact" << contact.detail<QContactDisplayLabel>().label();
+ emit this->incomingCall(m_cookie, number, contact.detail<QContactDisplayLabel>().label());
+ } else {
+ qDebug() << "unknown contact" << number;
+ emit this->incomingCall(m_cookie, number, QString());
+ }
+ });
+
+ request->setManager(m_contactManager);
+ request->start();
+}
+
+void TelepathyMonitor::callStateChanged(Tp::CallState state)
+{
+ qDebug() << "call state changed1";
+ Tp::CallChannel *channel = qobject_cast<Tp::CallChannel*>(sender());
+ uint cookie = m_currentCalls.key(channel);
+
+ qDebug() << "call state changed2" << state << "cookie:" << cookie;
+
+ switch (state) {
+ case Tp::CallStateActive:
+ emit callStarted(cookie);
+ m_currentCallStates[cookie] = Tp::CallStateActive;
+ break;
+ case Tp::CallStateEnded: {
+ Tp::CallState oldState = m_currentCallStates.value(cookie);
+ emit callEnded(cookie, oldState != Tp::CallStateActive);
+ m_currentCalls.take(cookie);
+ m_currentCallStates.take(cookie);
+ break;
+ }
+ default:
+ break;
+ }
+}
+
+void TelepathyMonitor::checkAndAddAccount(const Tp::AccountPtr& account)
+{
+ Tp::ConnectionCapabilities caps = account->capabilities();
+ // TODO: Later on we will need to filter for the right capabilities, and also allow dynamic account detection
+ // Don't check caps for now as a workaround for https://bugs.launchpad.net/ubuntu/+source/media-hub/+bug/1409125
+ // at least until we are able to find out the root cause of it (check rev 107 for the caps check)
+ auto tcm = new TelepathyCallMonitor(account);
+ connect(tcm, &TelepathyCallMonitor::callStarted, this, &TelepathyMonitor::onCallStarted);
+ m_callMonitors.append(tcm);
+}
diff --git a/rockworkd/platformintegration/sailfish/callchannelobserver.h b/rockworkd/platformintegration/sailfish/callchannelobserver.h
new file mode 100644
index 0000000..ba3415d
--- /dev/null
+++ b/rockworkd/platformintegration/sailfish/callchannelobserver.h
@@ -0,0 +1,74 @@
+#ifndef CALLCHANNELOBSERVER_H
+#define CALLCHANNELOBSERVER_H
+
+#include <TelepathyQt/AccountManager>
+#include <TelepathyQt/SimpleCallObserver>
+#include <TelepathyQt/PendingOperation>
+#include <TelepathyQt/PendingReady>
+#include <TelepathyQt/PendingAccount>
+#include <TelepathyQt/CallChannel>
+
+#include <QContactManager>
+
+QTCONTACTS_USE_NAMESPACE
+
+class TelepathyCallMonitor : public QObject
+{
+ Q_OBJECT
+public:
+ TelepathyCallMonitor(const Tp::AccountPtr& account):
+ mAccount(account),
+ mCallObserver(Tp::SimpleCallObserver::create(mAccount)) {
+ connect(mCallObserver.data(), SIGNAL(callStarted(Tp::CallChannelPtr)), SIGNAL(callStarted(Tp::CallChannelPtr)));
+// connect(mCallObserver.data(), SIGNAL(callEnded(Tp::CallChannelPtr,QString,QString)), SIGNAL(callEnded()));
+// connect(mCallObserver.data(), SIGNAL(streamedMediaCallStarted(Tp::StreamedMediaChannelPtr)), SIGNAL(offHook()));
+// connect(mCallObserver.data(), SIGNAL(streamedMediaCallEnded(Tp::StreamedMediaChannelPtr,QString,QString)), SIGNAL(onHook()));
+ }
+
+signals:
+ void callStarted(Tp::CallChannelPtr callChannel);
+ void callEnded();
+
+private:
+ Tp::AccountPtr mAccount;
+ Tp::SimpleCallObserverPtr mCallObserver;
+};
+
+class TelepathyMonitor: public QObject
+{
+ Q_OBJECT
+public:
+ TelepathyMonitor(QObject *parent = 0);
+
+ void hangupCall(uint cookie);
+
+private slots:
+ void accountManagerSetup();
+ void accountManagerReady(Tp::PendingOperation* operation);
+
+ void newAccount(const Tp::AccountPtr& account);
+ void accountReady(Tp::PendingOperation* operation);
+
+ void onCallStarted(Tp::CallChannelPtr callChannel);
+ void callStateChanged(Tp::CallState state);
+
+signals:
+ void incomingCall(uint cookie, const QString &number, const QString &name);
+ void callStarted(uint cookie);
+ void callEnded(uint cookie, bool missed);
+
+private:
+ void checkAndAddAccount(const Tp::AccountPtr& account);
+
+private:
+ Tp::AccountManagerPtr m_accountManager;
+ QList<TelepathyCallMonitor*> m_callMonitors;
+ QContactManager *m_contactManager;
+
+ QHash<uint, Tp::CallChannel*> m_currentCalls;
+ QHash<uint, Tp::CallState> m_currentCallStates;
+
+ uint m_cookie = 0;
+};
+
+#endif // CALLCHANNELOBSERVER_H
diff --git a/rockworkd/platformintegration/sailfish/organizeradapter.cpp b/rockworkd/platformintegration/sailfish/organizeradapter.cpp
new file mode 100644
index 0000000..416aad2
--- /dev/null
+++ b/rockworkd/platformintegration/sailfish/organizeradapter.cpp
@@ -0,0 +1,97 @@
+#include "organizeradapter.h"
+
+#include <QOrganizerItemFetchRequest>
+#include <QDebug>
+#include <QOrganizerEventOccurrence>
+#include <QOrganizerItemDetail>
+# include <extendedcalendar.h>
+# include <extendedstorage.h>
+
+QTORGANIZER_USE_NAMESPACE
+
+#define MANAGER "eds"
+#define MANAGER_FALLBACK "memory"
+
+OrganizerAdapter::OrganizerAdapter(QObject *parent) : QObject(parent)
+{
+ QString envManager(qgetenv("ALARM_BACKEND"));
+ if (envManager.isEmpty())
+ envManager = MANAGER;
+ if (!QOrganizerManager::availableManagers().contains(envManager)) {
+ envManager = MANAGER_FALLBACK;
+ }
+ m_manager = new QOrganizerManager(envManager);
+ m_manager->setParent(this);
+ connect(m_manager, &QOrganizerManager::dataChanged, this, &OrganizerAdapter::refresh);
+
+ mKCal::ExtendedCalendar::Ptr calendar = mKCal::ExtendedCalendar::Ptr ( new mKCal::ExtendedCalendar( QLatin1String( "UTC" ) ) );
+ mKCal::ExtendedStorage::Ptr storage = mKCal::ExtendedCalendar::defaultStorage( calendar );
+ if (storage->open()) {
+ mKCal::Notebook::List notebooks = storage->notebooks();
+ qDebug()<< "Notebooks: " + notebooks.count();
+ for (int ii = 0; ii < notebooks.count(); ++ii) {
+ if (!notebooks.at(ii)->isReadOnly()) {
+ m_calendars << CalendarInfo(normalizeCalendarName(notebooks.at(ii)->name()), notebooks.at(ii)->uid());
+ qDebug()<< "Notebook: " << notebooks.at(ii)->name() << notebooks.at(ii)->uid();
+ }
+ }
+ }
+}
+
+QString OrganizerAdapter::normalizeCalendarName(QString name)
+{
+ if (name == "qtn_caln_personal_caln") {
+ return tr("Personal");
+ }
+
+ return name;
+}
+
+void OrganizerAdapter::refresh()
+{
+ QList<CalendarEvent> items;
+ foreach (const QOrganizerItem &item, m_manager->items()) {
+ QOrganizerEvent organizerEvent(item);
+ if (organizerEvent.displayLabel().isEmpty()) {
+ continue;
+ }
+ CalendarEvent event;
+ event.setId(organizerEvent.id().toString());
+ event.setTitle(organizerEvent.displayLabel());
+ event.setDescription(organizerEvent.description());
+ event.setStartTime(organizerEvent.startDateTime());
+ event.setEndTime(organizerEvent.endDateTime());
+ event.setLocation(organizerEvent.location());
+ event.setComment(organizerEvent.comments().join(";"));
+ QStringList attendees;
+ foreach (const QOrganizerItemDetail &attendeeDetail, organizerEvent.details(QOrganizerItemDetail::TypeEventAttendee)) {
+ attendees.append(attendeeDetail.value(QOrganizerItemDetail::TypeEventAttendee + 1).toString());
+ }
+ event.setGuests(attendees);
+ event.setRecurring(organizerEvent.recurrenceRules().count() > 0);
+
+ items.append(event);
+
+ quint64 startTimestamp = QDateTime::currentMSecsSinceEpoch();
+ startTimestamp -= (1000 * 60 * 60 * 24 * 7);
+
+ foreach (const QOrganizerItem &occurranceItem, m_manager->itemOccurrences(item, QDateTime::fromMSecsSinceEpoch(startTimestamp), QDateTime::currentDateTime().addDays(7))) {
+ QOrganizerEventOccurrence organizerOccurrance(occurranceItem);
+ event.setId(organizerOccurrance.id().toString());
+ event.setStartTime(organizerOccurrance.startDateTime());
+ event.setEndTime(organizerOccurrance.endDateTime());
+ items.append(event);
+ }
+ }
+
+ if (m_items != items) {
+ m_items = items;
+ emit itemsChanged(m_items);
+ }
+
+}
+
+QList<CalendarEvent> OrganizerAdapter::items() const
+{
+ return m_items;
+}
diff --git a/rockworkd/platformintegration/sailfish/organizeradapter.h b/rockworkd/platformintegration/sailfish/organizeradapter.h
new file mode 100644
index 0000000..04ebfa3
--- /dev/null
+++ b/rockworkd/platformintegration/sailfish/organizeradapter.h
@@ -0,0 +1,44 @@
+#ifndef ORGANIZERADAPTER_H
+#define ORGANIZERADAPTER_H
+
+#include "libpebble/calendarevent.h"
+
+#include <QObject>
+
+#include <QOrganizerManager>
+#include <QOrganizerAbstractRequest>
+#include <QOrganizerEvent>
+
+QTORGANIZER_USE_NAMESPACE
+
+struct CalendarInfo
+{
+ QString name;
+ QString notebookUID;
+
+ CalendarInfo(const QString &name, const QString &notebookUID = QString())
+ : name(name), notebookUID(notebookUID) {}
+};
+
+class OrganizerAdapter : public QObject
+{
+ Q_OBJECT
+public:
+ explicit OrganizerAdapter(QObject *parent = 0);
+
+ QList<CalendarEvent> items() const;
+ QString normalizeCalendarName(QString name);
+
+public slots:
+ void refresh();
+
+signals:
+ void itemsChanged(const QList<CalendarEvent> &items);
+
+private:
+ QOrganizerManager *m_manager;
+ QList<CalendarEvent> m_items;
+ QList<CalendarInfo> m_calendars;
+};
+
+#endif // ORGANIZERADAPTER_H
diff --git a/rockworkd/platformintegration/sailfish/sailfishplatform.cpp b/rockworkd/platformintegration/sailfish/sailfishplatform.cpp
new file mode 100644
index 0000000..09e1426
--- /dev/null
+++ b/rockworkd/platformintegration/sailfish/sailfishplatform.cpp
@@ -0,0 +1,313 @@
+#include "sailfishplatform.h"
+
+#include "callchannelobserver.h"
+#include "organizeradapter.h"
+#include "syncmonitorclient.h"
+
+#include <QDBusConnection>
+#include <QDBusConnectionInterface>
+#include <QDebug>
+
+SailfishPlatform::SailfishPlatform(QObject *parent):
+ PlatformInterface(parent),
+ _pulseBus(NULL),
+ _maxVolume(0)
+{
+ // Notifications
+ QDBusConnection::sessionBus().registerObject("/org/freedesktop/Notifications", this, QDBusConnection::ExportAllSlots);
+ m_iface = new QDBusInterface("org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus");
+ m_iface->call("AddMatch", "interface='org.freedesktop.Notifications',member='Notify',type='method_call',eavesdrop='true'");
+ m_iface->call("AddMatch", "interface='org.freedesktop.Notifications',member='CloseNotification',type='method_call',eavesdrop='true'");
+
+ // Music
+ QDBusConnectionInterface *iface = QDBusConnection::sessionBus().interface();
+ const QStringList &services = iface->registeredServiceNames();
+ foreach (QString service, services) {
+ if (service.startsWith("org.mpris.MediaPlayer2.")) {
+ qDebug() << "have mpris service" << service;
+ m_mprisService = service;
+ fetchMusicMetadata();
+ QDBusConnection::sessionBus().connect(m_mprisService, "/org/mpris/MediaPlayer2", "", "PropertiesChanged", this, SLOT(mediaPropertiesChanged(QString,QVariantMap,QStringList)));
+ break;
+ }
+ }
+
+ QDBusMessage call = QDBusMessage::createMethodCall("org.PulseAudio1", "/org/pulseaudio/server_lookup1", "org.freedesktop.DBus.Properties", "Get" );
+ call << "org.PulseAudio.ServerLookup1" << "Address";
+ QDBusReply<QDBusVariant> lookupReply = QDBusConnection::sessionBus().call(call);
+ if (lookupReply.isValid()) {
+ //
+ qDebug() << "PulseAudio Bus address: " << lookupReply.value().variant().toString();
+ _pulseBus = new QDBusConnection(QDBusConnection::connectToPeer(lookupReply.value().variant().toString(), "org.PulseAudio1"));
+ }
+ // Query max volume
+ call = QDBusMessage::createMethodCall("com.Meego.MainVolume2", "/com/meego/mainvolume2",
+ "org.freedesktop.DBus.Properties", "Get");
+ call << "com.Meego.MainVolume2" << "StepCount";
+ QDBusReply<QDBusVariant> volumeMaxReply = _pulseBus->call(call);
+ if (volumeMaxReply.isValid()) {
+ _maxVolume = volumeMaxReply.value().variant().toUInt();
+ qDebug() << "Max volume: " << _maxVolume;
+ }
+ else {
+ qWarning() << "Could not read volume max, cannot adjust volume: " << volumeMaxReply.error().message();
+ }
+
+ // Calls
+ m_telepathyMonitor = new TelepathyMonitor(this);
+ connect(m_telepathyMonitor, &TelepathyMonitor::incomingCall, this, &SailfishPlatform::incomingCall);
+ connect(m_telepathyMonitor, &TelepathyMonitor::callStarted, this, &SailfishPlatform::callStarted);
+ connect(m_telepathyMonitor, &TelepathyMonitor::callEnded, this, &SailfishPlatform::callEnded);
+
+ // Organizer
+ m_organizerAdapter = new OrganizerAdapter(this);
+ m_organizerAdapter->refresh();
+ connect(m_organizerAdapter, &OrganizerAdapter::itemsChanged, this, &SailfishPlatform::organizerItemsChanged);
+ m_syncMonitorClient = new SyncMonitorClient(this);
+ connect(m_syncMonitorClient, &SyncMonitorClient::stateChanged, [this]() { if (m_syncMonitorClient->state() == "idle") m_organizerAdapter->refresh();});
+ m_syncTimer.start(1000 * 60 * 60);
+ connect(&m_syncTimer, &QTimer::timeout, [this]() { m_syncMonitorClient->sync({"calendar"});});
+ m_syncMonitorClient->sync({"calendar"});
+}
+
+QDBusInterface *SailfishPlatform::interface() const
+{
+ return m_iface;
+}
+
+uint SailfishPlatform::Notify(const QString &app_name, uint replaces_id, const QString &app_icon, const QString &summary, const QString &body, const QStringList &actions, const QVariantHash &hints, int expire_timeout)
+{
+ qDebug() << "Notification received" << app_name << replaces_id << app_icon << summary << body << actions << hints << expire_timeout;
+ QString owner = hints.value("x-nemo-owner", "").toString();
+
+ // Look up the notification category and its parameters
+ QString category = hints.value("category", "").toString();
+ QHash<QString, QString> categoryParams = this->getCategoryParams(category);
+
+ // Ignore transient and hidden notifications (notif hints override category hints)
+ // Hack this to accept transient -preview and -summary notifications, as we don't know how to decode the actual notifs yet
+ if (hints.value("transient", categoryParams.value("transient", "false")).toString() == "true") {
+ qDebug() << "Ignoring transient notification from " << owner;
+ return 0;
+ }
+ else if (hints.value("x-nemo-hidden", "false").toString() == "true" ) {
+ qDebug() << "Ignoring hidden notification from " << owner;
+ return 0;
+ }
+
+ Notification n(app_name);
+ if (owner == "twitter-notifications-client") {
+ n.setType(Notification::NotificationTypeTwitter);
+ n.setSourceName("Twitter");
+ } else if (category == "x-nemo.email") {
+ if (app_name.toLower().contains("gmail")) {
+ n.setType(Notification::NotificationTypeGMail);
+ n.setSourceName("GMail");
+ }
+ else {
+ n.setType(Notification::NotificationTypeEmail);
+ n.setSubject(app_name);
+ }
+ } else if (owner == "facebook-notifications-client") {
+ n.setType(Notification::NotificationTypeFacebook);
+ n.setSourceName("Facebook");
+ } else if (hints.value("x-nemo-origin-package").toString() == "org.telegram.messenger"
+ || category.startsWith("harbour.sailorgram")) {
+ n.setType(Notification::NotificationTypeTelegram);
+ n.setSourceName("Telegram");
+ } else if (hints.value("x-nemo-origin-package").toString() == "com.google.android.apps.babel"
+ || owner == "harbour-hangish") {
+ n.setType(Notification::NotificationTypeHangout);
+ n.setSourceName("Hangouts");
+ } else if (hints.value("x-nemo-origin-package").toString() == "com.whatsapp"
+ || owner.toLower().contains("whatsup")) {
+ n.setType(Notification::NotificationTypeWhatsApp);
+ n.setSourceName("Whatsapp");
+ } else if (app_name.contains("indicator-datetime")) {
+ n.setType(Notification::NotificationTypeReminder);
+ n.setSourceName("reminders");
+ } else {
+ n.setType(Notification::NotificationTypeGeneric);
+ }
+ n.setSender(summary);
+ n.setBody(body);
+ foreach (const QString &action, actions) {
+ if (action == "default") {
+ n.setActToken(hints.value("x-nemo-remote-action-default").toString());
+ break;
+ }
+ }
+ qDebug() << "have act token" << n.actToken();
+
+ emit notificationReceived(n);
+ // We never return something. We're just snooping in...
+ setDelayedReply(true);
+ return 0;
+}
+
+ QHash<QString, QString> SailfishPlatform::getCategoryParams(QString category)
+ {
+ if (!category.isEmpty()) {
+ QString categoryConfigFile = QString("/usr/share/lipstick/notificationcategories/%1.conf").arg(category);
+ QFile testFile(categoryConfigFile);
+ if (testFile.exists()) {
+ QHash<QString, QString> categories;
+ QSettings settings(categoryConfigFile, QSettings::IniFormat);
+ const QStringList settingKeys = settings.allKeys();
+ foreach (const QString &settingKey, settingKeys) {
+ categories[settingKey] = settings.value(settingKey).toString();
+ }
+ return categories;
+ }
+ }
+ return QHash<QString, QString>();
+ }
+
+void SailfishPlatform::sendMusicControlCommand(MusicControlButton controlButton)
+{
+ QString method;
+ switch (controlButton) {
+ case MusicControlPlayPause:
+ method = "PlayPause";
+ break;
+ case MusicControlSkipBack:
+ method = "Previous";
+ break;
+ case MusicControlSkipNext:
+ method = "Next";
+ break;
+ default:
+ ;
+ }
+
+ if (!method.isEmpty()) {
+ QDBusMessage call = QDBusMessage::createMethodCall(m_mprisService, "/org/mpris/MediaPlayer2", "org.mpris.MediaPlayer2.Player", method);
+ QDBusError err = QDBusConnection::sessionBus().call(call);
+
+ if (err.isValid()) {
+ qWarning() << "Error calling mpris method on" << m_mprisService << ":" << err.message();
+ }
+ return;
+ }
+
+ if (controlButton == MusicControlVolumeUp || controlButton == MusicControlVolumeDown) {
+ QDBusMessage call = QDBusMessage::createMethodCall("com.Meego.MainVolume2", "/com/meego/mainvolume2",
+ "org.freedesktop.DBus.Properties", "Get");
+ call << "com.Meego.MainVolume2" << "CurrentStep";
+
+ QDBusReply<QDBusVariant> volumeReply = _pulseBus->call(call);
+ if (volumeReply.isValid()) {
+ // Decide the new value for volume, taking limits into account
+ uint volume = volumeReply.value().variant().toUInt();
+ uint newVolume;
+ qDebug() << "Current volume: " << volumeReply.value().variant().toUInt();
+ if (controlButton == MusicControlVolumeUp && volume < _maxVolume-1 ) {
+ newVolume = volume + 1;
+ }
+ else if (controlButton == MusicControlVolumeDown && volume > 0) {
+ newVolume = volume - 1;
+ }
+ else {
+ qDebug() << "Volume already at limit";
+ newVolume = volume;
+ }
+
+ // If we have a new volume level, change it
+ if (newVolume != volume) {
+ qDebug() << "Setting volume: " << newVolume;
+
+ call = QDBusMessage::createMethodCall("com.Meego.MainVolume2", "/com/meego/mainvolume2",
+ "org.freedesktop.DBus.Properties", "Set");
+ call << "com.Meego.MainVolume2" << "CurrentStep" << QVariant::fromValue(QDBusVariant(newVolume));
+
+ QDBusError err = _pulseBus->call(call);
+ if (err.isValid()) {
+ qWarning() << err.message();
+ }
+ }
+ }
+ }
+}
+
+MusicMetaData SailfishPlatform::musicMetaData() const
+{
+ return m_musicMetaData;
+}
+
+void SailfishPlatform::hangupCall(uint cookie)
+{
+ m_telepathyMonitor->hangupCall(cookie);
+}
+
+QList<CalendarEvent> SailfishPlatform::organizerItems() const
+{
+ return m_organizerAdapter->items();
+}
+
+void SailfishPlatform::actionTriggered(const QString &actToken)
+{
+ QVariantMap action;
+ // Extract the element of the DBus call
+ QStringList elements(actToken.split(' ', QString::SkipEmptyParts));
+ if (elements.size() <= 3) {
+ qWarning() << "Unable to decode invalid remote action:" << actToken;
+ } else {
+ int index = 0;
+ action.insert(QStringLiteral("service"), elements.at(index++));
+ action.insert(QStringLiteral("path"), elements.at(index++));
+ action.insert(QStringLiteral("iface"), elements.at(index++));
+ action.insert(QStringLiteral("method"), elements.at(index++));
+
+ if (index < elements.size()) {
+ QVariantList args;
+ while (index < elements.size()) {
+ const QString &arg(elements.at(index++));
+ const QByteArray buffer(QByteArray::fromBase64(arg.toUtf8()));
+
+ QDataStream stream(buffer);
+ QVariant var;
+ stream >> var;
+ args.append(var);
+ }
+ action.insert(QStringLiteral("arguments"), args);
+ }
+ qDebug() << "Calling: " << action;
+ QDBusMessage call = QDBusMessage::createMethodCall(action.value("service").toString(), action.value("path").toString(), action.value("iface").toString(), action.value("method").toString());
+ if (action.contains("arguments")) call.setArguments(action.value("arguments").toList());
+ QDBusConnection::sessionBus().call(call);
+ }
+}
+
+void SailfishPlatform::fetchMusicMetadata()
+{
+ if (!m_mprisService.isEmpty()) {
+ QDBusMessage call = QDBusMessage::createMethodCall(m_mprisService, "/org/mpris/MediaPlayer2", "org.freedesktop.DBus.Properties", "Get");
+ call << "org.mpris.MediaPlayer2.Player" << "Metadata";
+ QDBusPendingCall pcall = QDBusConnection::sessionBus().asyncCall(call);
+ QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pcall, this);
+ connect(watcher, &QDBusPendingCallWatcher::finished, this, &SailfishPlatform::fetchMusicMetadataFinished);
+ }
+}
+
+void SailfishPlatform::fetchMusicMetadataFinished(QDBusPendingCallWatcher *watcher)
+{
+ watcher->deleteLater();
+ QDBusReply<QDBusVariant> reply = watcher->reply();
+ if (reply.isValid()) {
+ QVariantMap curMetadata = qdbus_cast<QVariantMap>(reply.value().variant().value<QDBusArgument>());
+ m_musicMetaData.artist = curMetadata.value("xesam:artist").toString();
+ m_musicMetaData.album = curMetadata.value("xesam:album").toString();
+ m_musicMetaData.title = curMetadata.value("xesam:title").toString();
+ emit musicMetadataChanged(m_musicMetaData);
+ } else {
+ qWarning() << reply.error().message();
+ }
+}
+
+void SailfishPlatform::mediaPropertiesChanged(const QString &interface, const QVariantMap &changedProps, const QStringList &invalidatedProps)
+{
+ Q_UNUSED(interface)
+ Q_UNUSED(changedProps)
+ Q_UNUSED(invalidatedProps)
+ fetchMusicMetadata();
+}
diff --git a/rockworkd/platformintegration/sailfish/sailfishplatform.h b/rockworkd/platformintegration/sailfish/sailfishplatform.h
new file mode 100644
index 0000000..e18b986
--- /dev/null
+++ b/rockworkd/platformintegration/sailfish/sailfishplatform.h
@@ -0,0 +1,58 @@
+#ifndef SAILFISHPLATFORM_H
+#define SAILFISHPLATFORM_H
+
+#include "libpebble/platforminterface.h"
+#include "libpebble/enums.h"
+
+#include <QDBusInterface>
+#include <TelepathyQt/AbstractClientObserver>
+
+class QDBusPendingCallWatcher;
+class TelepathyMonitor;
+class OrganizerAdapter;
+class SyncMonitorClient;
+
+class SailfishPlatform : public PlatformInterface, public QDBusContext
+{
+ Q_OBJECT
+ Q_CLASSINFO("D-Bus Interface", "org.freedesktop.Notifications")
+ Q_PROPERTY(QDBusInterface* interface READ interface)
+
+
+public:
+ SailfishPlatform(QObject *parent = 0);
+ QDBusInterface* interface() const;
+
+ void sendMusicControlCommand(MusicControlButton controlButton) override;
+ MusicMetaData musicMetaData() const override;
+ void hangupCall(uint cookie) override;
+ QHash<QString, QString> getCategoryParams(QString category);
+
+ QList<CalendarEvent> organizerItems() const override;
+
+ void actionTriggered(const QString &actToken) override;
+
+public slots:
+ uint Notify(const QString &app_name, uint replaces_id, const QString &app_icon, const QString &summary, const QString &body, const QStringList &actions, const QVariantHash &hints, int expire_timeout);
+
+
+private slots:
+ void fetchMusicMetadata();
+ void fetchMusicMetadataFinished(QDBusPendingCallWatcher *watcher);
+ void mediaPropertiesChanged(const QString &interface, const QVariantMap &changedProps, const QStringList &invalidatedProps);
+
+private:
+ QDBusInterface *m_iface;
+
+ QString m_mprisService;
+ MusicMetaData m_musicMetaData;
+ QDBusConnection *_pulseBus;
+ uint _maxVolume;
+
+ TelepathyMonitor *m_telepathyMonitor;
+ OrganizerAdapter *m_organizerAdapter;
+ SyncMonitorClient *m_syncMonitorClient;
+ QTimer m_syncTimer;
+};
+
+#endif // SAILFISHPLATFORM_H
diff --git a/rockworkd/platformintegration/sailfish/syncmonitorclient.cpp b/rockworkd/platformintegration/sailfish/syncmonitorclient.cpp
new file mode 100644
index 0000000..b43509e
--- /dev/null
+++ b/rockworkd/platformintegration/sailfish/syncmonitorclient.cpp
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2014 Canonical Ltd.
+ *
+ * This file is part of sync-monitor.
+ *
+ * sync-monitor is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 3.
+ *
+ * contact-service-app is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <QDebug>
+#include <QTimer>
+
+#include "syncmonitorclient.h"
+
+#define SYNCMONITOR_DBUS_SERVICE_NAME "com.canonical.SyncMonitor"
+#define SYNCMONITOR_DBUS_OBJECT_PATH "/com/canonical/SyncMonitor"
+#define SYNCMONITOR_DBUS_INTERFACE "com.canonical.SyncMonitor"
+
+
+SyncMonitorClient::SyncMonitorClient(QObject *parent)
+ : QObject(parent),
+ m_iface(0)
+{
+ m_iface = new QDBusInterface(SYNCMONITOR_DBUS_SERVICE_NAME,
+ SYNCMONITOR_DBUS_OBJECT_PATH,
+ SYNCMONITOR_DBUS_INTERFACE);
+ if (m_iface->lastError().isValid()) {
+ qWarning() << "Fail to connect with sync monitor:" << m_iface->lastError();
+ return;
+ }
+
+ connect(m_iface, SIGNAL(stateChanged()), SIGNAL(stateChanged()));
+ connect(m_iface, SIGNAL(enabledServicesChanged()), SIGNAL(enabledServicesChanged()));
+ m_iface->call("attach");
+}
+
+SyncMonitorClient::~SyncMonitorClient()
+{
+ if (m_iface) {
+ m_iface->call("detach");
+ delete m_iface;
+ m_iface = 0;
+ }
+}
+
+QString SyncMonitorClient::state() const
+{
+ if (m_iface) {
+ return m_iface->property("state").toString();
+ } else {
+ return "";
+ }
+}
+
+QStringList SyncMonitorClient::enabledServices() const
+{
+ if (m_iface) {
+ return m_iface->property("enabledServices").toStringList();
+ } else {
+ return QStringList();
+ }
+}
+
+/*!
+ Start a new sync for specified services
+*/
+void SyncMonitorClient::sync(const QStringList &services)
+{
+ if (m_iface) {
+ qDebug() << "starting sync!";
+ m_iface->call("sync", services);
+ }
+}
+
+/*!
+ Cancel current sync for specified services
+*/
+void SyncMonitorClient::cancel(const QStringList &services)
+{
+ if (m_iface) {
+ m_iface->call("cancel", services);
+ }
+}
+
+/*!
+ Chek if a specific service is enabled or not
+*/
+bool SyncMonitorClient::serviceIsEnabled(const QString &service)
+{
+ return enabledServices().contains(service);
+}
diff --git a/rockworkd/platformintegration/sailfish/syncmonitorclient.h b/rockworkd/platformintegration/sailfish/syncmonitorclient.h
new file mode 100644
index 0000000..1587ba5
--- /dev/null
+++ b/rockworkd/platformintegration/sailfish/syncmonitorclient.h
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2014 Canonical Ltd.
+ *
+ * This file is part of sync-monitor.
+ *
+ * sync-monitor is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 3.
+ *
+ * contact-service-app is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef SYNCMONITOR_QML_H
+#define SYNCMONITOR_QML_H
+
+#include <QObject>
+#include <QDBusInterface>
+
+class SyncMonitorClient : public QObject
+{
+ Q_OBJECT
+ Q_PROPERTY(QString state READ state NOTIFY stateChanged)
+ Q_PROPERTY(QStringList enabledServices READ enabledServices NOTIFY enabledServicesChanged)
+
+public:
+ SyncMonitorClient(QObject *parent = 0);
+ ~SyncMonitorClient();
+
+ QString state() const;
+ QStringList enabledServices() const;
+
+Q_SIGNALS:
+ void stateChanged();
+ void enabledServicesChanged();
+
+public Q_SLOTS:
+ void sync(const QStringList &services);
+ void cancel(const QStringList &services);
+ bool serviceIsEnabled(const QString &service);
+
+private:
+ QDBusInterface *m_iface;
+};
+
+#endif
diff --git a/rockworkd/platformintegration/sailfish/voicecallhandler.cpp b/rockworkd/platformintegration/sailfish/voicecallhandler.cpp
new file mode 100644
index 0000000..2ae5087
--- /dev/null
+++ b/rockworkd/platformintegration/sailfish/voicecallhandler.cpp
@@ -0,0 +1,372 @@
+#include "voicecallhandler.h"
+
+#include <QDebug>
+#include <QDBusInterface>
+#include <QDBusPendingReply>
+#include <QDBusReply>
+#include <QVariantMap>
+
+/*!
+ \class VoiceCallHandler
+ \brief This is the D-Bus proxy for communicating with the voice call manager
+ from a declarative context, this interface specifically interfaces with
+ the managers' voice call handler instances.
+*/
+class VoiceCallHandlerPrivate
+{
+ Q_DECLARE_PUBLIC(VoiceCallHandler)
+
+public:
+ VoiceCallHandlerPrivate(VoiceCallHandler *q, const QString &pHandlerId)
+ : q_ptr(q), handlerId(pHandlerId), interface(NULL)
+ , duration(0), status(0), emergency(false), incoming(false)
+ , multiparty(false) , forwarded(false), remoteHeld(false)
+ { /* ... */ }
+
+ VoiceCallHandler *q_ptr;
+
+ QString handlerId;
+
+ QDBusInterface *interface;
+
+ int duration;
+ int status;
+ QString statusText;
+ QString lineId;
+ QString providerId;
+ QDateTime startedAt;
+ bool emergency;
+ bool incoming;
+ bool multiparty;
+ bool forwarded;
+ bool remoteHeld;
+};
+
+/*!
+ Constructs a new proxy interface for the provided voice call handlerId.
+*/
+VoiceCallHandler::VoiceCallHandler(const QString &handlerId, QObject *parent)
+ : QObject(parent), l(metaObject()->className()), d_ptr(new VoiceCallHandlerPrivate(this, handlerId))
+{
+ Q_D(VoiceCallHandler);
+ qCDebug(l) << QString("Creating D-Bus interface to: ") + handlerId;
+ d->interface = new QDBusInterface("org.nemomobile.voicecall",
+ "/calls/" + handlerId,
+ "org.nemomobile.voicecall.VoiceCall",
+ QDBusConnection::sessionBus(),
+ this);
+ this->initialize(true);
+}
+
+VoiceCallHandler::~VoiceCallHandler()
+{
+ Q_D(VoiceCallHandler);
+ delete d;
+}
+
+void VoiceCallHandler::initialize(bool notifyError)
+{
+ Q_D(VoiceCallHandler);
+
+ if (d->interface->isValid()) {
+ if (getProperties()) {
+ emit durationChanged();
+ emit statusChanged();
+ emit lineIdChanged();
+ emit startedAtChanged();
+ emit multipartyChanged();
+ emit emergencyChanged();
+ emit forwardedChanged();
+
+ connect(d->interface, SIGNAL(error(QString)), SIGNAL(error(QString)));
+ connect(d->interface, SIGNAL(statusChanged(int, QString)), SLOT(onStatusChanged(int, QString)));
+ connect(d->interface, SIGNAL(lineIdChanged(QString)), SLOT(onLineIdChanged(QString)));
+ connect(d->interface, SIGNAL(durationChanged(int)), SLOT(onDurationChanged(int)));
+ connect(d->interface, SIGNAL(startedAtChanged(QDateTime)), SLOT(onStartedAtChanged(QDateTime)));
+ connect(d->interface, SIGNAL(emergencyChanged(bool)), SLOT(onEmergencyChanged(bool)));
+ connect(d->interface, SIGNAL(multipartyChanged(bool)), SLOT(onMultipartyChanged(bool)));
+ connect(d->interface, SIGNAL(forwardedChanged(bool)), SLOT(onForwardedChanged(bool)));
+ connect(d->interface, SIGNAL(remoteHeldChanged(bool)), SLOT(onRemoteHeldChanged(bool)));
+ }
+ else {
+ if (notifyError) emit this->error("Failed to get VoiceCall properties from VCM D-Bus service.");
+ }
+ }
+ else {
+ qCCritical(l) << d->interface->lastError().name() << d->interface->lastError().message();
+ }
+}
+
+bool VoiceCallHandler::getProperties()
+{
+ Q_D(VoiceCallHandler);
+
+ QDBusInterface props(d->interface->service(), d->interface->path(),
+ "org.freedesktop.DBus.Properties", d->interface->connection());
+
+ QDBusReply<QVariantMap> reply = props.call("GetAll", d->interface->interface());
+ if (reply.isValid()) {
+ QVariantMap props = reply.value();
+ qCDebug(l) << props;
+ d->providerId = props["providerId"].toString();
+ d->duration = props["duration"].toInt();
+ d->status = props["status"].toInt();
+ d->statusText = props["statusText"].toString();
+ d->lineId = props["lineId"].toString();
+ d->startedAt = QDateTime::fromMSecsSinceEpoch(props["startedAt"].toULongLong());
+ d->multiparty = props["isMultiparty"].toBool();
+ d->emergency = props["isEmergency"].toBool();
+ d->forwarded = props["isForwarded"].toBool();
+ d->incoming = props["isIncoming"].toBool();
+ d->remoteHeld = props["isIncoming"].toBool();
+ return true;
+ }
+ else {
+ qCCritical(l) << "Failed to get VoiceCall properties from VCM D-Bus service.";
+ return false;
+ }
+}
+
+void VoiceCallHandler::onDurationChanged(int duration)
+{
+ Q_D(VoiceCallHandler);
+ //qCDebug(l) <<"onDurationChanged"<<duration;
+ d->duration = duration;
+ emit durationChanged();
+}
+
+void VoiceCallHandler::onStatusChanged(int status, QString statusText)
+{
+ Q_D(VoiceCallHandler);
+ qCDebug(l) <<"onStatusChanged" << status << statusText;
+ d->status = status;
+ d->statusText = statusText;
+ if (status) getProperties(); // make sure all properties are present
+ emit statusChanged();
+}
+
+void VoiceCallHandler::onLineIdChanged(QString lineId)
+{
+ Q_D(VoiceCallHandler);
+ qCDebug(l) << "onLineIdChanged" << lineId;
+ d->lineId = lineId;
+ emit lineIdChanged();
+}
+
+void VoiceCallHandler::onStartedAtChanged(const QDateTime &startedAt)
+{
+ Q_D(VoiceCallHandler);
+ qCDebug(l) << "onStartedAtChanged" << startedAt;
+ d->startedAt = d->interface->property("startedAt").toDateTime();
+ emit startedAtChanged();
+}
+
+void VoiceCallHandler::onEmergencyChanged(bool isEmergency)
+{
+ Q_D(VoiceCallHandler);
+ qCDebug(l) << "onEmergencyChanged" << isEmergency;
+ d->emergency = isEmergency;
+ emit emergencyChanged();
+}
+
+void VoiceCallHandler::onMultipartyChanged(bool isMultiparty)
+{
+ Q_D(VoiceCallHandler);
+ qCDebug(l) << "onMultipartyChanged" << isMultiparty;
+ d->multiparty = isMultiparty;
+ emit multipartyChanged();
+}
+
+void VoiceCallHandler::onForwardedChanged(bool isForwarded)
+{
+ Q_D(VoiceCallHandler);
+ qCDebug(l) << "onForwardedChanged" << isForwarded;
+ d->forwarded = isForwarded;
+ emit forwardedChanged();
+}
+
+void VoiceCallHandler::onRemoteHeldChanged(bool isRemoteHeld)
+{
+ Q_D(VoiceCallHandler);
+ qCDebug(l) << "onRemoteHeldChanged" << isRemoteHeld;
+ d->forwarded = isRemoteHeld;
+ emit remoteHeldChanged();
+}
+
+/*!
+ Returns this voice calls' handler id.
+ */
+QString VoiceCallHandler::handlerId() const
+{
+ Q_D(const VoiceCallHandler);
+ return d->handlerId;
+}
+
+/*!
+ Returns this voice calls' provider id.
+ */
+QString VoiceCallHandler::providerId() const
+{
+ Q_D(const VoiceCallHandler);
+ return d->providerId;
+}
+
+/*!
+ Returns this voice calls' call status.
+ */
+int VoiceCallHandler::status() const
+{
+ Q_D(const VoiceCallHandler);
+ return d->status;
+}
+
+/*!
+ Returns this voice calls' call status as a symbolic string.
+ */
+QString VoiceCallHandler::statusText() const
+{
+ Q_D(const VoiceCallHandler);
+ return d->statusText;
+}
+
+/*!
+ Returns this voice calls' remote end-point line id.
+ */
+QString VoiceCallHandler::lineId() const
+{
+ Q_D(const VoiceCallHandler);
+ return d->lineId;
+}
+
+/*!
+ Returns this voice calls' started at property.
+ */
+QDateTime VoiceCallHandler::startedAt() const
+{
+ Q_D(const VoiceCallHandler);
+ return d->startedAt;
+}
+
+/*!
+ Returns this voice calls' duration property.
+ */
+int VoiceCallHandler::duration() const
+{
+ Q_D(const VoiceCallHandler);
+ return d->duration;
+}
+
+/*!
+ Returns this voice calls' incoming call flag property.
+ */
+bool VoiceCallHandler::isIncoming() const
+{
+ Q_D(const VoiceCallHandler);
+ return d->incoming;
+}
+
+/*!
+ Returns this voice calls' multiparty flag property.
+ */
+bool VoiceCallHandler::isMultiparty() const
+{
+ Q_D(const VoiceCallHandler);
+ return d->multiparty;
+}
+
+/*!
+ Returns this voice calls' forwarded flag property.
+ */
+bool VoiceCallHandler::isForwarded() const
+{
+ Q_D(const VoiceCallHandler);
+ return d->forwarded;
+}
+
+/*!
+ Returns this voice calls' emergency flag property.
+ */
+bool VoiceCallHandler::isEmergency() const
+{
+ Q_D(const VoiceCallHandler);
+ return d->emergency;
+}
+
+/*!
+ Returns this voice calls' remoteHeld flag property.
+ */
+bool VoiceCallHandler::isRemoteHeld() const
+{
+ Q_D(const VoiceCallHandler);
+ return d->remoteHeld;
+}
+
+/*!
+ Initiates answering this call, if the call is an incoming call.
+ */
+void VoiceCallHandler::answer()
+{
+ Q_D(VoiceCallHandler);
+ QDBusPendingCall call = d->interface->asyncCall("answer");
+
+ QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(call, this);
+ QObject::connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), SLOT(onPendingCallFinished(QDBusPendingCallWatcher*)));
+}
+
+/*!
+ Initiates droping the call, unless the call is disconnected.
+ */
+void VoiceCallHandler::hangup()
+{
+ Q_D(VoiceCallHandler);
+ QDBusPendingCall call = d->interface->asyncCall("hangup");
+
+ QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(call, this);
+ QObject::connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), SLOT(onPendingCallFinished(QDBusPendingCallWatcher*)));
+}
+
+/*!
+ Initiates holding the call, unless the call is disconnected.
+ */
+void VoiceCallHandler::hold(bool on)
+{
+ Q_D(VoiceCallHandler);
+ QDBusPendingCall call = d->interface->asyncCall("hold", on);
+
+ QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(call, this);
+ QObject::connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), SLOT(onPendingCallFinished(QDBusPendingCallWatcher*)));
+}
+
+/*!
+ Initiates deflecting the call to the provided target phone number.
+ */
+void VoiceCallHandler::deflect(const QString &target)
+{
+ Q_D(VoiceCallHandler);
+ QDBusPendingCall call = d->interface->asyncCall("deflect", target);
+
+ QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(call, this);
+ QObject::connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), SLOT(onPendingCallFinished(QDBusPendingCallWatcher*)));
+}
+
+void VoiceCallHandler::sendDtmf(const QString &tones)
+{
+ Q_D(VoiceCallHandler);
+ QDBusPendingCall call = d->interface->asyncCall("sendDtmf", tones);
+
+ QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(call, this);
+ QObject::connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), SLOT(onPendingCallFinished(QDBusPendingCallWatcher*)));
+}
+
+void VoiceCallHandler::onPendingCallFinished(QDBusPendingCallWatcher *watcher)
+{
+ QDBusPendingReply<bool> reply = *watcher;
+
+ if (reply.isError()) {
+ qCCritical(l) << QString::fromLatin1("Received error reply for member: %1 (%2)").arg(reply.reply().member()).arg(reply.error().message());
+ emit this->error(reply.error().message());
+ watcher->deleteLater();
+ } else {
+ qCDebug(l) << QString::fromLatin1("Received successful reply for member: %1").arg(reply.reply().member());
+ }
+}
diff --git a/rockworkd/platformintegration/sailfish/voicecallhandler.h b/rockworkd/platformintegration/sailfish/voicecallhandler.h
new file mode 100644
index 0000000..e718abb
--- /dev/null
+++ b/rockworkd/platformintegration/sailfish/voicecallhandler.h
@@ -0,0 +1,96 @@
+#ifndef VOICECALLHANDLER_H
+#define VOICECALLHANDLER_H
+
+#include <QObject>
+#include <QDateTime>
+#include <QDBusPendingCallWatcher>
+#include <QLoggingCategory>
+
+class VoiceCallHandler : public QObject
+{
+ Q_OBJECT
+ QLoggingCategory l;
+
+ Q_ENUMS(VoiceCallStatus)
+
+ Q_PROPERTY(QString handlerId READ handlerId CONSTANT)
+ Q_PROPERTY(QString providerId READ providerId CONSTANT)
+ Q_PROPERTY(int status READ status NOTIFY statusChanged)
+ Q_PROPERTY(QString statusText READ statusText NOTIFY statusChanged)
+ Q_PROPERTY(QString lineId READ lineId NOTIFY lineIdChanged)
+ Q_PROPERTY(QDateTime startedAt READ startedAt NOTIFY startedAtChanged)
+ Q_PROPERTY(int duration READ duration NOTIFY durationChanged)
+ Q_PROPERTY(bool isIncoming READ isIncoming CONSTANT)
+ Q_PROPERTY(bool isEmergency READ isEmergency NOTIFY emergencyChanged)
+ Q_PROPERTY(bool isMultiparty READ isMultiparty NOTIFY multipartyChanged)
+ Q_PROPERTY(bool isForwarded READ isForwarded NOTIFY forwardedChanged)
+ Q_PROPERTY(bool isRemoteHeld READ isRemoteHeld NOTIFY remoteHeldChanged)
+
+public:
+ enum VoiceCallStatus {
+ STATUS_NULL,
+ STATUS_ACTIVE,
+ STATUS_HELD,
+ STATUS_DIALING,
+ STATUS_ALERTING,
+ STATUS_INCOMING,
+ STATUS_WAITING,
+ STATUS_DISCONNECTED
+ };
+
+ explicit VoiceCallHandler(const QString &handlerId, QObject *parent = 0);
+ ~VoiceCallHandler();
+
+ QString handlerId() const;
+ QString providerId() const;
+ int status() const;
+ QString statusText() const;
+ QString lineId() const;
+ QDateTime startedAt() const;
+ int duration() const;
+ bool isIncoming() const;
+ bool isMultiparty() const;
+ bool isEmergency() const;
+ bool isForwarded() const;
+ bool isRemoteHeld() const;
+
+Q_SIGNALS:
+ void error(const QString &error);
+ void statusChanged();
+ void lineIdChanged();
+ void durationChanged();
+ void startedAtChanged();
+ void emergencyChanged();
+ void multipartyChanged();
+ void forwardedChanged();
+ void remoteHeldChanged();
+
+public Q_SLOTS:
+ void answer();
+ void hangup();
+ void hold(bool on);
+ void deflect(const QString &target);
+ void sendDtmf(const QString &tones);
+
+protected Q_SLOTS:
+ void initialize(bool notifyError = false);
+ bool getProperties();
+
+ void onPendingCallFinished(QDBusPendingCallWatcher *watcher);
+ void onDurationChanged(int duration);
+ void onStatusChanged(int status, QString statusText);
+ void onLineIdChanged(QString lineId);
+ void onStartedAtChanged(const QDateTime &startedAt);
+ void onEmergencyChanged(bool isEmergency);
+ void onMultipartyChanged(bool isMultiparty);
+ void onForwardedChanged(bool isForwarded);
+ void onRemoteHeldChanged(bool isRemoteHeld);
+
+private:
+ class VoiceCallHandlerPrivate *d_ptr;
+
+ Q_DISABLE_COPY(VoiceCallHandler)
+ Q_DECLARE_PRIVATE(VoiceCallHandler)
+};
+
+#endif // VOICECALLHANDLER_H
diff --git a/rockworkd/platformintegration/sailfish/voicecallmanager.cpp b/rockworkd/platformintegration/sailfish/voicecallmanager.cpp
new file mode 100644
index 0000000..afb3629
--- /dev/null
+++ b/rockworkd/platformintegration/sailfish/voicecallmanager.cpp
@@ -0,0 +1,315 @@
+#include "voicecallmanager.h"
+
+#include <QDebug>
+#include <QTimer>
+#include <QDBusInterface>
+#include <QDBusPendingReply>
+
+class VoiceCallManagerPrivate
+{
+ Q_DECLARE_PUBLIC(VoiceCallManager)
+
+public:
+ VoiceCallManagerPrivate(VoiceCallManager *q)
+ : q_ptr(q),
+ interface(NULL),
+ activeVoiceCall(NULL),
+ connected(false)
+ { /*...*/ }
+
+ VoiceCallManager *q_ptr;
+
+ QDBusInterface *interface;
+
+ QList<VoiceCallHandler*> voicecalls;
+ QHash<QString,VoiceCallProviderData> providers;
+
+ VoiceCallHandler* activeVoiceCall;
+
+ bool connected;
+};
+
+VoiceCallManager::VoiceCallManager(Settings *settings, QObject *parent)
+ : QObject(parent), l(metaObject()->className()), d_ptr(new VoiceCallManagerPrivate(this)), settings(settings)
+{
+ this->initialize();
+}
+
+VoiceCallManager::~VoiceCallManager()
+{
+ Q_D(VoiceCallManager);
+ delete d;
+}
+
+void VoiceCallManager::initialize(bool notifyError)
+{
+ Q_D(VoiceCallManager);
+ bool success = false;
+
+ delete d->interface;
+ d->interface = new QDBusInterface("org.nemomobile.voicecall",
+ "/",
+ "org.nemomobile.voicecall.VoiceCallManager",
+ QDBusConnection::sessionBus(),
+ this);
+
+ if(d->interface->isValid())
+ {
+ success = true;
+ success &= (bool)QObject::connect(d->interface, SIGNAL(error(QString)), SIGNAL(error(QString)));
+ success &= (bool)QObject::connect(d->interface, SIGNAL(voiceCallsChanged()), SLOT(onVoiceCallsChanged()));
+ success &= (bool)QObject::connect(d->interface, SIGNAL(providersChanged()), SLOT(onProvidersChanged()));
+ success &= (bool)QObject::connect(d->interface, SIGNAL(activeVoiceCallChanged()), SLOT(onActiveVoiceCallChanged()));
+ success &= (bool)QObject::connect(d->interface, SIGNAL(audioModeChanged()), SIGNAL(audioModeChanged()));
+ success &= (bool)QObject::connect(d->interface, SIGNAL(audioRoutedChanged()), SIGNAL(audioRoutedChanged()));
+ success &= (bool)QObject::connect(d->interface, SIGNAL(microphoneMutedChanged()), SIGNAL(microphoneMutedChanged()));
+ success &= (bool)QObject::connect(d->interface, SIGNAL(speakerMutedChanged()), SIGNAL(speakerMutedChanged()));
+
+ onVoiceCallsChanged();
+ onActiveVoiceCallChanged();
+ }
+
+ if(!(d->connected = success))
+ {
+ QTimer::singleShot(2000, this, SLOT(initialize()));
+ if(notifyError) emit this->error("Failed to connect to VCM D-Bus service.");
+ }
+}
+
+QDBusInterface* VoiceCallManager::interface() const
+{
+ Q_D(const VoiceCallManager);
+ return d->interface;
+}
+
+VoiceCallHandlerList VoiceCallManager::voiceCalls() const
+{
+ Q_D(const VoiceCallManager);
+ return d->voicecalls;
+}
+
+VoiceCallProviderHash VoiceCallManager::providers() const
+{
+ Q_D(const VoiceCallManager);
+ return d->providers;
+}
+
+QString VoiceCallManager::defaultProviderId() const
+{
+ Q_D(const VoiceCallManager);
+ if(d->providers.count() == 0) {
+ qCDebug(l) << Q_FUNC_INFO << "No provider added";
+ return QString::null;
+ }
+
+ QStringList keys = d->providers.keys();
+ qSort(keys);
+
+ VoiceCallProviderData provider = d->providers.value(keys.value(0));
+ return provider.id;
+}
+
+VoiceCallHandler* VoiceCallManager::activeVoiceCall() const
+{
+ Q_D(const VoiceCallManager);
+ return d->activeVoiceCall;
+}
+
+QString VoiceCallManager::audioMode() const
+{
+ Q_D(const VoiceCallManager);
+ return d->interface->property("audioMode").toString();
+}
+
+bool VoiceCallManager::isAudioRouted() const
+{
+ Q_D(const VoiceCallManager);
+ return d->interface->property("isAudioRouted").toBool();
+}
+
+bool VoiceCallManager::isMicrophoneMuted() const
+{
+ Q_D(const VoiceCallManager);
+ return d->interface->property("isMicrophoneMuted").toBool();
+}
+
+bool VoiceCallManager::isSpeakerMuted() const
+{
+ Q_D(const VoiceCallManager);
+ return d->interface->property("isSpeakerMuted").toBool();
+}
+
+void VoiceCallManager::dial(const QString &provider, const QString &msisdn)
+{
+ Q_D(VoiceCallManager);
+ QDBusPendingCall call = d->interface->asyncCall("dial", provider, msisdn);
+
+ QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(call, this);
+ QObject::connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), SLOT(onPendingCallFinished(QDBusPendingCallWatcher*)));
+}
+
+void VoiceCallManager::hangupAll()
+{
+ foreach (VoiceCallHandler* handler, voiceCalls()) {
+ handler->hangup();
+ }
+}
+
+void VoiceCallManager::silenceRingtone()
+{
+ Q_D(const VoiceCallManager);
+ QDBusPendingCall call = d->interface->asyncCall("silenceRingtone");
+ QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(call, this);
+ QObject::connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), SLOT(onPendingSilenceFinished(QDBusPendingCallWatcher*)));
+}
+
+/*
+ - Use of method calls instead of property setters to allow status checking.
+ */
+bool VoiceCallManager::setAudioMode(const QString &mode)
+{
+ Q_D(const VoiceCallManager);
+ QDBusPendingReply<bool> reply = d->interface->call("setAudioMode", mode);
+ return reply.isError() ? false : reply.value();
+}
+
+bool VoiceCallManager::setAudioRouted(bool on)
+{
+ Q_D(const VoiceCallManager);
+ QDBusPendingReply<bool> reply = d->interface->call("setAudioRouted", on);
+ return reply.isError() ? false : reply.value();
+}
+
+bool VoiceCallManager::setMuteMicrophone(bool on)
+{
+ Q_D(VoiceCallManager);
+ QDBusPendingReply<bool> reply = d->interface->call("setMuteMicrophone", on);
+ return reply.isError() ? false : reply.value();
+}
+
+bool VoiceCallManager::setMuteSpeaker(bool on)
+{
+ Q_D(VoiceCallManager);
+ QDBusPendingReply<bool> reply = d->interface->call("setMuteSpeaker", on);
+ return reply.isError() ? false : reply.value();
+}
+
+void VoiceCallManager::onVoiceCallsChanged()
+{
+ Q_D(VoiceCallManager);
+ QStringList nIds = d->interface->property("voiceCalls").toStringList();
+ QStringList oIds;
+
+ QStringList added;
+ QStringList removed;
+
+ // Map current call handlers to handler ids for easy indexing.
+ foreach(VoiceCallHandler *handler, d->voicecalls)
+ {
+ oIds.append(handler->handlerId());
+ }
+
+ // Index new handlers to be added.
+ foreach(QString nId, nIds)
+ {
+ if(!oIds.contains(nId)) added.append(nId);
+ }
+
+ // Index old handlers to be removed.
+ foreach(QString oId, oIds)
+ {
+ if(!nIds.contains(oId)) removed.append(oId);
+ }
+
+ // Remove handlers that need to be removed.
+ foreach(QString removeId, removed)
+ {
+ for (int i = 0; i < d->voicecalls.count(); ++i) {
+ VoiceCallHandler *handler = d->voicecalls.at(i);
+ if(handler->handlerId() == removeId)
+ {
+ handler->disconnect(this);
+ d->voicecalls.removeAt(i);
+ handler->deleteLater();
+ break;
+ }
+ }
+ }
+
+ // Add handlers that need to be added.
+ foreach(QString addId, added)
+ {
+ VoiceCallHandler *handler = new VoiceCallHandler(addId, this);
+ d->voicecalls.append(handler);
+ }
+
+ emit this->voiceCallsChanged();
+}
+
+void VoiceCallManager::onProvidersChanged()
+{
+ Q_D(VoiceCallManager);
+ d->providers.clear();
+ foreach(QString provider, d->interface->property("providers").toStringList())
+ {
+ QStringList parts = provider.split(':');
+ d->providers.insert(parts.first(), VoiceCallProviderData(parts.first(),
+ parts.last(),
+ parts.first()));
+ }
+
+ emit this->providersChanged();
+}
+
+void VoiceCallManager::onActiveVoiceCallChanged()
+{
+ Q_D(VoiceCallManager);
+ QString voiceCallId = d->interface->property("activeVoiceCall").toString();
+
+ if(d->voicecalls.count() == 0 || voiceCallId.isNull() || voiceCallId.isEmpty())
+ {
+ d->activeVoiceCall = NULL;
+ }
+ else
+ {
+ bool found = false;
+ d->activeVoiceCall = NULL;
+ foreach(VoiceCallHandler* handler, d->voicecalls)
+ {
+ if(handler->handlerId() == voiceCallId)
+ {
+ d->activeVoiceCall = handler;
+ found = true;
+ }
+ if(!found) d->activeVoiceCall = NULL;
+ }
+ }
+
+ emit this->activeVoiceCallChanged();
+}
+
+void VoiceCallManager::onPendingCallFinished(QDBusPendingCallWatcher *watcher)
+{
+ QDBusPendingReply<bool> reply = *watcher;
+
+ if (reply.isError()) {
+ emit this->error(reply.error().message());
+ } else {
+ qCDebug(l) << QString("Received successful reply for member: ") + reply.reply().member();
+ }
+
+ watcher->deleteLater();
+}
+
+void VoiceCallManager::onPendingSilenceFinished(QDBusPendingCallWatcher *watcher)
+{
+ QDBusPendingReply<> reply = *watcher;
+
+ if (reply.isError()) {
+ emit this->error(reply.error().message());
+ } else {
+ qCDebug(l) << QString("Received successful reply for member: ") + reply.reply().member();
+ }
+
+ watcher->deleteLater();
+}
diff --git a/rockworkd/platformintegration/sailfish/voicecallmanager.h b/rockworkd/platformintegration/sailfish/voicecallmanager.h
new file mode 100644
index 0000000..ec51230
--- /dev/null
+++ b/rockworkd/platformintegration/sailfish/voicecallmanager.h
@@ -0,0 +1,111 @@
+#ifndef VOICECALLMANAGER_H
+#define VOICECALLMANAGER_H
+
+#include "voicecallhandler.h"
+#include "settings.h"
+
+#include <QObject>
+#include <QDBusInterface>
+#include <QDBusPendingCallWatcher>
+#include <QLoggingCategory>
+
+class VoiceCallProviderData
+{
+public:
+ VoiceCallProviderData() {/*..*/}
+ VoiceCallProviderData(const QString &pId, const QString &pType, const QString &pLabel)
+ : id(pId), type(pType), label(pLabel) {/*...*/}
+
+ QString id;
+ QString type;
+ QString label;
+};
+
+typedef QHash<QString,VoiceCallProviderData> VoiceCallProviderHash;
+
+typedef QList<VoiceCallHandler*> VoiceCallHandlerList;
+
+class VoiceCallManager : public QObject
+{
+ Q_OBJECT
+ QLoggingCategory l;
+
+ Q_PROPERTY(QDBusInterface* interface READ interface)
+
+ Q_PROPERTY(VoiceCallHandlerList voiceCalls READ voiceCalls NOTIFY voiceCallsChanged)
+ Q_PROPERTY(VoiceCallProviderHash providers READ providers NOTIFY providersChanged)
+
+ Q_PROPERTY(QString defaultProviderId READ defaultProviderId NOTIFY defaultProviderChanged)
+
+ Q_PROPERTY(VoiceCallHandler* activeVoiceCall READ activeVoiceCall NOTIFY activeVoiceCallChanged)
+
+ Q_PROPERTY(QString audioMode READ audioMode WRITE setAudioMode NOTIFY audioModeChanged)
+ Q_PROPERTY(bool isAudioRouted READ isAudioRouted WRITE setAudioRouted NOTIFY audioRoutedChanged)
+ Q_PROPERTY(bool isMicrophoneMuted READ isMicrophoneMuted WRITE setMuteMicrophone NOTIFY microphoneMutedChanged)
+ Q_PROPERTY(bool isSpeakerMuted READ isSpeakerMuted WRITE setMuteSpeaker NOTIFY speakerMutedChanged)
+
+public:
+ explicit VoiceCallManager(Settings *settings, QObject *parent = 0);
+ ~VoiceCallManager();
+
+ QDBusInterface* interface() const;
+
+ VoiceCallHandlerList voiceCalls() const;
+ VoiceCallProviderHash providers() const;
+
+ QString defaultProviderId() const;
+
+ VoiceCallHandler* activeVoiceCall() const;
+
+ QString audioMode() const;
+ bool isAudioRouted() const;
+
+ bool isMicrophoneMuted() const;
+ bool isSpeakerMuted() const;
+
+Q_SIGNALS:
+ void error(const QString &message);
+
+ void providersChanged();
+ void voiceCallsChanged();
+
+ void defaultProviderChanged();
+
+ void activeVoiceCallChanged();
+
+ void audioModeChanged();
+ void audioRoutedChanged();
+ void microphoneMutedChanged();
+ void speakerMutedChanged();
+
+public Q_SLOTS:
+ void dial(const QString &providerId, const QString &msisdn);
+ void hangupAll();
+
+ void silenceRingtone();
+
+ bool setAudioMode(const QString &mode);
+ bool setAudioRouted(bool on);
+ bool setMuteMicrophone(bool on = true);
+ bool setMuteSpeaker(bool on = true);
+
+protected Q_SLOTS:
+ void initialize(bool notifyError = false);
+
+ void onProvidersChanged();
+ void onVoiceCallsChanged();
+ void onActiveVoiceCallChanged();
+
+ void onPendingCallFinished(QDBusPendingCallWatcher *watcher);
+ void onPendingSilenceFinished(QDBusPendingCallWatcher *watcher);
+
+private:
+ class VoiceCallManagerPrivate *d_ptr;
+
+ Settings *settings;
+
+ Q_DISABLE_COPY(VoiceCallManager)
+ Q_DECLARE_PRIVATE(VoiceCallManager)
+};
+
+#endif // VOICECALLMANAGER_H