diff options
Diffstat (limited to 'rockworkd/platformintegration/sailfish')
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 ¬ebookUID = 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 |
