From c9c5752281e6c79e27f2e7ab92a6fb77d1940fbb Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Thu, 10 Sep 2020 15:17:05 +0200 Subject: [PATCH 01/11] A first stab on Google Device Actions (not working at all yet) --- packaging/android/AndroidManifest.xml | 7 + packaging/android/build.gradle | 2 + .../nymeaapp/NymeaAppHomeControlsService.java | 127 ++++++++++++++++++ 3 files changed, 136 insertions(+) create mode 100644 packaging/android/src/io/guh/nymeaapp/NymeaAppHomeControlsService.java diff --git a/packaging/android/AndroidManifest.xml b/packaging/android/AndroidManifest.xml index 4bbbaaf7..d6469ea1 100644 --- a/packaging/android/AndroidManifest.xml +++ b/packaging/android/AndroidManifest.xml @@ -73,6 +73,13 @@ + + + + + + + diff --git a/packaging/android/build.gradle b/packaging/android/build.gradle index 69d802cd..957ca95f 100644 --- a/packaging/android/build.gradle +++ b/packaging/android/build.gradle @@ -30,6 +30,8 @@ dependencies { compile 'com.google.android.gms:play-services-base:15.0.1' compile 'com.google.firebase:firebase-messaging:18.0.0' compile 'com.google.firebase.messaging.cpp:firebase_messaging_cpp@aar' + implementation 'org.reactivestreams:reactive-streams:1.0.3' + implementation 'io.reactivex.rxjava2:rxjava:2.2.0' } apply plugin: 'com.google.gms.google-services' diff --git a/packaging/android/src/io/guh/nymeaapp/NymeaAppHomeControlsService.java b/packaging/android/src/io/guh/nymeaapp/NymeaAppHomeControlsService.java new file mode 100644 index 00000000..bb31d2b0 --- /dev/null +++ b/packaging/android/src/io/guh/nymeaapp/NymeaAppHomeControlsService.java @@ -0,0 +1,127 @@ +package io.guh.nymeaapp; + +import android.util.Log; +import android.content.Intent; +import android.app.PendingIntent; +import android.net.Uri; +import android.content.Context; +import android.provider.Settings.System; +import android.os.Build; +import android.service.controls.ControlsProviderService; +import android.service.controls.actions.ControlAction; +import android.service.controls.actions.BooleanAction; +import android.service.controls.Control; +import android.service.controls.DeviceTypes; + +import java.util.Random; +import java.util.concurrent.Flow.Publisher; +import java.util.function.Consumer; +import java.util.List; +import java.util.ArrayList; +import io.reactivex.Flowable; +import io.reactivex.processors.ReplayProcessor; +import org.reactivestreams.FlowAdapters; + +public class NymeaAppHomeControlsService extends ControlsProviderService { + + private ReplayProcessor updatePublisher; + private PendingIntent pi; + + @Override + public Publisher createPublisherForAllAvailable() { + Context context = getBaseContext(); + Intent i = new Intent(); +// PendingIntent pi = PendingIntent.getActivity(context, 1, i, PendingIntent.FLAG_UPDATE_CURRENT); + pi = PendingIntent.getActivity(context, 1, i, PendingIntent.FLAG_UPDATE_CURRENT); + List controls = new ArrayList<>(); + Control control = new Control.StatelessBuilder("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("TestLocation") + // Required: Type of device, i.e., thermostat, light, switch + .setDeviceType(DeviceTypes.TYPE_LIGHT) // For example, DeviceTypes.TYPE_THERMOSTAT + .build(); + controls.add(control); + // Create more controls here if needed and add it to the ArrayList + + // Uses the RxJava 2 library + return FlowAdapters.toFlowPublisher(Flowable.fromIterable(controls)); + } + + + @Override + public Publisher createPublisherFor(List controlIds) { + Context context = getBaseContext(); + /* 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(123)) { + Control control = new Control.StatefulBuilder("123", 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_LIGHT) // 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 + public void performControlAction(String controlId, ControlAction action, Consumer consumer) { + /* First, locate the control identified by the controlId. Once it is located, you can + * interpret the action appropriately for that specific device. For instance, the following + * assumes that the controlId is associated with a light, and the light can be turned on + * or off. + */ + if (action instanceof BooleanAction) { + + // Inform SystemUI that the action has been received and is being processed + consumer.accept(ControlAction.RESPONSE_OK); + + BooleanAction bAction = (BooleanAction) action; + // In this example, action.getNewState() will have the requested action: true for “On”, + // 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_LIGHT) // 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); + } + } +} From ee34f6a6239db512dd2e667963cdc889dca90455 Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Fri, 11 Sep 2020 00:58:56 +0200 Subject: [PATCH 02/11] It's appearing --- nymea-app/nymea-app.pro | 1 + packaging/android/AndroidManifest.xml | 11 ++-- ...rvice.java => NymeaAppControlService.java} | 55 ++++++++++--------- 3 files changed, 35 insertions(+), 32 deletions(-) rename packaging/android/src/io/guh/nymeaapp/{NymeaAppHomeControlsService.java => NymeaAppControlService.java} (65%) diff --git a/nymea-app/nymea-app.pro b/nymea-app/nymea-app.pro index a832a44d..480a2503 100644 --- a/nymea-app/nymea-app.pro +++ b/nymea-app/nymea-app.pro @@ -77,6 +77,7 @@ android { $$ANDROID_PACKAGE_SOURCE_DIR/gradlew.bat \ $$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/NymeaAppControlService.java \ $$ANDROID_PACKAGE_SOURCE_DIR/LICENSE # https://bugreports.qt.io/browse/QTBUG-83165 diff --git a/packaging/android/AndroidManifest.xml b/packaging/android/AndroidManifest.xml index d6469ea1..33c84d4d 100644 --- a/packaging/android/AndroidManifest.xml +++ b/packaging/android/AndroidManifest.xml @@ -73,12 +73,11 @@ - - - - - - + + + + + diff --git a/packaging/android/src/io/guh/nymeaapp/NymeaAppHomeControlsService.java b/packaging/android/src/io/guh/nymeaapp/NymeaAppControlService.java similarity index 65% rename from packaging/android/src/io/guh/nymeaapp/NymeaAppHomeControlsService.java rename to packaging/android/src/io/guh/nymeaapp/NymeaAppControlService.java index bb31d2b0..e5727f07 100644 --- a/packaging/android/src/io/guh/nymeaapp/NymeaAppHomeControlsService.java +++ b/packaging/android/src/io/guh/nymeaapp/NymeaAppControlService.java @@ -5,15 +5,12 @@ import android.content.Intent; import android.app.PendingIntent; import android.net.Uri; import android.content.Context; -import android.provider.Settings.System; -import android.os.Build; import android.service.controls.ControlsProviderService; import android.service.controls.actions.ControlAction; import android.service.controls.actions.BooleanAction; import android.service.controls.Control; import android.service.controls.DeviceTypes; -import java.util.Random; import java.util.concurrent.Flow.Publisher; import java.util.function.Consumer; import java.util.List; @@ -22,19 +19,20 @@ import io.reactivex.Flowable; import io.reactivex.processors.ReplayProcessor; import org.reactivestreams.FlowAdapters; -public class NymeaAppHomeControlsService extends ControlsProviderService { +public class NymeaAppControlService extends ControlsProviderService { private ReplayProcessor updatePublisher; - private PendingIntent pi; @Override public Publisher createPublisherForAllAvailable() { + Log.d("********************************* Creating publishers for all ****************************", "fff"); + Context context = getBaseContext(); Intent i = new Intent(); -// PendingIntent pi = PendingIntent.getActivity(context, 1, i, PendingIntent.FLAG_UPDATE_CURRENT); - pi = PendingIntent.getActivity(context, 1, i, PendingIntent.FLAG_UPDATE_CURRENT); + PendingIntent pi = PendingIntent.getActivity(context, 1, i, PendingIntent.FLAG_UPDATE_CURRENT); +// pi = PendingIntent.getActivity(context, 1, i, PendingIntent.FLAG_UPDATE_CURRENT); List controls = new ArrayList<>(); - Control control = new Control.StatelessBuilder("123", pi) + Control control = new Control.StatelessBuilder("e24b0d95-9982-4f9b-ad8b-2aa6b9aba8fd", pi) // Required: The name of the control .setTitle("TestControl") // Required: Usually the room where the control is located @@ -42,7 +40,7 @@ public class NymeaAppHomeControlsService extends ControlsProviderService { // Optional: Structure where the control is located, an example would be a house .setStructure("TestLocation") // Required: Type of device, i.e., thermostat, light, switch - .setDeviceType(DeviceTypes.TYPE_LIGHT) // For example, DeviceTypes.TYPE_THERMOSTAT + .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 @@ -54,6 +52,10 @@ public class NymeaAppHomeControlsService extends ControlsProviderService { @Override public Publisher createPublisherFor(List controlIds) { + Log.d("********************************* Creating publishers for one ****************************", ".."); +// for(int i = 0; i < controlIds.size(); i++) { +// Log.d("requested control id:", controlIds.get(i)); +// } Context context = getBaseContext(); /* 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 @@ -66,8 +68,9 @@ public class NymeaAppHomeControlsService extends ControlsProviderService { // For each controlId in controlIds - if (controlIds.contains(123)) { - Control control = new Control.StatefulBuilder("123", pi) + 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 @@ -75,7 +78,7 @@ public class NymeaAppHomeControlsService extends ControlsProviderService { // 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_LIGHT) // For example, DeviceTypes.TYPE_THERMOSTAT + .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(); @@ -107,21 +110,21 @@ public class NymeaAppHomeControlsService extends ControlsProviderService { * 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_LIGHT) // For example, DeviceTypes.TYPE_THERMOSTAT - // Required: Current status of the device - .setStatus(Control.STATUS_OK) // For example, Control.STATUS_OK - .build(); +// 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); +// // This is the publisher the application created during the call to createPublisherFor() +// updatePublisher.onNext(control); } } } From 09e259266b06654069a040e0d21b7e4480fb2d5d Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Mon, 14 Sep 2020 13:23:14 +0200 Subject: [PATCH 03/11] It's working (needs a lot more love still tho) --- androidservice/androidbinder.cpp | 97 +++++++ androidservice/androidbinder.h | 19 ++ androidservice/androidservice.pro | 34 +++ androidservice/service_main.cpp | 55 ++++ libnymea-app/devicemanager.cpp | 7 +- libnymea-app/devicemanager.h | 3 +- libnymea-app/engine.h | 3 - libnymea-common/libnymea-common.h | 7 - libnymea-common/libnymea-common.pro | 15 - nymea-app.pro | 5 + nymea-app/nymea-app.pro | 1 + nymea-app/platformhelper.cpp | 5 + nymea-app/platformhelper.h | 2 + .../android/platformhelperandroid.cpp | 37 ++- .../android/platformhelperandroid.h | 6 +- nymea-app/ui/RootItem.qml | 6 + packaging/android/AndroidManifest.xml | 28 ++ .../android/src/io/guh/nymeaapp/Action.java | 7 + .../src/io/guh/nymeaapp/NymeaAppActivity.java | 3 - .../guh/nymeaapp/NymeaAppControlService.java | 269 ++++++++++++------ .../nymeaapp/NymeaAppControlsActivity.java | 13 + .../src/io/guh/nymeaapp/NymeaAppService.java | 49 ++++ .../nymeaapp/NymeaAppServiceConnection.java | 205 +++++++++++++ .../android/src/io/guh/nymeaapp/State.java | 8 + .../android/src/io/guh/nymeaapp/Thing.java | 54 ++++ 25 files changed, 809 insertions(+), 129 deletions(-) create mode 100644 androidservice/androidbinder.cpp create mode 100644 androidservice/androidbinder.h create mode 100644 androidservice/androidservice.pro create mode 100644 androidservice/service_main.cpp delete mode 100644 libnymea-common/libnymea-common.h delete mode 100644 libnymea-common/libnymea-common.pro create mode 100644 packaging/android/src/io/guh/nymeaapp/Action.java create mode 100644 packaging/android/src/io/guh/nymeaapp/NymeaAppControlsActivity.java create mode 100644 packaging/android/src/io/guh/nymeaapp/NymeaAppService.java create mode 100644 packaging/android/src/io/guh/nymeaapp/NymeaAppServiceConnection.java create mode 100644 packaging/android/src/io/guh/nymeaapp/State.java create mode 100644 packaging/android/src/io/guh/nymeaapp/Thing.java diff --git a/androidservice/androidbinder.cpp b/androidservice/androidbinder.cpp new file mode 100644 index 00000000..029f17e3 --- /dev/null +++ b/androidservice/androidbinder.cpp @@ -0,0 +1,97 @@ +#include "androidbinder.h" +#include "engine.h" +#include "types/device.h" + +#include +#include +#include +#include +#include + +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("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("writeString", "(Ljava/lang/String;)V", QAndroidJniObject::fromString(jsonDoc.toJson()).object()); + } break; + case 2: {// ExecuteAction +// QString thingId = data.handle().callMethod("readString", "").toString(); +// jstring atId = data.handle().callMethod("readString", ""); +// QString actionTypeId = QAndroidJniObject::fromLocalRef(atId).toString(); +// jstring p = data.handle().callMethod("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; +} diff --git a/androidservice/androidbinder.h b/androidservice/androidbinder.h new file mode 100644 index 00000000..0647cff4 --- /dev/null +++ b/androidservice/androidbinder.h @@ -0,0 +1,19 @@ +#ifndef ANDROIDBINDER_H +#define ANDROIDBINDER_H + +#include + +#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 diff --git a/androidservice/androidservice.pro b/androidservice/androidservice.pro new file mode 100644 index 00000000..62c2cb68 --- /dev/null +++ b/androidservice/androidservice.pro @@ -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 + diff --git a/androidservice/service_main.cpp b/androidservice/service_main.cpp new file mode 100644 index 00000000..9a63316e --- /dev/null +++ b/androidservice/service_main.cpp @@ -0,0 +1,55 @@ +#include +#include +#include +#include +#include + +#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("sendBroadcast", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V", + QAndroidJniObject::fromString(thingId.toString()).object(), + QAndroidJniObject::fromString(stateTypeId.toString()).object(), + QAndroidJniObject::fromString(value.toString()).object()); + }); + + return app.exec(); +} diff --git a/libnymea-app/devicemanager.cpp b/libnymea-app/devicemanager.cpp index a2eaafd1..b2ce20d7 100644 --- a/libnymea-app/devicemanager.cpp +++ b/libnymea-app/devicemanager.cpp @@ -175,10 +175,11 @@ void DeviceManager::notificationReceived(const QVariantMap &data) qWarning() << "Device state change notification received for an unknown device"; 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"); -// qDebug() << "Device state changed for:" << dev->name() << "State name:" << dev->thingClass()->stateTypes()->getStateType(stateTyoeId) << "value:" << value; - dev->setStateValue(stateTyoeId, value); +// qDebug() << "Device state changed for:" << dev->name() << "State name:" << dev->thingClass()->stateTypes()->getStateType(stateTypeId) << "value:" << value; + dev->setStateValue(stateTypeId, value); + emit thingStateChanged(dev->id(), stateTypeId, value); } else if (notification == "Devices.DeviceAdded") { Device *dev = JsonTypes::unpackDevice(this, data.value("params").toMap().value("device").toMap(), m_thingClasses); if (!dev) { diff --git a/libnymea-app/devicemanager.h b/libnymea-app/devicemanager.h index b09d3ddc..1afcc01d 100644 --- a/libnymea-app/devicemanager.h +++ b/libnymea-app/devicemanager.h @@ -151,7 +151,8 @@ signals: void fetchingDataChanged(); 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: Vendors *m_vendors; diff --git a/libnymea-app/engine.h b/libnymea-app/engine.h index 300b7fb4..07b38c6a 100644 --- a/libnymea-app/engine.h +++ b/libnymea-app/engine.h @@ -61,9 +61,6 @@ class Engine : public QObject public: explicit Engine(QObject *parent = nullptr); - bool connected() const; - QString connectedHost() const; - DeviceManager *deviceManager() const; DeviceManager *thingManager() const; RuleManager *ruleManager() const; diff --git a/libnymea-common/libnymea-common.h b/libnymea-common/libnymea-common.h deleted file mode 100644 index 1fd15938..00000000 --- a/libnymea-common/libnymea-common.h +++ /dev/null @@ -1,7 +0,0 @@ -#include - -#if defined(LIBNYMEA_COMMON) -# define LIBNYMEA_COMMON_EXPORT Q_DECL_EXPORT -#else -# define LIBNYMEA_COMMON_EXPORT Q_DECL_IMPORT -#endif diff --git a/libnymea-common/libnymea-common.pro b/libnymea-common/libnymea-common.pro deleted file mode 100644 index a7e17797..00000000 --- a/libnymea-common/libnymea-common.pro +++ /dev/null @@ -1,15 +0,0 @@ -include(../config.pri) - -TARGET = nymea-common -TEMPLATE = lib -CONFIG += staticlib - -QT -= gui -QT += network - -HEADERS += \ - - -SOURCES += \ - - diff --git a/nymea-app.pro b/nymea-app.pro index 24e9cefe..63a6c6be 100644 --- a/nymea-app.pro +++ b/nymea-app.pro @@ -82,6 +82,11 @@ icons.path = /usr/share/ INSTALLS += desktopfile icons } +# Android service +android: { +SUBDIRS += androidservice +} + # Linux desktop (snap package) snap: { desktopfile.files = packaging/linux/nymea-app.desktop diff --git a/nymea-app/nymea-app.pro b/nymea-app/nymea-app.pro index 480a2503..c4ae3f28 100644 --- a/nymea-app/nymea-app.pro +++ b/nymea-app/nymea-app.pro @@ -78,6 +78,7 @@ android { $$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/NymeaAppControlService.java \ + $$ANDROID_PACKAGE_SOURCE_DIR/src/io/guh/nymeaapp/NymeaAppService.java \ $$ANDROID_PACKAGE_SOURCE_DIR/LICENSE # https://bugreports.qt.io/browse/QTBUG-83165 diff --git a/nymea-app/platformhelper.cpp b/nymea-app/platformhelper.cpp index 27a97d4d..e3226514 100644 --- a/nymea-app/platformhelper.cpp +++ b/nymea-app/platformhelper.cpp @@ -98,3 +98,8 @@ QString PlatformHelper::fromClipBoard() { return QApplication::clipboard()->text(); } + +void PlatformHelper::syncThings() +{ + // no-op by default +} diff --git a/nymea-app/platformhelper.h b/nymea-app/platformhelper.h index d0074ef7..de32c3a2 100644 --- a/nymea-app/platformhelper.h +++ b/nymea-app/platformhelper.h @@ -88,6 +88,8 @@ public: Q_INVOKABLE virtual void toClipBoard(const QString &text); Q_INVOKABLE virtual QString fromClipBoard(); + Q_INVOKABLE virtual void syncThings(); + signals: void permissionsRequestFinished(); void screenTimeoutChanged(); diff --git a/nymea-app/platformintegration/android/platformhelperandroid.cpp b/nymea-app/platformintegration/android/platformhelperandroid.cpp index 7332b8e9..e0861943 100644 --- a/nymea-app/platformintegration/android/platformhelperandroid.cpp +++ b/nymea-app/platformintegration/android/platformhelperandroid.cpp @@ -33,6 +33,7 @@ #include #include #include +#include // WindowManager.LayoutParams @@ -47,13 +48,16 @@ static PlatformHelperAndroid *m_instance; static QAndroidJniObject getAndroidWindow() { QAndroidJniObject window = QtAndroid::androidActivity().callObjectMethod("getWindow", "()Landroid/view/Window;"); - window.callMethod("addFlags", "(I)V", FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); - window.callMethod("clearFlags", "(I)V", FLAG_TRANSLUCENT_STATUS); return window; } 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; } @@ -123,6 +127,33 @@ void PlatformHelperAndroid::vibrate(PlatformHelper::HapticsFeedback feedbackType QtAndroid::androidActivity().callMethod("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("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) { PlatformHelper::setTopPanelColor(color); @@ -132,6 +163,8 @@ void PlatformHelperAndroid::setTopPanelColor(const QColor &color) QtAndroid::runOnAndroidThread([=]() { QAndroidJniObject window = getAndroidWindow(); + window.callMethod("addFlags", "(I)V", FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); + window.callMethod("clearFlags", "(I)V", FLAG_TRANSLUCENT_STATUS); window.callMethod("setStatusBarColor", "(I)V", color.rgba()); }); diff --git a/nymea-app/platformintegration/android/platformhelperandroid.h b/nymea-app/platformintegration/android/platformhelperandroid.h index b648b755..d393d549 100644 --- a/nymea-app/platformintegration/android/platformhelperandroid.h +++ b/nymea-app/platformintegration/android/platformhelperandroid.h @@ -31,9 +31,11 @@ #ifndef PLATFORMHELPERANDROID_H #define PLATFORMHELPERANDROID_H -#include #include "platformhelper.h" + +#include #include +#include class PlatformHelperAndroid : public PlatformHelper { @@ -55,6 +57,7 @@ public: QString deviceManufacturer() const override; Q_INVOKABLE void vibrate(HapticsFeedback feedbackType) override; + Q_INVOKABLE void syncThings() override; void setTopPanelColor(const QColor &color) override; void setTopPanelTheme(Theme theme); @@ -62,7 +65,6 @@ public: private: static void permissionRequestFinished(const QtAndroid::PermissionResultMap &); - }; #endif // PLATFORMHELPERANDROID_H diff --git a/nymea-app/ui/RootItem.qml b/nymea-app/ui/RootItem.qml index e38bc904..8fb50f6f 100644 --- a/nymea-app/ui/RootItem.qml +++ b/nymea-app/ui/RootItem.qml @@ -132,6 +132,12 @@ Item { initialItem: Page {} } + Button { + anchors.centerIn: parent + text: "bla" + onClicked: PlatformHelper.syncThings() + } + Component.onCompleted: { setupPushNotifications(); if (autoConnectHost.length > 0) { diff --git a/packaging/android/AndroidManifest.xml b/packaging/android/AndroidManifest.xml index 33c84d4d..85aabd73 100644 --- a/packaging/android/AndroidManifest.xml +++ b/packaging/android/AndroidManifest.xml @@ -62,8 +62,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packaging/android/src/io/guh/nymeaapp/Action.java b/packaging/android/src/io/guh/nymeaapp/Action.java new file mode 100644 index 00000000..7cd4735d --- /dev/null +++ b/packaging/android/src/io/guh/nymeaapp/Action.java @@ -0,0 +1,7 @@ +package io.guh.nymeaapp; + +public class Action { + public String typeId; + public String name; + public String displayName; +} diff --git a/packaging/android/src/io/guh/nymeaapp/NymeaAppActivity.java b/packaging/android/src/io/guh/nymeaapp/NymeaAppActivity.java index 1a923e5e..0abd2ec5 100644 --- a/packaging/android/src/io/guh/nymeaapp/NymeaAppActivity.java +++ b/packaging/android/src/io/guh/nymeaapp/NymeaAppActivity.java @@ -8,9 +8,6 @@ import android.telephony.TelephonyManager; import android.provider.Settings.Secure; import android.os.Vibrator; -//import com.google.firebase.messaging.MessageForwardingService; - - public class NymeaAppActivity extends org.qtproject.qt5.android.bindings.QtActivity { public String deviceSerial() diff --git a/packaging/android/src/io/guh/nymeaapp/NymeaAppControlService.java b/packaging/android/src/io/guh/nymeaapp/NymeaAppControlService.java index e5727f07..68afe3cd 100644 --- a/packaging/android/src/io/guh/nymeaapp/NymeaAppControlService.java +++ b/packaging/android/src/io/guh/nymeaapp/NymeaAppControlService.java @@ -2,129 +2,212 @@ package io.guh.nymeaapp; import android.util.Log; import android.content.Intent; +import android.content.ServiceConnection; +import android.content.ComponentName; import android.app.PendingIntent; import android.net.Uri; import android.content.Context; import android.service.controls.ControlsProviderService; -import android.service.controls.actions.ControlAction; -import android.service.controls.actions.BooleanAction; +import android.service.controls.actions.*; import android.service.controls.Control; 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.function.Consumer; import java.util.List; import java.util.ArrayList; +import java.util.HashMap; import io.reactivex.Flowable; import io.reactivex.processors.ReplayProcessor; import org.reactivestreams.FlowAdapters; +import org.json.*; 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(); - Intent i = new Intent(); - PendingIntent pi = PendingIntent.getActivity(context, 1, i, PendingIntent.FLAG_UPDATE_CURRENT); -// pi = PendingIntent.getActivity(context, 1, i, PendingIntent.FLAG_UPDATE_CURRENT); - List controls = new ArrayList<>(); - Control control = new Control.StatelessBuilder("e24b0d95-9982-4f9b-ad8b-2aa6b9aba8fd", 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("TestLocation") - // 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 + private void ensureServiceConnection() { + if (m_serviceConnection == null) { + m_serviceConnection = new NymeaAppServiceConnection(getBaseContext()) { + @Override public void onReady() { + process(); + } + @Override public void onUpdate(String thingId) { + if (m_updatePublisher != null && m_activeControlIds.contains(thingId)) { + Thing thing = m_serviceConnection.getThing(thingId); + Log.d(TAG, "Updating publisher for thing: " + thing.name + " id: " + thing.id); + m_updatePublisher.onNext(thingToControl(thing)); +// m_updatePublisher.onComplete(); + } + } + }; + } + if (!m_serviceConnection.isConnected()) { + Intent serviceIntent = new Intent(this, NymeaAppService.class); + bindService(serviceIntent, m_serviceConnection, Context.BIND_AUTO_CREATE); + } + } - // Uses the RxJava 2 library - return FlowAdapters.toFlowPublisher(Flowable.fromIterable(controls)); + private void process() { + Log.d(TAG, "Processing..."); + ensureServiceConnection(); + if (!m_serviceConnection.isReady()) { + Log.d(TAG, "Service connection is not ready yet..."); + return; + } + +// ArrayList 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 public Publisher createPublisherFor(List controlIds) { - Log.d("********************************* Creating publishers for one ****************************", ".."); -// for(int i = 0; i < controlIds.size(); i++) { -// Log.d("requested control id:", controlIds.get(i)); -// } - Context context = getBaseContext(); - /* 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); + Log.d(TAG, "Creating publishers for " + Integer.toString(controlIds.size())); + m_updatePublisher = ReplayProcessor.create(); + m_activeControlIds = controlIds; + process(); + return FlowAdapters.toFlowPublisher(m_updatePublisher); } @Override public void performControlAction(String controlId, ControlAction action, Consumer consumer) { - /* First, locate the control identified by the controlId. Once it is located, you can - * interpret the action appropriately for that specific device. For instance, the following - * assumes that the controlId is associated with a light, and the light can be turned on - * or off. - */ - if (action instanceof BooleanAction) { + Log.d(TAG, "Performing control action: " + controlId); +//// PendingAction pendingAction = new PendingAction(); +//// pendingAction.thingId = controlId; +//// pendingAction.actionTypeId = ""; +//// pendingAction.consumer = consumer; +//// m_pendingActions.put( - // Inform SystemUI that the action has been received and is being processed - consumer.accept(ControlAction.RESPONSE_OK); - - BooleanAction bAction = (BooleanAction) action; - // In this example, action.getNewState() will have the requested action: true for “On”, - // 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); + Thing thing = m_serviceConnection.getThing(controlId); + if (thing == null) { + Log.d(TAG, "Thing not found for id: " + controlId); + consumer.accept(ControlAction.RESPONSE_FAIL); + return; } + + 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(); } } diff --git a/packaging/android/src/io/guh/nymeaapp/NymeaAppControlsActivity.java b/packaging/android/src/io/guh/nymeaapp/NymeaAppControlsActivity.java new file mode 100644 index 00000000..64335642 --- /dev/null +++ b/packaging/android/src/io/guh/nymeaapp/NymeaAppControlsActivity.java @@ -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 +{ +} diff --git a/packaging/android/src/io/guh/nymeaapp/NymeaAppService.java b/packaging/android/src/io/guh/nymeaapp/NymeaAppService.java new file mode 100644 index 00000000..f5b8cef5 --- /dev/null +++ b/packaging/android/src/io/guh/nymeaapp/NymeaAppService.java @@ -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); + } +} diff --git a/packaging/android/src/io/guh/nymeaapp/NymeaAppServiceConnection.java b/packaging/android/src/io/guh/nymeaapp/NymeaAppServiceConnection.java new file mode 100644 index 00000000..06ef24b1 --- /dev/null +++ b/packaging/android/src/io/guh/nymeaapp/NymeaAppServiceConnection.java @@ -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 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 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); + } + } + } + } + }; +} diff --git a/packaging/android/src/io/guh/nymeaapp/State.java b/packaging/android/src/io/guh/nymeaapp/State.java new file mode 100644 index 00000000..c106700f --- /dev/null +++ b/packaging/android/src/io/guh/nymeaapp/State.java @@ -0,0 +1,8 @@ +package io.guh.nymeaapp; + +public class State { + public String typeId; + public String name; + public String displayName; + public String value; +} diff --git a/packaging/android/src/io/guh/nymeaapp/Thing.java b/packaging/android/src/io/guh/nymeaapp/Thing.java new file mode 100644 index 00000000..1211212c --- /dev/null +++ b/packaging/android/src/io/guh/nymeaapp/Thing.java @@ -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(); + + public ArrayList states = new ArrayList(); + public ArrayList actions = new ArrayList(); + + 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; + } +} From 63e5d6bf7742cbeb93c0bfba97bff6caac7ed83e Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Wed, 16 Sep 2020 17:27:19 +0200 Subject: [PATCH 04/11] Mostly finished now --- androidservice/androidbinder.cpp | 4 ++ androidservice/androidservice.pro | 26 ++++++-- androidservice/controlviews/Main.qml | 56 ++++++++++++++++ androidservice/controlviews/controlviews.qrc | 5 ++ .../controlviews/devicecontrolapplication.cpp | 62 ++++++++++++++++++ .../controlviews/devicecontrolapplication.h | 14 ++++ .../nymeaappservice/nymeaappservice.cpp | 43 +++++++++++++ .../nymeaappservice/nymeaappservice.h | 20 ++++++ androidservice/service_main.cpp | 64 +++++++------------ libnymea-app/devicemanager.h | 1 + nymea-app/nymea-app.pro | 2 - nymea-app/platformhelper.cpp | 5 -- nymea-app/platformhelper.h | 2 - .../android/platformhelperandroid.cpp | 40 ++++++------ .../android/platformhelperandroid.h | 1 - nymea-app/ui/Nymea.qml | 46 ------------- nymea-app/ui/RootItem.qml | 6 -- nymea-app/ui/devicepages/GarageThingPage.qml | 2 +- nymea-app/ui/grouping/GroupThingsPage.qml | 2 +- nymea-app/ui/mainviews/FavoritesView.qml | 2 +- nymea-app/ui/utils/NymeaUtils.qml | 49 ++++++++++++++ packaging/android/AndroidManifest.xml | 33 ++++++++-- .../guh/nymeaapp/NymeaAppControlService.java | 50 ++++++++++++--- .../nymeaapp/NymeaAppControlsActivity.java | 35 ++++++++++ .../src/io/guh/nymeaapp/NymeaAppService.java | 8 ++- .../nymeaapp/NymeaAppServiceConnection.java | 13 +++- 26 files changed, 441 insertions(+), 150 deletions(-) create mode 100644 androidservice/controlviews/Main.qml create mode 100644 androidservice/controlviews/controlviews.qrc create mode 100644 androidservice/controlviews/devicecontrolapplication.cpp create mode 100644 androidservice/controlviews/devicecontrolapplication.h create mode 100644 androidservice/nymeaappservice/nymeaappservice.cpp create mode 100644 androidservice/nymeaappservice/nymeaappservice.h diff --git a/androidservice/androidbinder.cpp b/androidservice/androidbinder.cpp index 029f17e3..8e923806 100644 --- a/androidservice/androidbinder.cpp +++ b/androidservice/androidbinder.cpp @@ -22,8 +22,12 @@ bool AndroidBinder::onTransact(int code, const QAndroidParcel &data, const QAndr switch (code) { case 0: { // Status request + qDebug() << "Engine is:" << m_engine->jsonRpcClient()->connected(); bool isReady = m_engine->jsonRpcClient()->connected() && !m_engine->thingManager()->fetchingData(); reply.handle().callMethod("writeBoolean", "(Z)V", isReady); + if (isReady) { + reply.handle().callMethod("writeString", "(Ljava/lang/String;)V", QAndroidJniObject::fromString(m_engine->jsonRpcClient()->currentHost()->name()).object()); + } } break; case 1: {// Things request QVariantList thingsList; diff --git a/androidservice/androidservice.pro b/androidservice/androidservice.pro index 62c2cb68..bf2b6ddf 100644 --- a/androidservice/androidservice.pro +++ b/androidservice/androidservice.pro @@ -16,19 +16,37 @@ LIBS += -L$${top_builddir}/libnymea-app/$${ANDROID_TARGET_ARCH} LIBS += -L$$top_builddir/libnymea-app/ -lnymea-app PRE_TARGETDEPS += ../libnymea-app +RESOURCES += controlviews/controlviews.qrc \ + ../nymea-app/resources.qrc \ + ../nymea-app/images.qrc \ + ../nymea-app/styles.qrc + +INCLUDEPATH += ../nymea-app/ + SOURCES += \ androidbinder.cpp \ + controlviews/devicecontrolapplication.cpp \ + nymeaappservice/nymeaappservice.cpp \ + ../nymea-app/stylecontroller.cpp \ + ../nymea-app/platformhelper.cpp \ + ../nymea-app/platformintegration/android/platformhelperandroid.cpp \ service_main.cpp -#HEADERS += servicemessenger.h - HEADERS += \ - androidbinder.h + androidbinder.h \ + controlviews/devicecontrolapplication.h \ + nymeaappservice/nymeaappservice.h \ + ../nymea-app/stylecontroller.h \ + ../nymea-app/platformhelper.h \ + ../nymea-app/platformintegration/android/platformhelperandroid.h \ DISTFILES += \ ../packaging/android/src/io/guh/nymeaapp/Action.java \ + ../packaging/android/src/io/guh/nymeaapp/NymeaAppControlService.java \ + ../packaging/android/src/io/guh/nymeaapp/NymeaAppService.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 + ../packaging/android/src/io/guh/nymeaapp/State.java \ + controlviews/Main.qml diff --git a/androidservice/controlviews/Main.qml b/androidservice/controlviews/Main.qml new file mode 100644 index 00000000..ad29e35a --- /dev/null +++ b/androidservice/controlviews/Main.qml @@ -0,0 +1,56 @@ +import QtQuick 2.8 +import QtQuick.Controls 2.2 +import QtQuick.Controls.Material 2.1 +import QtQuick.Layouts 1.2 +import Qt.labs.settings 1.0 +import Nymea 1.0 +import "qrc:/ui/devicepages/" + +ApplicationWindow { + id: app + visible: true + visibility: ApplicationWindow.FullScreen + + color: Material.background + + // Those variables must be present in the Style + title: appName + Material.primary: primaryColor + Material.accent: accentColor + Material.foreground: foregroundColor + + property int margins: 16 + property int bigMargins: 20 + property int extraSmallFont: 10 + property int smallFont: 13 + property int mediumFont: 16 + property int largeFont: 20 + property int iconSize: 30 + property int delegateHeight: 60 + property color backgroundColor: Material.background + + readonly property bool landscape: app.width > app.height + + ThingsProxy { + id: thingProxy + engine: _engine + filterDeviceId: controlledThingId + } + + property Thing controlledThing: engine.thingManager.fetchingData ? null : engine.thingManager.things.getThing(controlledThingId) + + onControlledThingChanged: { + loader.setSource("qrc:/ui/devicepages/" + NymeaUtils.interfaceListToDevicePage(controlledThing.thingClass.interfaces), {thing: controlledThing, header: null}) + } + + Loader { + id: loader + anchors.fill: parent + anchors.bottomMargin: app.margins // For some reason the bottom edge seems a bit off in the overlay + } + + onClosing: { + print("************* Control View closing") + } + +} diff --git a/androidservice/controlviews/controlviews.qrc b/androidservice/controlviews/controlviews.qrc new file mode 100644 index 00000000..f907b18e --- /dev/null +++ b/androidservice/controlviews/controlviews.qrc @@ -0,0 +1,5 @@ + + + Main.qml + + diff --git a/androidservice/controlviews/devicecontrolapplication.cpp b/androidservice/controlviews/devicecontrolapplication.cpp new file mode 100644 index 00000000..8122649b --- /dev/null +++ b/androidservice/controlviews/devicecontrolapplication.cpp @@ -0,0 +1,62 @@ +#include "devicecontrolapplication.h" + +#include "engine.h" +#include "connection/discovery/nymeadiscovery.h" +#include "connection/nymeahosts.h" +#include "libnymea-app-core.h" +#include "../nymea-app/stylecontroller.h" +#include "../nymea-app/platformhelper.h" +#include "../nymea-app/platformintegration/android/platformhelperandroid.h" + +#include +#include +#include +#include +#include + +QObject *platformHelperProvider(QQmlEngine *engine, QJSEngine *scriptEngine) +{ + Q_UNUSED(engine) + Q_UNUSED(scriptEngine) + return new PlatformHelperAndroid(); +} + +DeviceControlApplication::DeviceControlApplication(int argc, char *argv[]) : QApplication(argc, argv) +{ + setApplicationName("nymea-app"); + setOrganizationName("nymea"); + + qDebug() << "Creating QML view"; + QQmlApplicationEngine *qmlEngine = new QQmlApplicationEngine(this); + + Engine *m_engine = new Engine(this); + + QSettings settings; + settings.beginGroup("tabSettings0"); + QUuid lastConnected = settings.value("lastConnectedHost").toUuid(); + settings.endGroup(); + + NymeaDiscovery *discovery = new NymeaDiscovery(this); + + NymeaHost *host = discovery->nymeaHosts()->find(lastConnected); + qDebug() << "**** Tab settings" << lastConnected << host; + if (host) { + m_engine->jsonRpcClient()->connectToHost(host); + } + + QString thingId = QtAndroid::androidActivity().callObjectMethod("thingId").toString(); + + registerQmlTypes(); + + qmlRegisterSingletonType("Nymea", 1, 0, "PlatformHelper", platformHelperProvider); + qmlRegisterSingletonType(QUrl("qrc:///ui/utils/NymeaUtils.qml"), "Nymea", 1, 0, "NymeaUtils" ); + + StyleController styleController; + qmlEngine->rootContext()->setContextProperty("styleController", &styleController); + qmlEngine->rootContext()->setContextProperty("engine", m_engine); + qmlEngine->rootContext()->setContextProperty("_engine", m_engine); + qmlEngine->rootContext()->setContextProperty("controlledThingId", thingId); + + qmlEngine->load(QUrl(QLatin1String("qrc:/Main.qml"))); +} + diff --git a/androidservice/controlviews/devicecontrolapplication.h b/androidservice/controlviews/devicecontrolapplication.h new file mode 100644 index 00000000..4c80e850 --- /dev/null +++ b/androidservice/controlviews/devicecontrolapplication.h @@ -0,0 +1,14 @@ +#ifndef DEVICECONTROLAPPLICATION_H +#define DEVICECONTROLAPPLICATION_H + +#include + +class DeviceControlApplication : public QApplication +{ + Q_OBJECT +public: + explicit DeviceControlApplication(int argc, char *argv[]); + +}; + +#endif // DEVICECONTROLAPPLICATION_H diff --git a/androidservice/nymeaappservice/nymeaappservice.cpp b/androidservice/nymeaappservice/nymeaappservice.cpp new file mode 100644 index 00000000..27c38c31 --- /dev/null +++ b/androidservice/nymeaappservice/nymeaappservice.cpp @@ -0,0 +1,43 @@ +#include "nymeaappservice.h" +#include "androidbinder.h" + +#include +#include +#include + +#include "connection/discovery/nymeadiscovery.h" +#include "connection/nymeahosts.h" + +NymeaAppService::NymeaAppService(int argc, char **argv): + QAndroidService(argc, argv, [=](const QAndroidIntent &) { + qDebug() << "Android service onBind()"; + return new AndroidBinder{m_engine}; + }), + m_engine(new Engine(this)) +{ + setApplicationName("nymea-app"); + setOrganizationName("nymea"); + + 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) { + m_engine->jsonRpcClient()->connectToHost(host); + } + + QObject::connect(m_engine->thingManager(), &DeviceManager::thingStateChanged, [=](const QUuid &thingId, const QUuid &stateTypeId, const QVariant &value){ +// qDebug() << "**** State changed" << thingId << stateTypeId << value; + QtAndroid::androidService().callMethod("sendBroadcast", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V", + QAndroidJniObject::fromString(thingId.toString()).object(), + QAndroidJniObject::fromString(stateTypeId.toString()).object(), + QAndroidJniObject::fromString(value.toString()).object()); + }); + + +} diff --git a/androidservice/nymeaappservice/nymeaappservice.h b/androidservice/nymeaappservice/nymeaappservice.h new file mode 100644 index 00000000..89d441f9 --- /dev/null +++ b/androidservice/nymeaappservice/nymeaappservice.h @@ -0,0 +1,20 @@ +#ifndef NYMEAAPPSERVICE_H +#define NYMEAAPPSERVICE_H + +#include + +#include "engine.h" + +class NymeaAppService : public QAndroidService +{ + Q_OBJECT +public: + explicit NymeaAppService(int argc, char** argv); + +signals: +private: + Engine *m_engine = nullptr; + +}; + +#endif // NYMEAAPPSERVICE_H diff --git a/androidservice/service_main.cpp b/androidservice/service_main.cpp index 9a63316e..46efab22 100644 --- a/androidservice/service_main.cpp +++ b/androidservice/service_main.cpp @@ -1,55 +1,37 @@ #include -#include -#include #include -#include -#include "androidbinder.h" +#include "nymeaappservice/nymeaappservice.h" +#include "controlviews/devicecontrolapplication.h" -#include "engine.h" -#include "connection/discovery/nymeadiscovery.h" -#include "connection/nymeahosts.h" +#include +#include int main(int argc, char *argv[]) { qWarning() << "Service starting from a separate .so file"; + QLoggingCategory::setFilterRules("qt.remoteobjects.debug=true\n"); - 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); + QStringList args; + for (int i = 0; i < argc; i++) { + args.append(QByteArray(argv[i])); + qDebug() << "nymea-app: Added command line arg" << args.last(); } + QCommandLineParser parser; + QCommandLineOption controlActivityOption("controlActivity"); + parser.addOption(controlActivityOption); + parser.parse(args); - 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("sendBroadcast", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V", - QAndroidJniObject::fromString(thingId.toString()).object(), - QAndroidJniObject::fromString(stateTypeId.toString()).object(), - QAndroidJniObject::fromString(value.toString()).object()); - }); + QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); - return app.exec(); + QCoreApplication *app; + if (parser.isSet(controlActivityOption)) { + qDebug() << "nymea-app: Starting Device Control Activity"; + app = new DeviceControlApplication(argc, argv); + } else { + qDebug() << "nymea-app: Starting NymeaAppService background service"; + app = new NymeaAppService(argc, argv); + } + return app->exec(); } diff --git a/libnymea-app/devicemanager.h b/libnymea-app/devicemanager.h index 1afcc01d..f8e1fc1d 100644 --- a/libnymea-app/devicemanager.h +++ b/libnymea-app/devicemanager.h @@ -61,6 +61,7 @@ class DeviceManager : public JsonHandler Q_PROPERTY(bool fetchingData READ fetchingData NOTIFY fetchingDataChanged) + Q_ENUMS(RemovePolicy) public: enum RemovePolicy { RemovePolicyNone, diff --git a/nymea-app/nymea-app.pro b/nymea-app/nymea-app.pro index c4ae3f28..a832a44d 100644 --- a/nymea-app/nymea-app.pro +++ b/nymea-app/nymea-app.pro @@ -77,8 +77,6 @@ android { $$ANDROID_PACKAGE_SOURCE_DIR/gradlew.bat \ $$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/NymeaAppControlService.java \ - $$ANDROID_PACKAGE_SOURCE_DIR/src/io/guh/nymeaapp/NymeaAppService.java \ $$ANDROID_PACKAGE_SOURCE_DIR/LICENSE # https://bugreports.qt.io/browse/QTBUG-83165 diff --git a/nymea-app/platformhelper.cpp b/nymea-app/platformhelper.cpp index e3226514..27a97d4d 100644 --- a/nymea-app/platformhelper.cpp +++ b/nymea-app/platformhelper.cpp @@ -98,8 +98,3 @@ QString PlatformHelper::fromClipBoard() { return QApplication::clipboard()->text(); } - -void PlatformHelper::syncThings() -{ - // no-op by default -} diff --git a/nymea-app/platformhelper.h b/nymea-app/platformhelper.h index de32c3a2..d0074ef7 100644 --- a/nymea-app/platformhelper.h +++ b/nymea-app/platformhelper.h @@ -88,8 +88,6 @@ public: Q_INVOKABLE virtual void toClipBoard(const QString &text); Q_INVOKABLE virtual QString fromClipBoard(); - Q_INVOKABLE virtual void syncThings(); - signals: void permissionsRequestFinished(); void screenTimeoutChanged(); diff --git a/nymea-app/platformintegration/android/platformhelperandroid.cpp b/nymea-app/platformintegration/android/platformhelperandroid.cpp index e0861943..7dcdb315 100644 --- a/nymea-app/platformintegration/android/platformhelperandroid.cpp +++ b/nymea-app/platformintegration/android/platformhelperandroid.cpp @@ -127,32 +127,32 @@ void PlatformHelperAndroid::vibrate(PlatformHelper::HapticsFeedback feedbackType QtAndroid::androidActivity().callMethod("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() +//void PlatformHelperAndroid::syncThings() +//{ // QAndroidIntent serviceIntent(QtAndroid::androidActivity().object(), -// "io/guh/nymeaapp/NymeaAppControlService"); -// serviceIntent.putExtra("name", QByteArray("foobar")); +// "io/guh/nymeaapp/NymeaAppService"); +// QAndroidJniObject result = QtAndroid::androidActivity().callObjectMethod( +// "startService", +// "(Landroid/content/Intent;)Landroid/content/ComponentName;", +// serviceIntent.handle().object()); -// m_serviceConnection->handle().callMethod("syncThings", "(Ljava/lang/String;)V", "bla"); +//// QtAndroid::androidService() + +//// QAndroidIntent serviceIntent(QtAndroid::androidActivity().object(), +//// "io/guh/nymeaapp/NymeaAppControlService"); +//// serviceIntent.putExtra("name", QByteArray("foobar")); -// QAndroidJniObject result = QtAndroid::androidActivity().callObjectMethod( -// "syncThings", -// "(Landroid/content/Intent;)Landroid/content/ComponentName;", -// m_serviceConnection->handle().object()); -} +//// m_serviceConnection->handle().callMethod("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) { diff --git a/nymea-app/platformintegration/android/platformhelperandroid.h b/nymea-app/platformintegration/android/platformhelperandroid.h index d393d549..fed19a80 100644 --- a/nymea-app/platformintegration/android/platformhelperandroid.h +++ b/nymea-app/platformintegration/android/platformhelperandroid.h @@ -57,7 +57,6 @@ public: QString deviceManufacturer() const override; Q_INVOKABLE void vibrate(HapticsFeedback feedbackType) override; - Q_INVOKABLE void syncThings() override; void setTopPanelColor(const QColor &color) override; void setTopPanelTheme(Theme theme); diff --git a/nymea-app/ui/Nymea.qml b/nymea-app/ui/Nymea.qml index 87f17cb9..e50a72c0 100644 --- a/nymea-app/ui/Nymea.qml +++ b/nymea-app/ui/Nymea.qml @@ -456,52 +456,6 @@ ApplicationWindow { } } - function interfaceListToDevicePage(interfaceList) { - var page; - if (interfaceList.indexOf("media") >= 0) { - page = "MediaDevicePage.qml"; - } else if (interfaceList.indexOf("button") >= 0) { - page = "ButtonDevicePage.qml"; - } else if (interfaceList.indexOf("powerswitch") >= 0) { - page = "ButtonDevicePage.qml"; - } else if (interfaceList.indexOf("weather") >= 0) { - page = "WeatherDevicePage.qml"; - } else if (interfaceList.indexOf("heating") >= 0 || interfaceList.indexOf("thermostat") >= 0) { - page = "HeatingDevicePage.qml"; - } else if (interfaceList.indexOf("sensor") >= 0) { - page = "SensorDevicePage.qml"; - } else if (interfaceList.indexOf("inputtrigger") >= 0) { - page = "InputTriggerDevicePage.qml"; - } else if (interfaceList.indexOf("garagedoor") >= 0 ) { - page = "GarageThingPage.qml"; - } else if (interfaceList.indexOf("light") >= 0) { - page = "LightDevicePage.qml"; - } else if (interfaceList.indexOf("shutter") >= 0 || interfaceList.indexOf("blind") >= 0) { - page = "ShutterDevicePage.qml"; - } else if (interfaceList.indexOf("awning") >= 0) { - page = "AwningDevicePage.qml"; - } else if (interfaceList.indexOf("notifications") >= 0) { - page = "NotificationsDevicePage.qml"; - } else if (interfaceList.indexOf("fingerprintreader") >= 0) { - page = "FingerprintReaderDevicePage.qml"; - } else if (interfaceList.indexOf("smartmeter") >= 0) { - page = "SmartMeterDevicePage.qml" - } else if (interfaceList.indexOf("powersocket") >= 0) { - page = "PowersocketDevicePage.qml"; - } else if (interfaceList.indexOf("doorbell") >= 0) { - page = "DoorbellDevicePage.qml"; - } else if (interfaceList.indexOf("irrigation") >= 0) { - page = "IrrigationDevicePage.qml"; - } else if (interfaceList.indexOf("ventilation") >= 0) { - page = "VentilationDevicePage.qml"; - } else if (interfaceList.indexOf("barcodescanner") >= 0) { - page = "BarcodeScannerThingPage.qml"; - } else { - page = "GenericDevicePage.qml"; - } - print("Selecting page", page, "for interface list:", interfaceList) - return page; - } function pad(num, size) { var s = "000000000" + num; diff --git a/nymea-app/ui/RootItem.qml b/nymea-app/ui/RootItem.qml index 8fb50f6f..e38bc904 100644 --- a/nymea-app/ui/RootItem.qml +++ b/nymea-app/ui/RootItem.qml @@ -132,12 +132,6 @@ Item { initialItem: Page {} } - Button { - anchors.centerIn: parent - text: "bla" - onClicked: PlatformHelper.syncThings() - } - Component.onCompleted: { setupPushNotifications(); if (autoConnectHost.length > 0) { diff --git a/nymea-app/ui/devicepages/GarageThingPage.qml b/nymea-app/ui/devicepages/GarageThingPage.qml index e12e34cf..e314ad76 100644 --- a/nymea-app/ui/devicepages/GarageThingPage.qml +++ b/nymea-app/ui/devicepages/GarageThingPage.qml @@ -79,7 +79,7 @@ DevicePageBase { Layout.alignment: Qt.AlignHCenter property string currentImage: { if (root.isExtended) { - return app.pad(Math.round(root.percentageState.value / 10), 2) + "0" + return NymeaUtils.pad(Math.round(root.percentageState.value / 10), 2) + "0" } if (root.intermediatePositionStateType) { return root.stateState.value === "closed" ? "100" diff --git a/nymea-app/ui/grouping/GroupThingsPage.qml b/nymea-app/ui/grouping/GroupThingsPage.qml index b5ea065c..6966be01 100644 --- a/nymea-app/ui/grouping/GroupThingsPage.qml +++ b/nymea-app/ui/grouping/GroupThingsPage.qml @@ -129,7 +129,7 @@ Page { device: devicesInGroup.get(index) - onClicked: pageStack.push(Qt.resolvedUrl("../devicepages/" + app.interfaceListToDevicePage(deviceClass.interfaces)), {device: device}) + onClicked: pageStack.push(Qt.resolvedUrl("../devicepages/" + NymeaUtils.interfaceListToDevicePage(deviceClass.interfaces)), {device: device}) } } diff --git a/nymea-app/ui/mainviews/FavoritesView.qml b/nymea-app/ui/mainviews/FavoritesView.qml index 48c4eaf9..d54f10b9 100644 --- a/nymea-app/ui/mainviews/FavoritesView.qml +++ b/nymea-app/ui/mainviews/FavoritesView.qml @@ -64,7 +64,7 @@ MainViewBase { height: gridView.cellHeight device: engine.deviceManager.devices.getDevice(deviceId) - onClicked: pageStack.push(Qt.resolvedUrl("../devicepages/" + app.interfaceListToDevicePage(deviceClass.interfaces)), {device: device}) + onClicked: pageStack.push(Qt.resolvedUrl("../devicepages/" + NymeaUtils.interfaceListToDevicePage(deviceClass.interfaces)), {device: device}) onPressAndHold: root.editMode = true diff --git a/nymea-app/ui/utils/NymeaUtils.qml b/nymea-app/ui/utils/NymeaUtils.qml index 2ed6f93d..a705e64c 100644 --- a/nymea-app/ui/utils/NymeaUtils.qml +++ b/nymea-app/ui/utils/NymeaUtils.qml @@ -15,4 +15,53 @@ Item { } return str; } + + function interfaceListToDevicePage(interfaceList) { + print("**** getting page for interfaces", interfaceList) + var page; + if (interfaceList.indexOf("media") >= 0) { + page = "MediaDevicePage.qml"; + } else if (interfaceList.indexOf("button") >= 0) { + page = "ButtonDevicePage.qml"; + } else if (interfaceList.indexOf("powerswitch") >= 0) { + page = "ButtonDevicePage.qml"; + } else if (interfaceList.indexOf("weather") >= 0) { + page = "WeatherDevicePage.qml"; + } else if (interfaceList.indexOf("heating") >= 0 || interfaceList.indexOf("thermostat") >= 0) { + page = "HeatingDevicePage.qml"; + } else if (interfaceList.indexOf("sensor") >= 0) { + page = "SensorDevicePage.qml"; + } else if (interfaceList.indexOf("inputtrigger") >= 0) { + page = "InputTriggerDevicePage.qml"; + } else if (interfaceList.indexOf("garagedoor") >= 0 ) { + page = "GarageThingPage.qml"; + } else if (interfaceList.indexOf("light") >= 0) { + page = "LightDevicePage.qml"; + } else if (interfaceList.indexOf("shutter") >= 0 || interfaceList.indexOf("blind") >= 0) { + page = "ShutterDevicePage.qml"; + } else if (interfaceList.indexOf("awning") >= 0) { + page = "AwningDevicePage.qml"; + } else if (interfaceList.indexOf("notifications") >= 0) { + page = "NotificationsDevicePage.qml"; + } else if (interfaceList.indexOf("fingerprintreader") >= 0) { + page = "FingerprintReaderDevicePage.qml"; + } else if (interfaceList.indexOf("smartmeter") >= 0) { + page = "SmartMeterDevicePage.qml" + } else if (interfaceList.indexOf("powersocket") >= 0) { + page = "PowersocketDevicePage.qml"; + } else if (interfaceList.indexOf("doorbell") >= 0) { + page = "DoorbellDevicePage.qml"; + } else if (interfaceList.indexOf("irrigation") >= 0) { + page = "IrrigationDevicePage.qml"; + } else if (interfaceList.indexOf("ventilation") >= 0) { + page = "VentilationDevicePage.qml"; + } else if (interfaceList.indexOf("barcodescanner") >= 0) { + page = "BarcodeScannerThingPage.qml"; + } else { + page = "GenericDevicePage.qml"; + } + print("Selecting page", page, "for interface list:", interfaceList) + return page; + } + } diff --git a/packaging/android/AndroidManifest.xml b/packaging/android/AndroidManifest.xml index 85aabd73..69b817b6 100644 --- a/packaging/android/AndroidManifest.xml +++ b/packaging/android/AndroidManifest.xml @@ -63,8 +63,29 @@ - + + + + + + + + + + + + + + + + + + + + + + @@ -101,11 +122,11 @@ - - - - - + + + + + diff --git a/packaging/android/src/io/guh/nymeaapp/NymeaAppControlService.java b/packaging/android/src/io/guh/nymeaapp/NymeaAppControlService.java index 68afe3cd..b1f54ad3 100644 --- a/packaging/android/src/io/guh/nymeaapp/NymeaAppControlService.java +++ b/packaging/android/src/io/guh/nymeaapp/NymeaAppControlService.java @@ -26,6 +26,11 @@ import io.reactivex.processors.ReplayProcessor; import org.reactivestreams.FlowAdapters; import org.json.*; +// Android device controls service + +// This service is instantiated by the android device controls on demand. It will +// connect to the NymeaAppService and interact with nymea through that. + public class NymeaAppControlService extends ControlsProviderService { private String TAG = "nymea-app: NymeaAppControlService"; private NymeaAppServiceConnection m_serviceConnection; @@ -65,8 +70,6 @@ public class NymeaAppControlService extends ControlsProviderService { return; } -// ArrayList things = m_serviceConnection.getThings(); - for (Thing thing : m_serviceConnection.getThings()) { Log.d(TAG, "Processing thing: " + thing.name); @@ -147,6 +150,10 @@ public class NymeaAppControlService extends ControlsProviderService { actionTypeId = thing.actionByName("close").typeId; } param = ""; + } else if (thing.interfaces.contains("extendedvolumecontroller")) { + actionTypeId = thing.stateByName("volume").typeId; + FloatAction fAction = (FloatAction) action; + param = String.valueOf(Math.round(fAction.getNewValue())); } else { Log.d(TAG, "Unhandled action for: " + thing.name); consumer.accept(ControlAction.RESPONSE_FAIL); @@ -158,20 +165,31 @@ public class NymeaAppControlService extends ControlsProviderService { } + private HashMap m_intents = new HashMap(); + private Control thingToControl(Thing thing) { - Log.d(TAG, "Creating control for thing: " + thing.name + " id: " + thing.id); +// Log.d(TAG, "Creating control for thing: " + thing.name + " id: " + thing.id); + // NOTE: intentId 1 doesn't work for some reason I don't understand yet... so let's make sure we never add "1" to it by always added 100 + int intentId = m_intents.size() + 100; + PendingIntent pi; + if (m_intents.containsKey(thing.id)) { + intentId = m_intents.get(thing.id); + } else { + m_intents.put(thing.id, intentId); + } - // 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); + Intent intent = new Intent(context, NymeaAppControlsActivity.class); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_MULTIPLE_TASK); + intent.putExtra("thingId", thing.id); + pi = PendingIntent.getActivity(context, intentId, intent, PendingIntent.FLAG_UPDATE_CURRENT); + Log.d(TAG, "Created pendingintent for " + thing.name + " with id " + intentId + " and extra " + thing.id); - Control.StatefulBuilder builder = new Control.StatefulBuilder(thing.id, m_pi) + Control.StatefulBuilder builder = new Control.StatefulBuilder(thing.id, pi) .setTitle(thing.name) .setSubtitle(thing.className) - .setStructure("TestLocation"); + .setStructure(m_serviceConnection.nymeaName()); if (thing.interfaces.contains("impulsebasedgaragedoor")) { builder.setDeviceType(DeviceTypes.TYPE_GARAGE); @@ -202,12 +220,24 @@ public class NymeaAppControlService extends ControlsProviderService { State powerState = thing.stateByName("power"); ControlButton controlButton = new ControlButton(powerState.value.equals("true"), powerState.displayName); builder.setControlTemplate(new ToggleTemplate(thing.id, controlButton)); + } else if (thing.interfaces.contains("mediaplayer")) { + if (thing.stateByName("playerType").value == "video") { + builder.setDeviceType(DeviceTypes.TYPE_TV); + } else { + // FIXME: There doesn't seem to be a speaker DeviceType!?! + builder.setDeviceType(DeviceTypes.TYPE_TV); + } + if (thing.interfaces.contains("extendedvolumecontroller")) { + State volumeState = thing.stateByName("volume"); + RangeTemplate rangeTemplate = new RangeTemplate(thing.id, 0, 100, Float.parseFloat(volumeState.value), 1, volumeState.displayName); + builder.setControlTemplate(rangeTemplate); + } } 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); +// Log.d(TAG, "Created control for thing: " + thing.name + " id: " + thing.id); return builder.build(); } } diff --git a/packaging/android/src/io/guh/nymeaapp/NymeaAppControlsActivity.java b/packaging/android/src/io/guh/nymeaapp/NymeaAppControlsActivity.java index 64335642..01274bf3 100644 --- a/packaging/android/src/io/guh/nymeaapp/NymeaAppControlsActivity.java +++ b/packaging/android/src/io/guh/nymeaapp/NymeaAppControlsActivity.java @@ -7,7 +7,42 @@ import android.os.Build; import android.telephony.TelephonyManager; import android.provider.Settings.Secure; import android.os.Vibrator; +import android.os.Process; + +// An activity spawned by android device controls on demand. public class NymeaAppControlsActivity extends org.qtproject.qt5.android.bindings.QtActivity { + private static final String TAG = "nymea-app: NymeaAppControlActivity"; + + + @Override public void onPause() { + Log.d(TAG, "Pausing..."); + System.exit(0); + } + + @Override public void onResume() { + super.onResume(); + Log.d(TAG, "Resuming..."); + } + + + @Override public void onDestroy() { + Log.d(TAG, "Destroying..."); + } + + + public String thingId() + { + Log.d(TAG, "ThingId called!"); + Log.d(TAG, getIntent().getStringExtra("thingId")); + return getIntent().getStringExtra("thingId"); + } + + public void vibrate(int duration) + { + Vibrator v = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE); + v.vibrate(duration); + } + } diff --git a/packaging/android/src/io/guh/nymeaapp/NymeaAppService.java b/packaging/android/src/io/guh/nymeaapp/NymeaAppService.java index f5b8cef5..640a4888 100644 --- a/packaging/android/src/io/guh/nymeaapp/NymeaAppService.java +++ b/packaging/android/src/io/guh/nymeaapp/NymeaAppService.java @@ -6,6 +6,11 @@ import android.util.Log; import org.qtproject.qt5.android.bindings.QtService; +// Background service establishing a connection to nymea and providing data on android specific interfaces +// such as IBinder and BroadcastListener + +// This service loads the service_main Qt entry point and does most of its work in C++/Qt + public class NymeaAppService extends QtService { public static final String BROADCAST_STATE_CHANGE = "io.guh.nymeaapp.NymeaAppService.broadcast.stateChanged"; @@ -36,14 +41,13 @@ public class NymeaAppService extends QtService } 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"); +// Log.d(TAG, "Service sending broadcast"); sendBroadcast(sendToUiIntent); } } diff --git a/packaging/android/src/io/guh/nymeaapp/NymeaAppServiceConnection.java b/packaging/android/src/io/guh/nymeaapp/NymeaAppServiceConnection.java index 06ef24b1..c5ff87f8 100644 --- a/packaging/android/src/io/guh/nymeaapp/NymeaAppServiceConnection.java +++ b/packaging/android/src/io/guh/nymeaapp/NymeaAppServiceConnection.java @@ -22,6 +22,8 @@ import io.reactivex.processors.ReplayProcessor; import org.json.*; +// Helper class to establish a connection to the NymeaAppService and interact +// with that using IBinder and ServiceBroadcastListener public class NymeaAppServiceConnection implements ServiceConnection { private static final String TAG = "nymea-app: NymeaAppServiceConnection"; @@ -30,6 +32,7 @@ public class NymeaAppServiceConnection implements ServiceConnection { private boolean m_isReady = false; private Context m_context; + private String m_nymeaName = "nymea"; private ArrayList m_things = new ArrayList<>(); public NymeaAppServiceConnection(Context context) { @@ -45,6 +48,10 @@ public class NymeaAppServiceConnection implements ServiceConnection { return m_isConnectedToNymea; } + final public String nymeaName() { + return m_nymeaName; + } + final public boolean isReady() { return m_isReady; } @@ -93,6 +100,8 @@ public class NymeaAppServiceConnection implements ServiceConnection { ready = retParcel.readBoolean(); if (!ready) { Thread.sleep(100); + } else { + m_nymeaName = retParcel.readString(); } } while (!ready); Log.d(TAG, "Service connected to nymea!"); @@ -185,13 +194,13 @@ public class NymeaAppServiceConnection implements ServiceConnection { private BroadcastReceiver serviceMessageReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { - Log.d(TAG, "In OnReceive broadcast receiver"); +// 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); +// 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)) { From 3bc92633abdb45359872605ed8244a70968e0dd9 Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Wed, 16 Sep 2020 19:44:56 +0200 Subject: [PATCH 05/11] Add a splash when loading the activity --- androidservice/controlviews/Main.qml | 1 + packaging/android/AndroidManifest.xml | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/androidservice/controlviews/Main.qml b/androidservice/controlviews/Main.qml index ad29e35a..c87839d2 100644 --- a/androidservice/controlviews/Main.qml +++ b/androidservice/controlviews/Main.qml @@ -41,6 +41,7 @@ ApplicationWindow { onControlledThingChanged: { loader.setSource("qrc:/ui/devicepages/" + NymeaUtils.interfaceListToDevicePage(controlledThing.thingClass.interfaces), {thing: controlledThing, header: null}) + PlatformHelper.hideSplashScreen(); } Loader { diff --git a/packaging/android/AndroidManifest.xml b/packaging/android/AndroidManifest.xml index 69b817b6..a72d0fa5 100644 --- a/packaging/android/AndroidManifest.xml +++ b/packaging/android/AndroidManifest.xml @@ -86,6 +86,10 @@ + + + + From e9c345cc8bb3205f3fadbbe85cf16d8e601658ab Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Sun, 20 Sep 2020 00:53:48 +0200 Subject: [PATCH 06/11] Make the connection establishment non blocking --- androidservice/androidservice.pro | 4 +- .../controlviews/devicecontrolapplication.cpp | 2 + .../{ => nymeaappservice}/androidbinder.cpp | 7 +- .../{ => nymeaappservice}/androidbinder.h | 0 .../nymeaappservice/nymeaappservice.cpp | 41 +++-- .../nymeaappservice/nymeaappservice.h | 5 +- .../android/src/io/guh/nymeaapp/Action.java | 4 +- .../guh/nymeaapp/NymeaAppControlService.java | 32 ++-- .../src/io/guh/nymeaapp/NymeaAppService.java | 11 +- .../nymeaapp/NymeaAppServiceConnection.java | 142 ++++++++++-------- .../android/src/io/guh/nymeaapp/State.java | 4 +- .../android/src/io/guh/nymeaapp/Thing.java | 8 +- 12 files changed, 158 insertions(+), 102 deletions(-) rename androidservice/{ => nymeaappservice}/androidbinder.cpp (98%) rename androidservice/{ => nymeaappservice}/androidbinder.h (100%) diff --git a/androidservice/androidservice.pro b/androidservice/androidservice.pro index bf2b6ddf..8b6cfe84 100644 --- a/androidservice/androidservice.pro +++ b/androidservice/androidservice.pro @@ -24,18 +24,18 @@ RESOURCES += controlviews/controlviews.qrc \ INCLUDEPATH += ../nymea-app/ SOURCES += \ - androidbinder.cpp \ controlviews/devicecontrolapplication.cpp \ nymeaappservice/nymeaappservice.cpp \ + nymeaappservice/androidbinder.cpp \ ../nymea-app/stylecontroller.cpp \ ../nymea-app/platformhelper.cpp \ ../nymea-app/platformintegration/android/platformhelperandroid.cpp \ service_main.cpp HEADERS += \ - androidbinder.h \ controlviews/devicecontrolapplication.h \ nymeaappservice/nymeaappservice.h \ + nymeaappservice/androidbinder.h \ ../nymea-app/stylecontroller.h \ ../nymea-app/platformhelper.h \ ../nymea-app/platformintegration/android/platformhelperandroid.h \ diff --git a/androidservice/controlviews/devicecontrolapplication.cpp b/androidservice/controlviews/devicecontrolapplication.cpp index 8122649b..99978162 100644 --- a/androidservice/controlviews/devicecontrolapplication.cpp +++ b/androidservice/controlviews/devicecontrolapplication.cpp @@ -37,6 +37,8 @@ DeviceControlApplication::DeviceControlApplication(int argc, char *argv[]) : QAp settings.endGroup(); NymeaDiscovery *discovery = new NymeaDiscovery(this); + AWSClient::instance()->setConfig(settings.value("cloudEnvironment").toString()); + discovery->setAwsClient(AWSClient::instance()); NymeaHost *host = discovery->nymeaHosts()->find(lastConnected); qDebug() << "**** Tab settings" << lastConnected << host; diff --git a/androidservice/androidbinder.cpp b/androidservice/nymeaappservice/androidbinder.cpp similarity index 98% rename from androidservice/androidbinder.cpp rename to androidservice/nymeaappservice/androidbinder.cpp index 8e923806..8b3416b9 100644 --- a/androidservice/androidbinder.cpp +++ b/androidservice/nymeaappservice/androidbinder.cpp @@ -24,6 +24,7 @@ bool AndroidBinder::onTransact(int code, const QAndroidParcel &data, const QAndr case 0: { // Status request qDebug() << "Engine is:" << m_engine->jsonRpcClient()->connected(); bool isReady = m_engine->jsonRpcClient()->connected() && !m_engine->thingManager()->fetchingData(); + isReady = false; reply.handle().callMethod("writeBoolean", "(Z)V", isReady); if (isReady) { reply.handle().callMethod("writeString", "(Ljava/lang/String;)V", QAndroidJniObject::fromString(m_engine->jsonRpcClient()->currentHost()->name()).object()); @@ -34,7 +35,7 @@ bool AndroidBinder::onTransact(int code, const QAndroidParcel &data, const QAndr 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("id", thing->id()); thingMap.insert("name", thing->name()); thingMap.insert("className", thing->thingClass()->displayName()); thingMap.insert("interfaces", thing->thingClass()->interfaces()); @@ -42,7 +43,7 @@ bool AndroidBinder::onTransact(int code, const QAndroidParcel &data, const QAndr 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("stateTypeId", state->stateTypeId()); 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()); @@ -53,7 +54,7 @@ bool AndroidBinder::onTransact(int code, const QAndroidParcel &data, const QAndr 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("actionTypeId", actionType->id()); actionMap.insert("name", actionType->name()); actionMap.insert("displayName", actionType->displayName()); actions.append(actionMap); diff --git a/androidservice/androidbinder.h b/androidservice/nymeaappservice/androidbinder.h similarity index 100% rename from androidservice/androidbinder.h rename to androidservice/nymeaappservice/androidbinder.h diff --git a/androidservice/nymeaappservice/nymeaappservice.cpp b/androidservice/nymeaappservice/nymeaappservice.cpp index 27c38c31..756401b4 100644 --- a/androidservice/nymeaappservice/nymeaappservice.cpp +++ b/androidservice/nymeaappservice/nymeaappservice.cpp @@ -4,40 +4,61 @@ #include #include #include +#include #include "connection/discovery/nymeadiscovery.h" #include "connection/nymeahosts.h" NymeaAppService::NymeaAppService(int argc, char **argv): QAndroidService(argc, argv, [=](const QAndroidIntent &) { - qDebug() << "Android service onBind()"; return new AndroidBinder{m_engine}; - }), - m_engine(new Engine(this)) + }) { setApplicationName("nymea-app"); setOrganizationName("nymea"); + m_engine = new Engine(this); + QSettings settings; settings.beginGroup("tabSettings0"); QUuid lastConnected = settings.value("lastConnectedHost").toUuid(); settings.endGroup(); - NymeaDiscovery *discovery = new NymeaDiscovery(); + NymeaDiscovery *discovery = new NymeaDiscovery(this); + AWSClient::instance()->setConfig(settings.value("cloudEnvironment").toString()); + discovery->setAwsClient(AWSClient::instance()); NymeaHost *host = discovery->nymeaHosts()->find(lastConnected); - qDebug() << "**** Tab settings" << lastConnected << host; if (host) { m_engine->jsonRpcClient()->connectToHost(host); } QObject::connect(m_engine->thingManager(), &DeviceManager::thingStateChanged, [=](const QUuid &thingId, const QUuid &stateTypeId, const QVariant &value){ -// qDebug() << "**** State changed" << thingId << stateTypeId << value; - QtAndroid::androidService().callMethod("sendBroadcast", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V", - QAndroidJniObject::fromString(thingId.toString()).object(), - QAndroidJniObject::fromString(stateTypeId.toString()).object(), - QAndroidJniObject::fromString(value.toString()).object()); + QVariantMap params; + params.insert("thingId", thingId); + params.insert("stateTypeId", stateTypeId); + params.insert("value", value); + sendNotification("ThingStateChanged", params); }); + connect(m_engine->thingManager(), &DeviceManager::fetchingDataChanged, [=]() { + QVariantMap params; + params.insert("isReady", !m_engine->thingManager()->fetchingData()); + if (m_engine->jsonRpcClient()->connected()) { + params.insert("systemName", m_engine->jsonRpcClient()->currentHost()->name()); + } + sendNotification("ReadyStateChanged", params); + }); +} + +void NymeaAppService::sendNotification(const QString ¬ification, const QVariantMap ¶ms) +{ + QVariantMap data; + data.insert("notification", notification); + data.insert("params", params); + QString payload = QJsonDocument::fromVariant(data).toJson(); + QtAndroid::androidService().callMethod("sendBroadcast", + "(Ljava/lang/String;)V", + QAndroidJniObject::fromString(payload).object()); } diff --git a/androidservice/nymeaappservice/nymeaappservice.h b/androidservice/nymeaappservice/nymeaappservice.h index 89d441f9..4c2c4068 100644 --- a/androidservice/nymeaappservice/nymeaappservice.h +++ b/androidservice/nymeaappservice/nymeaappservice.h @@ -11,7 +11,10 @@ class NymeaAppService : public QAndroidService public: explicit NymeaAppService(int argc, char** argv); -signals: +private: + void sendNotification(const QString ¬ification, const QVariantMap ¶ms); + + private: Engine *m_engine = nullptr; diff --git a/packaging/android/src/io/guh/nymeaapp/Action.java b/packaging/android/src/io/guh/nymeaapp/Action.java index 7cd4735d..04923f02 100644 --- a/packaging/android/src/io/guh/nymeaapp/Action.java +++ b/packaging/android/src/io/guh/nymeaapp/Action.java @@ -1,7 +1,9 @@ package io.guh.nymeaapp; +import java.util.UUID; + public class Action { - public String typeId; + public UUID typeId; public String name; public String displayName; } diff --git a/packaging/android/src/io/guh/nymeaapp/NymeaAppControlService.java b/packaging/android/src/io/guh/nymeaapp/NymeaAppControlService.java index b1f54ad3..4899b8e8 100644 --- a/packaging/android/src/io/guh/nymeaapp/NymeaAppControlService.java +++ b/packaging/android/src/io/guh/nymeaapp/NymeaAppControlService.java @@ -20,6 +20,7 @@ import java.util.concurrent.Flow.Publisher; import java.util.function.Consumer; import java.util.List; import java.util.ArrayList; +import java.util.UUID; import java.util.HashMap; import io.reactivex.Flowable; import io.reactivex.processors.ReplayProcessor; @@ -46,8 +47,9 @@ public class NymeaAppControlService extends ControlsProviderService { @Override public void onReady() { process(); } - @Override public void onUpdate(String thingId) { - if (m_updatePublisher != null && m_activeControlIds.contains(thingId)) { + @Override public void onUpdate(UUID thingId) { + Log.d(TAG, "onUpdate()"); + if (m_updatePublisher != null && m_activeControlIds.contains(thingId.toString())) { Thing thing = m_serviceConnection.getThing(thingId); Log.d(TAG, "Updating publisher for thing: " + thing.name + " id: " + thing.id); m_updatePublisher.onNext(thingToControl(thing)); @@ -79,7 +81,7 @@ public class NymeaAppControlService extends ControlsProviderService { } if (m_updatePublisher != null) { - if (m_activeControlIds.contains(thing.id)) { + if (m_activeControlIds.contains(thing.id.toString())) { Log.d(TAG, "Adding stateful"); m_updatePublisher.onNext(thingToControl(thing)); } @@ -123,14 +125,14 @@ public class NymeaAppControlService extends ControlsProviderService { //// pendingAction.consumer = consumer; //// m_pendingActions.put( - Thing thing = m_serviceConnection.getThing(controlId); + Thing thing = m_serviceConnection.getThing(UUID.fromString(controlId)); if (thing == null) { Log.d(TAG, "Thing not found for id: " + controlId); consumer.accept(ControlAction.RESPONSE_FAIL); return; } - String actionTypeId; + UUID actionTypeId; String param; if (thing.interfaces.contains("dimmablelight") && action instanceof FloatAction) { actionTypeId = thing.stateByName("brightness").typeId; @@ -165,7 +167,7 @@ public class NymeaAppControlService extends ControlsProviderService { } - private HashMap m_intents = new HashMap(); + private HashMap m_intents = new HashMap(); private Control thingToControl(Thing thing) { // Log.d(TAG, "Creating control for thing: " + thing.name + " id: " + thing.id); @@ -182,23 +184,23 @@ public class NymeaAppControlService extends ControlsProviderService { Context context = getBaseContext(); Intent intent = new Intent(context, NymeaAppControlsActivity.class); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_MULTIPLE_TASK); - intent.putExtra("thingId", thing.id); + intent.putExtra("thingId", thing.id.toString()); pi = PendingIntent.getActivity(context, intentId, intent, PendingIntent.FLAG_UPDATE_CURRENT); Log.d(TAG, "Created pendingintent for " + thing.name + " with id " + intentId + " and extra " + thing.id); - Control.StatefulBuilder builder = new Control.StatefulBuilder(thing.id, pi) + Control.StatefulBuilder builder = new Control.StatefulBuilder(thing.id.toString(), pi) .setTitle(thing.name) .setSubtitle(thing.className) .setStructure(m_serviceConnection.nymeaName()); if (thing.interfaces.contains("impulsebasedgaragedoor")) { builder.setDeviceType(DeviceTypes.TYPE_GARAGE); - builder.setControlTemplate(new StatelessTemplate(thing.id)); + builder.setControlTemplate(new StatelessTemplate(thing.id.toString())); } 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)); + builder.setControlTemplate(new ToggleTemplate(thing.id.toString(), controlButton)); // } else if (thing.interfaces.contains("extendedstatefulgaragedoor")) { // builder.setDeviceTyoe(DeviceTypes.TYPE_GARAGE); @@ -210,16 +212,16 @@ public class NymeaAppControlService extends ControlsProviderService { 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)); + RangeTemplate rangeTemplate = new RangeTemplate(thing.id.toString(), 0, 100, Float.parseFloat(brightnessState.value), 1, brightnessState.displayName); + builder.setControlTemplate(new ToggleRangeTemplate(thing.id.toString(), controlButton, rangeTemplate)); } else { - builder.setControlTemplate(new ToggleTemplate(thing.id, controlButton)); + builder.setControlTemplate(new ToggleTemplate(thing.id.toString(), 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)); + builder.setControlTemplate(new ToggleTemplate(thing.id.toString(), controlButton)); } else if (thing.interfaces.contains("mediaplayer")) { if (thing.stateByName("playerType").value == "video") { builder.setDeviceType(DeviceTypes.TYPE_TV); @@ -229,7 +231,7 @@ public class NymeaAppControlService extends ControlsProviderService { } if (thing.interfaces.contains("extendedvolumecontroller")) { State volumeState = thing.stateByName("volume"); - RangeTemplate rangeTemplate = new RangeTemplate(thing.id, 0, 100, Float.parseFloat(volumeState.value), 1, volumeState.displayName); + RangeTemplate rangeTemplate = new RangeTemplate(thing.id.toString(), 0, 100, Float.parseFloat(volumeState.value), 1, volumeState.displayName); builder.setControlTemplate(rangeTemplate); } } else { diff --git a/packaging/android/src/io/guh/nymeaapp/NymeaAppService.java b/packaging/android/src/io/guh/nymeaapp/NymeaAppService.java index 640a4888..5bc6acf7 100644 --- a/packaging/android/src/io/guh/nymeaapp/NymeaAppService.java +++ b/packaging/android/src/io/guh/nymeaapp/NymeaAppService.java @@ -13,7 +13,7 @@ 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"; + public static final String NYMEA_APP_BROADCAST = "io.guh.nymeaapp.NymeaAppService.broadcast"; private static final String TAG = "nymea-app: NymeaAppService"; @@ -40,13 +40,10 @@ public class NymeaAppService extends QtService return ret; } - public void sendBroadcast(String thingId, String stateTypeId, String value) { + public void sendBroadcast(String payload) { 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); + sendToUiIntent.setAction(NYMEA_APP_BROADCAST); + sendToUiIntent.putExtra("data", payload); // Log.d(TAG, "Service sending broadcast"); sendBroadcast(sendToUiIntent); } diff --git a/packaging/android/src/io/guh/nymeaapp/NymeaAppServiceConnection.java b/packaging/android/src/io/guh/nymeaapp/NymeaAppServiceConnection.java index c5ff87f8..7fd7f8fd 100644 --- a/packaging/android/src/io/guh/nymeaapp/NymeaAppServiceConnection.java +++ b/packaging/android/src/io/guh/nymeaapp/NymeaAppServiceConnection.java @@ -2,6 +2,7 @@ package io.guh.nymeaapp; import java.util.List; import java.util.ArrayList; +import java.util.UUID; import android.util.Log; @@ -59,7 +60,7 @@ public class NymeaAppServiceConnection implements ServiceConnection { final public ArrayList getThings() { return m_things; } - final public Thing getThing(String thingId) { + final public Thing getThing(UUID thingId) { for (int i = 0; i < m_things.size(); i++) { if (m_things.get(i).id.equals(thingId)) { return m_things.get(i); @@ -70,13 +71,13 @@ public class NymeaAppServiceConnection implements ServiceConnection { public void onReady() {} public void onError() {} - public void onUpdate(String thingId) {} + public void onUpdate(UUID thingId) {} - final public void executeAction(String thingId, String actionTypeId, String param) { + final public void executeAction(UUID thingId, UUID actionTypeId, String param) { try { Parcel parcel = Parcel.obtain(); - parcel.writeByteArray(thingId.getBytes()); - parcel.writeByteArray(actionTypeId.getBytes()); + parcel.writeByteArray(thingId.toString().getBytes()); + parcel.writeByteArray(actionTypeId.toString().getBytes()); parcel.writeByteArray(param.getBytes()); Parcel retParcel = Parcel.obtain(); m_service.transact(2, parcel, retParcel, 0); @@ -90,29 +91,87 @@ public class NymeaAppServiceConnection implements ServiceConnection { Log.d(TAG, "Connected to NymeaAppService"); m_service = service; + registerServiceBroadcastReceiver(); + 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); - } else { - m_nymeaName = retParcel.readString(); - } - } while (!ready); - Log.d(TAG, "Service connected to nymea!"); - m_isConnectedToNymea = true; + Parcel parcel = Parcel.obtain(); + Parcel retParcel = Parcel.obtain(); + m_service.transact(0, parcel, retParcel, 0); + m_isReady = retParcel.readBoolean(); + if (!m_isReady) { + m_nymeaName = retParcel.readString(); + m_isConnectedToNymea = true; + Log.d(TAG, "Service is ready!"); + fetchThings(); + } else { + Log.d(TAG, "Service is not ready yet!"); + } } catch (Exception e) { Log.d(TAG, "Error while waiting for service to be connected to nymea"); m_service = null; onError(); return; } + } + @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.NYMEA_APP_BROADCAST); + 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.NYMEA_APP_BROADCAST.equals(intent.getAction())) { + String payload = intent.getStringExtra("data"); + try { + processBroadcast(payload); + } catch(JSONException e) { + Log.d(TAG, "Error parsing broadcast JSON: " + e.toString()); + } + } + } + }; + + private void processBroadcast(String payload) throws JSONException + { + JSONObject data = new JSONObject(payload); + JSONObject params = data.getJSONObject("params"); + Log.d(TAG, "Broadcast received from NymeaAppService: " + data.getString("notification")); + + if (data.getString("notification").equals("ThingStateChanged")) { + UUID thingId = UUID.fromString(params.getString("thingId")); + UUID stateTypeId = UUID.fromString(params.getString("stateTypeId")); + String value = params.getString("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); + } + } + } + + if (data.getString("notification").equals("ReadyStateChanged")) { + m_isReady = params.getBoolean("isReady"); + if (m_isReady) { + m_nymeaName = params.getString("systemName"); + fetchThings(); + } + } + } + + private void fetchThings() { String thingsList; try { Log.d(TAG, "Fetching things"); @@ -135,7 +194,7 @@ public class NymeaAppServiceConnection implements ServiceConnection { for (int i = 0; i < arr.length(); i++) { JSONObject entry = arr.getJSONObject(i); Thing thing = new Thing(); - thing.id = entry.getString("id"); + thing.id = UUID.fromString(entry.getString("id")); thing.name = entry.getString("name"); thing.className = entry.getString("className"); JSONArray ifaces = entry.getJSONArray("interfaces"); @@ -146,7 +205,7 @@ public class NymeaAppServiceConnection implements ServiceConnection { for (int j = 0; j < states.length(); j++) { JSONObject stateMap = states.getJSONObject(j); State s = new State(); - s.typeId = stateMap.getString("stateTypeId"); + s.typeId = UUID.fromString(stateMap.getString("stateTypeId")); s.name = stateMap.getString("name"); s.displayName = stateMap.getString("displayName"); s.value = stateMap.getString("value"); @@ -156,7 +215,7 @@ public class NymeaAppServiceConnection implements ServiceConnection { for (int j = 0; j < actions.length(); j++) { JSONObject actionMap = actions.getJSONObject(j); Action a = new Action(); - a.typeId = actionMap.getString("actionTypeId"); + a.typeId = UUID.fromString(actionMap.getString("actionTypeId")); a.name = actionMap.getString("name"); a.displayName = actionMap.getString("displayName"); thing.actions.add(a); @@ -165,7 +224,7 @@ public class NymeaAppServiceConnection implements ServiceConnection { } } catch (Exception e) { - Log.d(TAG, "Error parsing JSON from NymeaAppService: " + thingsList); + Log.d(TAG, "Error parsing JSON from NymeaAppService: " + e.toString()); m_service = null; m_isConnectedToNymea = false; onError(); @@ -175,40 +234,5 @@ public class NymeaAppServiceConnection implements ServiceConnection { 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); - } - } - } - } - }; } diff --git a/packaging/android/src/io/guh/nymeaapp/State.java b/packaging/android/src/io/guh/nymeaapp/State.java index c106700f..5b7053ea 100644 --- a/packaging/android/src/io/guh/nymeaapp/State.java +++ b/packaging/android/src/io/guh/nymeaapp/State.java @@ -1,7 +1,9 @@ package io.guh.nymeaapp; +import java.util.UUID; + public class State { - public String typeId; + public UUID typeId; public String name; public String displayName; public String value; diff --git a/packaging/android/src/io/guh/nymeaapp/Thing.java b/packaging/android/src/io/guh/nymeaapp/Thing.java index 1211212c..e5667831 100644 --- a/packaging/android/src/io/guh/nymeaapp/Thing.java +++ b/packaging/android/src/io/guh/nymeaapp/Thing.java @@ -4,10 +4,12 @@ import android.util.Log; import java.util.List; import java.util.ArrayList; +import java.util.UUID; + public class Thing { static final public String TAG = "nymea-app: Thing"; - public String id; + public UUID id; public String name; public String className; public List interfaces = new ArrayList(); @@ -24,7 +26,7 @@ public class Thing { return null; } - public State stateById(String stateTypeId) { + public State stateById(UUID stateTypeId) { for (int i = 0; i < states.size(); i++) { if (states.get(i).typeId.equals(stateTypeId)) { return states.get(i); @@ -43,7 +45,7 @@ public class Thing { return null; } - public Action actionById(String actionTypeId) { + public Action actionById(UUID actionTypeId) { for (int i = 0; i < actions.size(); i++) { if (actions.get(i).typeId.equals(actionTypeId)) { return actions.get(i); From c8a91ba9b1daed4bd54463b2b7dfca3dd9a8590d Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Sun, 20 Sep 2020 17:59:06 +0200 Subject: [PATCH 07/11] Support multiple nymea hosts --- .../controlviews/devicecontrolapplication.cpp | 26 +-- .../nymeaappservice/androidbinder.cpp | 196 +++++++++++++----- .../nymeaappservice/androidbinder.h | 8 +- .../nymeaappservice/nymeaappservice.cpp | 67 +++--- .../nymeaappservice/nymeaappservice.h | 4 +- config.pri | 3 + .../guh/nymeaapp/NymeaAppControlService.java | 93 ++++++--- .../nymeaapp/NymeaAppControlsActivity.java | 7 +- .../nymeaapp/NymeaAppServiceConnection.java | 186 ++++++++++------- .../src/io/guh/nymeaapp/NymeaHost.java | 13 ++ 10 files changed, 410 insertions(+), 193 deletions(-) create mode 100644 packaging/android/src/io/guh/nymeaapp/NymeaHost.java diff --git a/androidservice/controlviews/devicecontrolapplication.cpp b/androidservice/controlviews/devicecontrolapplication.cpp index 99978162..1b319967 100644 --- a/androidservice/controlviews/devicecontrolapplication.cpp +++ b/androidservice/controlviews/devicecontrolapplication.cpp @@ -26,27 +26,29 @@ DeviceControlApplication::DeviceControlApplication(int argc, char *argv[]) : QAp setApplicationName("nymea-app"); setOrganizationName("nymea"); - qDebug() << "Creating QML view"; - QQmlApplicationEngine *qmlEngine = new QQmlApplicationEngine(this); - - Engine *m_engine = new Engine(this); + QString nymeaId = QtAndroid::androidActivity().callObjectMethod("nymeaId").toString(); + QString thingId = QtAndroid::androidActivity().callObjectMethod("thingId").toString(); QSettings settings; - settings.beginGroup("tabSettings0"); - QUuid lastConnected = settings.value("lastConnectedHost").toUuid(); - settings.endGroup(); NymeaDiscovery *discovery = new NymeaDiscovery(this); AWSClient::instance()->setConfig(settings.value("cloudEnvironment").toString()); discovery->setAwsClient(AWSClient::instance()); + NymeaHost *host = discovery->nymeaHosts()->find(nymeaId); - NymeaHost *host = discovery->nymeaHosts()->find(lastConnected); - qDebug() << "**** Tab settings" << lastConnected << host; - if (host) { - m_engine->jsonRpcClient()->connectToHost(host); + if (!host) { + qWarning() << "No such nymea host:" << nymeaId; + // TODO: We could wait here until the discovery finds it... But it really should be cached already... + exit(1); } - QString thingId = QtAndroid::androidActivity().callObjectMethod("thingId").toString(); + Engine *m_engine = new Engine(this); + + qDebug() << "Connecting to:" << host; + m_engine->jsonRpcClient()->connectToHost(host); + + qDebug() << "Creating QML view"; + QQmlApplicationEngine *qmlEngine = new QQmlApplicationEngine(this); registerQmlTypes(); diff --git a/androidservice/nymeaappservice/androidbinder.cpp b/androidservice/nymeaappservice/androidbinder.cpp index 8b3416b9..dd0159ea 100644 --- a/androidservice/nymeaappservice/androidbinder.cpp +++ b/androidservice/nymeaappservice/androidbinder.cpp @@ -8,32 +8,53 @@ #include #include -AndroidBinder::AndroidBinder(Engine * engine): - m_engine(engine) +AndroidBinder::AndroidBinder(NymeaAppService *service): + m_service(service) { - 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 - qDebug() << "Engine is:" << m_engine->jsonRpcClient()->connected(); - bool isReady = m_engine->jsonRpcClient()->connected() && !m_engine->thingManager()->fetchingData(); - isReady = false; - reply.handle().callMethod("writeBoolean", "(Z)V", isReady); - if (isReady) { - reply.handle().callMethod("writeString", "(Ljava/lang/String;)V", QAndroidJniObject::fromString(m_engine->jsonRpcClient()->currentHost()->name()).object()); +// QString payload = data.readData(); + QString payload = data.handle().callObjectMethod("readString").toString(); + + QJsonParseError error; + QJsonDocument jsonDoc = QJsonDocument::fromJson(payload.toUtf8(), &error); + if (error.error != QJsonParseError::NoError) { + qWarning() << "Error parsing JSON from parcel:" << error.errorString(); + qWarning() << payload; + return false; + } + QVariantMap request = jsonDoc.toVariant().toMap(); + + if (request.value("method").toString() == "GetInstances") { + QVariantMap params; + QVariantList instances; + foreach (const QUuid &nymeaId, m_service->engines().keys()) { + Engine *engine = m_service->engines().value(nymeaId); + QVariantMap instance; + instance.insert("id", nymeaId); + instance.insert("isReady", engine->jsonRpcClient()->connected() && !engine->thingManager()->fetchingData()); + instance.insert("name", engine->jsonRpcClient()->currentHost()->name()); + instances.append(instance); + } + params.insert("instances", instances); + sendReply(reply, params); + return true; + } + + if (request.value("method").toString() == "GetThings") { + QUuid nymeaId = request.value("params").toMap().value("nymeaId").toUuid(); + Engine *engine = m_service->engines().value(nymeaId); + if (!engine) { + qWarning() << "Android client requested things for an invalid nymea instance:" << nymeaId; + return false; } - } 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); + for (int i = 0; i < engine->thingManager()->things()->rowCount(); i++) { + Device *thing = engine->thingManager()->things()->get(i); QVariantMap thingMap; thingMap.insert("id", thing->id()); thingMap.insert("name", thing->name()); @@ -62,41 +83,110 @@ bool AndroidBinder::onTransact(int code, const QAndroidParcel &data, const QAndr thingMap.insert("actions", actions); thingsList.append(thingMap); } - QJsonDocument jsonDoc = QJsonDocument::fromVariant(thingsList); - reply.handle().callMethod("writeString", "(Ljava/lang/String;)V", QAndroidJniObject::fromString(jsonDoc.toJson()).object()); - } break; - case 2: {// ExecuteAction -// QString thingId = data.handle().callMethod("readString", "").toString(); -// jstring atId = data.handle().callMethod("readString", ""); -// QString actionTypeId = QAndroidJniObject::fromLocalRef(atId).toString(); -// jstring p = data.handle().callMethod("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; + QVariantMap params; + params.insert("things", thingsList); + sendReply(reply, params); + return true; } - return true; + + if (request.value("method").toString() == "ExecuteAction") { + qDebug() << "ExecuteAction"; + QUuid nymeaId = request.value("params").toMap().value("nymeaId").toUuid(); + Engine *engine = m_service->engines().value(nymeaId); + if (!engine) { + qWarning() << "Android client requested executeAction for an invalid nymea instance:" << nymeaId; + return false; + } + QUuid thingId = request.value("params").toMap().value("thingId").toUuid(); + QUuid actionTypeId = request.value("params").toMap().value("actionTypeId").toUuid(); + QVariantList params = request.value("params").toMap().value("params").toList(); + + qDebug() << "**** executeAction:" << thingId << actionTypeId << params; + engine->thingManager()->executeAction(thingId, actionTypeId, params); + } + +// switch (code) { +// case 0: { // Status request +// bool isReady = m_engine->jsonRpcClient()->connected() && !m_engine->thingManager()->fetchingData(); +// isReady = false; +// reply.handle().callMethod("writeBoolean", "(Z)V", isReady); +// if (isReady) { +// reply.handle().callMethod("writeString", "(Ljava/lang/String;)V", QAndroidJniObject::fromString(m_engine->jsonRpcClient()->currentHost()->name()).object()); +// } +// } 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()); +// 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()); +// 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()); +// 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("writeString", "(Ljava/lang/String;)V", QAndroidJniObject::fromString(jsonDoc.toJson()).object()); +// } break; +// case 2: {// ExecuteAction +//// QString thingId = data.handle().callMethod("readString", "").toString(); +//// jstring atId = data.handle().callMethod("readString", ""); +//// QString actionTypeId = QAndroidJniObject::fromLocalRef(atId).toString(); +//// jstring p = data.handle().callMethod("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 false; +} + +void AndroidBinder::sendReply(const QAndroidParcel &reply, const QVariantMap ¶ms) +{ + QString payload = QJsonDocument::fromVariant(params).toJson(); + reply.handle().callMethod("writeString", "(Ljava/lang/String;)V", QAndroidJniObject::fromString(payload).object()); } diff --git a/androidservice/nymeaappservice/androidbinder.h b/androidservice/nymeaappservice/androidbinder.h index 0647cff4..36a9b795 100644 --- a/androidservice/nymeaappservice/androidbinder.h +++ b/androidservice/nymeaappservice/androidbinder.h @@ -3,17 +3,21 @@ #include +#include "nymeaappservice.h" #include "engine.h" class AndroidBinder : public QAndroidBinder { public: - explicit AndroidBinder(Engine *engine); + explicit AndroidBinder(NymeaAppService *service); bool onTransact(int code, const QAndroidParcel &data, const QAndroidParcel &reply, QAndroidBinder::CallType flags) override; private: - Engine *m_engine = nullptr; + void sendReply(const QAndroidParcel &reply, const QVariantMap ¶ms); + +private: + NymeaAppService *m_service = nullptr; }; #endif // ANDROIDBINDER_H diff --git a/androidservice/nymeaappservice/nymeaappservice.cpp b/androidservice/nymeaappservice/nymeaappservice.cpp index 756401b4..d2c673ff 100644 --- a/androidservice/nymeaappservice/nymeaappservice.cpp +++ b/androidservice/nymeaappservice/nymeaappservice.cpp @@ -11,44 +11,63 @@ NymeaAppService::NymeaAppService(int argc, char **argv): QAndroidService(argc, argv, [=](const QAndroidIntent &) { - return new AndroidBinder{m_engine}; + return new AndroidBinder{this}; }) { setApplicationName("nymea-app"); setOrganizationName("nymea"); - m_engine = new Engine(this); - QSettings settings; - settings.beginGroup("tabSettings0"); - QUuid lastConnected = settings.value("lastConnectedHost").toUuid(); - settings.endGroup(); NymeaDiscovery *discovery = new NymeaDiscovery(this); AWSClient::instance()->setConfig(settings.value("cloudEnvironment").toString()); discovery->setAwsClient(AWSClient::instance()); - NymeaHost *host = discovery->nymeaHosts()->find(lastConnected); - if (host) { - m_engine->jsonRpcClient()->connectToHost(host); + + for (int i = 0; i < 5; i++) { + settings.beginGroup(QString("tabSettings%1").arg(i)); + QUuid lastConnected = settings.value("lastConnectedHost").toUuid(); + settings.endGroup(); + + if (lastConnected.isNull()) { + continue; + } + NymeaHost *host = discovery->nymeaHosts()->find(lastConnected); + if (!host) { + continue; + } + + Engine *engine = new Engine(this); + engine->jsonRpcClient()->connectToHost(host); + m_engines.insert(host->uuid(), engine); + + + QObject::connect(engine->thingManager(), &DeviceManager::thingStateChanged, [=](const QUuid &thingId, const QUuid &stateTypeId, const QVariant &value){ + QVariantMap params; + params.insert("nymeaId", engine->jsonRpcClient()->currentHost()->uuid()); + params.insert("thingId", thingId); + params.insert("stateTypeId", stateTypeId); + params.insert("value", value); + sendNotification("ThingStateChanged", params); + }); + + connect(engine->thingManager(), &DeviceManager::fetchingDataChanged, [=]() { + qDebug() << "Fetching data changed"; + QVariantMap params; + params.insert("nymeaId", engine->jsonRpcClient()->currentHost()->uuid()); + params.insert("isReady", !engine->thingManager()->fetchingData()); + qDebug() << "Nymea host is ready" << engine->jsonRpcClient()->currentHost()->uuid(); + sendNotification("ReadyStateChanged", params); + }); } - QObject::connect(m_engine->thingManager(), &DeviceManager::thingStateChanged, [=](const QUuid &thingId, const QUuid &stateTypeId, const QVariant &value){ - QVariantMap params; - params.insert("thingId", thingId); - params.insert("stateTypeId", stateTypeId); - params.insert("value", value); - sendNotification("ThingStateChanged", params); - }); + qDebug() << "NymeaAppService started."; - connect(m_engine->thingManager(), &DeviceManager::fetchingDataChanged, [=]() { - QVariantMap params; - params.insert("isReady", !m_engine->thingManager()->fetchingData()); - if (m_engine->jsonRpcClient()->connected()) { - params.insert("systemName", m_engine->jsonRpcClient()->currentHost()->name()); - } - sendNotification("ReadyStateChanged", params); - }); +} + +QHash NymeaAppService::engines() const +{ + return m_engines; } void NymeaAppService::sendNotification(const QString ¬ification, const QVariantMap ¶ms) diff --git a/androidservice/nymeaappservice/nymeaappservice.h b/androidservice/nymeaappservice/nymeaappservice.h index 4c2c4068..6f6e653a 100644 --- a/androidservice/nymeaappservice/nymeaappservice.h +++ b/androidservice/nymeaappservice/nymeaappservice.h @@ -11,12 +11,14 @@ class NymeaAppService : public QAndroidService public: explicit NymeaAppService(int argc, char** argv); + QHash engines() const; + private: void sendNotification(const QString ¬ification, const QVariantMap ¶ms); private: - Engine *m_engine = nullptr; + QHash m_engines; }; diff --git a/config.pri b/config.pri index 9d0cae52..1dfee639 100644 --- a/config.pri +++ b/config.pri @@ -11,3 +11,6 @@ APP_REVISION=$$member(VERSION_INFO, 1) DEFINES+=APP_VERSION=\\\"$${APP_VERSION}\\\" android:QMAKE_POST_LINK += cp $$top_srcdir/version.txt $$top_builddir/ + +DISTFILES += \ + $$PWD/packaging/android/src/io/guh/nymeaapp/NymeaHost.java diff --git a/packaging/android/src/io/guh/nymeaapp/NymeaAppControlService.java b/packaging/android/src/io/guh/nymeaapp/NymeaAppControlService.java index 4899b8e8..f9b91d3d 100644 --- a/packaging/android/src/io/guh/nymeaapp/NymeaAppControlService.java +++ b/packaging/android/src/io/guh/nymeaapp/NymeaAppControlService.java @@ -36,7 +36,10 @@ public class NymeaAppControlService extends ControlsProviderService { private String TAG = "nymea-app: NymeaAppControlService"; private NymeaAppServiceConnection m_serviceConnection; + // For publishing all available private ReplayProcessor m_publisherForAll; + private ArrayList m_pendingForAll = new ArrayList(); // pending nymea ids to query + private ReplayProcessor m_updatePublisher; private List m_activeControlIds; @@ -44,52 +47,84 @@ public class NymeaAppControlService extends ControlsProviderService { private void ensureServiceConnection() { if (m_serviceConnection == null) { m_serviceConnection = new NymeaAppServiceConnection(getBaseContext()) { - @Override public void onReady() { - process(); + @Override public void onConnectedChanged(boolean connected) { + Log.d(TAG, "Connected to NymeaAppService. Known hosts: " + m_serviceConnection.getHosts().size()); + if (connected && m_publisherForAll != null) { + Log.d(TAG, "Processing all"); + processAll(); + } } - @Override public void onUpdate(UUID thingId) { + @Override public void onReadyChanged(UUID nymeaId, boolean ready) { + Log.d(TAG, "Nymea instance " + nymeaId.toString() + " ready state changed: " + Boolean.toString(ready)); + if (ready) { + process(nymeaId); + } + } + @Override public void onUpdate(UUID nymeaId, UUID thingId) { Log.d(TAG, "onUpdate()"); if (m_updatePublisher != null && m_activeControlIds.contains(thingId.toString())) { - Thing thing = m_serviceConnection.getThing(thingId); - Log.d(TAG, "Updating publisher for thing: " + thing.name + " id: " + thing.id); - m_updatePublisher.onNext(thingToControl(thing)); + Log.d(TAG, "Updating publisher for thing: " + thingId); + m_updatePublisher.onNext(thingToControl(nymeaId, thingId)); // m_updatePublisher.onComplete(); } } }; } - if (!m_serviceConnection.isConnected()) { - Intent serviceIntent = new Intent(this, NymeaAppService.class); - bindService(serviceIntent, m_serviceConnection, Context.BIND_AUTO_CREATE); + Intent serviceIntent = new Intent(this, NymeaAppService.class); + bindService(serviceIntent, m_serviceConnection, Context.BIND_AUTO_CREATE); + } + + private void processAll() { + ensureServiceConnection(); + if (m_serviceConnection.connected()) { + // Need to add all the pending before processing + if (m_publisherForAll != null) { + for (UUID nymeaId: m_serviceConnection.getHosts().keySet()) { + m_pendingForAll.add(nymeaId); + } + } + for (UUID nymeaId: m_serviceConnection.getHosts().keySet()) { + process(nymeaId); + } + } else { + Log.d(TAG, "Not connected to NymeaAppService yet..."); } } - private void process() { + private void process(UUID nymeaId) { Log.d(TAG, "Processing..."); ensureServiceConnection(); - if (!m_serviceConnection.isReady()) { + if (!m_serviceConnection.connected()) { + Log.d(TAG, "NymeaAppService not connected to nymea instance " + nymeaId + " yet."); + return; + } + if (!m_serviceConnection.getHosts().keySet().contains(nymeaId)) { Log.d(TAG, "Service connection is not ready yet..."); return; } - for (Thing thing : m_serviceConnection.getThings()) { + for (Thing thing : m_serviceConnection.getHosts().get(nymeaId).things.values()) { Log.d(TAG, "Processing thing: " + thing.name); if (m_publisherForAll != null) { Log.d(TAG, "Adding stateless"); - m_publisherForAll.onNext(thingToControl(thing)); + m_publisherForAll.onNext(thingToControl(nymeaId, thing.id)); } if (m_updatePublisher != null) { if (m_activeControlIds.contains(thing.id.toString())) { Log.d(TAG, "Adding stateful"); - m_updatePublisher.onNext(thingToControl(thing)); + m_updatePublisher.onNext(thingToControl(nymeaId, thing.id)); } } } + if (m_pendingForAll.contains(nymeaId)) { + m_pendingForAll.remove(nymeaId); + } + // The publisher for all needs to be completed when done - if (m_publisherForAll != null) { + if (m_publisherForAll != null && m_pendingForAll.isEmpty()) { Log.d(TAG, "Completing all publisher"); m_publisherForAll.onComplete(); } @@ -103,7 +138,7 @@ public class NymeaAppControlService extends ControlsProviderService { public Publisher createPublisherForAllAvailable() { Log.d(TAG, "Creating publishers for all"); m_publisherForAll = ReplayProcessor.create(); - process(); + processAll(); return FlowAdapters.toFlowPublisher(m_publisherForAll); } @@ -112,19 +147,20 @@ public class NymeaAppControlService extends ControlsProviderService { Log.d(TAG, "Creating publishers for " + Integer.toString(controlIds.size())); m_updatePublisher = ReplayProcessor.create(); m_activeControlIds = controlIds; - process(); + processAll(); return FlowAdapters.toFlowPublisher(m_updatePublisher); } @Override public void performControlAction(String controlId, ControlAction action, Consumer consumer) { Log.d(TAG, "Performing control action: " + controlId); -//// PendingAction pendingAction = new PendingAction(); -//// pendingAction.thingId = controlId; -//// pendingAction.actionTypeId = ""; -//// pendingAction.consumer = consumer; -//// m_pendingActions.put( + UUID nymeaId = m_serviceConnection.hostForThing(UUID.fromString(controlId)); + if (nymeaId == null) { + Log.d(TAG, "Nymea host not found for thing id: " + controlId); + consumer.accept(ControlAction.RESPONSE_FAIL); + return; + } Thing thing = m_serviceConnection.getThing(UUID.fromString(controlId)); if (thing == null) { Log.d(TAG, "Thing not found for id: " + controlId); @@ -162,17 +198,21 @@ public class NymeaAppControlService extends ControlsProviderService { return; } - m_serviceConnection.executeAction(thing.id, actionTypeId, param); + m_serviceConnection.executeAction(nymeaId, thing.id, actionTypeId, param); consumer.accept(ControlAction.RESPONSE_OK); } private HashMap m_intents = new HashMap(); - private Control thingToControl(Thing thing) { + private Control thingToControl(UUID nymeaId, UUID thingId) { // Log.d(TAG, "Creating control for thing: " + thing.name + " id: " + thing.id); - // NOTE: intentId 1 doesn't work for some reason I don't understand yet... so let's make sure we never add "1" to it by always added 100 + NymeaHost nymeaHost = m_serviceConnection.getHosts().get(nymeaId); + Thing thing = nymeaHost.things.get(thingId); + + // NOTE: intentId 1 doesn't work for some reason I don't understand yet... + // so let's make sure we never add "1" to it by always added 100 int intentId = m_intents.size() + 100; PendingIntent pi; if (m_intents.containsKey(thing.id)) { @@ -184,6 +224,7 @@ public class NymeaAppControlService extends ControlsProviderService { Context context = getBaseContext(); Intent intent = new Intent(context, NymeaAppControlsActivity.class); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_MULTIPLE_TASK); + intent.putExtra("nymeaId", nymeaId.toString()); intent.putExtra("thingId", thing.id.toString()); pi = PendingIntent.getActivity(context, intentId, intent, PendingIntent.FLAG_UPDATE_CURRENT); Log.d(TAG, "Created pendingintent for " + thing.name + " with id " + intentId + " and extra " + thing.id); @@ -191,7 +232,7 @@ public class NymeaAppControlService extends ControlsProviderService { Control.StatefulBuilder builder = new Control.StatefulBuilder(thing.id.toString(), pi) .setTitle(thing.name) .setSubtitle(thing.className) - .setStructure(m_serviceConnection.nymeaName()); + .setStructure(nymeaHost.name); if (thing.interfaces.contains("impulsebasedgaragedoor")) { builder.setDeviceType(DeviceTypes.TYPE_GARAGE); diff --git a/packaging/android/src/io/guh/nymeaapp/NymeaAppControlsActivity.java b/packaging/android/src/io/guh/nymeaapp/NymeaAppControlsActivity.java index 01274bf3..ffa9f070 100644 --- a/packaging/android/src/io/guh/nymeaapp/NymeaAppControlsActivity.java +++ b/packaging/android/src/io/guh/nymeaapp/NymeaAppControlsActivity.java @@ -32,10 +32,13 @@ public class NymeaAppControlsActivity extends org.qtproject.qt5.android.bindings } + public String nymeaId() + { + return getIntent().getStringExtra("nymeaId"); + } + public String thingId() { - Log.d(TAG, "ThingId called!"); - Log.d(TAG, getIntent().getStringExtra("thingId")); return getIntent().getStringExtra("thingId"); } diff --git a/packaging/android/src/io/guh/nymeaapp/NymeaAppServiceConnection.java b/packaging/android/src/io/guh/nymeaapp/NymeaAppServiceConnection.java index 7fd7f8fd..90573796 100644 --- a/packaging/android/src/io/guh/nymeaapp/NymeaAppServiceConnection.java +++ b/packaging/android/src/io/guh/nymeaapp/NymeaAppServiceConnection.java @@ -3,11 +3,13 @@ package io.guh.nymeaapp; import java.util.List; import java.util.ArrayList; import java.util.UUID; +import java.util.HashMap; import android.util.Log; import android.os.IBinder; import android.os.Parcel; +import android.os.RemoteException; import android.content.Intent; import android.content.IntentFilter; @@ -29,59 +31,63 @@ 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 String m_nymeaName = "nymea"; - private ArrayList m_things = new ArrayList<>(); + private boolean m_connected = false; + private HashMap m_nymeaHosts = new HashMap(); public NymeaAppServiceConnection(Context context) { super(); m_context = context; } - final public boolean isConnected() { - return m_service != null; + final public boolean connected() { + return m_connected; + } + public void onConnectedChanged(boolean connected) {}; + + public final HashMap getHosts() { + return m_nymeaHosts; } - final public boolean isConnectedToNymea() { - return m_isConnectedToNymea; - } - - final public String nymeaName() { - return m_nymeaName; - } - - final public boolean isReady() { - return m_isReady; - } - - final public ArrayList getThings() { - return m_things; - } final public Thing getThing(UUID thingId) { - for (int i = 0; i < m_things.size(); i++) { - if (m_things.get(i).id.equals(thingId)) { - return m_things.get(i); + for (HashMap.Entry entry : m_nymeaHosts.entrySet()) { + Thing thing = entry.getValue().things.get(thingId); + if (thing != null) { + return thing; + } + } + return null; + } + final public UUID hostForThing(UUID thingId) { + for (HashMap.Entry entry : m_nymeaHosts.entrySet()) { + Thing thing = entry.getValue().things.get(thingId); + if (thing != null) { + return entry.getKey(); } } return null; } - public void onReady() {} + public void onReadyChanged(UUID nymeaId, boolean ready) {} public void onError() {} - public void onUpdate(UUID thingId) {} + public void onUpdate(UUID nymeaId, UUID thingId) {} - final public void executeAction(UUID thingId, UUID actionTypeId, String param) { + final public void executeAction(UUID nymeaId, UUID thingId, UUID actionTypeId, String paramValue) { try { - Parcel parcel = Parcel.obtain(); - parcel.writeByteArray(thingId.toString().getBytes()); - parcel.writeByteArray(actionTypeId.toString().getBytes()); - parcel.writeByteArray(param.getBytes()); + JSONObject params = new JSONObject(); + params.put("nymeaId", nymeaId.toString()); + params.put("thingId", thingId.toString()); + params.put("actionTypeId", actionTypeId.toString()); + JSONArray actionParams = new JSONArray(); + JSONObject param = new JSONObject(); + param.put("paramTypeId", actionTypeId.toString()); + param.put("value", paramValue); + actionParams.put(param); + params.put("params", actionParams); + Parcel parcel = createRequest("ExecuteAction", params); Parcel retParcel = Parcel.obtain(); - m_service.transact(2, parcel, retParcel, 0); -// thingsList = retParcel.readString(); + m_service.transact(1, parcel, retParcel, 0); } catch (Exception e) { Log.d(TAG, "Error calling executeAction on NymeaAppService"); } @@ -94,30 +100,44 @@ public class NymeaAppServiceConnection implements ServiceConnection { registerServiceBroadcastReceiver(); try { - Parcel parcel = Parcel.obtain(); + Parcel parcel = createRequest("GetInstances"); Parcel retParcel = Parcel.obtain(); - m_service.transact(0, parcel, retParcel, 0); - m_isReady = retParcel.readBoolean(); - if (!m_isReady) { - m_nymeaName = retParcel.readString(); - m_isConnectedToNymea = true; - Log.d(TAG, "Service is ready!"); - fetchThings(); - } else { - Log.d(TAG, "Service is not ready yet!"); + + m_service.transact(1, parcel, retParcel, 0); + + JSONObject reply = new JSONObject(retParcel.readString()); + Log.d(TAG, "Instaces received: " + reply.toString()); + JSONArray instances = reply.getJSONArray("instances"); + for (int i = 0; i < instances.length(); i++) { + JSONObject instanceMap = instances.getJSONObject(i); + NymeaHost nymeaHost = new NymeaHost(); + nymeaHost.id = UUID.fromString(instanceMap.getString("id")); + nymeaHost.name = instanceMap.getString("name"); + nymeaHost.isReady = instanceMap.getBoolean("isReady"); + m_nymeaHosts.put(nymeaHost.id, nymeaHost); + } - } catch (Exception e) { - Log.d(TAG, "Error while waiting for service to be connected to nymea"); - m_service = null; + } catch (JSONException e) { + Log.d(TAG, "Error while processing JSON in communication with NymeaAppService: " + e.toString()); + onError(); + return; + } catch (RemoteException e) { + Log.d(TAG, "Error communicating with NymeaAppService: " + e.toString()); onError(); return; } + + m_connected = true; + onConnectedChanged(m_connected); } @Override public void onServiceDisconnected(ComponentName arg0) { m_service = null; - m_isConnectedToNymea = false; - m_isReady = false; + for (int i = 0; i < m_nymeaHosts.size(); i++) { + m_nymeaHosts.get(i).isReady = false; + } + m_connected = false; + onConnectedChanged(m_connected); } public void registerServiceBroadcastReceiver() { @@ -130,7 +150,7 @@ public class NymeaAppServiceConnection implements ServiceConnection { private BroadcastReceiver serviceMessageReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { -// Log.d(TAG, "In OnReceive broadcast receiver"); + Log.d(TAG, "In OnReceive broadcast receiver"); if (NymeaAppService.NYMEA_APP_BROADCAST.equals(intent.getAction())) { String payload = intent.getStringExtra("data"); try { @@ -147,52 +167,57 @@ public class NymeaAppServiceConnection implements ServiceConnection { JSONObject data = new JSONObject(payload); JSONObject params = data.getJSONObject("params"); Log.d(TAG, "Broadcast received from NymeaAppService: " + data.getString("notification")); + Log.d(TAG, params.toString()); if (data.getString("notification").equals("ThingStateChanged")) { + UUID nymeaId = UUID.fromString(params.getString("nymeaId")); UUID thingId = UUID.fromString(params.getString("thingId")); UUID stateTypeId = UUID.fromString(params.getString("stateTypeId")); String value = params.getString("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); - } + Thing thing = getThing(thingId); + if (thing != null) { + thing.stateById(stateTypeId).value = value; + onUpdate(nymeaId, thingId); + } else { + Log.d(TAG, "Got a state change notification for a thing we don't know!"); } } if (data.getString("notification").equals("ReadyStateChanged")) { - m_isReady = params.getBoolean("isReady"); - if (m_isReady) { - m_nymeaName = params.getString("systemName"); - fetchThings(); + UUID nymeaId = UUID.fromString(params.getString("nymeaId")); + NymeaHost host = m_nymeaHosts.get(nymeaId); + host.isReady = params.getBoolean("isReady"); + if (host.isReady) { + Log.d(TAG, "Host is ready. Fetching things..."); + fetchThings(nymeaId); + } else { + Log.d(TAG, "Host is not ready yet..."); } } } - private void fetchThings() { + private void fetchThings(UUID nymeaId) { + Log.d(TAG, "Fetching things"); String thingsList; try { - Log.d(TAG, "Fetching things"); - Parcel parcel = Parcel.obtain(); + JSONObject params = new JSONObject(); + params.put("nymeaId", nymeaId.toString()); + Parcel parcel = createRequest("GetThings", params); 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); + JSONObject result = new JSONObject(thingsList); + for (int i = 0; i < result.getJSONArray("things").length(); i++) { + JSONObject entry = result.getJSONArray("things").getJSONObject(i); Thing thing = new Thing(); thing.id = UUID.fromString(entry.getString("id")); thing.name = entry.getString("name"); @@ -220,19 +245,34 @@ public class NymeaAppServiceConnection implements ServiceConnection { a.displayName = actionMap.getString("displayName"); thing.actions.add(a); } - m_things.add(thing); + m_nymeaHosts.get(nymeaId).things.put(thing.id, thing); } } catch (Exception e) { Log.d(TAG, "Error parsing JSON from NymeaAppService: " + e.toString()); + Log.d(TAG, thingsList); m_service = null; - m_isConnectedToNymea = false; onError(); return; } - Log.d(TAG, "Fetched things"); - m_isReady = true; - onReady(); + Log.d(TAG, "Things fetched: " + m_nymeaHosts.get(nymeaId).things.size()); + m_nymeaHosts.get(nymeaId).isReady = true; + onReadyChanged(nymeaId, true); + } + + private Parcel createRequest(String method) throws JSONException { + return createRequest(method, null); + } + private Parcel createRequest(String method, JSONObject params) throws JSONException { + Parcel ret = Parcel.obtain(); + JSONObject payload = new JSONObject(); + payload.put("method", method); + if (params != null) { + payload.put("params", params); + } + Log.d(TAG, "Parcel payload: " + payload.toString()); + ret.writeString(payload.toString()); + return ret; } } diff --git a/packaging/android/src/io/guh/nymeaapp/NymeaHost.java b/packaging/android/src/io/guh/nymeaapp/NymeaHost.java new file mode 100644 index 00000000..a568563c --- /dev/null +++ b/packaging/android/src/io/guh/nymeaapp/NymeaHost.java @@ -0,0 +1,13 @@ +package io.guh.nymeaapp; + +import java.util.HashMap; +import java.util.ArrayList; +import java.util.UUID; + +public class NymeaHost { + + UUID id; + boolean isReady = false; + String name = ""; + HashMap things = new HashMap(); +} From aa6035d2378b653052cec9de764cdd29e64482d3 Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Mon, 21 Sep 2020 19:26:51 +0200 Subject: [PATCH 08/11] clean up android target abi --- nymea-app/nymea-app.pro | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/nymea-app/nymea-app.pro b/nymea-app/nymea-app.pro index a832a44d..f8c45fe2 100644 --- a/nymea-app/nymea-app.pro +++ b/nymea-app/nymea-app.pro @@ -81,6 +81,8 @@ android { # https://bugreports.qt.io/browse/QTBUG-83165 LIBS += -L$${top_builddir}/libnymea-app/$${ANDROID_TARGET_ARCH} + + ANDROID_ABIS = armeabi-v7a arm64-v8a } macx: { @@ -160,4 +162,3 @@ BR=$$BRANDING target.path = /usr/bin INSTALLS += target - From 298e3a5cc7044d9ebea12e6b7b61baa0f55d6f82 Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Mon, 21 Sep 2020 19:39:04 +0200 Subject: [PATCH 09/11] cleanup --- .../nymeaappservice/androidbinder.cpp | 77 ------------------- 1 file changed, 77 deletions(-) diff --git a/androidservice/nymeaappservice/androidbinder.cpp b/androidservice/nymeaappservice/androidbinder.cpp index dd0159ea..b0213fc5 100644 --- a/androidservice/nymeaappservice/androidbinder.cpp +++ b/androidservice/nymeaappservice/androidbinder.cpp @@ -105,83 +105,6 @@ bool AndroidBinder::onTransact(int code, const QAndroidParcel &data, const QAndr engine->thingManager()->executeAction(thingId, actionTypeId, params); } -// switch (code) { -// case 0: { // Status request -// bool isReady = m_engine->jsonRpcClient()->connected() && !m_engine->thingManager()->fetchingData(); -// isReady = false; -// reply.handle().callMethod("writeBoolean", "(Z)V", isReady); -// if (isReady) { -// reply.handle().callMethod("writeString", "(Ljava/lang/String;)V", QAndroidJniObject::fromString(m_engine->jsonRpcClient()->currentHost()->name()).object()); -// } -// } 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()); -// 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()); -// 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()); -// 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("writeString", "(Ljava/lang/String;)V", QAndroidJniObject::fromString(jsonDoc.toJson()).object()); -// } break; -// case 2: {// ExecuteAction -//// QString thingId = data.handle().callMethod("readString", "").toString(); -//// jstring atId = data.handle().callMethod("readString", ""); -//// QString actionTypeId = QAndroidJniObject::fromLocalRef(atId).toString(); -//// jstring p = data.handle().callMethod("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 false; } From aa040bd64fbf607257c2cf7a819e9bcb90562722 Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Mon, 21 Sep 2020 19:54:01 +0200 Subject: [PATCH 10/11] Fix depends --- nymea-app.pro | 1 + 1 file changed, 1 insertion(+) diff --git a/nymea-app.pro b/nymea-app.pro index 63a6c6be..89944346 100644 --- a/nymea-app.pro +++ b/nymea-app.pro @@ -85,6 +85,7 @@ INSTALLS += desktopfile icons # Android service android: { SUBDIRS += androidservice +androidservice.depends = libnymea-app } # Linux desktop (snap package) From a145f2789543936c3ac1ec235137020f47438947 Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Mon, 21 Sep 2020 20:41:41 +0200 Subject: [PATCH 11/11] bump target sdk version --- packaging/android/AndroidManifest.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packaging/android/AndroidManifest.xml b/packaging/android/AndroidManifest.xml index a72d0fa5..0b5b5f28 100644 --- a/packaging/android/AndroidManifest.xml +++ b/packaging/android/AndroidManifest.xml @@ -134,7 +134,7 @@ - +