It's working (needs a lot more love still tho)
This commit is contained in:
parent
ee34f6a623
commit
09e259266b
97
androidservice/androidbinder.cpp
Normal file
97
androidservice/androidbinder.cpp
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
#include "androidbinder.h"
|
||||||
|
#include "engine.h"
|
||||||
|
#include "types/device.h"
|
||||||
|
|
||||||
|
#include <QDebug>
|
||||||
|
#include <QAndroidParcel>
|
||||||
|
#include <QAndroidJniObject>
|
||||||
|
#include <QJsonDocument>
|
||||||
|
#include <QtAndroid>
|
||||||
|
|
||||||
|
AndroidBinder::AndroidBinder(Engine * engine):
|
||||||
|
m_engine(engine)
|
||||||
|
{
|
||||||
|
QAndroidParcel parcel;
|
||||||
|
parcel.writeData("foobar");
|
||||||
|
transact(10, parcel);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AndroidBinder::onTransact(int code, const QAndroidParcel &data, const QAndroidParcel &reply, QAndroidBinder::CallType flags)
|
||||||
|
{
|
||||||
|
qDebug() << "onTransact: code " << code << ", flags " << int(flags);
|
||||||
|
|
||||||
|
switch (code) {
|
||||||
|
case 0: { // Status request
|
||||||
|
bool isReady = m_engine->jsonRpcClient()->connected() && !m_engine->thingManager()->fetchingData();
|
||||||
|
reply.handle().callMethod<void>("writeBoolean", "(Z)V", isReady);
|
||||||
|
} break;
|
||||||
|
case 1: {// Things request
|
||||||
|
QVariantList thingsList;
|
||||||
|
for (int i = 0; i < m_engine->thingManager()->things()->rowCount(); i++) {
|
||||||
|
Device *thing = m_engine->thingManager()->things()->get(i);
|
||||||
|
QVariantMap thingMap;
|
||||||
|
thingMap.insert("id", thing->id().toString());
|
||||||
|
thingMap.insert("name", thing->name());
|
||||||
|
thingMap.insert("className", thing->thingClass()->displayName());
|
||||||
|
thingMap.insert("interfaces", thing->thingClass()->interfaces());
|
||||||
|
QVariantList states;
|
||||||
|
for (int j = 0; j < thing->states()->rowCount(); j++) {
|
||||||
|
State *state = thing->states()->get(j);
|
||||||
|
QVariantMap stateMap;
|
||||||
|
stateMap.insert("stateTypeId", state->stateTypeId().toString());
|
||||||
|
stateMap.insert("name", thing->thingClass()->stateTypes()->getStateType(state->stateTypeId())->name());
|
||||||
|
stateMap.insert("displayName", thing->thingClass()->stateTypes()->getStateType(state->stateTypeId())->displayName());
|
||||||
|
stateMap.insert("value", state->value());
|
||||||
|
states.append(stateMap);
|
||||||
|
}
|
||||||
|
thingMap.insert("states", states);
|
||||||
|
QVariantList actions;
|
||||||
|
for (int j = 0; j < thing->thingClass()->actionTypes()->rowCount(); j++) {
|
||||||
|
ActionType *actionType = thing->thingClass()->actionTypes()->get(j);
|
||||||
|
QVariantMap actionMap;
|
||||||
|
actionMap.insert("actionTypeId", actionType->id().toString());
|
||||||
|
actionMap.insert("name", actionType->name());
|
||||||
|
actionMap.insert("displayName", actionType->displayName());
|
||||||
|
actions.append(actionMap);
|
||||||
|
}
|
||||||
|
thingMap.insert("actions", actions);
|
||||||
|
thingsList.append(thingMap);
|
||||||
|
}
|
||||||
|
QJsonDocument jsonDoc = QJsonDocument::fromVariant(thingsList);
|
||||||
|
reply.handle().callMethod<void>("writeString", "(Ljava/lang/String;)V", QAndroidJniObject::fromString(jsonDoc.toJson()).object<jstring>());
|
||||||
|
} break;
|
||||||
|
case 2: {// ExecuteAction
|
||||||
|
// QString thingId = data.handle().callMethod<QAndroidJniObject>("readString", "").toString();
|
||||||
|
// jstring atId = data.handle().callMethod<jstring>("readString", "");
|
||||||
|
// QString actionTypeId = QAndroidJniObject::fromLocalRef(atId).toString();
|
||||||
|
// jstring p = data.handle().callMethod<jstring>("readString", "");
|
||||||
|
// QString param = QAndroidJniObject::fromLocalRef(p).toString();
|
||||||
|
qDebug() << "ExecuteAction";
|
||||||
|
QString thingId = data.readData();
|
||||||
|
QString actionTypeId = data.readData();
|
||||||
|
QString param = data.readData();
|
||||||
|
qDebug() << "**** executeAction:" << thingId << actionTypeId << param;
|
||||||
|
|
||||||
|
// FIXME: Only works with state generated actions!
|
||||||
|
QVariantMap paramMap;
|
||||||
|
paramMap.insert("paramTypeId", actionTypeId);
|
||||||
|
paramMap.insert("value", param);
|
||||||
|
m_engine->thingManager()->executeAction(thingId, actionTypeId, {paramMap});
|
||||||
|
|
||||||
|
} break;
|
||||||
|
// default:
|
||||||
|
// QAndroidBinder binder = data.readBinder();
|
||||||
|
|
||||||
|
// qDebug() << TAG << ": onTransact() received non-name data" << data.readVariant();
|
||||||
|
// reply.writeVariant(QVariant("Cannot process this!"));
|
||||||
|
|
||||||
|
// // send back message
|
||||||
|
// QAndroidParcel sendData, replyData;
|
||||||
|
// sendData.writeVariant(QVariant("Send me only names!"));
|
||||||
|
// binder.transact(0, sendData, &replyData);
|
||||||
|
// qDebug() << TAG << ": onTransact() received " << replyData.readData();
|
||||||
|
|
||||||
|
// break;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
19
androidservice/androidbinder.h
Normal file
19
androidservice/androidbinder.h
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
#ifndef ANDROIDBINDER_H
|
||||||
|
#define ANDROIDBINDER_H
|
||||||
|
|
||||||
|
#include <QAndroidBinder>
|
||||||
|
|
||||||
|
#include "engine.h"
|
||||||
|
|
||||||
|
class AndroidBinder : public QAndroidBinder
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit AndroidBinder(Engine *engine);
|
||||||
|
|
||||||
|
bool onTransact(int code, const QAndroidParcel &data, const QAndroidParcel &reply, QAndroidBinder::CallType flags) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
Engine *m_engine = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // ANDROIDBINDER_H
|
||||||
34
androidservice/androidservice.pro
Normal file
34
androidservice/androidservice.pro
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
TEMPLATE = lib
|
||||||
|
TARGET = service
|
||||||
|
CONFIG += dll
|
||||||
|
QT += core androidextras
|
||||||
|
QT += network qml quick quickcontrols2 svg websockets bluetooth charts
|
||||||
|
|
||||||
|
include(../config.pri)
|
||||||
|
include(../android_openssl/openssl.pri)
|
||||||
|
|
||||||
|
|
||||||
|
INCLUDEPATH += $$top_srcdir/libnymea-app/
|
||||||
|
|
||||||
|
# https://bugreports.qt.io/browse/QTBUG-83165
|
||||||
|
LIBS += -L$${top_builddir}/libnymea-app/$${ANDROID_TARGET_ARCH}
|
||||||
|
|
||||||
|
LIBS += -L$$top_builddir/libnymea-app/ -lnymea-app
|
||||||
|
PRE_TARGETDEPS += ../libnymea-app
|
||||||
|
|
||||||
|
SOURCES += \
|
||||||
|
androidbinder.cpp \
|
||||||
|
service_main.cpp
|
||||||
|
|
||||||
|
#HEADERS += servicemessenger.h
|
||||||
|
|
||||||
|
HEADERS += \
|
||||||
|
androidbinder.h
|
||||||
|
|
||||||
|
DISTFILES += \
|
||||||
|
../packaging/android/src/io/guh/nymeaapp/Action.java \
|
||||||
|
../packaging/android/src/io/guh/nymeaapp/NymeaAppControlsActivity.java \
|
||||||
|
../packaging/android/src/io/guh/nymeaapp/NymeaAppServiceConnection.java \
|
||||||
|
../packaging/android/src/io/guh/nymeaapp/Thing.java \
|
||||||
|
../packaging/android/src/io/guh/nymeaapp/State.java
|
||||||
|
|
||||||
55
androidservice/service_main.cpp
Normal file
55
androidservice/service_main.cpp
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
#include <QDebug>
|
||||||
|
#include <QAndroidService>
|
||||||
|
#include <QSettings>
|
||||||
|
#include <QCoreApplication>
|
||||||
|
#include <QtAndroid>
|
||||||
|
|
||||||
|
#include "androidbinder.h"
|
||||||
|
|
||||||
|
#include "engine.h"
|
||||||
|
#include "connection/discovery/nymeadiscovery.h"
|
||||||
|
#include "connection/nymeahosts.h"
|
||||||
|
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
qWarning() << "Service starting from a separate .so file";
|
||||||
|
|
||||||
|
|
||||||
|
Engine *engine = new Engine();
|
||||||
|
// engine->jsonRpcClient()->connectToHost()
|
||||||
|
|
||||||
|
|
||||||
|
QAndroidService app(argc, argv, [=](const QAndroidIntent &) {
|
||||||
|
qDebug() << "Android service onBind()";
|
||||||
|
return new AndroidBinder{engine};
|
||||||
|
});
|
||||||
|
|
||||||
|
app.setApplicationName("nymea-app");
|
||||||
|
app.setOrganizationName("nymea");
|
||||||
|
|
||||||
|
qDebug() << "Starting nymea app service";
|
||||||
|
|
||||||
|
QSettings settings;
|
||||||
|
settings.beginGroup("tabSettings0");
|
||||||
|
QUuid lastConnected = settings.value("lastConnectedHost").toUuid();
|
||||||
|
settings.endGroup();
|
||||||
|
|
||||||
|
NymeaDiscovery *discovery = new NymeaDiscovery();
|
||||||
|
|
||||||
|
NymeaHost *host = discovery->nymeaHosts()->find(lastConnected);
|
||||||
|
qDebug() << "**** Tab settings" << lastConnected << host;
|
||||||
|
if (host) {
|
||||||
|
engine->jsonRpcClient()->connectToHost(host);
|
||||||
|
}
|
||||||
|
|
||||||
|
QObject::connect(engine->thingManager(), &DeviceManager::thingStateChanged, [=](const QUuid &thingId, const QUuid &stateTypeId, const QVariant &value){
|
||||||
|
qDebug() << "**** State changed" << thingId << stateTypeId << value;
|
||||||
|
qDebug() << "Sending broadcast";
|
||||||
|
QtAndroid::androidService().callMethod<void>("sendBroadcast", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V",
|
||||||
|
QAndroidJniObject::fromString(thingId.toString()).object<jstring>(),
|
||||||
|
QAndroidJniObject::fromString(stateTypeId.toString()).object<jstring>(),
|
||||||
|
QAndroidJniObject::fromString(value.toString()).object<jstring>());
|
||||||
|
});
|
||||||
|
|
||||||
|
return app.exec();
|
||||||
|
}
|
||||||
@ -175,10 +175,11 @@ void DeviceManager::notificationReceived(const QVariantMap &data)
|
|||||||
qWarning() << "Device state change notification received for an unknown device";
|
qWarning() << "Device state change notification received for an unknown device";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
QUuid stateTyoeId = data.value("params").toMap().value("stateTypeId").toUuid();
|
QUuid stateTypeId = data.value("params").toMap().value("stateTypeId").toUuid();
|
||||||
QVariant value = data.value("params").toMap().value("value");
|
QVariant value = data.value("params").toMap().value("value");
|
||||||
// qDebug() << "Device state changed for:" << dev->name() << "State name:" << dev->thingClass()->stateTypes()->getStateType(stateTyoeId) << "value:" << value;
|
// qDebug() << "Device state changed for:" << dev->name() << "State name:" << dev->thingClass()->stateTypes()->getStateType(stateTypeId) << "value:" << value;
|
||||||
dev->setStateValue(stateTyoeId, value);
|
dev->setStateValue(stateTypeId, value);
|
||||||
|
emit thingStateChanged(dev->id(), stateTypeId, value);
|
||||||
} else if (notification == "Devices.DeviceAdded") {
|
} else if (notification == "Devices.DeviceAdded") {
|
||||||
Device *dev = JsonTypes::unpackDevice(this, data.value("params").toMap().value("device").toMap(), m_thingClasses);
|
Device *dev = JsonTypes::unpackDevice(this, data.value("params").toMap().value("device").toMap(), m_thingClasses);
|
||||||
if (!dev) {
|
if (!dev) {
|
||||||
|
|||||||
@ -151,7 +151,8 @@ signals:
|
|||||||
void fetchingDataChanged();
|
void fetchingDataChanged();
|
||||||
void notificationReceived(const QString &deviceId, const QString &eventTypeId, const QVariantList ¶ms);
|
void notificationReceived(const QString &deviceId, const QString &eventTypeId, const QVariantList ¶ms);
|
||||||
|
|
||||||
void eventTriggered(const QString &deviceId, const QString &eventTypeId, const QVariantMap params);
|
void eventTriggered(const QUuid &deviceId, const QUuid &eventTypeId, const QVariantMap params);
|
||||||
|
void thingStateChanged(const QUuid &deviceId, const QUuid &stateTypeId, const QVariant &value);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Vendors *m_vendors;
|
Vendors *m_vendors;
|
||||||
|
|||||||
@ -61,9 +61,6 @@ class Engine : public QObject
|
|||||||
public:
|
public:
|
||||||
explicit Engine(QObject *parent = nullptr);
|
explicit Engine(QObject *parent = nullptr);
|
||||||
|
|
||||||
bool connected() const;
|
|
||||||
QString connectedHost() const;
|
|
||||||
|
|
||||||
DeviceManager *deviceManager() const;
|
DeviceManager *deviceManager() const;
|
||||||
DeviceManager *thingManager() const;
|
DeviceManager *thingManager() const;
|
||||||
RuleManager *ruleManager() const;
|
RuleManager *ruleManager() const;
|
||||||
|
|||||||
@ -1,7 +0,0 @@
|
|||||||
#include <QtCore/QtGlobal>
|
|
||||||
|
|
||||||
#if defined(LIBNYMEA_COMMON)
|
|
||||||
# define LIBNYMEA_COMMON_EXPORT Q_DECL_EXPORT
|
|
||||||
#else
|
|
||||||
# define LIBNYMEA_COMMON_EXPORT Q_DECL_IMPORT
|
|
||||||
#endif
|
|
||||||
@ -1,15 +0,0 @@
|
|||||||
include(../config.pri)
|
|
||||||
|
|
||||||
TARGET = nymea-common
|
|
||||||
TEMPLATE = lib
|
|
||||||
CONFIG += staticlib
|
|
||||||
|
|
||||||
QT -= gui
|
|
||||||
QT += network
|
|
||||||
|
|
||||||
HEADERS += \
|
|
||||||
|
|
||||||
|
|
||||||
SOURCES += \
|
|
||||||
|
|
||||||
|
|
||||||
@ -82,6 +82,11 @@ icons.path = /usr/share/
|
|||||||
INSTALLS += desktopfile icons
|
INSTALLS += desktopfile icons
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Android service
|
||||||
|
android: {
|
||||||
|
SUBDIRS += androidservice
|
||||||
|
}
|
||||||
|
|
||||||
# Linux desktop (snap package)
|
# Linux desktop (snap package)
|
||||||
snap: {
|
snap: {
|
||||||
desktopfile.files = packaging/linux/nymea-app.desktop
|
desktopfile.files = packaging/linux/nymea-app.desktop
|
||||||
|
|||||||
@ -78,6 +78,7 @@ android {
|
|||||||
$$ANDROID_PACKAGE_SOURCE_DIR/src/io/guh/nymeaapp/NymeaAppActivity.java \
|
$$ANDROID_PACKAGE_SOURCE_DIR/src/io/guh/nymeaapp/NymeaAppActivity.java \
|
||||||
$$ANDROID_PACKAGE_SOURCE_DIR/src/io/guh/nymeaapp/NymeaAppNotificationService.java \
|
$$ANDROID_PACKAGE_SOURCE_DIR/src/io/guh/nymeaapp/NymeaAppNotificationService.java \
|
||||||
$$ANDROID_PACKAGE_SOURCE_DIR/src/io/guh/nymeaapp/NymeaAppControlService.java \
|
$$ANDROID_PACKAGE_SOURCE_DIR/src/io/guh/nymeaapp/NymeaAppControlService.java \
|
||||||
|
$$ANDROID_PACKAGE_SOURCE_DIR/src/io/guh/nymeaapp/NymeaAppService.java \
|
||||||
$$ANDROID_PACKAGE_SOURCE_DIR/LICENSE
|
$$ANDROID_PACKAGE_SOURCE_DIR/LICENSE
|
||||||
|
|
||||||
# https://bugreports.qt.io/browse/QTBUG-83165
|
# https://bugreports.qt.io/browse/QTBUG-83165
|
||||||
|
|||||||
@ -98,3 +98,8 @@ QString PlatformHelper::fromClipBoard()
|
|||||||
{
|
{
|
||||||
return QApplication::clipboard()->text();
|
return QApplication::clipboard()->text();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PlatformHelper::syncThings()
|
||||||
|
{
|
||||||
|
// no-op by default
|
||||||
|
}
|
||||||
|
|||||||
@ -88,6 +88,8 @@ public:
|
|||||||
Q_INVOKABLE virtual void toClipBoard(const QString &text);
|
Q_INVOKABLE virtual void toClipBoard(const QString &text);
|
||||||
Q_INVOKABLE virtual QString fromClipBoard();
|
Q_INVOKABLE virtual QString fromClipBoard();
|
||||||
|
|
||||||
|
Q_INVOKABLE virtual void syncThings();
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void permissionsRequestFinished();
|
void permissionsRequestFinished();
|
||||||
void screenTimeoutChanged();
|
void screenTimeoutChanged();
|
||||||
|
|||||||
@ -33,6 +33,7 @@
|
|||||||
#include <QAndroidJniObject>
|
#include <QAndroidJniObject>
|
||||||
#include <QtAndroid>
|
#include <QtAndroid>
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
|
#include <QAndroidIntent>
|
||||||
|
|
||||||
|
|
||||||
// WindowManager.LayoutParams
|
// WindowManager.LayoutParams
|
||||||
@ -47,13 +48,16 @@ static PlatformHelperAndroid *m_instance;
|
|||||||
static QAndroidJniObject getAndroidWindow()
|
static QAndroidJniObject getAndroidWindow()
|
||||||
{
|
{
|
||||||
QAndroidJniObject window = QtAndroid::androidActivity().callObjectMethod("getWindow", "()Landroid/view/Window;");
|
QAndroidJniObject window = QtAndroid::androidActivity().callObjectMethod("getWindow", "()Landroid/view/Window;");
|
||||||
window.callMethod<void>("addFlags", "(I)V", FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
|
|
||||||
window.callMethod<void>("clearFlags", "(I)V", FLAG_TRANSLUCENT_STATUS);
|
|
||||||
return window;
|
return window;
|
||||||
}
|
}
|
||||||
|
|
||||||
PlatformHelperAndroid::PlatformHelperAndroid(QObject *parent) : PlatformHelper(parent)
|
PlatformHelperAndroid::PlatformHelperAndroid(QObject *parent) : PlatformHelper(parent)
|
||||||
{
|
{
|
||||||
|
// QAndroidIntent serviceIntent(QtAndroid::androidActivity().object(), "io.guh.nymeaapp.NymeaAppControlService");
|
||||||
|
|
||||||
|
// m_serviceConnection = new DeviceControlServiceConnection();
|
||||||
|
// QtAndroid::bindService(serviceIntent, *m_serviceConnection, QtAndroid::BindFlag::AutoCreate);
|
||||||
|
|
||||||
m_instance = this;
|
m_instance = this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -123,6 +127,33 @@ void PlatformHelperAndroid::vibrate(PlatformHelper::HapticsFeedback feedbackType
|
|||||||
QtAndroid::androidActivity().callMethod<void>("vibrate","(I)V", duration);
|
QtAndroid::androidActivity().callMethod<void>("vibrate","(I)V", duration);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PlatformHelperAndroid::syncThings()
|
||||||
|
{
|
||||||
|
|
||||||
|
QAndroidIntent serviceIntent(QtAndroid::androidActivity().object(),
|
||||||
|
"io/guh/nymeaapp/NymeaAppService");
|
||||||
|
QAndroidJniObject result = QtAndroid::androidActivity().callObjectMethod(
|
||||||
|
"startService",
|
||||||
|
"(Landroid/content/Intent;)Landroid/content/ComponentName;",
|
||||||
|
serviceIntent.handle().object());
|
||||||
|
|
||||||
|
|
||||||
|
// QtAndroid::androidService()
|
||||||
|
|
||||||
|
// QAndroidIntent serviceIntent(QtAndroid::androidActivity().object(),
|
||||||
|
// "io/guh/nymeaapp/NymeaAppControlService");
|
||||||
|
// serviceIntent.putExtra("name", QByteArray("foobar"));
|
||||||
|
|
||||||
|
|
||||||
|
// m_serviceConnection->handle().callMethod<void>("syncThings", "(Ljava/lang/String;)V", "bla");
|
||||||
|
|
||||||
|
|
||||||
|
// QAndroidJniObject result = QtAndroid::androidActivity().callObjectMethod(
|
||||||
|
// "syncThings",
|
||||||
|
// "(Landroid/content/Intent;)Landroid/content/ComponentName;",
|
||||||
|
// m_serviceConnection->handle().object());
|
||||||
|
}
|
||||||
|
|
||||||
void PlatformHelperAndroid::setTopPanelColor(const QColor &color)
|
void PlatformHelperAndroid::setTopPanelColor(const QColor &color)
|
||||||
{
|
{
|
||||||
PlatformHelper::setTopPanelColor(color);
|
PlatformHelper::setTopPanelColor(color);
|
||||||
@ -132,6 +163,8 @@ void PlatformHelperAndroid::setTopPanelColor(const QColor &color)
|
|||||||
|
|
||||||
QtAndroid::runOnAndroidThread([=]() {
|
QtAndroid::runOnAndroidThread([=]() {
|
||||||
QAndroidJniObject window = getAndroidWindow();
|
QAndroidJniObject window = getAndroidWindow();
|
||||||
|
window.callMethod<void>("addFlags", "(I)V", FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
|
||||||
|
window.callMethod<void>("clearFlags", "(I)V", FLAG_TRANSLUCENT_STATUS);
|
||||||
window.callMethod<void>("setStatusBarColor", "(I)V", color.rgba());
|
window.callMethod<void>("setStatusBarColor", "(I)V", color.rgba());
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -31,9 +31,11 @@
|
|||||||
#ifndef PLATFORMHELPERANDROID_H
|
#ifndef PLATFORMHELPERANDROID_H
|
||||||
#define PLATFORMHELPERANDROID_H
|
#define PLATFORMHELPERANDROID_H
|
||||||
|
|
||||||
#include <QObject>
|
|
||||||
#include "platformhelper.h"
|
#include "platformhelper.h"
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
#include <QtAndroid>
|
#include <QtAndroid>
|
||||||
|
#include <QAndroidServiceConnection>
|
||||||
|
|
||||||
class PlatformHelperAndroid : public PlatformHelper
|
class PlatformHelperAndroid : public PlatformHelper
|
||||||
{
|
{
|
||||||
@ -55,6 +57,7 @@ public:
|
|||||||
QString deviceManufacturer() const override;
|
QString deviceManufacturer() const override;
|
||||||
|
|
||||||
Q_INVOKABLE void vibrate(HapticsFeedback feedbackType) override;
|
Q_INVOKABLE void vibrate(HapticsFeedback feedbackType) override;
|
||||||
|
Q_INVOKABLE void syncThings() override;
|
||||||
|
|
||||||
void setTopPanelColor(const QColor &color) override;
|
void setTopPanelColor(const QColor &color) override;
|
||||||
void setTopPanelTheme(Theme theme);
|
void setTopPanelTheme(Theme theme);
|
||||||
@ -62,7 +65,6 @@ public:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
static void permissionRequestFinished(const QtAndroid::PermissionResultMap &);
|
static void permissionRequestFinished(const QtAndroid::PermissionResultMap &);
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // PLATFORMHELPERANDROID_H
|
#endif // PLATFORMHELPERANDROID_H
|
||||||
|
|||||||
@ -132,6 +132,12 @@ Item {
|
|||||||
initialItem: Page {}
|
initialItem: Page {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Button {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
text: "bla"
|
||||||
|
onClicked: PlatformHelper.syncThings()
|
||||||
|
}
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
setupPushNotifications();
|
setupPushNotifications();
|
||||||
if (autoConnectHost.length > 0) {
|
if (autoConnectHost.length > 0) {
|
||||||
|
|||||||
@ -62,8 +62,36 @@
|
|||||||
<!-- extract android style -->
|
<!-- extract android style -->
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
|
|
||||||
|
<activity android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|layoutDirection|locale|fontScale|keyboard|keyboardHidden|navigation|mcc|mnc|density" android:name="io.guh.nymeaapp.NymeaAppControlsActivity" android:label="nymea:app" android:screenOrientation="unspecified" android:launchMode="singleTop">
|
||||||
|
<meta-data android:name="android.app.lib_name" android:value="service"/>
|
||||||
|
</activity>
|
||||||
|
|
||||||
<!-- For adding service(s) please check: https://wiki.qt.io/AndroidServices -->
|
<!-- For adding service(s) please check: https://wiki.qt.io/AndroidServices -->
|
||||||
|
|
||||||
|
<service android:process=":qt_service" android:name="io.guh.nymeaapp.NymeaAppService">
|
||||||
|
<meta-data android:name="android.app.lib_name" android:value="service"/>
|
||||||
|
<meta-data android:name="android.app.qt_sources_resource_id" android:resource="@array/qt_sources"/>
|
||||||
|
<meta-data android:name="android.app.repository" android:value="default"/>
|
||||||
|
<meta-data android:name="android.app.qt_libs_resource_id" android:resource="@array/qt_libs"/>
|
||||||
|
<meta-data android:name="android.app.bundled_libs_resource_id" android:resource="@array/bundled_libs"/>
|
||||||
|
|
||||||
|
<!-- Deploy Qt libs as part of package -->
|
||||||
|
<meta-data android:name="android.app.bundle_local_qt_libs" android:value="-- %%BUNDLE_LOCAL_QT_LIBS%% --"/>
|
||||||
|
|
||||||
|
<!-- Run with local libs -->
|
||||||
|
<meta-data android:name="android.app.use_local_qt_libs" android:value="-- %%USE_LOCAL_QT_LIBS%% --"/>
|
||||||
|
<meta-data android:name="android.app.libs_prefix" android:value="/data/local/tmp/qt/"/>
|
||||||
|
<meta-data android:name="android.app.load_local_libs_resource_id" android:resource="@array/load_local_libs"/>
|
||||||
|
<meta-data android:name="android.app.load_local_jars" android:value="-- %%INSERT_LOCAL_JARS%% --"/>
|
||||||
|
<meta-data android:name="android.app.static_init_classes" android:value="-- %%INSERT_INIT_CLASSES%% --"/>
|
||||||
|
<!-- Run with local libs -->
|
||||||
|
|
||||||
|
<!-- Background running -->
|
||||||
|
<meta-data android:name="android.app.background_running" android:value="true"/>
|
||||||
|
<!-- Background running -->
|
||||||
|
</service>
|
||||||
|
|
||||||
<service android:name="com.google.firebase.messaging.MessageForwardingService" android:exported="false">
|
<service android:name="com.google.firebase.messaging.MessageForwardingService" android:exported="false">
|
||||||
</service>
|
</service>
|
||||||
|
|
||||||
|
|||||||
7
packaging/android/src/io/guh/nymeaapp/Action.java
Normal file
7
packaging/android/src/io/guh/nymeaapp/Action.java
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
package io.guh.nymeaapp;
|
||||||
|
|
||||||
|
public class Action {
|
||||||
|
public String typeId;
|
||||||
|
public String name;
|
||||||
|
public String displayName;
|
||||||
|
}
|
||||||
@ -8,9 +8,6 @@ import android.telephony.TelephonyManager;
|
|||||||
import android.provider.Settings.Secure;
|
import android.provider.Settings.Secure;
|
||||||
import android.os.Vibrator;
|
import android.os.Vibrator;
|
||||||
|
|
||||||
//import com.google.firebase.messaging.MessageForwardingService;
|
|
||||||
|
|
||||||
|
|
||||||
public class NymeaAppActivity extends org.qtproject.qt5.android.bindings.QtActivity
|
public class NymeaAppActivity extends org.qtproject.qt5.android.bindings.QtActivity
|
||||||
{
|
{
|
||||||
public String deviceSerial()
|
public String deviceSerial()
|
||||||
|
|||||||
@ -2,129 +2,212 @@ package io.guh.nymeaapp;
|
|||||||
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.content.ServiceConnection;
|
||||||
|
import android.content.ComponentName;
|
||||||
import android.app.PendingIntent;
|
import android.app.PendingIntent;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.service.controls.ControlsProviderService;
|
import android.service.controls.ControlsProviderService;
|
||||||
import android.service.controls.actions.ControlAction;
|
import android.service.controls.actions.*;
|
||||||
import android.service.controls.actions.BooleanAction;
|
|
||||||
import android.service.controls.Control;
|
import android.service.controls.Control;
|
||||||
import android.service.controls.DeviceTypes;
|
import android.service.controls.DeviceTypes;
|
||||||
|
import android.service.controls.templates.*;
|
||||||
|
import android.os.Binder;
|
||||||
|
import android.os.IBinder;
|
||||||
|
import android.os.Parcel;
|
||||||
|
|
||||||
import java.util.concurrent.Flow.Publisher;
|
import java.util.concurrent.Flow.Publisher;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
import io.reactivex.Flowable;
|
import io.reactivex.Flowable;
|
||||||
import io.reactivex.processors.ReplayProcessor;
|
import io.reactivex.processors.ReplayProcessor;
|
||||||
import org.reactivestreams.FlowAdapters;
|
import org.reactivestreams.FlowAdapters;
|
||||||
|
import org.json.*;
|
||||||
|
|
||||||
public class NymeaAppControlService extends ControlsProviderService {
|
public class NymeaAppControlService extends ControlsProviderService {
|
||||||
|
private String TAG = "nymea-app: NymeaAppControlService";
|
||||||
|
private NymeaAppServiceConnection m_serviceConnection;
|
||||||
|
|
||||||
private ReplayProcessor updatePublisher;
|
private ReplayProcessor m_publisherForAll;
|
||||||
|
private ReplayProcessor m_updatePublisher;
|
||||||
|
private List m_activeControlIds;
|
||||||
|
|
||||||
@Override
|
|
||||||
public Publisher createPublisherForAllAvailable() {
|
|
||||||
Log.d("********************************* Creating publishers for all ****************************", "fff");
|
|
||||||
|
|
||||||
Context context = getBaseContext();
|
private void ensureServiceConnection() {
|
||||||
Intent i = new Intent();
|
if (m_serviceConnection == null) {
|
||||||
PendingIntent pi = PendingIntent.getActivity(context, 1, i, PendingIntent.FLAG_UPDATE_CURRENT);
|
m_serviceConnection = new NymeaAppServiceConnection(getBaseContext()) {
|
||||||
// pi = PendingIntent.getActivity(context, 1, i, PendingIntent.FLAG_UPDATE_CURRENT);
|
@Override public void onReady() {
|
||||||
List controls = new ArrayList<>();
|
process();
|
||||||
Control control = new Control.StatelessBuilder("e24b0d95-9982-4f9b-ad8b-2aa6b9aba8fd", pi)
|
}
|
||||||
// Required: The name of the control
|
@Override public void onUpdate(String thingId) {
|
||||||
.setTitle("TestControl")
|
if (m_updatePublisher != null && m_activeControlIds.contains(thingId)) {
|
||||||
// Required: Usually the room where the control is located
|
Thing thing = m_serviceConnection.getThing(thingId);
|
||||||
.setSubtitle("TestSubtitle")
|
Log.d(TAG, "Updating publisher for thing: " + thing.name + " id: " + thing.id);
|
||||||
// Optional: Structure where the control is located, an example would be a house
|
m_updatePublisher.onNext(thingToControl(thing));
|
||||||
.setStructure("TestLocation")
|
// m_updatePublisher.onComplete();
|
||||||
// Required: Type of device, i.e., thermostat, light, switch
|
}
|
||||||
.setDeviceType(DeviceTypes.TYPE_GENERIC_ON_OFF) // For example, DeviceTypes.TYPE_THERMOSTAT
|
}
|
||||||
.build();
|
};
|
||||||
controls.add(control);
|
}
|
||||||
// Create more controls here if needed and add it to the ArrayList
|
if (!m_serviceConnection.isConnected()) {
|
||||||
|
Intent serviceIntent = new Intent(this, NymeaAppService.class);
|
||||||
|
bindService(serviceIntent, m_serviceConnection, Context.BIND_AUTO_CREATE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Uses the RxJava 2 library
|
private void process() {
|
||||||
return FlowAdapters.toFlowPublisher(Flowable.fromIterable(controls));
|
Log.d(TAG, "Processing...");
|
||||||
|
ensureServiceConnection();
|
||||||
|
if (!m_serviceConnection.isReady()) {
|
||||||
|
Log.d(TAG, "Service connection is not ready yet...");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ArrayList<Thing> things = m_serviceConnection.getThings();
|
||||||
|
|
||||||
|
for (Thing thing : m_serviceConnection.getThings()) {
|
||||||
|
Log.d(TAG, "Processing thing: " + thing.name);
|
||||||
|
|
||||||
|
if (m_publisherForAll != null) {
|
||||||
|
Log.d(TAG, "Adding stateless");
|
||||||
|
m_publisherForAll.onNext(thingToControl(thing));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_updatePublisher != null) {
|
||||||
|
if (m_activeControlIds.contains(thing.id)) {
|
||||||
|
Log.d(TAG, "Adding stateful");
|
||||||
|
m_updatePublisher.onNext(thingToControl(thing));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The publisher for all needs to be completed when done
|
||||||
|
if (m_publisherForAll != null) {
|
||||||
|
Log.d(TAG, "Completing all publisher");
|
||||||
|
m_publisherForAll.onComplete();
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.d(TAG, "Done processing");
|
||||||
|
// We never close the update publisher as we need that one to send updates
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Publisher createPublisherForAllAvailable() {
|
||||||
|
Log.d(TAG, "Creating publishers for all");
|
||||||
|
m_publisherForAll = ReplayProcessor.create();
|
||||||
|
process();
|
||||||
|
return FlowAdapters.toFlowPublisher(m_publisherForAll);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Publisher createPublisherFor(List controlIds) {
|
public Publisher createPublisherFor(List controlIds) {
|
||||||
Log.d("********************************* Creating publishers for one ****************************", "..");
|
Log.d(TAG, "Creating publishers for " + Integer.toString(controlIds.size()));
|
||||||
// for(int i = 0; i < controlIds.size(); i++) {
|
m_updatePublisher = ReplayProcessor.create();
|
||||||
// Log.d("requested control id:", controlIds.get(i));
|
m_activeControlIds = controlIds;
|
||||||
// }
|
process();
|
||||||
Context context = getBaseContext();
|
return FlowAdapters.toFlowPublisher(m_updatePublisher);
|
||||||
/* Fill in details for the activity related to this device. On long press,
|
|
||||||
* this Intent will be launched in a bottomsheet. Please design the activity
|
|
||||||
* accordingly to fit a more limited space (about 2/3 screen height).
|
|
||||||
*/
|
|
||||||
Intent i = new Intent();
|
|
||||||
PendingIntent pi = PendingIntent.getActivity(context, 1, i, PendingIntent.FLAG_UPDATE_CURRENT);
|
|
||||||
|
|
||||||
updatePublisher = ReplayProcessor.create();
|
|
||||||
|
|
||||||
// For each controlId in controlIds
|
|
||||||
|
|
||||||
if (controlIds.contains("e24b0d95-9982-4f9b-ad8b-2aa6b9aba8fd")) {
|
|
||||||
Log.d("**", "control asked");
|
|
||||||
Control control = new Control.StatefulBuilder("e24b0d95-9982-4f9b-ad8b-2aa6b9aba8fd", pi)
|
|
||||||
// Required: The name of the control
|
|
||||||
.setTitle("TestTitle")
|
|
||||||
// Required: Usually the room where the control is located
|
|
||||||
.setSubtitle("TestSubTitle")
|
|
||||||
// Optional: Structure where the control is located, an example would be a house
|
|
||||||
.setStructure("TestStructure")
|
|
||||||
// Required: Type of device, i.e., thermostat, light, switch
|
|
||||||
.setDeviceType(DeviceTypes.TYPE_GENERIC_ON_OFF) // For example, DeviceTypes.TYPE_THERMOSTAT
|
|
||||||
// Required: Current status of the device
|
|
||||||
.setStatus(Control.STATUS_OK) // For example, Control.STATUS_OK
|
|
||||||
.build();
|
|
||||||
|
|
||||||
updatePublisher.onNext(control);
|
|
||||||
}
|
|
||||||
// Uses the Reactive Streams API
|
|
||||||
return FlowAdapters.toFlowPublisher(updatePublisher);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void performControlAction(String controlId, ControlAction action, Consumer consumer) {
|
public void performControlAction(String controlId, ControlAction action, Consumer consumer) {
|
||||||
/* First, locate the control identified by the controlId. Once it is located, you can
|
Log.d(TAG, "Performing control action: " + controlId);
|
||||||
* interpret the action appropriately for that specific device. For instance, the following
|
//// PendingAction pendingAction = new PendingAction();
|
||||||
* assumes that the controlId is associated with a light, and the light can be turned on
|
//// pendingAction.thingId = controlId;
|
||||||
* or off.
|
//// pendingAction.actionTypeId = "";
|
||||||
*/
|
//// pendingAction.consumer = consumer;
|
||||||
if (action instanceof BooleanAction) {
|
//// m_pendingActions.put(
|
||||||
|
|
||||||
// Inform SystemUI that the action has been received and is being processed
|
Thing thing = m_serviceConnection.getThing(controlId);
|
||||||
consumer.accept(ControlAction.RESPONSE_OK);
|
if (thing == null) {
|
||||||
|
Log.d(TAG, "Thing not found for id: " + controlId);
|
||||||
BooleanAction bAction = (BooleanAction) action;
|
consumer.accept(ControlAction.RESPONSE_FAIL);
|
||||||
// In this example, action.getNewState() will have the requested action: true for “On”,
|
return;
|
||||||
// false for “Off”.
|
|
||||||
|
|
||||||
/* This is where application logic/network requests would be invoked to update the state of
|
|
||||||
* the device.
|
|
||||||
* After updating, the application should use the publisher to update SystemUI with the new
|
|
||||||
* state.
|
|
||||||
*/
|
|
||||||
// Control control = new Control.StatefulBuilder("123", pi)
|
|
||||||
// // Required: The name of the control
|
|
||||||
// .setTitle("TestControl")
|
|
||||||
// // Required: Usually the room where the control is located
|
|
||||||
// .setSubtitle("TestSubTitle")
|
|
||||||
// // Optional: Structure where the control is located, an example would be a house
|
|
||||||
// .setStructure("TestStructure")
|
|
||||||
// // Required: Type of device, i.e., thermostat, light, switch
|
|
||||||
// .setDeviceType(DeviceTypes.TYPE_GENERIC_ON_OFF) // For example, DeviceTypes.TYPE_THERMOSTAT
|
|
||||||
// // Required: Current status of the device
|
|
||||||
// .setStatus(Control.STATUS_OK) // For example, Control.STATUS_OK
|
|
||||||
// .build();
|
|
||||||
|
|
||||||
// // This is the publisher the application created during the call to createPublisherFor()
|
|
||||||
// updatePublisher.onNext(control);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String actionTypeId;
|
||||||
|
String param;
|
||||||
|
if (thing.interfaces.contains("dimmablelight") && action instanceof FloatAction) {
|
||||||
|
actionTypeId = thing.stateByName("brightness").typeId;
|
||||||
|
FloatAction fAction = (FloatAction) action;
|
||||||
|
param = String.valueOf(Math.round(fAction.getNewValue()));
|
||||||
|
} else if (thing.interfaces.contains("power") && action instanceof BooleanAction) {
|
||||||
|
actionTypeId = thing.stateByName("power").typeId;
|
||||||
|
BooleanAction bAction = (BooleanAction) action;
|
||||||
|
param = bAction.getNewState() == true ? "true" : "false";
|
||||||
|
} else if (thing.interfaces.contains("closable") && action instanceof BooleanAction) {
|
||||||
|
BooleanAction bAction = (BooleanAction) action;
|
||||||
|
if (bAction.getNewState()) {
|
||||||
|
Log.d(TAG, "executing open");
|
||||||
|
actionTypeId = thing.actionByName("open").typeId;
|
||||||
|
} else {
|
||||||
|
Log.d(TAG, "executing close");
|
||||||
|
actionTypeId = thing.actionByName("close").typeId;
|
||||||
|
}
|
||||||
|
param = "";
|
||||||
|
} else {
|
||||||
|
Log.d(TAG, "Unhandled action for: " + thing.name);
|
||||||
|
consumer.accept(ControlAction.RESPONSE_FAIL);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_serviceConnection.executeAction(thing.id, actionTypeId, param);
|
||||||
|
consumer.accept(ControlAction.RESPONSE_OK);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private Control thingToControl(Thing thing) {
|
||||||
|
Log.d(TAG, "Creating control for thing: " + thing.name + " id: " + thing.id);
|
||||||
|
|
||||||
|
|
||||||
|
// TODO: Create Intent to launch control view
|
||||||
|
Context context = getBaseContext();
|
||||||
|
Intent intent = new Intent(this, NymeaAppActivity.class);
|
||||||
|
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||||
|
PendingIntent m_pi = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||||
|
|
||||||
|
Control.StatefulBuilder builder = new Control.StatefulBuilder(thing.id, m_pi)
|
||||||
|
.setTitle(thing.name)
|
||||||
|
.setSubtitle(thing.className)
|
||||||
|
.setStructure("TestLocation");
|
||||||
|
|
||||||
|
if (thing.interfaces.contains("impulsebasedgaragedoor")) {
|
||||||
|
builder.setDeviceType(DeviceTypes.TYPE_GARAGE);
|
||||||
|
builder.setControlTemplate(new StatelessTemplate(thing.id));
|
||||||
|
} else if (thing.interfaces.contains("statefulgaragedoor")) {
|
||||||
|
builder.setDeviceType(DeviceTypes.TYPE_GARAGE);
|
||||||
|
State stateState = thing.stateByName("state");
|
||||||
|
ControlButton controlButton = new ControlButton(stateState.value.equals("open"), stateState.displayName);
|
||||||
|
builder.setControlTemplate(new ToggleTemplate(thing.id, controlButton));
|
||||||
|
|
||||||
|
// } else if (thing.interfaces.contains("extendedstatefulgaragedoor")) {
|
||||||
|
// builder.setDeviceTyoe(DeviceTypes.TYPE_GARAGE);
|
||||||
|
|
||||||
|
} else if (thing.interfaces.contains("light")) {
|
||||||
|
builder.setDeviceType(DeviceTypes.TYPE_LIGHT);
|
||||||
|
State powerState = thing.stateByName("power");
|
||||||
|
ControlButton controlButton = new ControlButton(powerState.value.equals("true"), powerState.displayName);
|
||||||
|
|
||||||
|
if (thing.interfaces.contains("dimmablelight")) {
|
||||||
|
State brightnessState = thing.stateByName("brightness");
|
||||||
|
RangeTemplate rangeTemplate = new RangeTemplate(thing.id, 0, 100, Float.parseFloat(brightnessState.value), 1, brightnessState.displayName);
|
||||||
|
builder.setControlTemplate(new ToggleRangeTemplate(thing.id, controlButton, rangeTemplate));
|
||||||
|
} else {
|
||||||
|
builder.setControlTemplate(new ToggleTemplate(thing.id, controlButton));
|
||||||
|
}
|
||||||
|
} else if (thing.interfaces.contains("powersocket")) {
|
||||||
|
builder.setDeviceType(DeviceTypes.TYPE_OUTLET);
|
||||||
|
State powerState = thing.stateByName("power");
|
||||||
|
ControlButton controlButton = new ControlButton(powerState.value.equals("true"), powerState.displayName);
|
||||||
|
builder.setControlTemplate(new ToggleTemplate(thing.id, controlButton));
|
||||||
|
} else {
|
||||||
|
builder.setDeviceType(DeviceTypes.TYPE_GENERIC_ON_OFF);
|
||||||
|
}
|
||||||
|
builder.setStatus(Control.STATUS_OK);
|
||||||
|
|
||||||
|
Log.d(TAG, "Created control for thing: " + thing.name + " id: " + thing.id);
|
||||||
|
return builder.build();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,13 @@
|
|||||||
|
package io.guh.nymeaapp;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.telephony.TelephonyManager;
|
||||||
|
import android.provider.Settings.Secure;
|
||||||
|
import android.os.Vibrator;
|
||||||
|
|
||||||
|
public class NymeaAppControlsActivity extends org.qtproject.qt5.android.bindings.QtActivity
|
||||||
|
{
|
||||||
|
}
|
||||||
49
packaging/android/src/io/guh/nymeaapp/NymeaAppService.java
Normal file
49
packaging/android/src/io/guh/nymeaapp/NymeaAppService.java
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
package io.guh.nymeaapp;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import org.qtproject.qt5.android.bindings.QtService;
|
||||||
|
|
||||||
|
public class NymeaAppService extends QtService
|
||||||
|
{
|
||||||
|
public static final String BROADCAST_STATE_CHANGE = "io.guh.nymeaapp.NymeaAppService.broadcast.stateChanged";
|
||||||
|
|
||||||
|
private static final String TAG = "nymea-app: NymeaAppService";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate() {
|
||||||
|
super.onCreate();
|
||||||
|
Log.i(TAG, "Creating Service");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroy() {
|
||||||
|
super.onDestroy();
|
||||||
|
Log.i(TAG, "Destroying Service");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||||
|
int ret = super.onStartCommand(intent, flags, startId);
|
||||||
|
|
||||||
|
// Do some work
|
||||||
|
|
||||||
|
Log.d(TAG, "*************** Service started");
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sendBroadcast(String thingId, String stateTypeId, String value) {
|
||||||
|
// String name = new String(intent.getByteArrayExtra("name"));
|
||||||
|
Intent sendToUiIntent = new Intent();
|
||||||
|
sendToUiIntent.setAction(BROADCAST_STATE_CHANGE);
|
||||||
|
sendToUiIntent.putExtra("name", "io.guh.nymeaapp.NymeaAppService");
|
||||||
|
sendToUiIntent.putExtra("thingId", thingId);
|
||||||
|
sendToUiIntent.putExtra("stateTypeId", stateTypeId);
|
||||||
|
sendToUiIntent.putExtra("value", value);
|
||||||
|
Log.d(TAG, "Service sending broadcast");
|
||||||
|
sendBroadcast(sendToUiIntent);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,205 @@
|
|||||||
|
package io.guh.nymeaapp;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import android.os.IBinder;
|
||||||
|
import android.os.Parcel;
|
||||||
|
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.IntentFilter;
|
||||||
|
import android.content.BroadcastReceiver;
|
||||||
|
import android.content.ServiceConnection;
|
||||||
|
import android.content.ComponentName;
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import android.service.controls.Control;
|
||||||
|
import android.service.controls.DeviceTypes;
|
||||||
|
|
||||||
|
import io.reactivex.processors.ReplayProcessor;
|
||||||
|
|
||||||
|
import org.json.*;
|
||||||
|
|
||||||
|
|
||||||
|
public class NymeaAppServiceConnection implements ServiceConnection {
|
||||||
|
private static final String TAG = "nymea-app: NymeaAppServiceConnection";
|
||||||
|
private IBinder m_service;
|
||||||
|
private boolean m_isConnectedToNymea = false;
|
||||||
|
private boolean m_isReady = false;
|
||||||
|
private Context m_context;
|
||||||
|
|
||||||
|
private ArrayList<Thing> m_things = new ArrayList<>();
|
||||||
|
|
||||||
|
public NymeaAppServiceConnection(Context context) {
|
||||||
|
super();
|
||||||
|
m_context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
final public boolean isConnected() {
|
||||||
|
return m_service != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
final public boolean isConnectedToNymea() {
|
||||||
|
return m_isConnectedToNymea;
|
||||||
|
}
|
||||||
|
|
||||||
|
final public boolean isReady() {
|
||||||
|
return m_isReady;
|
||||||
|
}
|
||||||
|
|
||||||
|
final public ArrayList<Thing> getThings() {
|
||||||
|
return m_things;
|
||||||
|
}
|
||||||
|
final public Thing getThing(String thingId) {
|
||||||
|
for (int i = 0; i < m_things.size(); i++) {
|
||||||
|
if (m_things.get(i).id.equals(thingId)) {
|
||||||
|
return m_things.get(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onReady() {}
|
||||||
|
public void onError() {}
|
||||||
|
public void onUpdate(String thingId) {}
|
||||||
|
|
||||||
|
final public void executeAction(String thingId, String actionTypeId, String param) {
|
||||||
|
try {
|
||||||
|
Parcel parcel = Parcel.obtain();
|
||||||
|
parcel.writeByteArray(thingId.getBytes());
|
||||||
|
parcel.writeByteArray(actionTypeId.getBytes());
|
||||||
|
parcel.writeByteArray(param.getBytes());
|
||||||
|
Parcel retParcel = Parcel.obtain();
|
||||||
|
m_service.transact(2, parcel, retParcel, 0);
|
||||||
|
// thingsList = retParcel.readString();
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.d(TAG, "Error calling executeAction on NymeaAppService");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public void onServiceConnected(ComponentName className, IBinder service) {
|
||||||
|
Log.d(TAG, "Connected to NymeaAppService");
|
||||||
|
m_service = service;
|
||||||
|
|
||||||
|
try {
|
||||||
|
boolean ready = false;
|
||||||
|
Log.d(TAG, "Waiting for service to be connected to nymea...");
|
||||||
|
do {
|
||||||
|
Parcel parcel = Parcel.obtain();
|
||||||
|
Parcel retParcel = Parcel.obtain();
|
||||||
|
m_service.transact(0, parcel, retParcel, 0);
|
||||||
|
ready = retParcel.readBoolean();
|
||||||
|
if (!ready) {
|
||||||
|
Thread.sleep(100);
|
||||||
|
}
|
||||||
|
} while (!ready);
|
||||||
|
Log.d(TAG, "Service connected to nymea!");
|
||||||
|
m_isConnectedToNymea = true;
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.d(TAG, "Error while waiting for service to be connected to nymea");
|
||||||
|
m_service = null;
|
||||||
|
onError();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String thingsList;
|
||||||
|
try {
|
||||||
|
Log.d(TAG, "Fetching things");
|
||||||
|
Parcel parcel = Parcel.obtain();
|
||||||
|
Parcel retParcel = Parcel.obtain();
|
||||||
|
m_service.transact(1, parcel, retParcel, 0);
|
||||||
|
thingsList = retParcel.readString();
|
||||||
|
Log.d(TAG, "Things fetched");
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.d(TAG, "Error fetching things from NymeaAppService");
|
||||||
|
m_service = null;
|
||||||
|
m_isConnectedToNymea = false;
|
||||||
|
onError();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
Log.d(TAG, "Parsing JSON");
|
||||||
|
JSONArray arr = new JSONArray(thingsList);
|
||||||
|
for (int i = 0; i < arr.length(); i++) {
|
||||||
|
JSONObject entry = arr.getJSONObject(i);
|
||||||
|
Thing thing = new Thing();
|
||||||
|
thing.id = entry.getString("id");
|
||||||
|
thing.name = entry.getString("name");
|
||||||
|
thing.className = entry.getString("className");
|
||||||
|
JSONArray ifaces = entry.getJSONArray("interfaces");
|
||||||
|
for (int j = 0; j < ifaces.length(); j++) {
|
||||||
|
thing.interfaces.add(ifaces.get(j));
|
||||||
|
}
|
||||||
|
JSONArray states = entry.getJSONArray("states");
|
||||||
|
for (int j = 0; j < states.length(); j++) {
|
||||||
|
JSONObject stateMap = states.getJSONObject(j);
|
||||||
|
State s = new State();
|
||||||
|
s.typeId = stateMap.getString("stateTypeId");
|
||||||
|
s.name = stateMap.getString("name");
|
||||||
|
s.displayName = stateMap.getString("displayName");
|
||||||
|
s.value = stateMap.getString("value");
|
||||||
|
thing.states.add(s);
|
||||||
|
}
|
||||||
|
JSONArray actions = entry.getJSONArray("actions");
|
||||||
|
for (int j = 0; j < actions.length(); j++) {
|
||||||
|
JSONObject actionMap = actions.getJSONObject(j);
|
||||||
|
Action a = new Action();
|
||||||
|
a.typeId = actionMap.getString("actionTypeId");
|
||||||
|
a.name = actionMap.getString("name");
|
||||||
|
a.displayName = actionMap.getString("displayName");
|
||||||
|
thing.actions.add(a);
|
||||||
|
}
|
||||||
|
m_things.add(thing);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.d(TAG, "Error parsing JSON from NymeaAppService: " + thingsList);
|
||||||
|
m_service = null;
|
||||||
|
m_isConnectedToNymea = false;
|
||||||
|
onError();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.d(TAG, "Fetched things");
|
||||||
|
m_isReady = true;
|
||||||
|
onReady();
|
||||||
|
|
||||||
|
registerServiceBroadcastReceiver();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public void onServiceDisconnected(ComponentName arg0) {
|
||||||
|
m_service = null;
|
||||||
|
m_isConnectedToNymea = false;
|
||||||
|
m_isReady = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void registerServiceBroadcastReceiver() {
|
||||||
|
IntentFilter intentFilter = new IntentFilter();
|
||||||
|
intentFilter.addAction(NymeaAppService.BROADCAST_STATE_CHANGE);
|
||||||
|
m_context.registerReceiver(serviceMessageReceiver, intentFilter);
|
||||||
|
Log.d(TAG, "Registered broadcast receiver");
|
||||||
|
}
|
||||||
|
private BroadcastReceiver serviceMessageReceiver = new BroadcastReceiver() {
|
||||||
|
@Override
|
||||||
|
public void onReceive(Context context, Intent intent) {
|
||||||
|
Log.d(TAG, "In OnReceive broadcast receiver");
|
||||||
|
if (NymeaAppService.BROADCAST_STATE_CHANGE.equals(intent.getAction())) {
|
||||||
|
String name = intent.getStringExtra("name");
|
||||||
|
String thingId = intent.getStringExtra("thingId");
|
||||||
|
String stateTypeId = intent.getStringExtra("stateTypeId");
|
||||||
|
String value = intent.getStringExtra("value");
|
||||||
|
Log.d(TAG, "Thing state changed: " + thingId + " stateTypeId: " + stateTypeId + " value: " + value);
|
||||||
|
|
||||||
|
for (int i = 0; i < m_things.size(); i++) {
|
||||||
|
if (m_things.get(i).id.equals(thingId)) {
|
||||||
|
m_things.get(i).stateById(stateTypeId).value = value;
|
||||||
|
onUpdate(thingId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
8
packaging/android/src/io/guh/nymeaapp/State.java
Normal file
8
packaging/android/src/io/guh/nymeaapp/State.java
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
package io.guh.nymeaapp;
|
||||||
|
|
||||||
|
public class State {
|
||||||
|
public String typeId;
|
||||||
|
public String name;
|
||||||
|
public String displayName;
|
||||||
|
public String value;
|
||||||
|
}
|
||||||
54
packaging/android/src/io/guh/nymeaapp/Thing.java
Normal file
54
packaging/android/src/io/guh/nymeaapp/Thing.java
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
package io.guh.nymeaapp;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
public class Thing {
|
||||||
|
static final public String TAG = "nymea-app: Thing";
|
||||||
|
public String id;
|
||||||
|
public String name;
|
||||||
|
public String className;
|
||||||
|
public List interfaces = new ArrayList<State>();
|
||||||
|
|
||||||
|
public ArrayList<State> states = new ArrayList<State>();
|
||||||
|
public ArrayList<Action> actions = new ArrayList<Action>();
|
||||||
|
|
||||||
|
public State stateByName(String name) {
|
||||||
|
for (int i = 0; i < states.size(); i++) {
|
||||||
|
if (states.get(i).name.equals(name)) {
|
||||||
|
return states.get(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public State stateById(String stateTypeId) {
|
||||||
|
for (int i = 0; i < states.size(); i++) {
|
||||||
|
if (states.get(i).typeId.equals(stateTypeId)) {
|
||||||
|
return states.get(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Action actionByName(String name) {
|
||||||
|
for (int i = 0; i < actions.size(); i++) {
|
||||||
|
Log.d(TAG, "Thing has action: " + actions.get(i).name);
|
||||||
|
if (actions.get(i).name.equals(name)) {
|
||||||
|
return actions.get(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Action actionById(String actionTypeId) {
|
||||||
|
for (int i = 0; i < actions.size(); i++) {
|
||||||
|
if (actions.get(i).typeId.equals(actionTypeId)) {
|
||||||
|
return actions.get(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user