summaryrefslogtreecommitdiff
path: root/daemon
diff options
context:
space:
mode:
authorJavier <dev.git@javispedro.com>2014-11-30 17:27:08 +0100
committerJavier <dev.git@javispedro.com>2014-11-30 17:37:19 +0100
commit49f1261bf9d635d5e3d881e87a93ed4e76abfe90 (patch)
tree4432ecd771a3aca533914d02f9947cb497493395 /daemon
parentd55d1d472d5876f90dd95301d9f3b6bef6f4c494 (diff)
allow receiving responses to commands in watchconnector
* the skeleton is in place for watchconnector to allow query->response messages. I've used call/cc style because it is impossible to make QBluetoothSocket synchronous (waitForReadyRead() is a no-op) * remove watchcommands, instead create musicmanager to listen for the music endpoint. The other (simpler) endpoints are now listened in watchconnector itself. hangupAll() slot is moved to voicecallmanager. * instead of emitting signals for each received message, listeners can now register for receiving messages targeted towards a given endpoint * when reading from bluetoothsocket, properly handle short reads * remove useless 'watch' namespace * create appmanager, which mantains a database of installed apps (installed on the phone, that is; watch installed apps will come later) * all the *Managers are now instantiated by the main Manager itself * introduce Unpacker helper class for decoding watch messages * implement getAppbankStatus and getAppbankUuids messages and response parsers * remove file logging for now (20MB is bad for eMMC!) * use dbus object path /org/pebbled instead of /
Diffstat (limited to 'daemon')
-rw-r--r--daemon/appmanager.cpp109
-rw-r--r--daemon/appmanager.h31
-rw-r--r--daemon/daemon.cpp10
-rw-r--r--daemon/daemon.pro9
-rw-r--r--daemon/dbusadaptor.cpp5
-rw-r--r--daemon/dbusadaptor.h2
-rw-r--r--daemon/manager.cpp66
-rw-r--r--daemon/manager.h21
-rw-r--r--daemon/musicmanager.cpp97
-rw-r--r--daemon/musicmanager.h25
-rw-r--r--daemon/unpacker.h87
-rw-r--r--daemon/voicecallmanager.cpp7
-rw-r--r--daemon/voicecallmanager.h1
-rw-r--r--daemon/watchcommands.cpp122
-rw-r--r--daemon/watchcommands.h31
-rw-r--r--daemon/watchconnector.cpp211
-rw-r--r--daemon/watchconnector.h57
17 files changed, 634 insertions, 257 deletions
diff --git a/daemon/appmanager.cpp b/daemon/appmanager.cpp
index b4a3d68..34af3af 100644
--- a/daemon/appmanager.cpp
+++ b/daemon/appmanager.cpp
@@ -1,15 +1,112 @@
#include <QStandardPaths>
-#include <QUuid>
+#include <QJsonDocument>
+#include <QJsonObject>
+#include <QDir>
#include "appmanager.h"
AppManager::AppManager(QObject *parent)
- : QObject(parent)
+ : QObject(parent),
+ _watcher(new QFileSystemWatcher(this))
{
+ connect(_watcher, &QFileSystemWatcher::directoryChanged,
+ this, &AppManager::rescan);
+
+ QDir dataDir(QStandardPaths::writableLocation(QStandardPaths::DataLocation));
+ if (!dataDir.exists("apps")) {
+ if (!dataDir.mkdir("apps")) {
+ logger()->warn() << "could not create dir" << dataDir.absoluteFilePath("apps");
+ }
+ }
+ logger()->debug() << "install apps in" << dataDir.absoluteFilePath("apps");
+
+ rescan();
+}
+
+QStringList AppManager::appPaths() const
+{
+ return QStandardPaths::locateAll(QStandardPaths::DataLocation,
+ QLatin1String("apps"),
+ QStandardPaths::LocateDirectory);
+}
+
+void AppManager::rescan()
+{
+ QStringList watchedDirs = _watcher->directories();
+ if (!watchedDirs.isEmpty()) _watcher->removePaths(watchedDirs);
+ QStringList watchedFiles = _watcher->files();
+ if (!watchedFiles.isEmpty()) _watcher->removePaths(watchedFiles);
+ _apps.clear();
+ _names.clear();
+
+ Q_FOREACH(const QString &path, appPaths()) {
+ QDir dir(path);
+ _watcher->addPath(dir.absolutePath());
+ logger()->debug() << "scanning dir" << dir.absolutePath();
+ QStringList entries = dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot | QDir::Readable | QDir::Executable);
+ logger()->debug() << "scanning dir results" << entries;
+ Q_FOREACH(const QString &path, entries) {
+ QString appPath = dir.absoluteFilePath(path);
+ _watcher->addPath(appPath);
+ if (dir.exists(path + "/appinfo.json")) {
+ _watcher->addPath(appPath + "/appinfo.json");
+ scanApp(appPath);
+ }
+ }
+ }
+
+ logger()->debug() << "now watching" << _watcher->directories() << _watcher->files();
}
-QString AppManager::getAppDir(const QUuid& uuid) const
+void AppManager::scanApp(const QString &path)
{
- return QStandardPaths::locate(QStandardPaths::DataLocation,
- QString("apps/%1").arg(uuid.toString()),
- QStandardPaths::LocateDirectory);
+ logger()->debug() << "scanning app" << path;
+ QDir appDir(path);
+ if (!appDir.isReadable()) {
+ logger()->warn() << "app" << appDir.absolutePath() << "is not readable";
+ return;
+ }
+
+ QFile appInfoFile(path + "/appinfo.json");
+ if (!appInfoFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
+ logger()->warn() << "cannot open app info file" << appInfoFile.fileName() << ":"
+ << appInfoFile.errorString();
+ return;
+ }
+
+ QJsonParseError parseError;
+ QJsonDocument doc = QJsonDocument::fromJson(appInfoFile.readAll(), &parseError);
+ if (parseError.error != QJsonParseError::NoError) {
+ logger()->warn() << "cannot parse app info file" << appInfoFile.fileName() << ":"
+ << parseError.errorString();
+ return;
+ }
+
+ const QJsonObject root = doc.object();
+ AppInfo info;
+ info.uuid = QUuid(root["uuid"].toString());
+ info.shortName = root["shortName"].toString();
+ info.longName = root["longName"].toString();
+ info.company = root["companyName"].toString();
+ info.versionCode = root["versionCode"].toInt();
+ info.versionLabel = root["versionLabel"].toString();
+
+ const QJsonObject watchapp = root["watchapp"].toObject();
+ info.isWatchface = watchapp["watchface"].toBool();
+ info.isJSKit = appDir.exists("pebble-js-app.js");
+
+ const QJsonObject appkeys = root["appKeys"].toObject();
+ for (QJsonObject::const_iterator it = appkeys.constBegin(); it != appkeys.constEnd(); ++it) {
+ info.appKeys.insert(it.key(), it.value().toInt());
+ }
+
+ if (info.uuid.isNull() || info.shortName.isEmpty()) {
+ logger()->warn() << "invalid or empty uuid/name in" << appInfoFile.fileName();
+ return;
+ }
+
+ _apps.insert(info.uuid, info);
+ _names.insert(info.shortName, info.uuid);
+
+ const char *type = info.isWatchface ? "watchface" : "app";
+ logger()->debug() << "found installed" << type << info.shortName << info.versionLabel << "with uuid" << info.uuid.toString();
}
diff --git a/daemon/appmanager.h b/daemon/appmanager.h
index 59d0bd7..5e150ab 100644
--- a/daemon/appmanager.h
+++ b/daemon/appmanager.h
@@ -2,19 +2,46 @@
#define APPMANAGER_H
#include <QObject>
+#include <QHash>
+#include <QUuid>
+#include <QFileSystemWatcher>
+#include <Log4Qt/Logger>
class AppManager : public QObject
{
Q_OBJECT
+ LOG4QT_DECLARE_QCLASS_LOGGER
public:
explicit AppManager(QObject *parent = 0);
+ struct AppInfo {
+ QUuid uuid;
+ QString shortName;
+ QString longName;
+ QString company;
+ int versionCode;
+ QString versionLabel;
+ bool isWatchface;
+ bool isJSKit;
+ QHash<QString, int> appKeys;
+ QString path;
+ };
+
+ QStringList appPaths() const;
+
bool installPebbleApp(const QString &pbwFile);
- QList<QUuid> allInstalledApps() const;
+public slots:
+ void rescan();
+
+private:
+ void scanApp(const QString &path);
- QString getAppDir(const QUuid &uuid) const;
+private:
+ QFileSystemWatcher *_watcher;
+ QHash<QUuid, AppInfo> _apps;
+ QHash<QString, QUuid> _names;
};
#endif // APPMANAGER_H
diff --git a/daemon/daemon.cpp b/daemon/daemon.cpp
index 80ae667..c9456c6 100644
--- a/daemon/daemon.cpp
+++ b/daemon/daemon.cpp
@@ -78,17 +78,11 @@ int main(int argc, char *argv[])
Log4Qt::Logger::logger(QLatin1String("Main Logger"))->info() << argv[0] << APP_VERSION;
Settings settings;
- watch::WatchConnector watch;
- DBusConnector dbus;
- VoiceCallManager voice(&settings);
- NotificationManager notifications(&settings);
- AppManager apps(&settings);
- Manager manager(&watch, &dbus, &voice, &notifications, &apps, &settings);
+ Manager manager(&settings);
+ Q_UNUSED(manager);
signal(SIGINT, signalhandler);
signal(SIGTERM, signalhandler);
- QObject::connect(&app, SIGNAL(aboutToQuit()), &watch, SLOT(endPhoneCall()));
- QObject::connect(&app, SIGNAL(aboutToQuit()), &watch, SLOT(disconnect()));
return app.exec();
}
diff --git a/daemon/daemon.pro b/daemon/daemon.pro
index 1799d18..768b50e 100644
--- a/daemon/daemon.pro
+++ b/daemon/daemon.pro
@@ -21,8 +21,8 @@ SOURCES += \
watchconnector.cpp \
dbusconnector.cpp \
dbusadaptor.cpp \
- watchcommands.cpp \
- appmanager.cpp
+ appmanager.cpp \
+ musicmanager.cpp
HEADERS += \
manager.h \
@@ -32,9 +32,10 @@ HEADERS += \
watchconnector.h \
dbusconnector.h \
dbusadaptor.h \
- watchcommands.h \
settings.h \
- appmanager.h
+ appmanager.h \
+ musicmanager.h \
+ unpacker.h
OTHER_FILES += \
org.pebbled.xml \
diff --git a/daemon/dbusadaptor.cpp b/daemon/dbusadaptor.cpp
index 3332551..7bbf623 100644
--- a/daemon/dbusadaptor.cpp
+++ b/daemon/dbusadaptor.cpp
@@ -75,10 +75,13 @@ void PebbledAdaptor::time()
QMetaObject::invokeMethod(parent(), "time");
}
-
void PebbledAdaptor::reconnect()
{
// handle method call org.pebbled.reconnect
QMetaObject::invokeMethod(parent(), "reconnect");
}
+void PebbledAdaptor::test()
+{
+ QMetaObject::invokeMethod(parent(), "test");
+}
diff --git a/daemon/dbusadaptor.h b/daemon/dbusadaptor.h
index 715a41b..54a0963 100644
--- a/daemon/dbusadaptor.h
+++ b/daemon/dbusadaptor.h
@@ -46,6 +46,7 @@ class PebbledAdaptor: public QDBusAbstractAdaptor
" <method name=\"time\"/>\n"
" <method name=\"disconnect\"/>\n"
" <method name=\"reconnect\"/>\n"
+" <method name=\"test\"/>\n"
" </interface>\n"
"")
public:
@@ -70,6 +71,7 @@ public Q_SLOTS: // METHODS
void ping(int val);
void time();
void reconnect();
+ void test();
Q_SIGNALS: // SIGNALS
void connectedChanged();
void pebbleChanged();
diff --git a/daemon/manager.cpp b/daemon/manager.cpp
index 7761864..8a8acf4 100644
--- a/daemon/manager.cpp
+++ b/daemon/manager.cpp
@@ -5,9 +5,15 @@
#include <QtContacts/QContact>
#include <QtContacts/QContactPhoneNumber>
-Manager::Manager(watch::WatchConnector *watch, DBusConnector *dbus, VoiceCallManager *voice, NotificationManager *notifications, AppManager *apps, Settings *settings) :
- QObject(0), watch(watch), dbus(dbus), voice(voice), notifications(notifications), apps(apps),
- commands(new WatchCommands(watch, this)), settings(settings), notification(MNotification::DeviceEvent)
+Manager::Manager(Settings *settings, QObject *parent) :
+ QObject(parent), settings(settings),
+ watch(new WatchConnector(this)),
+ dbus(new DBusConnector(this)),
+ voice(new VoiceCallManager(settings, this)),
+ notifications(new NotificationManager(settings, this)),
+ music(new MusicManager(watch, this)),
+ apps(new AppManager(this)),
+ notification(MNotification::DeviceEvent)
{
connect(settings, SIGNAL(valueChanged(QString)), SLOT(onSettingChanged(const QString&)));
connect(settings, SIGNAL(valuesChanged()), SLOT(onSettingsChanged()));
@@ -22,6 +28,24 @@ Manager::Manager(watch::WatchConnector *watch, DBusConnector *dbus, VoiceCallMan
numberFilter.setMatchFlags(QContactFilter::MatchPhoneNumber);
connect(watch, SIGNAL(connectedChanged()), SLOT(onConnectedChanged()));
+ watch->setEndpointHandler(WatchConnector::watchPHONE_VERSION,
+ [this](const QByteArray& data) {
+ Q_UNUSED(data);
+ watch->sendPhoneVersion();
+ return true;
+ });
+ watch->setEndpointHandler(WatchConnector::watchPHONE_CONTROL,
+ [this](const QByteArray& data) {
+ if (data.at(0) == WatchConnector::callHANGUP) {
+ voice->hangupAll();
+ }
+ return true;
+ });
+ watch->setEndpointHandler(WatchConnector::watchDATA_LOGGING,
+ [this](const QByteArray& data) {
+ //logger()->debug() << data.toHex();
+ return true;
+ });
connect(voice, SIGNAL(activeVoiceCallChanged()), SLOT(onActiveVoiceCallChanged()));
connect(voice, SIGNAL(error(const QString &)), SLOT(onVoiceError(const QString &)));
@@ -32,13 +56,10 @@ Manager::Manager(watch::WatchConnector *watch, DBusConnector *dbus, VoiceCallMan
connect(notifications, SIGNAL(twitterNotify(const QString &,const QString &)), SLOT(onTwitterNotify(const QString &,const QString &)));
connect(notifications, SIGNAL(facebookNotify(const QString &,const QString &)), SLOT(onFacebookNotify(const QString &,const QString &)));
- connect(watch, SIGNAL(messageDecoded(uint,QByteArray)), commands, SLOT(processMessage(uint,QByteArray)));
- connect(commands, SIGNAL(hangup()), SLOT(hangupAll()));
-
PebbledProxy *proxy = new PebbledProxy(this);
PebbledAdaptor *adaptor = new PebbledAdaptor(proxy);
QDBusConnection session = QDBusConnection::sessionBus();
- session.registerObject("/", proxy);
+ session.registerObject("/org/pebbled", proxy);
session.registerService("org.pebbled");
connect(dbus, SIGNAL(pebbleChanged()), adaptor, SIGNAL(pebbleChanged()));
connect(watch, SIGNAL(connectedChanged()), adaptor, SIGNAL(connectedChanged()));
@@ -52,7 +73,7 @@ Manager::Manager(watch::WatchConnector *watch, DBusConnector *dbus, VoiceCallMan
"org.freedesktop.DBus.Properties", "PropertiesChanged",
this, SLOT(onMprisPropertiesChanged(QString,QMap<QString,QVariant>,QStringList)));
- connect(this, SIGNAL(mprisMetadataChanged(QVariantMap)), commands, SLOT(onMprisMetadataChanged(QVariantMap)));
+ connect(this, SIGNAL(mprisMetadataChanged(QVariantMap)), music, SLOT(onMprisMetadataChanged(QVariantMap)));
// Set BT icon for notification
notification.setImage("icon-system-bluetooth-device");
@@ -62,7 +83,10 @@ Manager::Manager(watch::WatchConnector *watch, DBusConnector *dbus, VoiceCallMan
connect(dbus, SIGNAL(pebbleChanged()), SLOT(onPebbleChanged()));
dbus->findPebble();
}
+}
+Manager::~Manager()
+{
}
void Manager::onSettingChanged(const QString &key)
@@ -247,13 +271,6 @@ void Manager::onEmailNotify(const QString &sender, const QString &data,const QSt
watch->sendEmailNotification(sender, data, subject);
}
-void Manager::hangupAll()
-{
- foreach (VoiceCallHandler* handler, voice->voiceCalls()) {
- handler->hangup();
- }
-}
-
void Manager::onMprisPropertiesChanged(QString interface, QMap<QString,QVariant> changed, QStringList invalidated)
{
logger()->debug() << interface << changed << invalidated;
@@ -369,3 +386,22 @@ void Manager::transliterateMessage(const QString &text)
logger()->debug() << "String after transliteration:" << text;
}
}
+
+bool Manager::uploadApp(const QUuid &uuid, int slot)
+{
+ // TODO
+ return false;
+}
+
+void Manager::test()
+{
+ logger()->debug() << "Starting test";
+
+ watch->getAppbankStatus([this](const QString &s) {
+ logger()->debug() << "Callback invoked" << s;
+ });
+
+ watch->getAppbankUuids([this](const QList<QUuid> &uuids) {
+ logger()->debug() << "Callback invoked. UUIDs:" << uuids.size();
+ });
+}
diff --git a/daemon/manager.h b/daemon/manager.h
index 5946bf0..b210700 100644
--- a/daemon/manager.h
+++ b/daemon/manager.h
@@ -5,8 +5,8 @@
#include "dbusconnector.h"
#include "voicecallmanager.h"
#include "notificationmanager.h"
+#include "musicmanager.h"
#include "appmanager.h"
-#include "watchcommands.h"
#include "settings.h"
#include <QObject>
@@ -35,16 +35,15 @@ class Manager :
QBluetoothLocalDevice btDevice;
- watch::WatchConnector *watch;
+ Settings *settings;
+
+ WatchConnector *watch;
DBusConnector *dbus;
VoiceCallManager *voice;
NotificationManager *notifications;
+ MusicManager *music;
AppManager *apps;
- WatchCommands *commands;
-
- Settings *settings;
-
MNotification notification;
QContactManager *contacts;
@@ -57,7 +56,8 @@ class Manager :
QScopedPointer<icu::Transliterator> transliterator;
public:
- explicit Manager(watch::WatchConnector *watch, DBusConnector *dbus, VoiceCallManager *voice, NotificationManager *notifications, AppManager *apps, Settings *settings);
+ explicit Manager(Settings *settings, QObject *parent = 0);
+ ~Manager();
Q_INVOKABLE QString findPersonByNumber(QString number);
Q_INVOKABLE QString getCurrentProfile();
@@ -65,6 +65,8 @@ public:
QVariantMap mprisMetadata;
QVariantMap getMprisMetadata() { return mprisMetadata; }
+ Q_INVOKABLE bool uploadApp(const QUuid &uuid, int slot = -1);
+
protected:
void transliterateMessage(const QString &text);
@@ -72,10 +74,10 @@ signals:
void mprisMetadataChanged(QVariantMap);
public slots:
- void hangupAll();
void applyProfile();
-protected slots:
+private slots:
+ void test();
void onSettingChanged(const QString &key);
void onSettingsChanged();
void onPebbleChanged();
@@ -114,6 +116,7 @@ public slots:
void time() { static_cast<Manager*>(parent())->watch->time(); }
void disconnect() { static_cast<Manager*>(parent())->watch->disconnect(); }
void reconnect() { static_cast<Manager*>(parent())->watch->reconnect(); }
+ void test() { static_cast<Manager*>(parent())->test(); }
};
diff --git a/daemon/musicmanager.cpp b/daemon/musicmanager.cpp
new file mode 100644
index 0000000..abea715
--- /dev/null
+++ b/daemon/musicmanager.cpp
@@ -0,0 +1,97 @@
+#include <QtDBus>
+#include "musicmanager.h"
+
+MusicManager::MusicManager(WatchConnector *watch, QObject *parent)
+ : QObject(parent), watch(watch)
+{
+ watch->setEndpointHandler(WatchConnector::watchMUSIC_CONTROL, [this](const QByteArray& data) {
+ musicControl(WatchConnector::MusicControl(data.at(0)));
+ return true;
+ });
+}
+
+void MusicManager::onMprisMetadataChanged(QVariantMap metadata)
+{
+ QString track = metadata.value("xesam:title").toString();
+ QString album = metadata.value("xesam:album").toString();
+ QString artist = metadata.value("xesam:artist").toString();
+ logger()->debug() << __FUNCTION__ << track << album << artist;
+ watch->sendMusicNowPlaying(track, album, artist);
+}
+
+void MusicManager::musicControl(WatchConnector::MusicControl operation)
+{
+ logger()->debug() << "Operation:" << operation;
+
+ QString mpris = parent()->property("mpris").toString();
+ if (mpris.isEmpty()) {
+ logger()->debug() << "No mpris interface active";
+ return;
+ }
+
+ QString method;
+
+ switch(operation) {
+ case WatchConnector::musicPLAY_PAUSE:
+ method = "PlayPause";
+ break;
+ case WatchConnector::musicPAUSE:
+ method = "Pause";
+ break;
+ case WatchConnector::musicPLAY:
+ method = "Play";
+ break;
+ case WatchConnector::musicNEXT:
+ method = "Next";
+ break;
+ case WatchConnector::musicPREVIOUS:
+ method = "Previous";
+ break;
+ case WatchConnector::musicVOLUME_UP:
+ case WatchConnector::musicVOLUME_DOWN: {
+ QDBusConnection bus = QDBusConnection::sessionBus();
+ QDBusReply<QDBusVariant> VolumeReply = bus.call(
+ QDBusMessage::createMethodCall(mpris, "/org/mpris/MediaPlayer2", "org.freedesktop.DBus.Properties", "Get")
+ << "org.mpris.MediaPlayer2.Player" << "Volume");
+ if (VolumeReply.isValid()) {
+ double volume = VolumeReply.value().variant().toDouble();
+ if (operation == WatchConnector::musicVOLUME_UP) {
+ volume += 0.1;
+ }
+ else {
+ volume -= 0.1;
+ }
+ logger()->debug() << "Setting volume" << volume;
+ QDBusError err = QDBusConnection::sessionBus().call(
+ QDBusMessage::createMethodCall(mpris, "/org/mpris/MediaPlayer2", "org.freedesktop.DBus.Properties", "Set")
+ << "org.mpris.MediaPlayer2.Player" << "Volume" << QVariant::fromValue(QDBusVariant(volume)));
+ if (err.isValid()) {
+ logger()->error() << err.message();
+ }
+ } else {
+ logger()->error() << VolumeReply.error().message();
+ }
+ }
+ return;
+ case WatchConnector::musicGET_NOW_PLAYING:
+ onMprisMetadataChanged(parent()->property("mprisMetadata").toMap());
+ return;
+
+ case WatchConnector::musicSEND_NOW_PLAYING:
+ logger()->warn() << "Operation" << operation << "not supported";
+ return;
+ }
+
+ if (method.isEmpty()) {
+ logger()->error() << "Requested unsupported operation" << operation;
+ return;
+ }
+
+ logger()->debug() << operation << "->" << method;
+
+ QDBusError err = QDBusConnection::sessionBus().call(
+ QDBusMessage::createMethodCall(mpris, "/org/mpris/MediaPlayer2", "org.mpris.MediaPlayer2.Player", method));
+ if (err.isValid()) {
+ logger()->error() << err.message();
+ }
+}
diff --git a/daemon/musicmanager.h b/daemon/musicmanager.h
new file mode 100644
index 0000000..ca86ce3
--- /dev/null
+++ b/daemon/musicmanager.h
@@ -0,0 +1,25 @@
+#ifndef MUSICMANAGER_H
+#define MUSICMANAGER_H
+
+#include <QObject>
+#include "watchconnector.h"
+
+class MusicManager : public QObject
+{
+ Q_OBJECT
+ LOG4QT_DECLARE_QCLASS_LOGGER
+
+public:
+ explicit MusicManager(WatchConnector *watch, QObject *parent = 0);
+
+private:
+ void musicControl(WatchConnector::MusicControl operation);
+
+private slots:
+ void onMprisMetadataChanged(QVariantMap metadata);
+
+private:
+ WatchConnector *watch;
+};
+
+#endif // MUSICMANAGER_H
diff --git a/daemon/unpacker.h b/daemon/unpacker.h
new file mode 100644
index 0000000..94908cb
--- /dev/null
+++ b/daemon/unpacker.h
@@ -0,0 +1,87 @@
+#ifndef UNPACKER_H
+#define UNPACKER_H
+
+#include <QtEndian>
+#include <QByteArray>
+#include <QString>
+#include <QUuid>
+
+class Unpacker
+{
+public:
+ Unpacker(const QByteArray &data);
+
+ template <typename T>
+ T read();
+
+ QString readFixedString(int n);
+
+ QUuid readUuid();
+
+ void skip(int n);
+
+ bool bad() const;
+
+private:
+ const uchar * p();
+ bool checkBad(int n = 0);
+
+ const QByteArray &_buf;
+ int _offset;
+ bool _bad;
+};
+
+inline Unpacker::Unpacker(const QByteArray &data)
+ : _buf(data), _offset(0), _bad(false)
+{
+}
+
+template <typename T>
+inline T Unpacker::read()
+{
+ if (checkBad(sizeof(T))) return 0;
+ const uchar *u = p();
+ _offset += sizeof(T);
+ return qFromBigEndian<T>(u);
+}
+
+inline QString Unpacker::readFixedString(int n)
+{
+ if (checkBad(n)) return QString();
+ const char *u = &_buf.constData()[_offset];
+ _offset += n;
+ return QString::fromUtf8(u, strnlen(u, n));
+}
+
+inline QUuid Unpacker::readUuid()
+{
+ if (checkBad(16)) return QString();
+ _offset += 16;
+ return QUuid::fromRfc4122(_buf.mid(_offset - 16, 16));
+}
+
+inline void Unpacker::skip(int n)
+{
+ _offset += n;
+ checkBad();
+}
+
+inline bool Unpacker::bad() const
+{
+ return _bad;
+}
+
+inline const uchar * Unpacker::p()
+{
+ return reinterpret_cast<const uchar *>(&_buf.constData()[_offset]);
+}
+
+inline bool Unpacker::checkBad(int n)
+{
+ if (_offset + n > _buf.size()) {
+ _bad = true;
+ }
+ return _bad;
+}
+
+#endif // UNPACKER_H
diff --git a/daemon/voicecallmanager.cpp b/daemon/voicecallmanager.cpp
index ac03b51..9fd4339 100644
--- a/daemon/voicecallmanager.cpp
+++ b/daemon/voicecallmanager.cpp
@@ -148,6 +148,13 @@ void VoiceCallManager::dial(const QString &provider, const QString &msisdn)
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);
diff --git a/daemon/voicecallmanager.h b/daemon/voicecallmanager.h
index b7241ef..5c21269 100644
--- a/daemon/voicecallmanager.h
+++ b/daemon/voicecallmanager.h
@@ -80,6 +80,7 @@ Q_SIGNALS:
public Q_SLOTS:
void dial(const QString &providerId, const QString &msisdn);
+ void hangupAll();
void silenceRingtone();
diff --git a/daemon/watchcommands.cpp b/daemon/watchcommands.cpp
deleted file mode 100644
index b16efb5..0000000
--- a/daemon/watchcommands.cpp
+++ /dev/null
@@ -1,122 +0,0 @@
-#include "watchcommands.h"
-
-#include <QDBusConnection>
-#include <QDBusMessage>
-#include <QDBusReply>
-
-using namespace watch;
-
-WatchCommands::WatchCommands(WatchConnector *watch, QObject *parent) :
- QObject(parent), watch(watch)
-{}
-
-void WatchCommands::processMessage(uint endpoint, QByteArray data)
-{
- logger()->debug() << __FUNCTION__ << endpoint << "/" << data.toHex() << data.length();
- switch (endpoint) {
- case WatchConnector::watchPHONE_VERSION:
- watch->sendPhoneVersion();
- break;
- case WatchConnector::watchPHONE_CONTROL:
- if (data.at(0) == WatchConnector::callHANGUP) {
- emit hangup();
- }
- break;
- case WatchConnector::watchMUSIC_CONTROL:
- musicControl(WatchConnector::MusicControl(data.at(0)));
- break;
- case WatchConnector::watchSYSTEM_MESSAGE:
- logger()->info() << "Got SYSTEM_MESSAGE" << WatchConnector::SystemMessage(data.at(0));
- // TODO: handle systemBLUETOOTH_START_DISCOVERABLE/systemBLUETOOTH_END_DISCOVERABLE
- break;
-
- default:
- logger()->info() << __FUNCTION__ << "endpoint" << endpoint << "not supported yet";
- }
-}
-
-void WatchCommands::onMprisMetadataChanged(QVariantMap metadata)
-{
- QString track = metadata.value("xesam:title").toString();
- QString album = metadata.value("xesam:album").toString();
- QString artist = metadata.value("xesam:artist").toString();
- logger()->debug() << __FUNCTION__ << track << album << artist;
- watch->sendMusicNowPlaying(track, album, artist);
-}
-
-void WatchCommands::musicControl(WatchConnector::MusicControl operation)
-{
- logger()->debug() << "Operation:" << operation;
-
- QString mpris = parent()->property("mpris").toString();
- if (mpris.isEmpty()) {
- logger()->debug() << "No mpris interface active";
- return;
- }
-
- QString method;
-
- switch(operation) {
- case WatchConnector::musicPLAY_PAUSE:
- method = "PlayPause";
- break;
- case WatchConnector::musicPAUSE:
- method = "Pause";
- break;
- case WatchConnector::musicPLAY:
- method = "Play";
- break;
- case WatchConnector::musicNEXT:
- method = "Next";
- break;
- case WatchConnector::musicPREVIOUS:
- method = "Previous";
- break;
- case WatchConnector::musicVOLUME_UP:
- case WatchConnector::musicVOLUME_DOWN: {
- QDBusReply<QDBusVariant> VolumeReply = QDBusConnection::sessionBus().call(
- QDBusMessage::createMethodCall(mpris, "/org/mpris/MediaPlayer2", "org.freedesktop.DBus.Properties", "Get")
- << "org.mpris.MediaPlayer2.Player" << "Volume");
- if (VolumeReply.isValid()) {
- double volume = VolumeReply.value().variant().toDouble();
- if (operation == WatchConnector::musicVOLUME_UP) {
- volume += 0.1;
- }
- else {
- volume -= 0.1;
- }
- logger()->debug() << "Setting volume" << volume;
- QDBusError err = QDBusConnection::sessionBus().call(
- QDBusMessage::createMethodCall(mpris, "/org/mpris/MediaPlayer2", "org.freedesktop.DBus.Properties", "Set")
- << "org.mpris.MediaPlayer2.Player" << "Volume" << QVariant::fromValue(QDBusVariant(volume)));
- if (err.isValid()) {
- logger()->error() << err.message();
- }
- }
- else {
- logger()->error() << VolumeReply.error().message();
- }
- }
- return;
- case WatchConnector::musicGET_NOW_PLAYING:
- onMprisMetadataChanged(parent()->property("mprisMetadata").toMap());
- return;
-
- case WatchConnector::musicSEND_NOW_PLAYING:
- logger()->warn() << "Operation" << operation << "not supported";
- return;
- }
-
- if (method.isEmpty()) {
- logger()->error() << "Requested unsupported operation" << operation;
- return;
- }
-
- logger()->debug() << operation << "->" << method;
-
- QDBusError err = QDBusConnection::sessionBus().call(
- QDBusMessage::createMethodCall(mpris, "/org/mpris/MediaPlayer2", "org.mpris.MediaPlayer2.Player", method));
- if (err.isValid()) {
- logger()->error() << err.message();
- }
-}
diff --git a/daemon/watchcommands.h b/daemon/watchcommands.h
deleted file mode 100644
index 8626b7c..0000000
--- a/daemon/watchcommands.h
+++ /dev/null
@@ -1,31 +0,0 @@
-#ifndef WATCHCOMMANDS_H
-#define WATCHCOMMANDS_H
-
-#include "watchconnector.h"
-#include <Log4Qt/Logger>
-
-#include <QObject>
-
-class WatchCommands : public QObject
-{
- Q_OBJECT
- LOG4QT_DECLARE_QCLASS_LOGGER
-
- watch::WatchConnector *watch;
-
-public:
- explicit WatchCommands(watch::WatchConnector *watch, QObject *parent = 0);
-
-signals:
- void hangup();
-
-public slots:
- void processMessage(uint endpoint, QByteArray data);
-
-protected slots:
- void onMprisMetadataChanged(QVariantMap metadata);
- void musicControl(watch::WatchConnector::MusicControl operation);
-
-};
-
-#endif // WATCHCOMMANDS_H
diff --git a/daemon/watchconnector.cpp b/daemon/watchconnector.cpp
index a240b04..61eeb67 100644
--- a/daemon/watchconnector.cpp
+++ b/daemon/watchconnector.cpp
@@ -1,11 +1,12 @@
-#include "watchconnector.h"
-#include <QTimer>
#include <QDateTime>
#include <QMetaEnum>
-using namespace watch;
+#include "watchconnector.h"
+#include "unpacker.h"
+
+static const int RECONNECT_TIMEOUT = 500; //ms
-static int RECONNECT_TIMEOUT = 500; //ms
+using std::function;
WatchConnector::WatchConnector(QObject *parent) :
QObject(parent), socket(nullptr), is_connected(false)
@@ -83,49 +84,95 @@ void WatchConnector::handleWatch(const QString &name, const QString &address)
QString WatchConnector::decodeEndpoint(uint val)
{
- QMetaEnum Endpoints = staticMetaObject.enumerator(staticMetaObject.indexOfEnumerator("Endpoints"));
+ QMetaEnum Endpoints = staticMetaObject.enumerator(staticMetaObject.indexOfEnumerator("Endpoint"));
const char *endpoint = Endpoints.valueToKey(val);
return endpoint ? QString(endpoint) : QString("watchUNKNOWN_%1").arg(val);
}
-void WatchConnector::decodeMsg(QByteArray data)
+void WatchConnector::setEndpointHandler(uint endpoint, EndpointHandlerFunc func)
{
- //Sometimes pebble sends a "00", we ignore it without future action
- if (data.length() == 1 && data.at(0) == 0) {
- return;
- }
-
- if (data.length() < 4) {
- logger()->error() << "Can not decode message data length invalid: " << data.toHex();
- return;
+ if (func) {
+ handlers.insert(endpoint, func);
+ } else {
+ handlers.remove(endpoint);
}
+}
- unsigned int datalen = 0;
- int index = 0;
- datalen = (data.at(index) << 8) + data.at(index+1);
- index += 2;
+void WatchConnector::clearEndpointHandler(uint endpoint)
+{
+ handlers.remove(endpoint);
+}
- unsigned int endpoint = 0;
- endpoint = (data.at(index) << 8) + data.at(index+1);
- index += 2;
+bool WatchConnector::dispatchMessage(uint endpoint, const QByteArray &data)
+{
+ auto tmp_it = tmpHandlers.find(endpoint);
+ if (tmp_it != tmpHandlers.end()) {
+ QList<EndpointHandlerFunc>& funcs = tmp_it.value();
+ bool ok = false;
+ if (!funcs.empty()) {
+ if (funcs.first()(data)) {
+ ok = true;
+ funcs.removeFirst();
+ }
+ }
+ if (funcs.empty()) {
+ tmpHandlers.erase(tmp_it);
+ }
+ if (ok) {
+ return true;
+ }
+ }
- logger()->debug() << "Length:" << datalen << "Endpoint:" << decodeEndpoint(endpoint);
- logger()->debug() << "Data:" << data.mid(index).toHex();
+ auto it = handlers.find(endpoint);
+ if (it != handlers.end()) {
+ if (it.value() && it.value()(data)) {
+ return true;
+ }
+ }
- emit messageDecoded(endpoint, data.mid(index, datalen));
+ logger()->info() << "message to endpoint" << decodeEndpoint(endpoint) << "was not dispatched";
+ emit messageReceived(endpoint, data);
+ return false;
}
void WatchConnector::onReadSocket()
{
- logger()->debug() << "read";
+ static const int header_length = 4;
+
+ logger()->debug() << "readyRead bytesAvailable =" << socket->bytesAvailable();
QBluetoothSocket *socket = qobject_cast<QBluetoothSocket *>(sender());
- if (!socket) return;
+ Q_ASSERT(socket && socket == this->socket);
+
+ while (socket->bytesAvailable() >= header_length) {
+ // Do nothing if there is no message to read.
+ if (socket->bytesAvailable() < header_length) {
+ if (socket->bytesAvailable() > 0) {
+ logger()->debug() << "incomplete header in read buffer";
+ }
+ return;
+ }
+
+ uchar header[header_length];
+ socket->peek(reinterpret_cast<char*>(header), header_length);
+
+ quint16 message_length, endpoint;
+ message_length = qFromBigEndian<quint16>(&header[0]);
+ endpoint = qFromBigEndian<quint16>(&header[sizeof(quint16)]);
+
+ // Now wait for the entire message
+ if (socket->bytesAvailable() < header_length + message_length) {
+ logger()->debug() << "incomplete msg body in read buffer";
+ return;
+ }
+
+ socket->read(header_length); // Skip the header
- while (socket->bytesAvailable()) {
- QByteArray line = socket->readAll();
- emit messageReceived(socket->peerName(), QString::fromUtf8(line.constData(), line.length()));
- decodeMsg(line);
+ QByteArray data = socket->read(message_length);
+
+ logger()->debug() << "received message of length" << message_length << "to endpoint" << decodeEndpoint(endpoint);
+
+ dispatchMessage(endpoint, data);
}
}
@@ -181,13 +228,11 @@ void WatchConnector::onError(QBluetoothSocket::SocketError error)
void WatchConnector::sendData(const QByteArray &data)
{
- writeData = data;
+ writeData.append(data);
if (socket == nullptr) {
logger()->debug() << "No socket - reconnecting";
reconnect();
- return;
- }
- if (is_connected) {
+ } else if (is_connected) {
logger()->debug() << "Writing" << data.length() << "bytes to socket";
socket->write(data);
}
@@ -195,13 +240,13 @@ void WatchConnector::sendData(const QByteArray &data)
void WatchConnector::onBytesWritten(qint64 bytes)
{
- writeData = writeData.mid(bytes);
+ writeData.remove(0, bytes);
logger()->debug() << "Socket written" << bytes << "bytes," << writeData.length() << "left";
}
-void WatchConnector::sendMessage(uint endpoint, QByteArray data)
+void WatchConnector::sendMessage(uint endpoint, const QByteArray &data)
{
- logger()->debug() << "Sending message";
+ logger()->debug() << "sending message to endpoint" << decodeEndpoint(endpoint);
QByteArray msg;
// First send the length
@@ -390,3 +435,95 @@ void WatchConnector::endPhoneCall(uint cookie)
{
phoneControl(callEND, cookie, QStringList());
}
+
+void WatchConnector::getAppbankStatus(const std::function<void(const QString &s)>& callback)
+{
+ sendMessage(watchAPP_MANAGER, QByteArray(1, appmgrGET_APPBANK_STATUS));
+
+ tmpHandlers[watchAPP_MANAGER].append([this, callback](const QByteArray &data) {
+ if (data.at(0) != appmgrGET_APPBANK_STATUS) {
+ return false;
+ }
+ logger()->debug() << "getAppbankStatus response" << data.toHex();
+
+ if (data.size() < 9) {
+ logger()->warn() << "invalid getAppbankStatus response";
+ return true;
+ }
+
+ Unpacker u(data);
+
+ u.skip(sizeof(quint8));
+
+ unsigned int num_banks = u.read<quint32>();
+ unsigned int apps_installed = u.read<quint32>();
+
+ logger()->debug() << num_banks << "/" << apps_installed;
+
+ for (unsigned int i = 0; i < apps_installed; i++) {
+ unsigned int id = u.read<quint32>();
+ unsigned int index = u.read<quint32>();
+ QString name = u.readFixedString(32);
+ QString company = u.readFixedString(32);
+ unsigned int flags = u.read<quint32>();
+ unsigned short version = u.read<quint16>();
+
+ logger()->debug() << id << index << name << company << flags << version;
+
+ if (u.bad()) {
+ logger()->warn() << "short read";
+ return true;
+ }
+ }
+
+ logger()->debug() << "finished";
+
+ return true;
+ });
+}
+
+void WatchConnector::getAppbankUuids(const function<void(const QList<QUuid> &)>& callback)
+{
+ sendMessage(watchAPP_MANAGER, QByteArray(1, appmgrGET_APPBANK_UUIDS));
+
+ tmpHandlers[watchAPP_MANAGER].append([this, callback](const QByteArray &data) {
+ if (data.at(0) != appmgrGET_APPBANK_UUIDS) {
+ return false;
+ }
+ logger()->debug() << "getAppbankUuids response" << data.toHex();
+
+ if (data.size() < 5) {
+ logger()->warn() << "invalid getAppbankUuids response";
+ return true;
+ }
+
+ Unpacker u(data);
+
+ u.skip(sizeof(quint8));
+
+ unsigned int apps_installed = u.read<quint32>();
+
+ logger()->debug() << apps_installed;
+
+ QList<QUuid> uuids;
+
+ for (unsigned int i = 0; i < apps_installed; i++) {
+ QUuid uuid = u.readUuid();
+
+ logger()->debug() << uuid.toString();
+
+ if (u.bad()) {
+ logger()->warn() << "short read";
+ return true;
+ }
+
+ uuids.push_back(uuid);
+ }
+
+ logger()->debug() << "finished";
+
+ callback(uuids);
+
+ return true;
+ });
+}
diff --git a/daemon/watchconnector.h b/daemon/watchconnector.h
index b64e31b..01c3dac 100644
--- a/daemon/watchconnector.h
+++ b/daemon/watchconnector.h
@@ -30,6 +30,7 @@
#ifndef WATCHCONNECTOR_H
#define WATCHCONNECTOR_H
+#include <functional>
#include <QObject>
#include <QPointer>
#include <QStringList>
@@ -39,25 +40,18 @@
#include <QBluetoothServiceInfo>
#include <Log4Qt/Logger>
-#if QT_VERSION < QT_VERSION_CHECK(5, 2, 0)
-using namespace QtBluetooth;
-#endif
-
-namespace watch
-{
-
class WatchConnector : public QObject
{
Q_OBJECT
LOG4QT_DECLARE_QCLASS_LOGGER
- Q_ENUMS(Endpoints)
+ Q_ENUMS(Endpoint)
Q_PROPERTY(QString name READ name NOTIFY nameChanged)
Q_PROPERTY(QString connected READ isConnected NOTIFY connectedChanged)
public:
- enum Endpoints {
+ enum Endpoint {
watchTIME = 11,
watchVERSION = 16,
watchPHONE_VERSION = 17,
@@ -111,6 +105,11 @@ public:
systemBLUETOOTH_START_DISCOVERABLE = 6,
systemBLUETOOTH_END_DISCOVERABLE = 7
};
+ enum AppManager {
+ appmgrGET_APPBANK_STATUS = 1,
+ appmgrGET_APPBANK_UUIDS = 5
+ };
+
enum {
leadEMAIL = 0,
leadSMS = 1,
@@ -139,27 +138,41 @@ public:
osLINUX = 4,
osWINDOWS = 5
};
+ enum {
+ DEFAULT_TIMEOUT_MSECS = 100
+ };
+ typedef std::function<bool(const QByteArray &)> EndpointHandlerFunc;
explicit WatchConnector(QObject *parent = 0);
virtual ~WatchConnector();
- bool isConnected() const { return is_connected; }
- QString name() const { return socket != nullptr ? socket->peerName() : ""; }
- QString timeStamp();
- QString decodeEndpoint(uint val);
+ inline bool isConnected() const { return is_connected; }
+ inline QString name() const { return socket != nullptr ? socket->peerName() : ""; }
+
+ void setEndpointHandler(uint endpoint, EndpointHandlerFunc func);
+ void clearEndpointHandler(uint endpoint);
+
+ static QString timeStamp();
+ static QString decodeEndpoint(uint val);
+
+ void getAppbankStatus(const std::function<void(const QString &s)>& callback);
+ void getAppbankUuids(const std::function<void(const QList<QUuid> &uuids)>& callback);
signals:
- void messageReceived(QString peer, QString msg);
- void messageDecoded(uint endpoint, QByteArray data);
+ void messageReceived(uint endpoint, const QByteArray &data);
void nameChanged();
void connectedChanged();
public slots:
- void sendData(const QByteArray &data);
- void sendMessage(uint endpoint, QByteArray data);
+ void deviceConnect(const QString &name, const QString &address);
+ void disconnect();
+ void reconnect();
+
+ void sendMessage(uint endpoint, const QByteArray &data);
void ping(uint val);
void time();
+
void sendNotification(uint lead, QString sender, QString data, QString subject);
void sendSMSNotification(QString sender, QString data);
void sendEmailNotification(QString sender, QString data, QString subject);
@@ -176,8 +189,7 @@ public slots:
void startPhoneCall(uint cookie=0);
void endPhoneCall(uint cookie=0);
- void deviceConnect(const QString &name, const QString &address);
- void disconnect();
+private slots:
void deviceDiscovered(const QBluetoothDeviceInfo&);
void handleWatch(const QString &name, const QString &address);
void onReadSocket();
@@ -185,18 +197,19 @@ public slots:
void onConnected();
void onDisconnected();
void onError(QBluetoothSocket::SocketError error);
- void reconnect();
private:
- void decodeMsg(QByteArray data);
+ void sendData(const QByteArray &data);
+ bool dispatchMessage(uint endpoint, const QByteArray &data);
QPointer<QBluetoothSocket> socket;
+ QHash<uint, QList<EndpointHandlerFunc>> tmpHandlers;
+ QHash<uint, EndpointHandlerFunc> handlers;
bool is_connected;
QByteArray writeData;
QTimer reconnectTimer;
QString _last_name;
QString _last_address;
};
-}
#endif // WATCHCONNECTOR_H